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