// ProcessFrontendRequest checks the requests sent from a frontend. If the // request method is one that must be handled by btcwallet, the // request is processed here. Otherwise, the request is sent to btcd // and btcd's reply is routed back to the frontend. func ProcessFrontendRequest(msg []byte, ws bool) *btcjson.Reply { // Parse marshaled command. cmd, err := btcjson.ParseMarshaledCmd(msg) if err != nil || cmd.Id() == nil { // Invalid JSON-RPC request. response := &btcjson.Reply{ Error: &btcjson.ErrInvalidRequest, } return response } id := cmd.Id() var result interface{} var jsonErr *btcjson.Error // Check for a handler to reply to cmd. If none exist, defer handlng // to btcd. if f, ok := rpcHandlers[cmd.Method()]; ok { result, jsonErr = f(cmd) } else if f, ok := wsHandlers[cmd.Method()]; ws && ok { result, jsonErr = f(cmd) } else { // btcwallet does not have a handler for the command, so ask // btcd. result, jsonErr = DeferToBtcd(cmd) } // Create and return response. response := &btcjson.Reply{ Id: &id, Result: result, Error: jsonErr, } return response }
// ParseRequest parses a command or notification out of a JSON-RPC request, // returning any errors as a JSON-RPC error. func ParseRequest(msg []byte) (btcjson.Cmd, *btcjson.Error) { cmd, err := btcjson.ParseMarshaledCmd(msg) if err != nil || cmd.Id() == nil { return cmd, &btcjson.ErrInvalidRequest } return cmd, nil }
// ProcessBtcwalletMessage unmarshalls the JSON notification or // reply received from btcwallet and decides how to handle it. func ProcessBtcwalletMessage(b []byte) { // Idea: instead of reading btcwallet messages from just one // websocket connection, maybe use two so the same connection isn't // used for both notifications and responses? Should make handling // must faster as unnecessary unmarshal attempts could be avoided. // Check for notifications first. if req, err := btcjson.ParseMarshaledCmd(b); err == nil { // btcwallet should not be sending Requests except for // notifications. Check for a nil id. if req.Id() != nil { // Invalid response log.Printf("[WRN] btcwallet sent a non-notification JSON-RPC Request (Id: %v)", req.Id()) return } // Message is a notification. Check the method and dispatch // correct handler, or if no handler, log a warning. if ntfnHandler, ok := notificationHandlers[req.Method()]; ok { ntfnHandler(req) } else { // No handler; log warning. log.Printf("[WRN] unhandled notification with method %v", req.Method()) } return } // b is not a Request notification, so it must be a Response. // Attempt to parse it as one and handle. var r btcjson.Reply if err := json.Unmarshal(b, &r); err != nil { log.Print("[WRN] Unable to unmarshal btcwallet message as notificatoion or response") return } // Check for a valid ID. btcgui only sends numbers as IDs, so // perform an appropiate type check. if r.Id == nil { // Responses with no IDs cannot be handled. log.Print("[WRN] Unable to process btcwallet response without ID") return } id, ok := (*r.Id).(float64) if !ok { log.Printf("[WRN] Unable to process btcwallet response with non-number ID %v", *r.Id) return } replyHandlers.Lock() defer replyHandlers.Unlock() if f, ok := replyHandlers.m[uint64(id)]; ok { delete(replyHandlers.m, uint64(id)) f(r.Result, r.Error) } else { log.Print("[WRN] No handler for btcwallet response") } }
func TestCmds(t *testing.T) { for _, test := range cmdtests { c, err := test.f() if err != nil { t.Errorf("%s: failed to run func: %v", test.name, err) continue } mc, err := c.MarshalJSON() if err != nil { t.Errorf("%s: failed to marshal cmd: %v", test.name, err) continue } c2, err := btcjson.ParseMarshaledCmd(mc) if err != nil { t.Errorf("%s: failed to ummarshal cmd: %v", test.name, err) continue } if !reflect.DeepEqual(test.result, c2) { t.Errorf("%s: unmarshal not as expected. "+ "got %v wanted %v", test.name, spew.Sdump(c2), spew.Sdump(test.result)) } if !reflect.DeepEqual(c, c2) { t.Errorf("%s: unmarshal not as we started with. "+ "got %v wanted %v", test.name, spew.Sdump(c2), spew.Sdump(c)) } // id from Id func must match result. if c.Id() != test.result.Id() { t.Errorf("%s: Id returned incorrect id. "+ "got %v wanted %v", test.name, c.Id(), test.result.Id()) } // method from Method func must match result. if c.Method() != test.result.Method() { t.Errorf("%s: Method returned incorrect method. "+ "got %v wanted %v", test.name, c.Method(), test.result.Method()) } // Read marshaled command back into c. Should still // match result. c.UnmarshalJSON(mc) if !reflect.DeepEqual(test.result, c) { t.Errorf("%s: unmarshal not as expected. "+ "got %v wanted %v", test.name, spew.Sdump(c), spew.Sdump(test.result)) } } }
// unmarshalNotification attempts to unmarshal a marshaled JSON-RPC // notification (Request with a nil or no ID). func unmarshalNotification(s string) (btcjson.Cmd, error) { req, err := btcjson.ParseMarshaledCmd([]byte(s)) if err != nil { return nil, err } if req.Id() != nil { return nil, errors.New("id is non-nil") } return req, nil }
// parseCmd parses a marshaled known command, returning any errors as a // btcjson.Error that can be used in replies. The returned cmd may still // be non-nil if b is at least a valid marshaled JSON-RPC message. func parseCmd(b []byte) (btcjson.Cmd, *btcjson.Error) { cmd, err := btcjson.ParseMarshaledCmd(b) if err != nil { jsonErr, ok := err.(btcjson.Error) if !ok { jsonErr = btcjson.Error{ Code: btcjson.ErrParse.Code, Message: err.Error(), } } return cmd, &jsonErr } return cmd, nil }
func TestNtfns(t *testing.T) { for _, test := range ntfntests { // create notification. n := test.f() // verify that id is nil. if n.Id() != nil { t.Error("%s: notification should not have non-nil id %v", test.name, n.Id()) continue } mn, err := n.MarshalJSON() if err != nil { t.Errorf("%s: failed to marshal notification: %v", test.name, err) continue } n2, err := btcjson.ParseMarshaledCmd(mn) if err != nil { t.Errorf("%s: failed to ummarshal cmd: %v", test.name, err) continue } if !reflect.DeepEqual(test.result, n2) { t.Errorf("%s: unmarshal not as expected. "+ "got %v wanted %v", test.name, spew.Sdump(n2), spew.Sdump(test.result)) } if !reflect.DeepEqual(n, n2) { t.Errorf("%s: unmarshal not as we started with. "+ "got %v wanted %v", test.name, spew.Sdump(n2), spew.Sdump(n)) } // Read marshaled notification back into n. Should still // match result. n.UnmarshalJSON(mn) if !reflect.DeepEqual(test.result, n) { t.Errorf("%s: unmarshal not as expected. "+ "got %v wanted %v", test.name, spew.Sdump(n), spew.Sdump(test.result)) } } }
// handleMessage is the main handler for incoming requests. It enforces // authentication, parses the incoming json, looks up and executes handlers // (including pass through for standard RPC commands), sends the appropriate // response. It also detects commands which are marked as long-running and // sends them off to the asyncHander for processing. func (c *Client) handleMessage(msg []byte) { // Attempt to unmarshal the message as a known JSON-RPC command. if cmd, err := btcjson.ParseMarshaledCmd(msg); err == nil { // Commands that have an ID associated with them are not // notifications. Since this is a client, it should not // be receiving non-notifications. if cmd.Id() != nil { // Invalid response log.Warnf("Remote server sent a non-notification "+ "JSON-RPC Request (Id: %v)", cmd.Id()) return } // Deliver the notification. log.Tracef("Received notification [%s]", cmd.Method()) c.handleNotification(cmd) return } // The message was not a command/notification, so it should be a reply // to a previous request. var r btcjson.Reply if err := json.Unmarshal([]byte(msg), &r); err != nil { log.Warnf("Unable to unmarshal inbound message as " + "notification or response") return } // Ensure the reply has an id. if r.Id == nil { log.Warnf("Received response with no id") return } // Ensure the id is the expected type. fid, ok := (*r.Id).(float64) if !ok { log.Warnf("Received unexpected id type: %T (value %v)", *r.Id, *r.Id) return } id := uint64(fid) log.Tracef("Received response for id %d (result %v)", id, r.Result) request := c.removeRequest(id) // Nothing more to do if there is no request associated with this reply. if request == nil || request.responseChan == nil { log.Warnf("Received unexpected reply: %s (id %d)", r.Result, id) return } // Unmarshal the reply into a concrete result if possible and deliver // it to the associated channel. reply, err := btcjson.ReadResultCmd(request.cmd.Method(), []byte(msg)) if err != nil { log.Warnf("Failed to unmarshal reply to command [%s] "+ "(id %d): %v", request.cmd.Method(), id, err) request.responseChan <- &futureResult{reply: nil, err: err} return } // Since the command was successful, examine it to see if it's a // notification, and if is, add it to the notification state so it // can automatically be re-established on reconnect. c.trackRegisteredNtfns(request.cmd) // Deliver the reply. request.responseChan <- &futureResult{reply: &reply, err: nil} }
// jsonRead abstracts the JSON unmarshalling and reply handling used // by both RPC and websockets. If called from websocket code, a non-nil // wallet notification channel can be used to automatically register // various notifications for the wallet. func jsonRead(body []byte, s *rpcServer, walletNotification chan []byte) (reply btcjson.Reply, err error) { cmd, err := btcjson.ParseMarshaledCmd(body) if err != nil { var id interface{} if cmd != nil { // Unmarshaling a valid JSON-RPC message succeeded. Use // the provided id for errors. id = cmd.Id() } if jsonErr, ok := err.(btcjson.Error); ok { reply = btcjson.Reply{ Result: nil, Error: &jsonErr, Id: &id, } } else { reply = btcjson.Reply{ Result: nil, Error: &jsonErr, Id: &id, } } log.Tracef("RPCS: reply: %v", reply) return reply, err } log.Tracef("RPCS: received: %v", cmd) id := cmd.Id() handler, ok := handlers[cmd.Method()] if !ok { reply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrMethodNotFound, Id: &id, } return reply, btcjson.ErrMethodNotFound } result, err := handler(s, cmd, walletNotification) if err != nil { if jsonErr, ok := err.(btcjson.Error); ok { reply = btcjson.Reply{ Error: &jsonErr, Id: &id, } err = errors.New(jsonErr.Message) } else { // In the case where we did not have a btcjson // error to begin with, make a new one to send, // but this really should not happen. rawJSONError := btcjson.Error{ Code: btcjson.ErrInternal.Code, Message: err.Error(), } reply = btcjson.Reply{ Error: &rawJSONError, Id: &id, } } } else { reply = btcjson.Reply{ Result: result, Id: &id, } } return reply, err }