Example #1
0
// 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
}
Example #2
0
// 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
}
Example #3
0
// 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")
	}
}
Example #4
0
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))
		}
	}
}
Example #5
0
// 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
}
Example #6
0
// 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
}
Example #7
0
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}
}
Example #9
0
// 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
}