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