major cleanup/refactor

delete mprpc
move can frame to it's own library
create CANID type (better extended id support)
rework database format, only uses name + json now
busEvent and rawJsonEvent don't store the Id anymore
This commit is contained in:
saji 2024-02-12 09:45:23 -06:00
parent 1812807581
commit c4bdf122a8
21 changed files with 1585 additions and 2922 deletions

View file

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"github.com/kschamplin/gotelem" "github.com/kschamplin/gotelem"
"github.com/kschamplin/gotelem/mprpc"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -35,8 +34,3 @@ func client(ctx *cli.Context) error {
return nil return nil
} }
// the client should connect to a TCP server and listen to packets.
func CANFrameHandler(f *gotelem.Frame) (*mprpc.RPCEmpty, error) {
fmt.Printf("got frame, %v\n", f)
return nil, nil
}

View file

@ -8,11 +8,13 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"regexp"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/kschamplin/gotelem/internal/can"
"github.com/kschamplin/gotelem/skylab" "github.com/kschamplin/gotelem/skylab"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/exp/slog" "golang.org/x/exp/slog"
@ -50,15 +52,164 @@ required for piping candump into skylabify. Likewise, data should be stored with
Name: "verbose", Name: "verbose",
Aliases: []string{"v"}, Aliases: []string{"v"},
}, },
&cli.StringFlag{
Name: "format",
Aliases: []string{"f"},
Usage: "the format of the incoming data. One of 'telem', 'candump'",
},
} }
app.Action = run app.Action = run
app.HideHelp = true
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
panic(err) panic(err)
} }
} }
// A FormatError is an error when parsing a format. Typically we simply ignore
// these and move on, but they can optionally wrap another error that is fatal.
type FormatError struct {
msg string
err error
}
func (e *FormatError) Error() string {
return fmt.Sprintf("%s:%s", e.msg, e.err.Error())
}
func (e *FormatError) Unwrap() error {
return e.err
}
func NewFormatError(msg string, err error) error {
return &FormatError{msg: msg, err: err}
}
// A Parser takes a string containing one line of a particular log file
// and returns an associated skylab.BusEvent representing the packet.
// if no packet is found, an error is returned instead.
type ParserFunc func(string) (skylab.BusEvent, error)
func parseCanDumpLine(dumpLine string) (b skylab.BusEvent, err error) {
// dumpline looks like this:
// (1684538768.521889) can0 200#8D643546
// remove trailing newline
dumpLine = strings.TrimSpace(dumpLine)
segments := strings.Split(dumpLine, " ")
var unixSeconds, unixMicros int64
fmt.Sscanf(segments[0], "(%d.%d)", &unixSeconds, &unixMicros)
b.Timestamp = time.Unix(unixSeconds, unixMicros)
// now we extract the remaining data:
hexes := strings.Split(segments[2], "#") // first portion is id, second is data
id, err := strconv.ParseUint(hexes[0], 16, 64)
if err != nil {
err = NewFormatError("failed to parse id", err)
return
}
if (len(hexes[1]) % 2) != 0 {
err = NewFormatError("odd number of hex characters", nil)
return
}
rawData, err := hex.DecodeString(hexes[1])
if err != nil {
err = NewFormatError("failed to decode hex data", err)
return
}
frame := can.Frame{
// TODO: fix extended ids. we assume not extended for now.
Id: can.CanID{Id: uint32(id), Extended: false},
Data: rawData,
Kind: can.CanDataFrame,
}
b.Data, err = skylab.FromCanFrame(frame)
if err != nil {
err = NewFormatError("failed to parse can frame", err)
return
}
// set the name
b.Name = b.Data.String()
return
}
func parseTelemLogLine(line string) (b skylab.BusEvent, err error) {
// strip trailng newline since we rely on it being gone
line = strings.TrimSpace(line)
// data is of the form
// 1698180835.318 0619D80564080EBE241
// the second part there is 3 nibbles (12 bits, 3 hex chars) for can ID,
// the rest is data.
// this regex does the processing.
r := regexp.MustCompile(`^(\d+).(\d{3}) (\w{3})(\w+)$`)
// these files tend to get corrupted. there are all kinds of nasties that can happen.
// defense against random panics
defer func() {
if r := recover(); r != nil {
err = NewFormatError("caught panic", nil)
}
}()
a := r.FindStringSubmatch(line)
if a == nil {
err = NewFormatError("no regex match", nil)
return
}
var unixSeconds, unixMillis int64
// note that a contains 5 elements, the first being the full match.
// so we start from the second element
unixSeconds, err = strconv.ParseInt(a[1], 10, 0)
if err != nil {
err = NewFormatError("failed to parse unix seconds", err)
return
}
unixMillis, err = strconv.ParseInt(a[2], 10, 0)
if err != nil {
err = NewFormatError("failed to parse unix millis", err)
return
}
ts := time.Unix(unixSeconds, unixMillis*1e6)
id, err := strconv.ParseUint(a[3], 16, 16)
if err != nil {
err = NewFormatError("failed to parse id", err)
return
}
if len(a[4])%2 != 0 {
// odd hex chars, protect against a panic
err = NewFormatError("wrong amount of hex chars", nil)
}
rawData, err := hex.DecodeString(a[4])
if err != nil {
err = NewFormatError("failed to parse hex data", err)
return
}
frame := can.Frame{
Id: can.CanID{Id: uint32(id), Extended: false},
Data: rawData,
Kind: can.CanDataFrame,
}
b.Timestamp = ts
b.Data, err = skylab.FromCanFrame(frame)
if err != nil {
err = NewFormatError("failed to parse can frame", err)
return
}
b.Name = b.Data.String()
return
}
var parseMap = map[string]ParserFunc{
"telem": parseTelemLogLine,
"candump": parseCanDumpLine,
}
func run(ctx *cli.Context) (err error) { func run(ctx *cli.Context) (err error) {
path := ctx.Args().Get(0) path := ctx.Args().Get(0)
if path == "" { if path == "" {
@ -66,7 +217,6 @@ func run(ctx *cli.Context) (err error) {
cli.ShowAppHelpAndExit(ctx, int(syscall.EINVAL)) cli.ShowAppHelpAndExit(ctx, int(syscall.EINVAL))
} }
var istream *os.File var istream *os.File
if path == "-" { if path == "-" {
istream = os.Stdin istream = os.Stdin
@ -77,57 +227,43 @@ func run(ctx *cli.Context) (err error) {
} }
} }
canDumpReader := bufio.NewReader(istream) fileReader := bufio.NewReader(istream)
var pfun ParserFunc
pfun, ok := parseMap[ctx.String("format")]
if !ok {
fmt.Println("invalid format!")
cli.ShowAppHelpAndExit(ctx, int(syscall.EINVAL))
}
n_err := 0
unknown_packets := 0
for { for {
// dumpline looks like this: line, err := fileReader.ReadString('\n')
// (1684538768.521889) can0 200#8D643546
dumpLine, err := canDumpReader.ReadString('\n')
if err != nil { if err != nil {
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
return nil return nil
} }
return err return err // i/o failures are fatal
} }
// remove trailing newline f, err := pfun(line)
dumpLine = strings.TrimSpace(dumpLine)
segments := strings.Split(dumpLine, " ")
var cd skylab.BusEvent
var unixSeconds, unixMicros int64
fmt.Sscanf(segments[0], "(%d.%d)", &unixSeconds, &unixMicros)
cd.Timestamp = time.Unix(unixSeconds, unixMicros*1000) // the canlog does usec precision for the decimal part.
// this is for the latter part, we need to split id/data
hexes := strings.Split(segments[2], "#")
// get the id
id, err := strconv.ParseUint(hexes[0], 16, 64)
if err != nil {
return err
}
cd.Id = uint32(id)
// get the data to a []byte
rawData, err := hex.DecodeString(hexes[1])
if err != nil {
return err
}
// parse the data []byte to a skylab packet
cd.Data, err = skylab.FromCanFrame(uint32(cd.Id), rawData)
var idErr *skylab.UnknownIdError var idErr *skylab.UnknownIdError
if errors.As(err, &idErr) { if errors.As(err, &idErr) {
// unknown id // unknown id
slog.Info("unknown id", "err", err) slog.Info("unknown id", "err", err)
unknown_packets++
continue continue
} else if err != nil { } else if err != nil {
return err // TODO: we should consider absorbing all errors.
fmt.Printf("got an error %v\n", err)
n_err++
continue
} }
// format and print out the JSON. // format and print out the JSON.
out, _ := json.Marshal(&cd) out, _ := json.Marshal(&f)
fmt.Println(string(out)) fmt.Println(string(out))
} }

View file

