Files
ham/protocol/meshcore/payload.go

853 lines
18 KiB
Go

package meshcore
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"time"
"git.maze.io/go/ham/protocol/meshcore/crypto"
)
var (
zeroTime time.Time
zeroPositionBytes = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
)
type Payload interface {
fmt.Stringer
// Packet returns the underlying raw packet (if available, can be nil).
Packet() *Packet
// Marhal encodes the payload to bytes.
Marshal() []byte
// Unmarshal decodes the payload from bytes.
Unmarshal([]byte) error
}
type Acknowledgement struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
// Checksum of message timestamp, text and sender public key.
Checksum uint32 `json:"checksum"`
}
func (ack *Acknowledgement) String() string {
return fmt.Sprintf("checksum=%08X", ack.Checksum)
}
func (ack *Acknowledgement) Packet() *Packet {
return ack.Raw
}
func (ack *Acknowledgement) Marshal() []byte {
var b [4]byte
binary.BigEndian.PutUint32(b[:], ack.Checksum)
return b[:]
}
func (ack *Acknowledgement) Unmarshal(b []byte) error {
if len(b) < 4 {
return io.ErrUnexpectedEOF
}
ack.Checksum = binary.BigEndian.Uint32(b)
return nil
}
type RequestType byte
const (
GetStats RequestType = iota + 1
KeepAlive
GetTelemetryData
GetMinMaxAvgData
GetAccessList
GetNeighbors
GetOwnerInfo
)
func (rt RequestType) String() string {
switch rt {
case GetStats:
return "get stats"
case KeepAlive:
return "keep alive"
case GetTelemetryData:
return "get telemetry data"
case GetMinMaxAvgData:
return "get min/max/avg data"
case GetAccessList:
return "get access list"
case GetNeighbors:
return "get neighbors"
case GetOwnerInfo:
return "get owner info"
default:
return fmt.Sprintf("invalid %#02x", byte(rt))
}
}
type Request struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
EncryptedData
// Only available after successful decryption:
Content *RequestContent `json:"content,omitempty"`
}
type RequestContent struct {
// Time of sending the request.
Time time.Time `json:"time"`
// Type of request.
Type RequestType `json:"type"`
// Data is the request payload.
Data []byte `json:"data"`
}
func (req *RequestContent) String() string {
return fmt.Sprintf("time=%s, type=%s, data=(%d bytes)", req.Time, req.Type.String(), len(req.Data))
}
func (req *Request) String() string {
if req.Content == nil {
return req.EncryptedData.String()
}
return req.Content.String()
}
func (req *Request) Packet() *Packet {
return req.Raw
}
type Response struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
EncryptedData
// Only available after successful decryption:
Content *ResponseContent `json:"content,omitempty"`
}
type ResponseContent struct {
// Tag.
Tag uint32 `json:"tag"`
// Content of the response.
Content []byte `json:"content"`
}
func (res *ResponseContent) String() string {
return fmt.Sprintf("tag=%08X content=%q", res.Tag, res.Content)
}
func (res *Response) String() string {
if res.Content == nil {
return res.EncryptedData.String()
}
return res.Content.String()
}
func (res *Response) Packet() *Packet {
return res.Raw
}
type TextType byte
const (
PlainText TextType = iota
CLICommand
SignedPlainText
)
type Text struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
EncryptedData
// Only available after successful decryption:
Content *TextContent `json:"content,omitempty"`
}
type TextContent struct {
// Time of sending the message.
Time time.Time `json:"time"`
// Type of text.
Type TextType `json:"type"`
// Attempt for thie packet.
Attempt uint8 `json:"attempt"`
// Message contains the text message.
Message []byte `json:"message"`
}
func (text *Text) Packet() *Packet {
return text.Raw
}
type EncryptedData struct {
// Destination hash is the first byte of the recipient public key.
Destination byte `json:"dst"`
// Source hash is the first byte of the sender public key.
Source byte `json:"src"`
// CipherMAC is the message authenticator.
CipherMAC uint16 `json:"cipher_mac"`
// CipherText is the encrypted message.
CipherText []byte `json:"cipher_text"`
}
func (enc *EncryptedData) String() string {
return fmt.Sprintf("dst=%02X src=%02X mac=%04X text=<encrypted %d bytes>",
enc.Destination,
enc.Source,
enc.CipherMAC,
len(enc.CipherText))
}
func (enc *EncryptedData) Marshal() []byte {
b := make([]byte, 4)
b[0] = enc.Destination
b[1] = enc.Source
binary.BigEndian.PutUint16(b[2:], enc.CipherMAC)
return append(b, enc.CipherText...)
}
func (enc *EncryptedData) Unmarshal(b []byte) error {
if len(b) < 4 {
return io.ErrUnexpectedEOF
}
enc.Destination = b[0]
enc.Source = b[1]
enc.CipherMAC = binary.BigEndian.Uint16(b[2:])
enc.CipherText = make([]byte, len(b)-4)
copy(enc.CipherText, b[4:])
return nil
}
type GroupText struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
EncryptedGroupData
// Only available after successful decryption:
Content *GroupTextContent `json:"content,omitempty"`
}
type GroupTextContent struct {
// Group this was sent on (not part of the packet).
Group *Group `json:"group"`
// Time of sending.
Time time.Time `json:"time"`
// Flags is generally 0x00 indicating a plain text message.
Type TextType `json:"type"`
// Attempt is the number of retries.
Attempt uint8 `json:"attempt"`
// Text sent to the group. Typically contains a "<name>: " prefix.
Text string `json:"text"`
}
func (text *GroupTextContent) String() string {
return fmt.Sprintf("time=%s type=%02X attempt=%d text=%q",
text.Time,
text.Type,
text.Attempt,
text.Text)
}
func (text *GroupText) String() string {
if text.Content == nil {
return text.EncryptedGroupData.String()
}
return text.Content.String()
}
func (text *GroupText) Packet() *Packet {
return text.Raw
}
func (text *GroupText) Decrypt(group *Group) error {
b, err := group.Secret.MACThenDecrypt(text.CipherText, text.CipherMAC)
if err != nil {
return err
}
if len(b) < 5 {
return io.ErrUnexpectedEOF
}
text.Content = &GroupTextContent{
Group: group,
Time: decodeTime(b),
Type: TextType(b[4] >> 2),
Attempt: b[4] & 0x03,
Text: decodeCString(b[5:]),
}
return nil
}
type GroupData struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
EncryptedGroupData
// Content contains the group datagram.
Content []byte `json:"content,omitempty"`
}
func (data *GroupData) String() string {
if data.Content == nil {
return data.EncryptedGroupData.String()
}
return fmt.Sprintf("data=%q", data.Content)
}
func (data *GroupData) Packet() *Packet {
return data.Raw
}
func (data *GroupData) Decrypt(group *Group) error {
b, err := group.Secret.MACThenDecrypt(data.CipherText, data.CipherMAC)
if err != nil {
return err
}
data.Content = b
return nil
}
type EncryptedGroupData struct {
// ChannelHash is the first byte of the channel public key.
ChannelHash byte `json:"channel_hash"`
// CipherMAC is the message authenticator.
CipherMAC uint16 `json:"cipher_mac"`
// CipherText is the encrypted message.
CipherText []byte `json:"cipher_text"`
}
func (enc *EncryptedGroupData) String() string {
return fmt.Sprintf("channel=%02X mac=%04X text=<encrypted %d bytes>",
enc.ChannelHash,
enc.CipherMAC,
len(enc.CipherText))
}
func (enc *EncryptedGroupData) Marshal() []byte {
b := make([]byte, 3)
b[0] = enc.ChannelHash
binary.BigEndian.PutUint16(b[1:], enc.CipherMAC)
return append(b, enc.CipherText...)
}
func (enc *EncryptedGroupData) Unmarshal(b []byte) error {
if len(b) < 3 {
return io.ErrUnexpectedEOF
}
enc.ChannelHash = b[0]
enc.CipherMAC = binary.BigEndian.Uint16(b[1:])
enc.CipherText = make([]byte, len(b)-3)
copy(enc.CipherText, b[3:])
return nil
}
type NodeType byte
const (
Chat NodeType = iota + 1
Repeater
Room
Sensor
)
func (nt NodeType) String() string {
switch nt {
case Chat:
return "chat"
case Repeater:
return "repeater"
case Room:
return "room"
case Sensor:
return "sensor"
default:
return fmt.Sprintf("<unknown %02X>", byte(nt))
}
}
const (
advertHasPosition = 0x10
advertHasFeature1 = 0x20
advertHasFeature2 = 0x40
advertHasName = 0x80
)
type Advert struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
PublicKey *crypto.PublicKey `json:"public_key"`
Time time.Time `json:"time"`
Signature crypto.Signature `json:"signature"`
Type NodeType `json:"node_type"`
Position *Position `json:"position,omitempty"`
Feature1 *uint16 `json:"feature1,omitempty"`
Feature2 *uint16 `json:"feature2,omitempty"`
Name string `json:"name,omitempty"`
}
func (adv *Advert) String() string {
return fmt.Sprintf("type=%s position=%s name=%q key=%s",
adv.Type,
adv.Position,
adv.Name,
formatPublicKey(adv.PublicKey.Bytes()))
}
func (adv *Advert) Packet() *Packet {
return adv.Raw
}
func (adv *Advert) Marshal() []byte {
b := make([]byte, crypto.PublicKeySize+4+64+1)
copy(b, adv.PublicKey.Bytes())
encodeTime(b[crypto.PublicKeySize:], adv.Time)
copy(b[crypto.PublicKeySize+4:], adv.Signature[:])
const flagsOffset = crypto.PublicKeySize + 4 + 64
b[flagsOffset] = byte(adv.Type) & 0x7
if adv.Position != nil {
b[flagsOffset] |= advertHasPosition
b = append(b, adv.Position.Marshal()...)
}
if adv.Feature1 != nil {
b[flagsOffset] |= advertHasFeature1
b = append(b, byte(*adv.Feature1))
b = append(b, byte(*adv.Feature1>>8))
}
if adv.Feature2 != nil {
b[flagsOffset] |= advertHasFeature2
b = append(b, byte(*adv.Feature2))
b = append(b, byte(*adv.Feature2>>8))
}
if len(adv.Name) > 0 {
b[flagsOffset] |= advertHasName
b = append(b, []byte(adv.Name)...)
}
return b
}
func (adv *Advert) Unmarshal(b []byte) error {
if len(b) < crypto.PublicKeySize+4+64+1 {
return io.ErrUnexpectedEOF
}
var (
n int
err error
)
// parse public key
if adv.PublicKey, err = crypto.NewPublicKey(b[:crypto.PublicKeySize]); err != nil {
return err
}
n += crypto.PublicKeySize
// parse time
adv.Time = decodeTime(b[n:])
n += 4
// parse signature
n += copy(adv.Signature[:], b[n:])
// parse flags
flags, rest := b[n], b[n+1:]
adv.Type = NodeType(flags & 0x07)
if flags&advertHasPosition != 0 {
if len(rest) < 8 {
return io.ErrUnexpectedEOF
}
// nb: some repeaters have no location and send 0,0; we're going to ignore these
if !bytes.Equal(rest[:8], zeroPositionBytes) {
adv.Position = new(Position)
if err = adv.Position.Unmarshal(rest); err != nil {
return err
}
}
rest = rest[8:]
}
if flags&advertHasFeature1 != 0 {
if len(rest) < 2 {
return io.ErrUnexpectedEOF
}
adv.Feature1 = new(uint16)
*adv.Feature1, rest = binary.LittleEndian.Uint16(rest), rest[2:]
}
if flags&advertHasFeature2 != 0 {
if len(rest) < 2 {
return io.ErrUnexpectedEOF
}
adv.Feature2 = new(uint16)
*adv.Feature2, rest = binary.LittleEndian.Uint16(rest), rest[2:]
}
if flags&advertHasName != 0 {
adv.Name = string(rest)
}
return nil
}
type AnonymousRequest struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
// Destination hash is the first byte of the recipient public key.
Destination byte `json:"dst"`
// PublicKey of the sender.
PublicKey *crypto.PublicKey `json:"public_key"`
// CipherMAC is the message authenticator.
CipherMAC uint16 `json:"cipher_mac"`
// CipherText is the encrypted message.
CipherText []byte `json:"cipher_text"`
}
func (req *AnonymousRequest) String() string {
return fmt.Sprintf("dst=%02X key=%s mac=%02X text=<encrypted %d bytes>",
req.Destination,
formatPublicKey(req.PublicKey.Bytes()),
req.CipherMAC,
len(req.CipherText))
}
func (req *AnonymousRequest) Packet() *Packet {
return req.Raw
}
func (req *AnonymousRequest) Marshal() []byte {
b := make([]byte, 1+crypto.PublicKeySize+2)
b[0] = req.Destination
n := 1 + copy(b[1:], req.PublicKey.Bytes())
binary.BigEndian.PutUint16(b[n:], req.CipherMAC)
return append(b, req.CipherText...)
}
func (req *AnonymousRequest) Unmarshal(b []byte) error {
if len(b) < 1+crypto.PublicKeySize+2 {
return io.ErrUnexpectedEOF
}
var err error
req.Destination = b[0]
if req.PublicKey, err = crypto.NewPublicKey(b[1 : 1+crypto.PublicKeySize]); err != nil {
return err
}
req.CipherMAC = binary.BigEndian.Uint16(b[1+crypto.PublicKeySize:])
req.CipherText = make([]byte, len(b)-1-crypto.PublicKeySize-2)
copy(req.CipherText, b[1+crypto.PublicKeySize+2:])
return nil
}
type Path struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
EncryptedData
}
func (path *Path) Packet() *Packet {
return path.Raw
}
type Trace struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
Tag uint32 `json:"tag"`
AuthCode uint32 `json:"authcode"`
Flags byte `json:"flags"`
Path []byte `json:"path"`
}
func (trace *Trace) String() string {
return fmt.Sprintf("tag=%08X authcode=%08X flags=%02X path=%s",
trace.Tag,
trace.AuthCode,
trace.Flags,
formatPath(trace.Path))
}
func (trace *Trace) Packet() *Packet {
return trace.Raw
}
func (trace *Trace) Marshal() []byte {
b := make([]byte, 4+4+1)
binary.LittleEndian.PutUint32(b[0:], trace.Tag)
binary.LittleEndian.PutUint32(b[4:], trace.AuthCode)
b[8] = trace.Flags
return append(b, trace.Path...)
}
func (trace *Trace) Unmarshal(b []byte) error {
if len(b) < 9 {
return io.ErrUnexpectedEOF
}
trace.Tag = binary.LittleEndian.Uint32(b[0:])
trace.AuthCode = binary.LittleEndian.Uint32(b[4:])
trace.Flags = b[8]
trace.Path = make([]byte, len(b)-9)
copy(trace.Path, b[9:])
return nil
}
type Multipart struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
Remaining uint8 `json:"remaining"`
Type PayloadType `json:"type"`
Data []byte `json:"data"`
}
func (multi *Multipart) String() string {
return fmt.Sprintf("remaining=%d type=%s data=%q",
multi.Remaining,
multi.Type.String(),
multi.Data)
}
func (multi *Multipart) Packet() *Packet {
return multi.Raw
}
func (multi *Multipart) Marshal() []byte {
return append([]byte{
multi.Remaining<<4 | byte(multi.Type&0x0F),
}, multi.Data...)
}
func (multi *Multipart) Unmarshal(b []byte) error {
if len(b) < 1 {
return io.ErrUnexpectedEOF
}
multi.Remaining = b[0] >> 4
multi.Type = PayloadType(b[0] & 0x0F)
multi.Data = make([]byte, len(b)-1)
copy(multi.Data, b[1:])
return nil
}
type ControlType byte
const (
DiscoverRequest ControlType = 0x80
DiscoverResponse ControlType = 0x90
)
type Control struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
// Type of control packet.
Type ControlType `json:"type"`
// Request for discovery.
Request *ControlDiscoverRequest `json:"request,omitempty"`
// Response for discovery.
Response *ControlDiscoverResponse `json:"response,omitempty"`
// Data contains the data bytes for unknown/unparsed control types.
Data []byte `json:"data"`
}
type ControlDiscoverRequest struct {
Flags byte `json:"flags"` // upper 4 bits
PrefixOnly bool `json:"prefix_only"` // lower 1 bit
TypeFilter byte `json:"type_filter"`
Tag uint32 `json:"tag"`
Since *time.Time `json:"since,omitempty"`
}
func (req ControlDiscoverRequest) String() string {
var since string
if req.Since != nil {
since = " " + req.Since.String()
}
return fmt.Sprintf("flags=%X prefixonly=%t filter=%08b tag=%08X"+since,
req.Flags,
req.PrefixOnly,
req.TypeFilter,
req.Tag)
}
type ControlDiscoverResponse struct {
Flags byte `json:"flags"` // upper 4 bits
NodeType NodeType `json:"node_type"` // lower 4 bits
SNR float64 `json:"snr"`
Tag uint32 `json:"tag"`
PublicKey []byte `json:"public_key"` // 8 or 32 bytes
}
func (res ControlDiscoverResponse) String() string {
return fmt.Sprintf("flags=%X node=%s snr=%g tag=%08X key=%s",
res.Flags,
res.NodeType.String(),
res.SNR,
res.Tag,
formatPublicKey(res.PublicKey))
}
func (control *Control) String() string {
switch {
case control.Request != nil:
return "request " + control.Request.String()
case control.Response != nil:
return "response " + control.Response.String()
default:
return fmt.Sprintf("type=%02X data=%q", control.Type, control.Data)
}
}
func (control *Control) Packet() *Packet {
return control.Raw
}
func (control *Control) Marshal() []byte {
b := []byte{byte(control.Type)}
switch control.Type {
case DiscoverRequest:
if control.Request != nil {
b = append(b,
control.Request.Flags<<4, // 1
control.Request.TypeFilter, // 2
0x00, 0x00, 0x00, 0x00, // 3-6: tag
0x00, 0x00, 0x00, 0x00, // 7-10: since
)
if control.Request.PrefixOnly {
b[1] |= 0x01
}
binary.LittleEndian.PutUint32(b[3:], control.Request.Tag)
if control.Request.Since != nil {
encodeTime(b[7:], *control.Request.Since)
}
}
case DiscoverResponse:
if control.Response != nil {
b = append(b,
control.Response.Flags<<4|byte(control.Response.NodeType&0x0F), // 1
byte(control.Response.SNR*4), // 2
0x00, 0x00, 0x00, 0x00, // 3-6: tag
)
binary.LittleEndian.PutUint32(b[3:], control.Response.Tag)
}
b = append(b, control.Response.PublicKey...)
default:
b = append(b, control.Data...)
}
return b
}
func (control *Control) Unmarshal(b []byte) error {
if len(b) < 1 {
return io.ErrUnexpectedEOF
}
control.Type = ControlType(b[0] >> 4)
switch control.Type {
case DiscoverRequest:
if len(b) < 7 {
return io.ErrUnexpectedEOF
}
control.Request = &ControlDiscoverRequest{
Flags: b[1] >> 4,
PrefixOnly: b[1]&0x01 != 0,
TypeFilter: b[2],
Tag: binary.LittleEndian.Uint32(b[3:]),
}
if len(b) >= 11 {
since := decodeTime(b[7:])
control.Request.Since = &since
}
case DiscoverResponse:
if len(b) < 15 {
return io.ErrUnexpectedEOF
}
control.Response = &ControlDiscoverResponse{
Flags: b[1] >> 4,
NodeType: NodeType(b[1] & 0x0F),
SNR: float64(int8(b[2])) / 4,
Tag: binary.LittleEndian.Uint32(b[3:]),
}
control.Response.PublicKey = make([]byte, len(b)-6)
copy(control.Response.PublicKey, b[6:])
default:
control.Data = make([]byte, len(b)-1)
copy(control.Data, b[1:])
}
return nil
}
type RawCustom struct {
// Raw packet (optional).
Raw *Packet `json:"-"`
// Data in the payload.
Data []byte `json:"data"`
}
func (raw *RawCustom) String() string {
return fmt.Sprintf("data=%q", raw.Data)
}
func (raw *RawCustom) Packet() *Packet {
return raw.Raw
}
func (raw *RawCustom) Marshal() []byte {
return raw.Data
}
func (raw *RawCustom) Unmarshal(b []byte) error {
raw.Data = make([]byte, len(b))
copy(raw.Data, b)
return nil
}
var (
_ Payload = (*Acknowledgement)(nil)
_ Payload = (*Request)(nil)
_ Payload = (*Response)(nil)
_ Payload = (*Text)(nil)
_ Payload = (*GroupText)(nil)
_ Payload = (*GroupData)(nil)
)