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  }