github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/rpc/utils.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 package rpc 13 14 import ( 15 "bufio" 16 "context" 17 crand "crypto/rand" 18 "encoding/binary" 19 "encoding/hex" 20 "math/big" 21 "math/rand" 22 "reflect" 23 "strings" 24 "sync" 25 "time" 26 "unicode" 27 "unicode/utf8" 28 ) 29 30 var ( 31 subscriptionIDGenMu sync.Mutex 32 subscriptionIDGen = idGenerator() 33 ) 34 35 // Is this an exported - upper case - name? 36 func isExported(name string) bool { 37 rune, _ := utf8.DecodeRuneInString(name) 38 return unicode.IsUpper(rune) 39 } 40 41 // Is this type exported or a builtin? 42 func isExportedOrBuiltinType(t reflect.Type) bool { 43 for t.Kind() == reflect.Ptr { 44 t = t.Elem() 45 } 46 // PkgPath will be non-empty even for an exported type, 47 // so we need to check the type name as well. 48 return isExported(t.Name()) || t.PkgPath() == "" 49 } 50 51 var contextType = reflect.TypeOf((*context.Context)(nil)).Elem() 52 53 // isContextType returns an indication if the given t is of context.Context or *context.Context type 54 func isContextType(t reflect.Type) bool { 55 for t.Kind() == reflect.Ptr { 56 t = t.Elem() 57 } 58 return t == contextType 59 } 60 61 var errorType = reflect.TypeOf((*error)(nil)).Elem() 62 63 // Implements this type the error interface 64 func isErrorType(t reflect.Type) bool { 65 for t.Kind() == reflect.Ptr { 66 t = t.Elem() 67 } 68 return t.Implements(errorType) 69 } 70 71 var subscriptionType = reflect.TypeOf((*Subscription)(nil)).Elem() 72 73 // isSubscriptionType returns an indication if the given t is of Subscription or *Subscription type 74 func isSubscriptionType(t reflect.Type) bool { 75 for t.Kind() == reflect.Ptr { 76 t = t.Elem() 77 } 78 return t == subscriptionType 79 } 80 81 // isPubSub tests whether the given method has as as first argument a context.Context 82 // and returns the pair (Subscription, error) 83 func isPubSub(methodType reflect.Type) bool { 84 // numIn(0) is the receiver type 85 if methodType.NumIn() < 2 || methodType.NumOut() != 2 { 86 return false 87 } 88 89 return isContextType(methodType.In(1)) && 90 isSubscriptionType(methodType.Out(0)) && 91 isErrorType(methodType.Out(1)) 92 } 93 94 // formatName will convert to first character to lower case 95 func formatName(name string) string { 96 ret := []rune(name) 97 if len(ret) > 0 { 98 ret[0] = unicode.ToLower(ret[0]) 99 } 100 return string(ret) 101 } 102 103 var bigIntType = reflect.TypeOf((*big.Int)(nil)).Elem() 104 105 // Indication if this type should be serialized in hex 106 func isHexNum(t reflect.Type) bool { 107 if t == nil { 108 return false 109 } 110 for t.Kind() == reflect.Ptr { 111 t = t.Elem() 112 } 113 114 return t == bigIntType 115 } 116 117 // suitableCallbacks iterates over the methods of the given type. It will determine if a method satisfies the criteria 118 // for a RPC callback or a subscription callback and adds it to the collection of callbacks or subscriptions. See server 119 // documentation for a summary of these criteria. 120 func suitableCallbacks(rcvr reflect.Value, typ reflect.Type) (callbacks, subscriptions) { 121 callbacks := make(callbacks) 122 subscriptions := make(subscriptions) 123 124 METHODS: 125 for m := 0; m < typ.NumMethod(); m++ { 126 method := typ.Method(m) 127 mtype := method.Type 128 mname := formatName(method.Name) 129 if method.PkgPath != "" { // method must be exported 130 continue 131 } 132 133 var h callback 134 h.isSubscribe = isPubSub(mtype) 135 h.rcvr = rcvr 136 h.method = method 137 h.errPos = -1 138 139 firstArg := 1 140 numIn := mtype.NumIn() 141 if numIn >= 2 && mtype.In(1) == contextType { 142 h.hasCtx = true 143 firstArg = 2 144 } 145 146 if h.isSubscribe { 147 h.argTypes = make([]reflect.Type, numIn-firstArg) // skip rcvr type 148 for i := firstArg; i < numIn; i++ { 149 argType := mtype.In(i) 150 if isExportedOrBuiltinType(argType) { 151 h.argTypes[i-firstArg] = argType 152 } else { 153 continue METHODS 154 } 155 } 156 157 subscriptions[mname] = &h 158 continue METHODS 159 } 160 161 // determine method arguments, ignore first arg since it's the receiver type 162 // Arguments must be exported or builtin types 163 h.argTypes = make([]reflect.Type, numIn-firstArg) 164 for i := firstArg; i < numIn; i++ { 165 argType := mtype.In(i) 166 if !isExportedOrBuiltinType(argType) { 167 continue METHODS 168 } 169 h.argTypes[i-firstArg] = argType 170 } 171 172 // check that all returned values are exported or builtin types 173 for i := 0; i < mtype.NumOut(); i++ { 174 if !isExportedOrBuiltinType(mtype.Out(i)) { 175 continue METHODS 176 } 177 } 178 179 // when a method returns an error it must be the last returned value 180 h.errPos = -1 181 for i := 0; i < mtype.NumOut(); i++ { 182 if isErrorType(mtype.Out(i)) { 183 h.errPos = i 184 break 185 } 186 } 187 188 if h.errPos >= 0 && h.errPos != mtype.NumOut()-1 { 189 continue METHODS 190 } 191 192 switch mtype.NumOut() { 193 case 0, 1, 2: 194 if mtype.NumOut() == 2 && h.errPos == -1 { // method must one return value and 1 error 195 continue METHODS 196 } 197 callbacks[mname] = &h 198 } 199 } 200 201 return callbacks, subscriptions 202 } 203 204 // idGenerator helper utility that generates a (pseudo) random sequence of 205 // bytes that are used to generate identifiers. 206 func idGenerator() *rand.Rand { 207 if seed, err := binary.ReadVarint(bufio.NewReader(crand.Reader)); err == nil { 208 return rand.New(rand.NewSource(seed)) 209 } 210 return rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) 211 } 212 213 // NewID generates a identifier that can be used as an identifier in the RPC interface. 214 // e.g. filter and subscription identifier. 215 func NewID() ID { 216 subscriptionIDGenMu.Lock() 217 defer subscriptionIDGenMu.Unlock() 218 219 id := make([]byte, 16) 220 for i := 0; i < len(id); i += 7 { 221 val := subscriptionIDGen.Int63() 222 for j := 0; i+j < len(id) && j < 7; j++ { 223 id[i+j] = byte(val) 224 val >>= 8 225 } 226 } 227 228 rpcId := hex.EncodeToString(id) 229 // rpc ID's are RPC quantities, no leading zero's and 0 is 0x0 230 rpcId = strings.TrimLeft(rpcId, "0") 231 if rpcId == "" { 232 rpcId = "0" 233 } 234 235 return ID("0x" + rpcId) 236 }