More protocols
This commit is contained in:
854
protocol/meshcore/payload.go
Normal file
854
protocol/meshcore/payload.go
Normal file
@@ -0,0 +1,854 @@
|
||||
package meshcore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"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:])
|
||||
log.Printf("time: %s", adv.Time)
|
||||
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)
|
||||
)
|
||||
Reference in New Issue
Block a user