850 lines
18 KiB
Go
850 lines
18 KiB
Go
package meshcore
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"git.maze.io/go/ham/protocol/meshcore/crypto"
|
|
)
|
|
|
|
var 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)
|
|
)
|