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