Browse Source

rpc: clean up encoding of request and response messages (#7721)

Instead of having the exported Request and Response type expose the version
directly, delegate version injection to an unexported shim.  We already had the
shim; this just boosts it to the top level and does a bit more checking.

* Check JSON-RPC version marker.
pull/7726/head
M. J. Fromberger 3 years ago
committed by GitHub
parent
commit
20886fdb39
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 73 additions and 61 deletions
  1. +73
    -61
      rpc/jsonrpc/types/types.go

+ 73
- 61
rpc/jsonrpc/types/types.go View File

@ -47,49 +47,47 @@ func idFromInterface(idInterface interface{}) (jsonrpcid, error) {
// REQUEST // REQUEST
type RPCRequest struct { type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
ID jsonrpcid `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{}
ID jsonrpcid
Method string
Params json.RawMessage
} }
// UnmarshalJSON custom JSON unmarshaling due to jsonrpcid being string or int
type rpcRequestJSON struct {
V string `json:"jsonrpc"` // must be "2.0"
ID interface{} `json:"id,omitempty"`
M string `json:"method"`
P json.RawMessage `json:"params"`
}
// UnmarshalJSON decodes a request from a JSON-RPC 2.0 request object.
func (req *RPCRequest) UnmarshalJSON(data []byte) error { func (req *RPCRequest) UnmarshalJSON(data []byte) error {
unsafeReq := struct {
JSONRPC string `json:"jsonrpc"`
ID interface{} `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{}
}{}
err := json.Unmarshal(data, &unsafeReq)
if err != nil {
var wrapper rpcRequestJSON
if err := json.Unmarshal(data, &wrapper); err != nil {
return err return err
} else if wrapper.V != "" && wrapper.V != "2.0" {
return fmt.Errorf("invalid version: %q", wrapper.V)
} }
if unsafeReq.ID == nil { // notification
return nil
}
req.JSONRPC = unsafeReq.JSONRPC
req.Method = unsafeReq.Method
req.Params = unsafeReq.Params
id, err := idFromInterface(unsafeReq.ID)
if err != nil {
return err
if wrapper.ID != nil {
id, err := idFromInterface(wrapper.ID)
if err != nil {
return fmt.Errorf("invalid request ID: %w", err)
}
req.ID = id
} }
req.ID = id
req.Method = wrapper.M
req.Params = wrapper.P
return nil return nil
} }
func NewRPCRequest(id jsonrpcid, method string, params json.RawMessage) RPCRequest {
return RPCRequest{
JSONRPC: "2.0",
ID: id,
Method: method,
Params: params,
}
// MarshalJSON marshals a request with the appropriate version tag.
func (req RPCRequest) MarshalJSON() ([]byte, error) {
return json.Marshal(rpcRequestJSON{
V: "2.0",
ID: req.ID,
M: req.Method,
P: req.Params,
})
} }
func (req RPCRequest) String() string { func (req RPCRequest) String() string {
@ -102,7 +100,11 @@ func ParamsToRequest(id jsonrpcid, method string, params interface{}) (RPCReques
if err != nil { if err != nil {
return RPCRequest{}, err return RPCRequest{}, err
} }
return NewRPCRequest(id, method, payload), nil
return RPCRequest{
ID: id,
Method: method,
Params: payload,
}, nil
} }
//---------------------------------------- //----------------------------------------
@ -123,52 +125,62 @@ func (err RPCError) Error() string {
} }
type RPCResponse struct { type RPCResponse struct {
JSONRPC string `json:"jsonrpc"`
ID jsonrpcid `json:"id,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
Error *RPCError `json:"error,omitempty"`
ID jsonrpcid
Result json.RawMessage
Error *RPCError
}
type rpcResponseJSON struct {
V string `json:"jsonrpc"` // must be "2.0"
ID interface{} `json:"id,omitempty"`
R json.RawMessage `json:"result,omitempty"`
E *RPCError `json:"error,omitempty"`
} }
// UnmarshalJSON custom JSON unmarshaling due to jsonrpcid being string or int
// UnmarshalJSON decodes a response from a JSON-RPC 2.0 response object.
func (resp *RPCResponse) UnmarshalJSON(data []byte) error { func (resp *RPCResponse) UnmarshalJSON(data []byte) error {
unsafeResp := &struct {
JSONRPC string `json:"jsonrpc"`
ID interface{} `json:"id,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
Error *RPCError `json:"error,omitempty"`
}{}
err := json.Unmarshal(data, &unsafeResp)
if err != nil {
var wrapper rpcResponseJSON
if err := json.Unmarshal(data, &wrapper); err != nil {
return err return err
} else if wrapper.V != "" && wrapper.V != "2.0" {
return fmt.Errorf("invalid version: %q", wrapper.V)
} }
resp.JSONRPC = unsafeResp.JSONRPC
resp.Error = unsafeResp.Error
resp.Result = unsafeResp.Result
if unsafeResp.ID == nil {
return nil
}
id, err := idFromInterface(unsafeResp.ID)
if err != nil {
return err
if wrapper.ID != nil {
id, err := idFromInterface(wrapper.ID)
if err != nil {
return fmt.Errorf("invalid response ID: %w", err)
}
resp.ID = id
} }
resp.ID = id
resp.Error = wrapper.E
resp.Result = wrapper.R
return nil return nil
} }
// MarshalJSON marshals a response with the appropriate version tag.
func (resp RPCResponse) MarshalJSON() ([]byte, error) {
return json.Marshal(rpcResponseJSON{
V: "2.0",
ID: resp.ID,
R: resp.Result,
E: resp.Error,
})
}
func NewRPCSuccessResponse(id jsonrpcid, res interface{}) RPCResponse { func NewRPCSuccessResponse(id jsonrpcid, res interface{}) RPCResponse {
result, err := json.Marshal(res) result, err := json.Marshal(res)
if err != nil { if err != nil {
return RPCInternalError(id, fmt.Errorf("error marshaling response: %w", err)) return RPCInternalError(id, fmt.Errorf("error marshaling response: %w", err))
} }
return RPCResponse{JSONRPC: "2.0", ID: id, Result: result}
return RPCResponse{ID: id, Result: result}
} }
func NewRPCErrorResponse(id jsonrpcid, code int, msg string, data string) RPCResponse { func NewRPCErrorResponse(id jsonrpcid, code int, msg string, data string) RPCResponse {
return RPCResponse{ return RPCResponse{
JSONRPC: "2.0",
ID: id,
Error: &RPCError{Code: code, Message: msg, Data: data},
ID: id,
Error: &RPCError{Code: code, Message: msg, Data: data},
} }
} }


Loading…
Cancel
Save