github.com/etherite/go-etherite@v0.0.0-20171015192807-5f4dd87b2f6e/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/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  }