@ -57,6 +57,7 @@ func apiV1(broker *Broker, db *db.TelemDb) chi.Router {
// this API only accepts JSON. // this API only accepts JSON.
r.Use(middleware.AllowContentType("application/json")) r.Use(middleware.AllowContentType("application/json"))
// no caching - always get the latest data. // no caching - always get the latest data.
// TODO: add a smart short expiry cache for queries that take a while.
r.Use(middleware.NoCache) r.Use(middleware.NoCache)
r.Get("/schema", func(w http.ResponseWriter, r *http.Request) { r.Get("/schema", func(w http.ResponseWriter, r *http.Request) {
@ -78,7 +79,7 @@ func apiV1(broker *Broker, db *db.TelemDb) chi.Router {
db.AddEvents(pkgs...) db.AddEvents(pkgs...)
}) })
r.Get("/", func(w http.ResponseWriter, r *http.Request) { r.Get("/", func(w http.ResponseWriter, r *http.Request) {
// this should use http query params o return a list of packets. // this should use http query params to return a list of packets.
}) })
@ -113,6 +114,7 @@ type apiV1Subscriber struct {
idFilter []uint32 // list of Ids to subscribe to. If it's empty, subscribes to all. idFilter []uint32 // list of Ids to subscribe to. If it's empty, subscribes to all.
} }
// this is a websocket stream.
func apiV1PacketSubscribe(broker *Broker, db *db.TelemDb) http.HandlerFunc { func apiV1PacketSubscribe(broker *Broker, db *db.TelemDb) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
conn_id := r.RemoteAddr + uuid.New().String() conn_id := r.RemoteAddr + uuid.New().String()

View file

@ -2,20 +2,27 @@
// //
// It has a generic can Frame (packet), as well as a filter type. // It has a generic can Frame (packet), as well as a filter type.
// we also define standard interfaces for objects that can accept // we also define standard interfaces for objects that can accept
// can frames. We can use this pattern to easily extend the capabiltiies of the program // can frames. We can use this pattern to easily extend the capabilities of the program
// by writing "adapters" to various devices/formats (xbee, sqlite, network socket, socketcan) // by writing "adapters" to various devices/formats (xbee, socketcan)
package gotelem package can
type CanID struct {
Id uint32
Extended bool // since the id itself is not enough.
}
// Frame represents a protocol-agnostic CAN frame. The Id can be standard or extended, // Frame represents a protocol-agnostic CAN frame. The Id can be standard or extended,
// but if it is extended, the Kind should be EFF. // but if it is extended, the Kind should be EFF.
type Frame struct { type Frame struct {
Id uint32 Id CanID
Data []byte Data []byte
Kind Kind Kind Kind
} }
// TODO: should this be replaced
type CANFrame interface { type CANFrame interface {
Id() uint32 Id()
Data() []byte Data() []byte
Type() Kind Type() Kind
} }
@ -26,8 +33,7 @@ type CANFrame interface {
type Kind uint8 type Kind uint8
const ( const (
CanSFFFrame Kind = iota // Standard ID Frame CanDataFrame Kind = iota // Standard ID Frame
CanEFFFrame // Extended ID Frame
CanRTRFrame // Remote Transmission Request Frame CanRTRFrame // Remote Transmission Request Frame
CanErrFrame // Error Frame CanErrFrame // Error Frame
) )

View file

@ -49,7 +49,7 @@ func OpenTelemDb(path string, options ...TelemDbOption) (tdb *TelemDb, err error
return return
} }
// get latest version of migrations - then run the SQL in order. // get latest version of migrations - then run the SQL in order to perform them
fmt.Printf("starting version %d\n", version) fmt.Printf("starting version %d\n", version)
version, err = RunMigrations(tdb) version, err = RunMigrations(tdb)
@ -72,7 +72,7 @@ func (tdb *TelemDb) SetVersion(version int) error {
// sql expression to insert a bus event into the packets database.1 // sql expression to insert a bus event into the packets database.1
const sqlInsertEvent = ` const sqlInsertEvent = `
INSERT INTO "bus_events" (ts, id, name, data) VALUES ($1, $2, $3, json($4)); INSERT INTO "bus_events" (ts, name, data) VALUES ($1, $2, json($3));
` `
// AddEvent adds the bus event to the database. // AddEvent adds the bus event to the database.
@ -84,7 +84,12 @@ func (tdb *TelemDb) AddEventsCtx(ctx context.Context, events ...skylab.BusEvent)
return return
} }
for _, b := range events { sqlStmt := sqlInsertEvent
const rowSql = "(?, ?, json(?))"
inserts := make([]string, len(events))
vals := []interface{}{}
for idx, b := range events {
inserts[idx] = rowSql
var j []byte var j []byte
j, err = json.Marshal(b.Data) j, err = json.Marshal(b.Data)
@ -92,14 +97,23 @@ func (tdb *TelemDb) AddEventsCtx(ctx context.Context, events ...skylab.BusEvent)
tx.Rollback() tx.Rollback()
return return
} }
_, err = tx.ExecContext(ctx, sqlInsertEvent, b.Timestamp.UnixMilli(), b.Id, b.Data.String(), j) vals = append(vals, b.Timestamp.UnixMilli(), b.Data.String(), j)
}
// construct the full statement now
sqlStmt = sqlStmt + strings.Join(inserts, ",")
stmt, err := tx.PrepareContext(ctx, sqlStmt)
if err != nil {
tx.Rollback()
return
}
//TODO: log the number of rows modified/inserted
_, err = stmt.ExecContext(ctx, vals...)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return return
} }
}
tx.Commit() tx.Commit()
return return
} }
@ -109,6 +123,7 @@ func (tdb *TelemDb) AddEvents(events ...skylab.BusEvent) (err error) {
return tdb.AddEventsCtx(context.Background(), events...) return tdb.AddEventsCtx(context.Background(), events...)
} }
/// Query fragment guide: /// Query fragment guide:
/// We need to be able to easily construct safe(!) and meaningful queries programatically /// We need to be able to easily construct safe(!) and meaningful queries programatically
/// so we make some new types that can be turned into SQL fragments that go inside the where clause. /// so we make some new types that can be turned into SQL fragments that go inside the where clause.
@ -121,27 +136,6 @@ type QueryFrag interface {
Query() string Query() string
} }
// QueryIdRange represents a range of IDs to select for, inclusive.
type QueryIdRange struct {
Start uint32
End uint32
}
func (q *QueryIdRange) Query() string {
return fmt.Sprintf("id BETWEEN %d AND %d", q.Start, q.End)
}
// QueryIds selects for individual CAN ids
type QueryIds []uint32
func (q QueryIds) Query() string {
var idStrings []string
for _, id := range q {
idStrings = append(idStrings, strconv.FormatUint(uint64(id), 10))
}
return fmt.Sprintf("id IN (%s)", strings.Join(idStrings, ","))
}
// QueryTimeRange represents a query of a specific time range. For "before" or "after" queries, // QueryTimeRange represents a query of a specific time range. For "before" or "after" queries,
// use time.Unix(0,0) or time.Now() in start and end respectively. // use time.Unix(0,0) or time.Now() in start and end respectively.
type QueryTimeRange struct { type QueryTimeRange struct {
@ -204,9 +198,9 @@ func (tdb *TelemDb) GetEvents(limit int, where ...QueryFrag) (events []skylab.Bu
BusEv := skylab.BusEvent{ BusEv := skylab.BusEvent{
Timestamp: time.UnixMilli(int64(ev.Timestamp)), Timestamp: time.UnixMilli(int64(ev.Timestamp)),
Id: ev.Id, Name: ev.Name,
} }
BusEv.Data, err = skylab.FromJson(ev.Id, ev.Data) BusEv.Data, err = skylab.FromJson(ev.Name, ev.Data)
// FIXME: this is slow! // FIXME: this is slow!
events = append(events, BusEv) events = append(events, BusEv)

View file

@ -1,12 +1,11 @@
CREATE TABLE "bus_events" ( CREATE TABLE "bus_events" (
"ts" INTEGER NOT NULL, -- timestamp, unix milliseconds "ts" INTEGER NOT NULL, -- timestamp, unix milliseconds
"id" INTEGER NOT NULL, -- can ID
"name" TEXT NOT NULL, -- name of base packet "name" TEXT NOT NULL, -- name of base packet
"data" TEXT NOT NULL CHECK(json_valid(data)) -- JSON object describing the data, including index if any "data" TEXT NOT NULL CHECK(json_valid(data)) -- JSON object describing the data, including index if any
); );
CREATE INDEX "ids_timestamped" ON "bus_events" ( CREATE INDEX "ids_timestamped" ON "bus_events" (
"id", "name",
"ts" DESC "ts" DESC
); );

View file

@ -1,344 +0,0 @@
/*
mprpc is a simple bidirectional RPC library using the MessagePack-RPC spec.
It fully implements the spec and additionally provides Go `error handling by
converting the error to a standard format for other clients.
mprpc does not have a typical server/client designation - both use "handlers",
which expose methods to be called over the network. A "client" would be an
RPCConn which doesn't expose any services, and a "server" would be an RPCConn
that doesn't make any `Call`s to the other side.
This lack of discrete server and client enables mprpc to implement a basic
"streaming" architecture on top of the MessagePack-RPC spec, which does not
include streaming primitives. Instead, we can provide simple "service handlers"
as a callback/destination for streaming data.
For example, a "client" could subscribe to events from the "server", by
providing a callback service to point events to. Then, the "server" would
Notify() the callback service with the new event as an argument every time it
occured. While this may be less optimal than protocol-level streaming, it is
far simpler.
# Generic Helper Functions
The idiomatic way to use mprpc is to use the generic functions that are provided
as helpers. They allow the programmer to easily wrap existing functions in a
closure that automatically encodes and decodes the parameters and results to
their MessagePack representations. See the Make* generic functions for more
information.
// Assume myParam and myResult are MessagePack-enabled structs.
// Use `msgp` to generate the required functions for them.
// this is our plain function - we can call it locally to test.
func myPlainFunction(p myParam) (r myResult, err error)
// wrapped is a ServiceFunc that can be passed to rpcConn.RegisterHandler
var wrapped := MakeService(myPlainFunction)
The generic functions allow for flexiblity and elegant code while still keeping
the underlying implementation reflect-free. For more complex functions (i.e
multiple parameters or return types), a second layer of indirection can be used.
There is also a `MakeCaller` function that can make a stub function that handles
encoding the arguments and decoding the response for a remote procedure.
*/
package mprpc
import (
"errors"
"io"
"github.com/tinylib/msgp/msgp"
"golang.org/x/exp/slog"
)
// ServiceFunc is a RPC service handler.
// It can be created manually, or by using the generic MakeService function on a
//
// func(msgp.Encoder) (msgp.Decoder, error)
//
// type.
type ServiceFunc func(params msgp.Raw) (res msgp.Raw, err error)
// RPCConn is a single RPC communication pair.
// It is used by both the
// "server" aka listener, and client.
type RPCConn struct {
// TODO: use io.readwritecloser?
rwc io.ReadWriteCloser
handlers map[string]ServiceFunc
ct rpcConnTrack
logger slog.Logger
}
// creates a new RPC connection on top of an io.ReadWriteCloser. Can be
// pre-seeded with handlers.
func NewRPC(rwc io.ReadWriteCloser, logger *slog.Logger, initialHandlers map[string]ServiceFunc) (rpc *RPCConn, err error) {
rpc = &RPCConn{
rwc: rwc,
handlers: make(map[string]ServiceFunc),
ct: NewRPCConnTrack(),
}
if initialHandlers != nil {
for k, v := range initialHandlers {
rpc.handlers[k] = v
}
}
return
}
// Call intiates an RPC call to a remote method and returns the
// response, or the error, if any. To make calling easier, you can
// construct a "Caller" with MakeCaller
func (rpc *RPCConn) Call(method string, params msgp.Raw) (msgp.Raw, error) {
// TODO: error handling.
id, cb := rpc.ct.Claim()
req := NewRequest(id, method, params)
w := msgp.NewWriter(rpc.rwc)
req.EncodeMsg(w)
// block and wait for response.
resp := <-cb
return resp.Result, &resp.Error
}
// Notify initiates a notification to a remote method. It does not
// return any information. There is no response from the server.
// This method will not block nor will it inform the caller if any errors occur.
func (rpc *RPCConn) Notify(method string, params msgp.Raw) {
// TODO: return an error if there's a local problem?
req := NewNotification(method, params)
w := msgp.NewWriter(rpc.rwc)
req.EncodeMsg(w)
}
// Register a new handler to be called by the remote side. An error
// is returned if the handler name is already in use.
func (rpc *RPCConn) RegisterHandler(name string, fn ServiceFunc) error {
// TODO: check if name in use.
// TODO: mutex lock for sync (or use sync.map?
rpc.handlers[name] = fn
rpc.logger.Info("registered a new handler", "name", name, "fn", fn)
return nil
}
// Removes a handler, if it exists. Never errors. No-op if the name
// is not a registered handler.
func (rpc *RPCConn) RemoveHandler(name string) error {
delete(rpc.handlers, name)
return nil
}
// Serve runs the server. It will dispatch goroutines to handle each method
// call. This can (and should in most cases) be run in the background to allow
// for sending and receving on the same connection.
func (rpc *RPCConn) Serve() {
// construct a stream reader.
msgReader := msgp.NewReader(rpc.rwc)
// read a request/notification from the connection.
var rawmsg msgp.Raw = make(msgp.Raw, 0, 4)
for {
err := rawmsg.DecodeMsg(msgReader)
if err != nil {
if errors.Is(err, io.EOF) {
rpc.logger.Info("reached EOF, stopping server")
return
}
rpc.logger.Warn("error decoding message", "err", err)
continue
}
rpcIntf, err := parseRPC(rawmsg)
if err != nil {
rpc.logger.Warn("Could not parse RPC message", "err", err)
continue
}
switch rpcObject := rpcIntf.(type) {
case Request:
// the object is a request - we must dispatch a goroutine
// that will call the handler and also send a return value.
go rpc.dispatch(rpcObject)
case Notification:
go rpc.dispatchNotif(rpcObject)
case Response:
cbCh, err := rpc.ct.Clear(rpcObject.MsgId)
if err != nil {
rpc.logger.Warn("could not get rpc callback", "msgid", rpcObject.MsgId, "err", err)
continue
}
cbCh <- rpcObject
default:
panic("invalid rpcObject!")
}
}
}
// INTERNAL functions for rpcConn
// dispatch is an internal method used to execute a Request sent by the remote:w
func (rpc *RPCConn) dispatch(req Request) {
result, err := rpc.handlers[req.Method](req.Params)
if err != nil {
rpc.logger.Warn("error dispatching rpc function", "method", req.Method, "err", err)
}
// construct the response frame.
var rpcE *RPCError = MakeRPCError(err)
w := msgp.NewWriter(rpc.rwc)
response := NewResponse(req.MsgId, *rpcE, result)
response.EncodeMsg(w)
}
// dispatchNotif is like dispatch, but for Notifications. This means that it never replies,
// even if there is an error.
func (rpc *RPCConn) dispatchNotif(req Notification) {
_, err := rpc.handlers[req.Method](req.Params)
if err != nil {
// log the error, but don't do anything about it.
rpc.logger.Warn("error dispatching rpc function", "method", req.Method, "err", err)
}
}
// Next, we define some helper generic functions that can be used to make
// implementing a msg wrapper easier.
// msgpackObject is anything that has implemented all the msgpack interfaces.
type msgpackObject interface {
msgp.Decodable
msgp.Encodable
msgp.MarshalSizer
msgp.Unmarshaler
}
// MakeService is a generic wrapper function. It takes a function with the signature
// of func(T msgpObject)(R msgpObject, error) where T and R can be *concrete* types.
// and returns a new function that handles conversion to/from msgp.Raw.
// The function returned can be used by the RPCConn as a handler function.
// This function can typically have it's paramters inferred.
func MakeService[T, R msgpackObject](fn func(T) (R, error)) ServiceFunc {
return func(p msgp.Raw) (msgp.Raw, error) {
// decode the raw data into a new underlying type.
var params T
_, err := params.UnmarshalMsg(p)
if err != nil {
return nil, err
}
// now, call the function fn with the given params, and record the value.
resp, err := fn(params)
if err != nil {
return nil, err
}
return resp.MarshalMsg([]byte{})
}
}
// should the RPCConn/method name be baked into the function or should they be
// part of the returned function paramters?
// MakeCaller creates a simple wrapper around a parameter of call. The method name
// and RPC connection can be given to the returned function to make a RPC call on that
// function with the given type parameters.
//
// This function is slightly obtuse compared to MakeBoundCaller but is more flexible
// since you can reuse the same function across multiple connections and method names.
//
// This generic function must always have it's type paratmers declared explicitly.
// They cannot be inferred from the given parameters.
func MakeCaller[T, R msgpackObject]() func(string, T, *RPCConn) (R, error) {
return func(method string, param T, rpc *RPCConn) (R, error) {
rawParam, err := param.MarshalMsg([]byte{})
if err != nil {
var emtpyR R
return emtpyR, err
}
rawResponse, err := rpc.Call(method, rawParam)
if err != nil {
var emtpyR R
return emtpyR, err
}
var resp R
_, err = resp.UnmarshalMsg(rawResponse)
return resp, err
}
}
// MakeBoundCaller is like MakeCaller, except the RPC connection and method name are
// fixed and cannot be adjusted later. This function is more elegant but less flexible
// than MakeCaller and should be used when performance is not critical.
//
// This generic function must always have it's type paratmers declared explicitly.
// They cannot be inferred from the given parameters.
func MakeBoundCaller[T, R msgpackObject](rpc *RPCConn, method string) func(T) (R, error) {
return func(param T) (R, error) {
// encode parameters
// invoke rpc.Call
// await response
// unpack values.
rawParam, _ := param.MarshalMsg([]byte{})
rawResponse, err := rpc.Call(method, rawParam)
if err != nil {
var emtpyR R
return emtpyR, err
}
var resp R
_, err = resp.UnmarshalMsg(rawResponse)
return resp, err
}
}
// MakeNotifier creates a new notification function that notifies the remote
func MakeNotifier[T msgpackObject](method string) func(T, *RPCConn) error {
return func(param T, rpc *RPCConn) error {
rawParam, err := param.MarshalMsg([]byte{})
rpc.Notify(method, rawParam)
return err
}
}

View file

@ -1,170 +0,0 @@
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
}
// we need to describe an empty data that will be excluded in the msgp
// for functions without an argument or return value.
type RPCEmpty struct {
}

View file

@ -1,577 +0,0 @@
package mprpc
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"github.com/tinylib/msgp/msgp"
)
// DecodeMsg implements msgp.Decodable
func (z *Notification) DecodeMsg(dc *msgp.Reader) (err error) {
var zb0001 uint32
zb0001, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 2 {
err = msgp.ArrayError{Wanted: 2, Got: zb0001}
return
}
z.Method, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Method")
return
}
err = z.Params.DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, "Params")
return
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *Notification) EncodeMsg(en *msgp.Writer) (err error) {
// array header, size 2
err = en.Append(0x92)
if err != nil {
return
}
err = en.WriteString(z.Method)
if err != nil {
err = msgp.WrapError(err, "Method")
return
}
err = z.Params.EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, "Params")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *Notification) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// array header, size 2
o = append(o, 0x92)
o = msgp.AppendString(o, z.Method)
o, err = z.Params.MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, "Params")
return
}
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *Notification) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0001 uint32
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 2 {
err = msgp.ArrayError{Wanted: 2, Got: zb0001}
return
}
z.Method, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Method")
return
}
bts, err = z.Params.UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "Params")
return
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *Notification) Msgsize() (s int) {
s = 1 + msgp.StringPrefixSize + len(z.Method) + z.Params.Msgsize()
return
}
// DecodeMsg implements msgp.Decodable
func (z *RPCEmpty) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
return
}
// EncodeMsg implements msgp.Encodable
func (z RPCEmpty) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 0
err = en.Append(0x80)
if err != nil {
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z RPCEmpty) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 0
o = append(o, 0x80)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *RPCEmpty) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z RPCEmpty) Msgsize() (s int) {
s = 1
return
}
// DecodeMsg implements msgp.Decodable
func (z *RPCError) DecodeMsg(dc *msgp.Reader) (err error) {
var zb0001 uint32
zb0001, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 2 {
err = msgp.ArrayError{Wanted: 2, Got: zb0001}
return
}
z.Code, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "Code")
return
}
z.Desc, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Desc")
return
}
return
}
// EncodeMsg implements msgp.Encodable
func (z RPCError) EncodeMsg(en *msgp.Writer) (err error) {
// array header, size 2
err = en.Append(0x92)
if err != nil {
return
}
err = en.WriteInt(z.Code)
if err != nil {
err = msgp.WrapError(err, "Code")
return
}
err = en.WriteString(z.Desc)
if err != nil {
err = msgp.WrapError(err, "Desc")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z RPCError) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// array header, size 2
o = append(o, 0x92)
o = msgp.AppendInt(o, z.Code)
o = msgp.AppendString(o, z.Desc)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *RPCError) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0001 uint32
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 2 {
err = msgp.ArrayError{Wanted: 2, Got: zb0001}
return
}
z.Code, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Code")
return
}
z.Desc, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Desc")
return
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z RPCError) Msgsize() (s int) {
s = 1 + msgp.IntSize + msgp.StringPrefixSize + len(z.Desc)
return
}
// DecodeMsg implements msgp.Decodable
func (z *RPCType) DecodeMsg(dc *msgp.Reader) (err error) {
{
var zb0001 int
zb0001, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err)
return
}
(*z) = RPCType(zb0001)
}
return
}
// EncodeMsg implements msgp.Encodable
func (z RPCType) EncodeMsg(en *msgp.Writer) (err error) {
err = en.WriteInt(int(z))
if err != nil {
err = msgp.WrapError(err)
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z RPCType) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
o = msgp.AppendInt(o, int(z))
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *RPCType) UnmarshalMsg(bts []byte) (o []byte, err error) {
{
var zb0001 int
zb0001, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
(*z) = RPCType(zb0001)
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z RPCType) Msgsize() (s int) {
s = msgp.IntSize
return
}
// DecodeMsg implements msgp.Decodable
func (z *Request) DecodeMsg(dc *msgp.Reader) (err error) {
var zb0001 uint32
zb0001, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 3 {
err = msgp.ArrayError{Wanted: 3, Got: zb0001}
return
}
z.MsgId, err = dc.ReadUint32()
if err != nil {
err = msgp.WrapError(err, "MsgId")
return
}
z.Method, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Method")
return
}
err = z.Params.DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, "Params")
return
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *Request) EncodeMsg(en *msgp.Writer) (err error) {
// array header, size 3
err = en.Append(0x93)
if err != nil {
return
}
err = en.WriteUint32(z.MsgId)
if err != nil {
err = msgp.WrapError(err, "MsgId")
return
}
err = en.WriteString(z.Method)
if err != nil {
err = msgp.WrapError(err, "Method")
return
}
err = z.Params.EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, "Params")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *Request) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// array header, size 3
o = append(o, 0x93)
o = msgp.AppendUint32(o, z.MsgId)
o = msgp.AppendString(o, z.Method)
o, err = z.Params.MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, "Params")
return
}
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *Request) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0001 uint32
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 3 {
err = msgp.ArrayError{Wanted: 3, Got: zb0001}
return
}
z.MsgId, bts, err = msgp.ReadUint32Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "MsgId")
return
}
z.Method, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Method")
return
}
bts, err = z.Params.UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "Params")
return
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *Request) Msgsize() (s int) {
s = 1 + msgp.Uint32Size + msgp.StringPrefixSize + len(z.Method) + z.Params.Msgsize()
return
}
// DecodeMsg implements msgp.Decodable
func (z *Response) DecodeMsg(dc *msgp.Reader) (err error) {
var zb0001 uint32
zb0001, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 3 {
err = msgp.ArrayError{Wanted: 3, Got: zb0001}
return
}
z.MsgId, err = dc.ReadUint32()
if err != nil {
err = msgp.WrapError(err, "MsgId")
return
}
var zb0002 uint32
zb0002, err = dc.ReadArrayHeader()
if err != nil {
err = msgp.WrapError(err, "Error")
return
}
if zb0002 != 2 {
err = msgp.ArrayError{Wanted: 2, Got: zb0002}
return
}
z.Error.Code, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "Error", "Code")
return
}
z.Error.Desc, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Error", "Desc")
return
}
err = z.Result.DecodeMsg(dc)
if err != nil {
err = msgp.WrapError(err, "Result")
return
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *Response) EncodeMsg(en *msgp.Writer) (err error) {
// array header, size 3
err = en.Append(0x93)
if err != nil {
return
}
err = en.WriteUint32(z.MsgId)
if err != nil {
err = msgp.WrapError(err, "MsgId")
return
}
// array header, size 2
err = en.Append(0x92)
if err != nil {
return
}
err = en.WriteInt(z.Error.Code)
if err != nil {
err = msgp.WrapError(err, "Error", "Code")
return
}
err = en.WriteString(z.Error.Desc)
if err != nil {
err = msgp.WrapError(err, "Error", "Desc")
return
}
err = z.Result.EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, "Result")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *Response) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// array header, size 3
o = append(o, 0x93)
o = msgp.AppendUint32(o, z.MsgId)
// array header, size 2
o = append(o, 0x92)
o = msgp.AppendInt(o, z.Error.Code)
o = msgp.AppendString(o, z.Error.Desc)
o, err = z.Result.MarshalMsg(o)
if err != nil {
err = msgp.WrapError(err, "Result")
return
}
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *Response) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0001 uint32
zb0001, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
if zb0001 != 3 {
err = msgp.ArrayError{Wanted: 3, Got: zb0001}
return
}
z.MsgId, bts, err = msgp.ReadUint32Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "MsgId")
return
}
var zb0002 uint32
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Error")
return
}
if zb0002 != 2 {
err = msgp.ArrayError{Wanted: 2, Got: zb0002}
return
}
z.Error.Code, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Error", "Code")
return
}
z.Error.Desc, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Error", "Desc")
return
}
bts, err = z.Result.UnmarshalMsg(bts)
if err != nil {
err = msgp.WrapError(err, "Result")
return
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *Response) Msgsize() (s int) {
s = 1 + msgp.Uint32Size + 1 + msgp.IntSize + msgp.StringPrefixSize + len(z.Error.Desc) + z.Result.Msgsize()
return
}

