github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/rpc/utils.go (about)

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