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  }