View file

@ -1,575 +0,0 @@
package mprpc
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"bytes"
"testing"
"github.com/tinylib/msgp/msgp"
)
func TestMarshalUnmarshalNotification(t *testing.T) {
v := Notification{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsgNotification(b *testing.B) {
v := Notification{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsgNotification(b *testing.B) {
v := Notification{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshalNotification(b *testing.B) {
v := Notification{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodeNotification(t *testing.T) {
v := Notification{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodeNotification Msgsize() is inaccurate")
}
vn := Notification{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodeNotification(b *testing.B) {
v := Notification{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodeNotification(b *testing.B) {
v := Notification{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}
func TestMarshalUnmarshalRPCEmpty(t *testing.T) {
v := RPCEmpty{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsgRPCEmpty(b *testing.B) {
v := RPCEmpty{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsgRPCEmpty(b *testing.B) {
v := RPCEmpty{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshalRPCEmpty(b *testing.B) {
v := RPCEmpty{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodeRPCEmpty(t *testing.T) {
v := RPCEmpty{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodeRPCEmpty Msgsize() is inaccurate")
}
vn := RPCEmpty{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodeRPCEmpty(b *testing.B) {
v := RPCEmpty{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodeRPCEmpty(b *testing.B) {
v := RPCEmpty{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}
func TestMarshalUnmarshalRPCError(t *testing.T) {
v := RPCError{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsgRPCError(b *testing.B) {
v := RPCError{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsgRPCError(b *testing.B) {
v := RPCError{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshalRPCError(b *testing.B) {
v := RPCError{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodeRPCError(t *testing.T) {
v := RPCError{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodeRPCError Msgsize() is inaccurate")
}
vn := RPCError{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodeRPCError(b *testing.B) {
v := RPCError{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodeRPCError(b *testing.B) {
v := RPCError{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}
func TestMarshalUnmarshalRequest(t *testing.T) {
v := Request{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsgRequest(b *testing.B) {
v := Request{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsgRequest(b *testing.B) {
v := Request{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshalRequest(b *testing.B) {
v := Request{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodeRequest(t *testing.T) {
v := Request{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodeRequest Msgsize() is inaccurate")
}
vn := Request{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodeRequest(b *testing.B) {
v := Request{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodeRequest(b *testing.B) {
v := Request{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}
func TestMarshalUnmarshalResponse(t *testing.T) {
v := Response{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsgResponse(b *testing.B) {
v := Response{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsgResponse(b *testing.B) {
v := Response{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshalResponse(b *testing.B) {
v := Response{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodeResponse(t *testing.T) {
v := Response{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodeResponse Msgsize() is inaccurate")
}
vn := Response{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodeResponse(b *testing.B) {
v := Response{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodeResponse(b *testing.B) {
v := Response{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}

View file

@ -1 +0,0 @@
package mprpc_test

View file

@ -1,69 +0,0 @@
package mprpc
import (
"errors"
"math/rand"
"sync"
)
// RPCConntrack is a request-response tracker that is used to connect
// the response to the appropriate caller.
type rpcConnTrack struct {
ct map[uint32]chan Response
mu sync.RWMutex
}
func NewRPCConnTrack() rpcConnTrack {
return rpcConnTrack{
ct: make(map[uint32]chan Response),
}
}
// Get attempts to get a random mark from the mutex.
func (c *rpcConnTrack) Claim() (uint32, chan Response) {
var val uint32
for {
//
newVal := rand.Uint32()
// BUG(saji): rpcConnTrack collisions are inefficient.
// collision is *rare* - so we just try again.
// I hope to god you don't saturate this tracker.
c.mu.RLock()
if _, exist := c.ct[newVal]; !exist {
val = newVal
c.mu.RUnlock()
break
}
c.mu.RUnlock()
}
// claim it
// the channel should be buffered. We only expect one value to go through.
// so the size is fixed to 1.
ch := make(chan Response, 1)
c.mu.Lock()
c.ct[val] = ch
c.mu.Unlock()
return val, ch
}
// Clear deletes the connection from the tracker and returns the channel
// associated with it. The caller can use the channel afterwards
// to send the response. It is the caller's responsibility to close the channel.
func (c *rpcConnTrack) Clear(val uint32) (chan Response, error) {
c.mu.RLock()
ch, ok := c.ct[val]
c.mu.RUnlock()
if !ok {
return nil, errors.New("invalid msg id")
}
c.mu.Lock()
delete(c.ct, val)
c.mu.Unlock()
return ch, nil
}

View file

@ -18,36 +18,37 @@ import (
// SkylabFile is a yaml file from skylab. // SkylabFile is a yaml file from skylab.
type SkylabFile struct { type SkylabFile struct {
Packets []PacketDef `json:"packets"` Packets []PacketDef `yaml:"packets"`
Boards []BoardDef `json:"boards"` Boards []BoardDef `yaml:"boards"`
} }
type BoardDef struct { type BoardDef struct {
Name string `json:"name"` Name string `yaml:"name"`
Transmit []string `json:"transmit"` Transmit []string `yaml:"transmit"`
Receive []string `json:"receive"` Receive []string `yaml:"receive"`
} }
// data field. // data field.
type FieldDef struct { type FieldDef struct {
Name string `json:"name"` Name string `yaml:"name"`
Type string `json:"type"` Type string `yaml:"type"`
Units string `json:"units"` Units string `yaml:"units"`
Conversion float32 `json:"conversion"` Conversion float32 `yaml:"conversion"`
Bits []struct { Bits []struct {
Name string `json:"name"` Name string `yaml:"name"`
} `json:"bits"` } `yaml:"bits"`
} }
// a PacketDef is a full can packet. // a PacketDef is a full can packet.
type PacketDef struct { type PacketDef struct {
Name string `json:"name"` Name string `yaml:"name"`
Description string `json:"description"` Description string `yaml:"description"`
Id uint32 `json:"id"` Id uint32 `yaml:"id"`
Endian string `json:"endian"` Endian string `yaml:"endian"`
Repeat int `json:"repeat"` Extended bool `yaml:"is_extended"`
Offset int `json:"offset"` Repeat int `yaml:"repeat"`
Data []FieldDef `json:"data"` Offset int `yaml:"offset"`
Data []FieldDef `yaml:"data"`
} }
// we need to generate bitfield types. // we need to generate bitfield types.
@ -273,6 +274,20 @@ func mapf(format string, els []int) []string {
return resp return resp
} }
func idToString(p PacketDef) string {
if p.Repeat > 0 {
resp := make([]string, p.Repeat)
for idx := 0; idx < p.Repeat; idx++ {
resp[idx] = fmt.Sprintf("can.CanID{ Id: 0x%X, Extended: %t }", int(p.Id)+idx*p.Offset, p.Extended)
}
return strings.Join(resp, ",")
} else {
return fmt.Sprintf("can.CanID{ Id: 0x%X, Extended: %t }", p.Id, p.Extended)
}
}
func main() { func main() {
// read path as the first arg, glob it for yamls, read each yaml into a skylabFile. // read path as the first arg, glob it for yamls, read each yaml into a skylabFile.
// then take each skylab file, put all the packets into one big array. // then take each skylab file, put all the packets into one big array.
@ -318,6 +333,7 @@ func main() {
"mapf": mapf, "mapf": mapf,
"maptype": MapType, "maptype": MapType,
"json": json.Marshal, "json": json.Marshal,
"idToString": idToString,
} }
tmpl, err := template.New("golang.go.tmpl").Funcs(fnMap).ParseGlob("templates/*.go.tmpl") tmpl, err := template.New("golang.go.tmpl").Funcs(fnMap).ParseGlob("templates/*.go.tmpl")

View file

@ -84,18 +84,16 @@ func ToCanFrame(p Packet) (id uint32, data []byte, err error) {
// ---- other wire encoding business ---- // ---- other wire encoding business ----
// internal structure for partially decoding json object. // internal structure for partially decoding json object.
// includes
type RawJsonEvent struct { type RawJsonEvent struct {
Timestamp int64 `json:"ts" db:"ts"` Timestamp int64 `json:"ts" db:"ts"`
Id uint32 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Data json.RawMessage `json:"data"` Data json.RawMessage `json:"data"`
} }
// BusEvent is a timestamped Skylab packet // BusEvent is a timestamped Skylab packet - it contains
type BusEvent struct { type BusEvent struct {
Timestamp time.Time `json:"ts"` Timestamp time.Time `json:"ts"`
Id uint32 `json:"id"` Name string `json:"id"`
Data Packet `json:"data"` Data Packet `json:"data"`
} }
@ -103,10 +101,10 @@ func (e BusEvent) MarshalJSON() (b []byte, err error) {
// create the underlying raw event // create the underlying raw event
j := &RawJsonEvent{ j := &RawJsonEvent{
Timestamp: e.Timestamp.UnixMilli(), Timestamp: e.Timestamp.UnixMilli(),
Id: e.Id, Name: e.Name,
Name: e.Data.String(),
} }
// now we use the magic Packet -> map[string]interface{} function // now we use the magic Packet -> map[string]interface{} function
// FIXME: this uses reflection and isn't good for the economy
j.Data, err = json.Marshal(e.Data) j.Data, err = json.Marshal(e.Data)
if err != nil { if err != nil {
return nil, err return nil, err
@ -126,42 +124,12 @@ func (e *BusEvent) UnmarshalJSON(b []byte) error {
} }
e.Timestamp = time.UnixMilli(j.Timestamp) e.Timestamp = time.UnixMilli(j.Timestamp)
e.Id = j.Id e.Name = j.Name
e.Data, err = FromJson(j.Id, j.Data) e.Data, err = FromJson(j.Name, j.Data)
return err return err
} }
func (e *BusEvent) MarshalMsg(b []byte) ([]byte, error) {
// we need to send the bytes as a []byte instead of
// an object like the JSON one (lose self-documenting)
data, err := e.Data.MarshalPacket()
if err != nil {
return nil, err
}
rawEv := &msgpRawEvent{
Timestamp: uint32(e.Timestamp.UnixMilli()),
Id: uint32(e.Id),
Data: data,
}
return rawEv.MarshalMsg(b)
}
func (e *BusEvent) UnmarshalMsg(b []byte) ([]byte, error) {
rawEv := &msgpRawEvent{}
remain, err := rawEv.UnmarshalMsg(b)
if err != nil {
return remain, err
}
e.Timestamp = time.UnixMilli(int64(rawEv.Timestamp))
e.Id = rawEv.Id
e.Data, err = FromCanFrame(rawEv.Id, rawEv.Data)
return remain, err
}
// we need to be able to parse the JSON as well. this is done using the // we need to be able to parse the JSON as well. this is done using the
// generator since we can use the switch/case thing since it's the fastest // generator since we can use the switch/case thing since it's the fastest

File diff suppressed because one or more lines are too long

View file

@ -402,6 +402,78 @@ func TestJSONBmsChargerResponse(t *testing.T) {
} }
}
func TestMarshalUnmarshalChassisIsolationFault(t *testing.T) {
v := &ChassisIsolationFault{}
bin, err := v.MarshalPacket()
if err != nil {
t.Fatal(err)
}
err = v.UnmarshalPacket(bin)
if err != nil {
t.Fatal(err)
}
}
func TestJSONChassisIsolationFault(t *testing.T) {
v := &ChassisIsolationFault{}
rawData, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
id, _ := v.CANId()
p, err := FromJson(id, rawData)
if err != nil {
t.Fatal(err)
}
switch underlying := p.(type) {
case *ChassisIsolationFault:
break
default:
t.Fatalf("didn't match type: %T, %v", underlying, underlying)
}
}
func TestMarshalUnmarshalBmsImdInfo(t *testing.T) {
v := &BmsImdInfo{}
bin, err := v.MarshalPacket()
if err != nil {
t.Fatal(err)
}
err = v.UnmarshalPacket(bin)
if err != nil {
t.Fatal(err)
}
}
func TestJSONBmsImdInfo(t *testing.T) {
v := &BmsImdInfo{}
rawData, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
id, _ := v.CANId()
p, err := FromJson(id, rawData)
if err != nil {
t.Fatal(err)
}
switch underlying := p.(type) {
case *BmsImdInfo:
break
default:
t.Fatalf("didn't match type: %T, %v", underlying, underlying)
}
} }
func TestMarshalUnmarshalDashboardPedalPercentages(t *testing.T) { func TestMarshalUnmarshalDashboardPedalPercentages(t *testing.T) {
v := &DashboardPedalPercentages{} v := &DashboardPedalPercentages{}
@ -762,6 +834,78 @@ func TestJSONArrayPower(t *testing.T) {
} }
}
func TestMarshalUnmarshalArrayEnergy(t *testing.T) {
v := &ArrayEnergy{}
bin, err := v.MarshalPacket()
if err != nil {
t.Fatal(err)
}
err = v.UnmarshalPacket(bin)
if err != nil {
t.Fatal(err)
}
}
func TestJSONArrayEnergy(t *testing.T) {
v := &ArrayEnergy{}
rawData, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
id, _ := v.CANId()
p, err := FromJson(id, rawData)
if err != nil {
t.Fatal(err)
}
switch underlying := p.(type) {
case *ArrayEnergy:
break
default:
t.Fatalf("didn't match type: %T, %v", underlying, underlying)
}
}
func TestMarshalUnmarshalArrayEnergyReset(t *testing.T) {
v := &ArrayEnergyReset{}
bin, err := v.MarshalPacket()
if err != nil {
t.Fatal(err)
}
err = v.UnmarshalPacket(bin)
if err != nil {
t.Fatal(err)
}
}
func TestJSONArrayEnergyReset(t *testing.T) {
v := &ArrayEnergyReset{}
rawData, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
id, _ := v.CANId()
p, err := FromJson(id, rawData)
if err != nil {
t.Fatal(err)
}
switch underlying := p.(type) {
case *ArrayEnergyReset:
break
default:
t.Fatalf("didn't match type: %T, %v", underlying, underlying)
}
} }
func TestMarshalUnmarshalVisionTurnSignalsCommand(t *testing.T) { func TestMarshalUnmarshalVisionTurnSignalsCommand(t *testing.T) {
v := &VisionTurnSignalsCommand{} v := &VisionTurnSignalsCommand{}
@ -1663,8 +1807,8 @@ func TestJSONTrackerData(t *testing.T) {
} }
func TestMarshalUnmarshalTritiumMotorDrive(t *testing.T) { func TestMarshalUnmarshalTritiumMotorDriveL(t *testing.T) {
v := &TritiumMotorDrive{} v := &TritiumMotorDriveL{}
bin, err := v.MarshalPacket() bin, err := v.MarshalPacket()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -1675,9 +1819,9 @@ func TestMarshalUnmarshalTritiumMotorDrive(t *testing.T) {
} }
} }
func TestJSONTritiumMotorDrive(t *testing.T) { func TestJSONTritiumMotorDriveL(t *testing.T) {
v := &TritiumMotorDrive{} v := &TritiumMotorDriveL{}
rawData, err := json.Marshal(v) rawData, err := json.Marshal(v)
if err != nil { if err != nil {
@ -1691,7 +1835,7 @@ func TestJSONTritiumMotorDrive(t *testing.T) {
} }
switch underlying := p.(type) { switch underlying := p.(type) {
case *TritiumMotorDrive: case *TritiumMotorDriveL:
break break
default: default:
t.Fatalf("didn't match type: %T, %v", underlying, underlying) t.Fatalf("didn't match type: %T, %v", underlying, underlying)
@ -1699,8 +1843,8 @@ func TestJSONTritiumMotorDrive(t *testing.T) {
} }
func TestMarshalUnmarshalTritiumMotorPower(t *testing.T) { func TestMarshalUnmarshalTritiumMotorPowerL(t *testing.T) {
v := &TritiumMotorPower{} v := &TritiumMotorPowerL{}
bin, err := v.MarshalPacket() bin, err := v.MarshalPacket()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -1711,9 +1855,9 @@ func TestMarshalUnmarshalTritiumMotorPower(t *testing.T) {
} }
} }
func TestJSONTritiumMotorPower(t *testing.T) { func TestJSONTritiumMotorPowerL(t *testing.T) {
v := &TritiumMotorPower{} v := &TritiumMotorPowerL{}
rawData, err := json.Marshal(v) rawData, err := json.Marshal(v)
if err != nil { if err != nil {
@ -1727,7 +1871,7 @@ func TestJSONTritiumMotorPower(t *testing.T) {
} }
switch underlying := p.(type) { switch underlying := p.(type) {
case *TritiumMotorPower: case *TritiumMotorPowerL:
break break
default: default:
t.Fatalf("didn't match type: %T, %v", underlying, underlying) t.Fatalf("didn't match type: %T, %v", underlying, underlying)
@ -1735,8 +1879,8 @@ func TestJSONTritiumMotorPower(t *testing.T) {
} }
func TestMarshalUnmarshalTritiumReset(t *testing.T) { func TestMarshalUnmarshalTritiumResetL(t *testing.T) {
v := &TritiumReset{} v := &TritiumResetL{}
bin, err := v.MarshalPacket() bin, err := v.MarshalPacket()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -1747,9 +1891,9 @@ func TestMarshalUnmarshalTritiumReset(t *testing.T) {
} }
} }
func TestJSONTritiumReset(t *testing.T) { func TestJSONTritiumResetL(t *testing.T) {
v := &TritiumReset{} v := &TritiumResetL{}
rawData, err := json.Marshal(v) rawData, err := json.Marshal(v)
if err != nil { if err != nil {
@ -1763,7 +1907,115 @@ func TestJSONTritiumReset(t *testing.T) {
} }
switch underlying := p.(type) { switch underlying := p.(type) {
case *TritiumReset: case *TritiumResetL:
break
default:
t.Fatalf("didn't match type: %T, %v", underlying, underlying)
}
}
func TestMarshalUnmarshalTritiumMotorDriveR(t *testing.T) {
v := &TritiumMotorDriveR{}
bin, err := v.MarshalPacket()
if err != nil {
t.Fatal(err)
}
err = v.UnmarshalPacket(bin)
if err != nil {
t.Fatal(err)
}
}
func TestJSONTritiumMotorDriveR(t *testing.T) {
v := &TritiumMotorDriveR{}
rawData, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
id, _ := v.CANId()
p, err := FromJson(id, rawData)
if err != nil {
t.Fatal(err)
}
switch underlying := p.(type) {
case *TritiumMotorDriveR:
break
default:
t.Fatalf("didn't match type: %T, %v", underlying, underlying)
}
}
func TestMarshalUnmarshalTritiumMotorPowerR(t *testing.T) {
v := &TritiumMotorPowerR{}
bin, err := v.MarshalPacket()
if err != nil {
t.Fatal(err)
}
err = v.UnmarshalPacket(bin)
if err != nil {
t.Fatal(err)
}
}
func TestJSONTritiumMotorPowerR(t *testing.T) {
v := &TritiumMotorPowerR{}
rawData, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
id, _ := v.CANId()
p, err := FromJson(id, rawData)
if err != nil {
t.Fatal(err)
}
switch underlying := p.(type) {
case *TritiumMotorPowerR:
break
default:
t.Fatalf("didn't match type: %T, %v", underlying, underlying)
}
}
func TestMarshalUnmarshalTritiumResetR(t *testing.T) {
v := &TritiumResetR{}
bin, err := v.MarshalPacket()
if err != nil {
t.Fatal(err)
}
err = v.UnmarshalPacket(bin)
if err != nil {
t.Fatal(err)
}
}
func TestJSONTritiumResetR(t *testing.T) {
v := &TritiumResetR{}
rawData, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
id, _ := v.CANId()
p, err := FromJson(id, rawData)
if err != nil {
t.Fatal(err)
}
switch underlying := p.(type) {
case *TritiumResetR:
break break
default: default:
t.Fatalf("didn't match type: %T, %v", underlying, underlying) t.Fatalf("didn't match type: %T, %v", underlying, underlying)

View file

@ -1,18 +0,0 @@
package skylab
//go:generate msgp -unexported
// internal structure for handling
type msgpRawEvent struct {
Timestamp uint32 `msg:"ts"`
Id uint32 `msg:"id"`
Data []byte `msg:"data"`
}
// internal structure to represent a raw can packet over the network.
// this is what's sent over the solar car to lead xbee connection
// for brevity while still having some robustness.
type msgpRawPacket struct {
Id uint32 `msg:"id"`
Data []byte `msg:"data"`
}

View file

@ -1,288 +0,0 @@
package skylab
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"github.com/tinylib/msgp/msgp"
)
// DecodeMsg implements msgp.Decodable
func (z *msgpRawEvent) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "ts":
z.Timestamp, err = dc.ReadUint32()
if err != nil {
err = msgp.WrapError(err, "Timestamp")
return
}
case "id":
z.Id, err = dc.ReadUint32()
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
case "data":
z.Data, err = dc.ReadBytes(z.Data)
if err != nil {
err = msgp.WrapError(err, "Data")
return
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *msgpRawEvent) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 3
// write "ts"
err = en.Append(0x83, 0xa2, 0x74, 0x73)
if err != nil {
return
}
err = en.WriteUint32(z.Timestamp)
if err != nil {
err = msgp.WrapError(err, "Timestamp")
return
}
// write "id"
err = en.Append(0xa2, 0x69, 0x64)
if err != nil {
return
}
err = en.WriteUint32(z.Id)
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
// write "data"
err = en.Append(0xa4, 0x64, 0x61, 0x74, 0x61)
if err != nil {
return
}
err = en.WriteBytes(z.Data)
if err != nil {
err = msgp.WrapError(err, "Data")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *msgpRawEvent) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 3
// string "ts"
o = append(o, 0x83, 0xa2, 0x74, 0x73)
o = msgp.AppendUint32(o, z.Timestamp)
// string "id"
o = append(o, 0xa2, 0x69, 0x64)
o = msgp.AppendUint32(o, z.Id)
// string "data"
o = append(o, 0xa4, 0x64, 0x61, 0x74, 0x61)
o = msgp.AppendBytes(o, z.Data)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *msgpRawEvent) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "ts":
z.Timestamp, bts, err = msgp.ReadUint32Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "Timestamp")
return
}
case "id":
z.Id, bts, err = msgp.ReadUint32Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
case "data":
z.Data, bts, err = msgp.ReadBytesBytes(bts, z.Data)
if err != nil {
err = msgp.WrapError(err, "Data")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *msgpRawEvent) Msgsize() (s int) {
s = 1 + 3 + msgp.Uint32Size + 3 + msgp.Uint32Size + 5 + msgp.BytesPrefixSize + len(z.Data)
return
}
// DecodeMsg implements msgp.Decodable
func (z *msgpRawPacket) DecodeMsg(dc *msgp.Reader) (err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "id":
z.Id, err = dc.ReadUint32()
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
case "data":
z.Data, err = dc.ReadBytes(z.Data)
if err != nil {
err = msgp.WrapError(err, "Data")
return
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
return
}
// EncodeMsg implements msgp.Encodable
func (z *msgpRawPacket) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 2
// write "id"
err = en.Append(0x82, 0xa2, 0x69, 0x64)
if err != nil {
return
}
err = en.WriteUint32(z.Id)
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
// write "data"
err = en.Append(0xa4, 0x64, 0x61, 0x74, 0x61)
if err != nil {
return
}
err = en.WriteBytes(z.Data)
if err != nil {
err = msgp.WrapError(err, "Data")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *msgpRawPacket) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 2
// string "id"
o = append(o, 0x82, 0xa2, 0x69, 0x64)
o = msgp.AppendUint32(o, z.Id)
// string "data"
o = append(o, 0xa4, 0x64, 0x61, 0x74, 0x61)
o = msgp.AppendBytes(o, z.Data)
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *msgpRawPacket) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var zb0001 uint32
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
for zb0001 > 0 {
zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
switch msgp.UnsafeString(field) {
case "id":
z.Id, bts, err = msgp.ReadUint32Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "Id")
return
}
case "data":
z.Data, bts, err = msgp.ReadBytesBytes(bts, z.Data)
if err != nil {
err = msgp.WrapError(err, "Data")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err)
return
}
}
}
o = bts
return
}
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *msgpRawPacket) Msgsize() (s int) {
s = 1 + 3 + msgp.Uint32Size + 5 + msgp.BytesPrefixSize + len(z.Data)
return
}

View file

@ -1,236 +0,0 @@
package skylab
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
import (
"bytes"
"testing"
"github.com/tinylib/msgp/msgp"
)
func TestMarshalUnmarshalmsgpRawEvent(t *testing.T) {
v := msgpRawEvent{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsgmsgpRawEvent(b *testing.B) {
v := msgpRawEvent{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsgmsgpRawEvent(b *testing.B) {
v := msgpRawEvent{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshalmsgpRawEvent(b *testing.B) {
v := msgpRawEvent{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodemsgpRawEvent(t *testing.T) {
v := msgpRawEvent{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodemsgpRawEvent Msgsize() is inaccurate")
}
vn := msgpRawEvent{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodemsgpRawEvent(b *testing.B) {
v := msgpRawEvent{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodemsgpRawEvent(b *testing.B) {
v := msgpRawEvent{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}
func TestMarshalUnmarshalmsgpRawPacket(t *testing.T) {
v := msgpRawPacket{}
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := v.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func BenchmarkMarshalMsgmsgpRawPacket(b *testing.B) {
v := msgpRawPacket{}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.MarshalMsg(nil)
}
}
func BenchmarkAppendMsgmsgpRawPacket(b *testing.B) {
v := msgpRawPacket{}
bts := make([]byte, 0, v.Msgsize())
bts, _ = v.MarshalMsg(bts[0:0])
b.SetBytes(int64(len(bts)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bts, _ = v.MarshalMsg(bts[0:0])
}
}
func BenchmarkUnmarshalmsgpRawPacket(b *testing.B) {
v := msgpRawPacket{}
bts, _ := v.MarshalMsg(nil)
b.ReportAllocs()
b.SetBytes(int64(len(bts)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := v.UnmarshalMsg(bts)
if err != nil {
b.Fatal(err)
}
}
}
func TestEncodeDecodemsgpRawPacket(t *testing.T) {
v := msgpRawPacket{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodemsgpRawPacket Msgsize() is inaccurate")
}
vn := msgpRawPacket{}
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}
func BenchmarkEncodemsgpRawPacket(b *testing.B) {
v := msgpRawPacket{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
en := msgp.NewWriter(msgp.Nowhere)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v.EncodeMsg(en)
}
en.Flush()
}
func BenchmarkDecodemsgpRawPacket(b *testing.B) {
v := msgpRawPacket{}
var buf bytes.Buffer
msgp.Encode(&buf, &v)
b.SetBytes(int64(buf.Len()))
rd := msgp.NewEndlessReader(buf.Bytes(), b)
dc := msgp.NewReader(rd)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := v.DecodeMsg(dc)
if err != nil {
b.Fatal(err)
}
}
}

View file

@ -84,7 +84,9 @@ func (p *{{$structName}}) String() string {
package skylab package skylab
import ( import (
"errors"
"encoding/binary" "encoding/binary"
"github.com/kschamplin/gotelem/internal/can"
"encoding/json" "encoding/json"
) )
@ -96,37 +98,42 @@ const (
{{- end}} {{- end}}
) )
var nameToIdMap = map[string]can.CanID{
}
// list of every packet ID. can be used for O(1) checks. // list of every packet ID. can be used for O(1) checks.
var idMap = map[uint32]bool{ var idMap = map[can.CanID]bool{
{{ range $p := .Packets -}} {{ range $p := .Packets -}}
{{ if $p.Repeat }} {{ if $p.Repeat }}
{{ range $idx := Nx (int $p.Id) $p.Repeat $p.Offset -}} {{ range $idx := Nx (int $p.Id) $p.Repeat $p.Offset -}}
{{ $idx | printf "0x%X"}}: true, can.CanID{ Id: {{ $idx | printf "0x%X"}}, Extended: {{$p.Extended}} }: true,
{{ end }} {{ end }}
{{- else }} {{- else }}
{{ $p.Id | printf "0x%X" }}: true, can.CanID{ Id: {{ $p.Id | printf "0x%X" }}, Extended: {{$p.Extended}} }: true,
{{- end}} {{- end}}
{{- end}} {{- end}}
} }
// FromCanFrame creates a Packet from a given CAN ID and data payload. // FromCanFrame creates a Packet from a given CAN ID and data payload.
// If the CAN ID is unknown, it will return an error. // If the CAN ID is unknown, it will return an error.
func FromCanFrame(id uint32, data []byte) (Packet, error) { func FromCanFrame(f can.Frame) (Packet, error) {
id := f.Id
if !idMap[id] { if !idMap[id] {
return nil, &UnknownIdError{ id } return nil, &UnknownIdError{ id.Id }
} }
switch id { switch id {
{{- range $p := .Packets }} {{- range $p := .Packets }}
{{- if $p.Repeat }} {{- if $p.Repeat }}
case {{ Nx (int $p.Id) $p.Repeat $p.Offset | mapf "0x%X" | strJoin ", " -}}: case {{ $p | idToString -}}:
var res = &{{camelCase $p.Name true}}{} var res = &{{camelCase $p.Name true}}{}
res.UnmarshalPacket(data) res.UnmarshalPacket(f.Data)
res.Idx = id - {{$p.Id | printf "0x%X" }} res.Idx = id.Id - {{$p.Id | printf "0x%X" }}
return res, nil return res, nil
{{- else }} {{- else }}
case {{ $p.Id | printf "0x%X" }}: case {{ $p | idToString }}:
var res = &{{camelCase $p.Name true}}{} var res = &{{camelCase $p.Name true}}{}
res.UnmarshalPacket(data) res.UnmarshalPacket(f.Data)
return res, nil return res, nil
{{- end}} {{- end}}
{{- end}} {{- end}}
@ -136,28 +143,18 @@ func FromCanFrame(id uint32, data []byte) (Packet, error) {
} }
func FromJson (id uint32, raw []byte) (Packet, error) { func FromJson (name string, raw []byte) (Packet, error) {
if !idMap[id] { switch name {
return nil, &UnknownIdError{ id }
}
switch id {
{{- range $p := .Packets }} {{- range $p := .Packets }}
{{- if $p.Repeat }} case "{{ $p.Name }}":
case {{ Nx (int $p.Id) $p.Repeat $p.Offset | mapf "0x%X" | strJoin ", " -}}:
var res = &{{camelCase $p.Name true}}{}
err := json.Unmarshal(raw, res)
res.Idx = id - {{ $p.Id | printf "0x%X" }}
return res, err
{{- else }}
case {{ $p.Id | printf "0x%X" }}:
var res = &{{camelCase $p.Name true}}{} var res = &{{camelCase $p.Name true}}{}
err := json.Unmarshal(raw, res) err := json.Unmarshal(raw, res)
return res, err return res, err
{{- end }}
{{- end }} {{- end }}
} }
panic("This should never happen. CAN ID didn't match but was in ID map") return nil, errors.New("unknown packet name")
} }
{{range .Packets -}} {{range .Packets -}}

View file

@ -11,7 +11,7 @@ import (
"fmt" "fmt"
"net" "net"
"github.com/kschamplin/gotelem" "github.com/kschamplin/gotelem/internal/can"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -128,21 +128,22 @@ func (sck *CanSocket) SetFilters(filters []CanFilter) error {
} }
// Send sends a CAN frame // Send sends a CAN frame
func (sck *CanSocket) Send(msg *gotelem.Frame) error { func (sck *CanSocket) Send(msg *can.Frame) error {
buf := make([]byte, fdFrameSize) buf := make([]byte, fdFrameSize)
idToWrite := msg.Id idToWrite := msg.Id.Id
switch msg.Kind { if (msg.Id.Extended) {
case gotelem.CanSFFFrame:
idToWrite &= unix.CAN_SFF_MASK
case gotelem.CanEFFFrame:
idToWrite &= unix.CAN_EFF_MASK idToWrite &= unix.CAN_EFF_MASK
idToWrite |= unix.CAN_EFF_FLAG idToWrite |= unix.CAN_EFF_FLAG
case gotelem.CanRTRFrame: }
switch msg.Kind {
case can.CanRTRFrame:
idToWrite |= unix.CAN_RTR_FLAG idToWrite |= unix.CAN_RTR_FLAG
case gotelem.CanErrFrame: case can.CanErrFrame:
return errors.New("you can't send error frames") return errors.New("you can't send error frames")
default: default:
return errors.New("unknown frame type") return errors.New("unknown frame type")
@ -175,7 +176,7 @@ func (sck *CanSocket) Send(msg *gotelem.Frame) error {
return nil return nil
} }
func (sck *CanSocket) Recv() (*gotelem.Frame, error) { func (sck *CanSocket) Recv() (*can.Frame, error) {
// todo: support extended frames. // todo: support extended frames.
buf := make([]byte, fdFrameSize) buf := make([]byte, fdFrameSize)
@ -184,25 +185,33 @@ func (sck *CanSocket) Recv() (*gotelem.Frame, error) {
return nil, err return nil, err
} }
id := binary.LittleEndian.Uint32(buf[0:4]) raw_id := binary.LittleEndian.Uint32(buf[0:4])
var k gotelem.Kind var id can.CanID
if id&unix.CAN_EFF_FLAG != 0 { id.Id = raw_id
if raw_id&unix.CAN_EFF_FLAG != 0 {
// extended id frame // extended id frame
k = gotelem.CanEFFFrame id.Extended = true;
} else { } else {
// it's a normal can frame // it's a normal can frame
k = gotelem.CanSFFFrame id.Extended = false;
} }
if id&unix.CAN_ERR_FLAG != 0 { var k can.Kind = can.CanDataFrame
if raw_id&unix.CAN_ERR_FLAG != 0 {
// we got an error... // we got an error...
k = can.CanErrFrame
}
if raw_id & unix.CAN_RTR_FLAG != 0 {
k = can.CanRTRFrame
} }
dataLength := uint8(buf[4]) dataLength := uint8(buf[4])
result := &gotelem.Frame{ result := &can.Frame{
Id: id & unix.CAN_EFF_MASK, Id: id,
Kind: k, Kind: k,
Data: buf[8 : dataLength+8], Data: buf[8 : dataLength+8],
} }