gotelem/mprpc/rpc_msg.go
2023-05-04 19:15:03 -05:00

163 lines
4 KiB
Go

package mprpc
import (
"errors"
"github.com/tinylib/msgp/msgp"
)
// this file is a simple implementation of the msgpack-rpc data formats.
// RPCType is the message type that is being sent.
type RPCType int
const (
RequestType RPCType = 0
ResponseType RPCType = 1
NotificationType RPCType = 2
)
// the messagepack RPC spec requires that the RPC wire formts are ordered arrays,
// aka tuples. we can use msgp options to make them tuple automatically,
// based on the order they are declared. This makes the order of these
// structs *critical*! Do not touch!
//go:generate msgp
//msgp:tuple Request
//msgp:tuple Response
//msgp:tuple Notification
// Request represents a function call that expects a Response.
type Request struct {
// should always be zero.
msgtype RPCType `msg:"type"`
// MsgId is used to match a Response with a Request
MsgId uint32 `msg:"msgid"`
// Method is the name of the method/service to execute on the remote
Method string `msg:"method"`
// Params is the arguments of the method/service. It can be any
// MessagePack-serializable type.
Params msgp.Raw `msg:"params,allownil"`
}
func NewRequest(msgid uint32, method string, params msgp.Raw) *Request {
return &Request{
msgtype: 0,
MsgId: msgid,
Method: method,
Params: params,
}
}
// A Response is the result and error given from calling a service.
type Response struct {
// should always be one.
msgtype RPCType `msg:"type"`
// MsgId is an identifier used to match this Response with the Request that created it.
MsgId uint32 `msg:"msgid"`
// Error is the error encountered while attempting to execute the method, if any.
Error RPCError `msg:"error,allownil"`
// Result is the raw object that was returned by the calling method. It
// can be any MessagePack-serializable object.
Result msgp.Raw `msg:"result,allownil"`
}
func NewResponse(msgid uint32, respErr RPCError, res msgp.Raw) *Response {
return &Response{
msgtype: 1,
MsgId: msgid,
Error: respErr,
Result: res,
}
}
// A notification is a function call that does not care if the call
// succeeds and ignores responses.
type Notification struct {
// should always be *2*
msgtype RPCType `msg:"type"`
Method string `msg:"method"`
Params msgp.Raw `msg:"params,allownil"`
}
func NewNotification(method string, params msgp.Raw) *Notification {
return &Notification{
msgtype: 2,
Method: method,
Params: params,
}
}
// getMsgType uses raw messagpack RPC to return the underlying message type from
// the raw array given by b.
func getMsgType(b msgp.Raw) RPCType {
size, next, err := msgp.ReadArrayHeaderBytes(b)
if err != nil {
panic(err)
}
if size == 3 { // hot path for notifications.
return NotificationType
}
vtype, _, err := msgp.ReadIntBytes(next)
if err != nil {
panic(err)
}
// todo: use readIntf instead? returns a []interface{} and we can map it ourselves...
return RPCType(vtype)
}
// parseRPC takes a raw message and decodes it based on the first value
// of the array (the type). It returns the decoded object. Callers
// can use a type-switch to determine the type of the data.
func parseRPC(raw msgp.Raw) (interface{}, error) {
t := getMsgType(raw)
switch RPCType(t) {
case RequestType:
// create and return a request struct.
req := &Request{}
_, err := req.UnmarshalMsg(raw)
return req, err
case ResponseType:
res := &Response{}
_, err := res.UnmarshalMsg(raw)
return res, err
case NotificationType:
notif := &Notification{}
_, err := notif.UnmarshalMsg(raw)
return notif, err
default:
// uh oh.
return nil, errors.New("unmatched RPC type")
}
}
//msgp:tuple RPCError
// RPCError is a common RPC error format. It is basically a clone of the
// JSON-RPC error format. We use it so we know what to expect there.
type RPCError struct {
Code int
Desc string
}
// Converts a Go error into a RPC error.
func MakeRPCError(err error) *RPCError {
if err == nil {
return nil
}
return &RPCError{
Code: -1,
Desc: err.Error(),
}
}
// Implements the Error interface for RPCError
func (r *RPCError) Error() string {
return r.Desc
}