1 package btcjson
2 3 import (
4 "encoding/json"
5 "fmt"
6 "reflect"
7 "sort"
8 "strconv"
9 "strings"
10 "sync"
11 )
12 13 // UsageFlag define flags that specify additional properties about the circumstances under which a command can be used.
14 type UsageFlag uint32
15 16 // MethodInfo keeps track of information about each registered method such as the parameter information.
17 type MethodInfo struct {
18 MaxParams int
19 NumReqParams int
20 numOptParams int
21 defaults map[int]reflect.Value
22 flags UsageFlag
23 usage string
24 }
25 26 const (
27 // UFWalletOnly indicates that the command can only be used with an RPC server that supports wallet commands.
28 UFWalletOnly UsageFlag = 1 << iota
29 // UFWebsocketOnly indicates that the command can only be used when communicating with an RPC server over
30 // websockets. This typically applies to notifications and notification registration functions since neiher makes
31 // since when using a single-shot HTTP-POST request.
32 UFWebsocketOnly
33 // UFNotification indicates that the command is actually a notification. This means when it is marshalled, the ID
34 // must be nil.
35 UFNotification
36 // highestUsageFlagBit is the maximum usage flag bit and is used in the stringer and tests to ensure all of the
37 // above constants have been tested.
38 highestUsageFlagBit
39 )
40 41 var (
42 concreteTypeToMethod = make(map[reflect.Type]string)
43 methodToConcreteType = make(map[string]reflect.Type)
44 methodToInfo = make(map[string]MethodInfo)
45 // These fields are used to map the registered types to method names.
46 registerLock sync.RWMutex
47 // Map of UsageFlag values back to their constant names for pretty printing.
48 usageFlagStrings = map[UsageFlag]string{
49 UFWalletOnly: "UFWalletOnly",
50 UFWebsocketOnly: "UFWebsocketOnly",
51 UFNotification: "UFNotification",
52 }
53 )
54 55 // String returns the UsageFlag in human-readable form.
56 func (fl UsageFlag) String() string {
57 // No flags are set.
58 if fl == 0 {
59 return "0x0"
60 }
61 // Add individual bit flags.
62 s := ""
63 for flag := UFWalletOnly; flag < highestUsageFlagBit; flag <<= 1 {
64 if fl&flag == flag {
65 s += usageFlagStrings[flag] + "|"
66 fl -= flag
67 }
68 }
69 // Add remaining value as raw hex.
70 s = strings.TrimRight(s, "|")
71 if fl != 0 {
72 s += "|0x" + strconv.FormatUint(uint64(fl), 16)
73 }
74 s = strings.TrimLeft(s, "|")
75 return s
76 }
77 78 // MustRegisterCmd performs the same function as RegisterCmd except it panics if there is an error. This should only be
79 // called from package init functions.
80 func MustRegisterCmd(method string, cmd interface{}, flags UsageFlag) {
81 if e := RegisterCmd(method, cmd, flags); E.Chk(e) {
82 panic(fmt.Sprintf("failed to register type %q: %v\n", method,
83 e,
84 ),
85 )
86 }
87 RegisteredCommands[method] = cmd
88 }
89 90 var RegisteredCommands = make(map[string]interface{})
91 92 /*
93 RegisterCmd registers a new command that will automatically marshal to and from JSON-RPC with full type checking and
94 positional parameter support. It also accepts usage flags which identify the circumstances under which the command can
95 be used.
96 97 This package automatically registers all of the exported commands by default using this function, however it is also
98 exported so callers can easily register custom types.
99 100 The type format is very strict since it needs to be able to automatically marshal to and from JSON-RPC 1.0. The
101 following enumerates the requirements:
102 103 - The provided command must be a single pointer to a struct
104 105 - All fields must be exported
106 107 - The order of the positional parameters in the marshalled JSON will be in the same order as declared in the struct
108 definition
109 110 - Struct embedding is not supported
111 112 - Struct fields may NOT be channels, functions, complex, or interface
113 114 - A field in the provided struct with a pointer is treated as optional
115 116 - Multiple indirections (i.e **int) are not supported
117 118 - Once the first optional field (pointer) is encountered, the remaining fields must also be optional fields (pointers)
119 as required by positional netparams
120 121 - A field that has a 'jsonrpcdefault' struct tag must be an optional field (pointer)
122 123 NOTE: This function only needs to be able to examine the structure of the passed struct, so it does not need to be an
124 actual instance. Therefore, it is recommended to simply pass a nil pointer cast to the appropriate type. For example,
125 (*FooCmd)(nil).
126 */
127 func RegisterCmd(method string, cmd interface{}, flags UsageFlag) (e error) {
128 registerLock.Lock()
129 defer registerLock.Unlock()
130 if _, ok := methodToConcreteType[method]; ok {
131 str := fmt.Sprintf("method %q is already registered", method)
132 return makeError(ErrDuplicateMethod, str)
133 }
134 // Ensure that no unrecognized flag bits were specified.
135 if ^(highestUsageFlagBit-1)&flags != 0 {
136 str := fmt.Sprintf("invalid usage flags specified for method "+
137 "%s: %v", method, flags,
138 )
139 return makeError(ErrInvalidUsageFlags, str)
140 }
141 rtp := reflect.TypeOf(cmd)
142 if rtp.Kind() != reflect.Ptr {
143 str := fmt.Sprintf("type must be *struct not '%s (%s)'", rtp,
144 rtp.Kind(),
145 )
146 return makeError(ErrInvalidType, str)
147 }
148 rt := rtp.Elem()
149 if rt.Kind() != reflect.Struct {
150 str := fmt.Sprintf("type must be *struct not '%s (*%s)'",
151 rtp, rt.Kind(),
152 )
153 return makeError(ErrInvalidType, str)
154 }
155 // Enumerate the struct fields to validate them and gather parameter information.
156 numFields := rt.NumField()
157 numOptFields := 0
158 defaults := make(map[int]reflect.Value)
159 for i := 0; i < numFields; i++ {
160 rtf := rt.Field(i)
161 if rtf.Anonymous {
162 str := fmt.Sprintf("embedded fields are not supported "+
163 "(field name: %q)", rtf.Name,
164 )
165 return makeError(ErrEmbeddedType, str)
166 }
167 if rtf.PkgPath != "" {
168 str := fmt.Sprintf("unexported fields are not supported "+
169 "(field name: %q)", rtf.Name,
170 )
171 return makeError(ErrUnexportedField, str)
172 }
173 // Disallow types that can't be JSON encoded. Also, determine if the field is optional based on it being a pointer.
174 var isOptional bool
175 switch kind := rtf.Type.Kind(); kind {
176 case reflect.Ptr:
177 isOptional = true
178 kind = rtf.Type.Elem().Kind()
179 fallthrough
180 default:
181 if !isAcceptableKind(kind) {
182 str := fmt.Sprintf("unsupported field type "+
183 "'%s (%s)' (field name %q)", rtf.Type,
184 baseKindString(rtf.Type), rtf.Name,
185 )
186 return makeError(ErrUnsupportedFieldType, str)
187 }
188 }
189 // Count the optional fields and ensure all fields after the first optional field are also optional.
190 if isOptional {
191 numOptFields++
192 } else {
193 if numOptFields > 0 {
194 str := fmt.Sprintf("all fields after the first "+
195 "optional field must also be optional "+
196 "(field name %q)", rtf.Name,
197 )
198 return makeError(ErrNonOptionalField, str)
199 }
200 }
201 // Ensure the default value can be unmarshalled into the type and that defaults are only specified for optional
202 // fields.
203 if tag := rtf.Tag.Get("jsonrpcdefault"); tag != "" {
204 if !isOptional {
205 str := fmt.Sprintf("required fields must not "+
206 "have a default specified (field name "+
207 "%q)", rtf.Name,
208 )
209 return makeError(ErrNonOptionalDefault, str)
210 }
211 rvf := reflect.New(rtf.Type.Elem())
212 e := json.Unmarshal([]byte(tag), rvf.Interface())
213 if e != nil {
214 E.Ln(e)
215 str := fmt.Sprintf("default value of %q is "+
216 "the wrong type (field name %q)", tag,
217 rtf.Name,
218 )
219 return makeError(ErrMismatchedDefault, str)
220 }
221 defaults[i] = rvf
222 }
223 }
224 // Update the registration maps.
225 methodToConcreteType[method] = rtp
226 methodToInfo[method] = MethodInfo{
227 MaxParams: numFields,
228 NumReqParams: numFields - numOptFields,
229 numOptParams: numOptFields,
230 defaults: defaults,
231 flags: flags,
232 }
233 concreteTypeToMethod[rtp] = method
234 return nil
235 }
236 237 // RegisteredCmdMethods returns a sorted list of methods for all registered commands.
238 func RegisteredCmdMethods() []string {
239 registerLock.Lock()
240 defer registerLock.Unlock()
241 methods := make([]string, 0, len(methodToInfo))
242 for k := range methodToInfo {
243 methods = append(methods, k)
244 }
245 sort.Strings(methods)
246 return methods
247 }
248 249 // baseKindString returns the base kind for a given reflect.Type after indirecting through all pointers.
250 func baseKindString(rt reflect.Type) string {
251 numIndirects := 0
252 for rt.Kind() == reflect.Ptr {
253 numIndirects++
254 rt = rt.Elem()
255 }
256 return fmt.Sprintf("%s%s", strings.Repeat("*", numIndirects), rt.Kind())
257 }
258 259 // isAcceptableKind returns whether or not the passed field type is a supported type. It is called after the first
260 // pointer indirection, so further pointers are not supported.
261 func isAcceptableKind(kind reflect.Kind) bool {
262 switch kind {
263 case reflect.Chan:
264 fallthrough
265 case reflect.Complex64:
266 fallthrough
267 case reflect.Complex128:
268 fallthrough
269 case reflect.Func:
270 fallthrough
271 case reflect.Ptr:
272 fallthrough
273 case reflect.Interface:
274 return false
275 }
276 return true
277 }
278