package btcjson import ( "encoding/json" "fmt" "reflect" "sort" "strconv" "strings" ) // makeParams creates a slice of interface values for the given struct. func makeParams(rt reflect.Type, rv reflect.Value) []interface{} { numFields := rt.NumField() params := make([]interface{}, 0, numFields) for i := 0; i < numFields; i++ { rtf := rt.Field(i) rvf := rv.Field(i) if rtf.Type.Kind() == reflect.Ptr { if rvf.IsNil() { break } rvf.Elem() } params = append(params, rvf.Interface()) } return params } // MarshalCmd marshals the passed command to a JSON-RPC request byte slice that is suitable for transmission to an RPC // server. The provided command type must be a registered type. All commands provided by this package are registered by // default. func MarshalCmd(id interface{}, cmd interface{}) ([]byte, error) { // Look up the cmd type and error out if not registered. rt := reflect.TypeOf(cmd) registerLock.RLock() method, ok := concreteTypeToMethod[rt] registerLock.RUnlock() if !ok { str := fmt.Sprintf("%q is not registered", method) return nil, makeError(ErrUnregisteredMethod, str) } // The provided command must not be nil. rv := reflect.ValueOf(cmd) if rv.IsNil() { str := "the specified command is nil" return nil, makeError(ErrInvalidType, str) } // Create a slice of interface values in the order of the struct fields while respecting pointer fields as optional // netparams and only adding them if they are non-nil. params := makeParams(rt.Elem(), rv.Elem()) // Generate and marshal the final JSON-RPC request. rawCmd, e := NewRequest(id, method, params) if e != nil { E.Ln(e) return nil, e } return json.Marshal(rawCmd) } // MarshalRequest marshals the passed command to a btcjson.Request func MarshalRequest(id interface{}, cmd interface{}) (rawCmd *Request, e error) { // Look up the cmd type and error out if not registered. rt := reflect.TypeOf(cmd) registerLock.RLock() method, ok := concreteTypeToMethod[rt] registerLock.RUnlock() if !ok { str := fmt.Sprintf("%q is not registered", method) return nil, makeError(ErrUnregisteredMethod, str) } // The provided command must not be nil. rv := reflect.ValueOf(cmd) if rv.IsNil() { str := "the specified command is nil" return nil, makeError(ErrInvalidType, str) } // Create a slice of interface values in the order of the struct fields while respecting pointer fields as optional // netparams and only adding them if they are non-nil. params := makeParams(rt.Elem(), rv.Elem()) // Generate and marshal the final JSON-RPC request. rawCmd, e = NewRequest(id, method, params) if e != nil { E.Ln(e) return nil, e } return } // checkNumParams ensures the supplied number of netparams is at least the minimum required number for the command and // less than the maximum allowed. func checkNumParams(numParams int, info *MethodInfo) (e error) { if numParams < info.NumReqParams || numParams > info.MaxParams { if info.NumReqParams == info.MaxParams { str := fmt.Sprintf( "wrong number of netparams (expected "+ "%d, received %d)", info.NumReqParams, numParams, ) return makeError(ErrNumParams, str) } str := fmt.Sprintf( "wrong number of netparams (expected "+ "between %d and %d, received %d)", info.NumReqParams, info.MaxParams, numParams, ) return makeError(ErrNumParams, str) } return nil } // populateDefaults populates default values into any remaining optional struct fields that did not have parameters // explicitly provided. The caller should have previously checked that the number of parameters being passed is at least // the required number of parameters to avoid unnecessary work in this function, but since required fields never have // default values, it will work properly even without the check. func populateDefaults(numParams int, info *MethodInfo, rv reflect.Value) { // When there are no more parameters left in the supplied parameters, any remaining struct fields must be optional. // Thus, populate them with their associated default value as needed. for i := numParams; i < info.MaxParams; i++ { rvf := rv.Field(i) if defaultVal, ok := info.defaults[i]; ok { rvf.Set(defaultVal) } } } // UnmarshalCmd unmarshalls a JSON-RPC request into a suitable concrete command so long as the method type contained within the marshalled request is registered. func UnmarshalCmd(r *Request) (ii interface{}, e error) { registerLock.RLock() rtp, ok := methodToConcreteType[r.Method] info := methodToInfo[r.Method] registerLock.RUnlock() if !ok { str := fmt.Sprintf("%q is not registered", r.Method) return nil, makeError(ErrUnregisteredMethod, str) } rt := rtp.Elem() rvp := reflect.New(rt) rv := rvp.Elem() // Ensure the number of parameters are correct. numParams := len(r.Params) if e = checkNumParams(numParams, &info); E.Chk(e) { return nil, e } // Loop through each of the struct fields and unmarshal the associated parameter into them. for i := 0; i < numParams; i++ { rvf := rv.Field(i) // Unmarshal the parameter into the struct field. concreteVal := rvf.Addr().Interface() if e = json.Unmarshal(r.Params[i], &concreteVal); E.Chk(e) { // The most common error is the wrong type, so explicitly detect that error and make it nicer. fieldName := strings.ToLower(rt.Field(i).Name) if jerr, ok := e.(*json.UnmarshalTypeError); ok { str := fmt.Sprintf( "parameter #%d '%s' must "+ "be type %v (got %v)", i+1, fieldName, jerr.Type, jerr.Value, ) return nil, makeError(ErrInvalidType, str) } // Fallback to showing the underlying error. str := fmt.Sprintf( "parameter #%d '%s' failed to "+ "unmarshal: %v", i+1, fieldName, e, ) return nil, makeError(ErrInvalidType, str) } } // When there are less supplied parameters than the total number of netparams, any remaining struct fields must be // optional. Thus, populate them with their associated default value as needed. if numParams < info.MaxParams { populateDefaults(numParams, &info, rv) } return rvp.Interface(), nil } // isNumeric returns whether the passed reflect kind is a signed or unsigned integer of any magnitude or a float of any // magnitude. func isNumeric(kind reflect.Kind) bool { switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: return true } return false } // typesMaybeCompatible returns whether the source type can possibly be assigned to the destination type. This is // intended as a relatively quick check to weed out obviously invalid conversions. func typesMaybeCompatible(dest reflect.Type, src reflect.Type) bool { // The same types are obviously compatible. if dest == src { return true } // When both types are numeric, they are potentially compatible. srcKind := src.Kind() destKind := dest.Kind() if isNumeric(destKind) && isNumeric(srcKind) { return true } if srcKind == reflect.String { // Strings can potentially be converted to numeric types. if isNumeric(destKind) { return true } switch destKind { // Strings can potentially be converted to bools by strconv.ParseBool. case reflect.Bool: return true // Strings can be converted to any other type which has as underlying type of string. case reflect.String: return true // Strings can potentially be converted to arrays, slice, structs, and maps via json.Unmarshal. case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map: return true } } return false } // baseType returns the type of the argument after indirecting through all pointers along with how many indirections // were necessary. func baseType(arg reflect.Type) (reflect.Type, int) { var numIndirects int for arg.Kind() == reflect.Ptr { arg = arg.Elem() numIndirects++ } return arg, numIndirects } // assignField is the main workhorse for the NewCmd function which handles assigning the provided source value to the // destination field. It supports direct type assignments, indirection, conversion of numeric types, and unmarshaling of // strings into arrays, slices, structs, and maps via json.Unmarshal. func assignField( paramNum int, fieldName string, dest reflect.Value, src reflect.Value, ) (e error) { // Just error now when the types have no chance of being compatible. destBaseType, destIndirects := baseType(dest.Type()) srcBaseType, srcIndirects := baseType(src.Type()) if !typesMaybeCompatible(destBaseType, srcBaseType) { str := fmt.Sprintf( "parameter #%d '%s' must be type %v (got "+ "%v)", paramNum, fieldName, destBaseType, srcBaseType, ) return makeError(ErrInvalidType, str) } // Chk if it's possible to simply set the dest to the provided source. This is the case when the base types are // the same or they are both pointers that can be indirected to be the same without needing to create pointers for // the destination field. if destBaseType == srcBaseType && srcIndirects >= destIndirects { for i := 0; i < srcIndirects-destIndirects; i++ { src = src.Elem() } dest.Set(src) return nil } // When the destination has more indirects than the source, the extra pointers have to be created. Only create // enough pointers to reach the same level of indirection as the source so the dest can simply be set to the // provided source when the types are the same. destIndirectsRemaining := destIndirects if destIndirects > srcIndirects { indirectDiff := destIndirects - srcIndirects for i := 0; i < indirectDiff; i++ { dest.Set(reflect.New(dest.Type().Elem())) dest = dest.Elem() destIndirectsRemaining-- } } if destBaseType == srcBaseType { dest.Set(src) return nil } // Make any remaining pointers needed to get to the base dest type since the above direct assign was not possible // and conversions are done against the base types. for i := 0; i < destIndirectsRemaining; i++ { dest.Set(reflect.New(dest.Type().Elem())) dest = dest.Elem() } // Indirect through to the base source value. for src.Kind() == reflect.Ptr { src = src.Elem() } // Perform supported type conversions. switch src.Kind() { // Source value is a signed integer of various magnitude. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: switch dest.Kind() { // Destination is a signed integer of various magnitude. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: srcInt := src.Int() if dest.OverflowInt(srcInt) { str := fmt.Sprintf( "parameter #%d '%s' "+ "overflows destination type %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } dest.SetInt(srcInt) // Destination is an unsigned integer of various magnitude. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: srcInt := src.Int() if srcInt < 0 || dest.OverflowUint(uint64(srcInt)) { str := fmt.Sprintf( "parameter #%d '%s' "+ "overflows destination type %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } dest.SetUint(uint64(srcInt)) default: str := fmt.Sprintf( "parameter #%d '%s' must be type "+ "%v (got %v)", paramNum, fieldName, destBaseType, srcBaseType, ) return makeError(ErrInvalidType, str) } // Source value is an unsigned integer of various magnitude. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: switch dest.Kind() { // Destination is a signed integer of various magnitude. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: srcUint := src.Uint() if srcUint > uint64(1<<63)-1 { str := fmt.Sprintf( "parameter #%d '%s' "+ "overflows destination type %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } if dest.OverflowInt(int64(srcUint)) { str := fmt.Sprintf( "parameter #%d '%s' "+ "overflows destination type %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } dest.SetInt(int64(srcUint)) // Destination is an unsigned integer of various magnitude. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: srcUint := src.Uint() if dest.OverflowUint(srcUint) { str := fmt.Sprintf( "parameter #%d '%s' "+ "overflows destination type %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } dest.SetUint(srcUint) default: str := fmt.Sprintf( "parameter #%d '%s' must be type "+ "%v (got %v)", paramNum, fieldName, destBaseType, srcBaseType, ) return makeError(ErrInvalidType, str) } // Source value is a float. case reflect.Float32, reflect.Float64: destKind := dest.Kind() if destKind != reflect.Float32 && destKind != reflect.Float64 { str := fmt.Sprintf( "parameter #%d '%s' must be type "+ "%v (got %v)", paramNum, fieldName, destBaseType, srcBaseType, ) return makeError(ErrInvalidType, str) } srcFloat := src.Float() if dest.OverflowFloat(srcFloat) { str := fmt.Sprintf( "parameter #%d '%s' overflows "+ "destination type %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } dest.SetFloat(srcFloat) // Source value is a string. case reflect.String: switch dest.Kind() { // String -> bool case reflect.Bool: b, e := strconv.ParseBool(src.String()) if e != nil { E.Ln(e) str := fmt.Sprintf( "parameter #%d '%s' must "+ "parse to a %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } dest.SetBool(b) // String -> signed integer of varying size. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: srcInt, e := strconv.ParseInt(src.String(), 0, 0) if e != nil { E.Ln(e) str := fmt.Sprintf( "parameter #%d '%s' must "+ "parse to a %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } if dest.OverflowInt(srcInt) { str := fmt.Sprintf( "parameter #%d '%s' "+ "overflows destination type %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } dest.SetInt(srcInt) // String -> unsigned integer of varying size. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: srcUint, e := strconv.ParseUint(src.String(), 0, 0) if e != nil { E.Ln(e) str := fmt.Sprintf( "parameter #%d '%s' must "+ "parse to a %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } if dest.OverflowUint(srcUint) { str := fmt.Sprintf( "parameter #%d '%s' "+ "overflows destination type %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } dest.SetUint(srcUint) // String -> float of varying size. case reflect.Float32, reflect.Float64: srcFloat, e := strconv.ParseFloat(src.String(), 0) if e != nil { E.Ln(e) str := fmt.Sprintf( "parameter #%d '%s' must "+ "parse to a %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } if dest.OverflowFloat(srcFloat) { str := fmt.Sprintf( "parameter #%d '%s' "+ "overflows destination type %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } dest.SetFloat(srcFloat) // String -> string (typecast). case reflect.String: dest.SetString(src.String()) // String -> arrays, slices, structs, and maps via // json.Unmarshal. case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map: concreteVal := dest.Addr().Interface() e := json.Unmarshal([]byte(src.String()), &concreteVal) if e != nil { E.Ln(e) str := fmt.Sprintf( "parameter #%d '%s' must "+ "be valid JSON which unsmarshals to a %v", paramNum, fieldName, destBaseType, ) return makeError(ErrInvalidType, str) } dest.Set(reflect.ValueOf(concreteVal).Elem()) } } return nil } // NewCmd provides a generic mechanism to create a new command that can marshal to a JSON-RPC request while respecting // the requirements of the provided method. // // The method must have been registered with the package already along with its type definition. All methods associated // with the commands exported by this package are already registered by default. // // The arguments are most efficient when they are the exact same type as the underlying field in the command struct // associated with the the method, however this function also will perform a variety of conversions to make it more // flexible. // // This allows, for example, command line args which are strings to be passed unaltered. In particular, the following // conversions are supported: // // - Conversion between any size signed or unsigned integer so long as the value does not overflow the destination // type // // - Conversion between float32 and float64 so long as the value does not overflow the destination type // // - Conversion from string to boolean for everything strconv.ParseBool recognizes // // - Conversion from string to any size integer for everything strconv.ParseInt and strconv.ParseUint recognizes // // - Conversion from string to any size float for everything strconv.ParseFloat recognizes // // - Conversion from string to arrays, slices, structs, and maps by treating the string as marshalled JSON and calling // json.Unmarshal into the destination field func NewCmd(method string, args ...interface{}) (interface{}, error) { // Look up details about the provided method. Any methods that aren't registered are an error. registerLock.RLock() rtp, ok := methodToConcreteType[method] info := methodToInfo[method] registerLock.RUnlock() if !ok { str := fmt.Sprintf("%q is not registered", method) e := makeError(ErrUnregisteredMethod, str) E.Chk(e) return nil, e } // Ensure the number of parameters are correct. numParams := len(args) if e := checkNumParams(numParams, &info); E.Chk(e) { return nil, e } // Create the appropriate command type for the method. Since all types are enforced to be a pointer to a struct at // registration time, it's safe to indirect to the struct now. rvp := reflect.New(rtp.Elem()) rv := rvp.Elem() rt := rtp.Elem() // Loop through each of the struct fields and assign the associated parameter into them after checking its type // validity. for i := 0; i < numParams; i++ { // Attempt to assign each of the arguments to the according struct field. rvf := rv.Field(i) fieldName := strings.ToLower(rt.Field(i).Name) e := assignField(i+1, fieldName, rvf, reflect.ValueOf(args[i])) if e != nil { E.Ln(e) return nil, e } } return rvp.Interface(), nil } // MethodToInfo gets the information about a method func MethodToInfo(method string) *MethodInfo { F.Ln(method) T.S(methodToInfo[method]) if v, ok := methodToInfo[method]; ok { return &v } return nil } // GetMethods returns the list of methods available func GetMethods() (out []string) { out = make([]string, len(methodToInfo)) var i int for v := range methodToInfo { out[i] = v i++ } sort.Strings(out) return }