303 lines
6.7 KiB
Go
303 lines
6.7 KiB
Go
package meshcore
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
minPacketSize = 2
|
|
maxPathSize = 64
|
|
maxPayloadSize = 184
|
|
)
|
|
|
|
type Packet struct {
|
|
// SNR is the signal-to-noise ratio.
|
|
SNR float64 `json:"snr"`
|
|
|
|
// RSSI is the received signal strength indicator (in dBm).
|
|
RSSI int8 `json:"rssi"`
|
|
|
|
// Raw bytes (optional).
|
|
Raw []byte `json:"raw,omitempty"`
|
|
|
|
// RouteType is the type of route for this packet.
|
|
RouteType RouteType `json:"route_type"`
|
|
|
|
// PayloadType is the type of payload for this packet.
|
|
PayloadType PayloadType `json:"payload_type"`
|
|
|
|
// TransportCodes are set by transport route types.
|
|
TransportCodes []uint16 `json:"transport_codes,omitempty"`
|
|
|
|
// Path are repeater hashes.
|
|
Path []byte `json:"path"`
|
|
|
|
// Payload is the raw (encoded) payload.
|
|
Payload []byte `json:"payload,omitempty"`
|
|
}
|
|
|
|
func Decode(data []byte) (Payload, error) {
|
|
packet := new(Packet)
|
|
if err := packet.UnmarshalBytes(data); err != nil {
|
|
return nil, err
|
|
}
|
|
return packet.Decode()
|
|
}
|
|
|
|
func (packet *Packet) Decode() (Payload, error) {
|
|
var payload Payload
|
|
switch packet.PayloadType {
|
|
case TypeRequest:
|
|
payload = &Request{Raw: packet}
|
|
case TypeResponse:
|
|
payload = &Response{Raw: packet}
|
|
case TypeText:
|
|
payload = &Text{Raw: packet}
|
|
case TypeAck:
|
|
payload = &Acknowledgement{Raw: packet}
|
|
case TypeAdvert:
|
|
payload = &Advert{Raw: packet}
|
|
case TypeGroupText:
|
|
payload = &GroupText{Raw: packet}
|
|
case TypeGroupData:
|
|
payload = &GroupData{Raw: packet}
|
|
case TypeAnonRequest:
|
|
payload = &AnonymousRequest{Raw: packet}
|
|
case TypePath:
|
|
payload = &Path{Raw: packet}
|
|
case TypeTrace:
|
|
payload = &Trace{Raw: packet}
|
|
case TypeMultipart:
|
|
payload = &Multipart{Raw: packet}
|
|
case TypeControl:
|
|
payload = &Control{Raw: packet}
|
|
case TypeRawCustom:
|
|
payload = &RawCustom{Raw: packet}
|
|
default:
|
|
return nil, fmt.Errorf("meshcore: invalid payload type %#02x", packet.PayloadType)
|
|
}
|
|
|
|
if err := payload.Unmarshal(packet.Payload); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return payload, nil
|
|
}
|
|
|
|
func (packet *Packet) String() string {
|
|
s := []string{
|
|
packet.RouteType.String(),
|
|
packet.PayloadType.String(),
|
|
}
|
|
if len(packet.TransportCodes) == 2 {
|
|
s = append(s, fmt.Sprintf("%02X%02X", packet.TransportCodes[0], packet.TransportCodes[1]))
|
|
}
|
|
if len(packet.Path) > 0 {
|
|
s = append(s, formatPath(packet.Path))
|
|
} else {
|
|
s = append(s, "direct")
|
|
}
|
|
return strings.Join(s, " ")
|
|
}
|
|
|
|
func (packet *Packet) MarshalBytes() []byte {
|
|
var (
|
|
data [1 + 4 + 1 + maxPathSize + maxPayloadSize]byte
|
|
offset int
|
|
)
|
|
data[offset] = byte(packet.RouteType&0x03) | byte((packet.PayloadType&0x0f)<<2)
|
|
offset += 1
|
|
|
|
if packet.RouteType.HasTransportCodes() {
|
|
binary.LittleEndian.PutUint16(data[offset:], packet.TransportCodes[0])
|
|
offset += 2
|
|
binary.LittleEndian.PutUint16(data[offset:], packet.TransportCodes[1])
|
|
offset += 2
|
|
}
|
|
|
|
data[offset] = byte(len(packet.Path))
|
|
offset += 1
|
|
offset += copy(data[offset:], packet.Path)
|
|
offset += copy(data[offset:], packet.Payload)
|
|
return data[:offset]
|
|
}
|
|
|
|
func (packet *Packet) UnmarshalBytes(data []byte) error {
|
|
if len(data) < minPacketSize {
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
|
|
packet.Raw = make([]byte, len(data))
|
|
copy(packet.Raw, data)
|
|
|
|
packet.RouteType = RouteType(data[0] & 0x03)
|
|
packet.PayloadType = PayloadType((data[0] >> 2) & 0x0f)
|
|
offset := 1
|
|
|
|
if packet.RouteType.HasTransportCodes() {
|
|
if len(data) < minPacketSize+4 {
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
|
|
packet.TransportCodes = []uint16{
|
|
binary.LittleEndian.Uint16(data[offset+0:]),
|
|
binary.LittleEndian.Uint16(data[offset+2:]),
|
|
}
|
|
offset += 4
|
|
} else {
|
|
packet.TransportCodes = nil
|
|
}
|
|
|
|
pathLength := int(data[offset])
|
|
offset += 1
|
|
if pathLength > maxPathSize {
|
|
return fmt.Errorf("meshcore: path length %d exceeds maximum of %d", pathLength, maxPathSize)
|
|
} else if pathLength > len(data[offset:]) {
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
packet.Path = make([]byte, pathLength)
|
|
offset += copy(packet.Path, data[offset:])
|
|
|
|
payloadLength := len(data[offset:])
|
|
if payloadLength > maxPayloadSize {
|
|
return fmt.Errorf("meshcore: payload length %d exceeds maximum of %d", payloadLength, maxPayloadSize)
|
|
}
|
|
packet.Payload = make([]byte, payloadLength)
|
|
copy(packet.Payload, data[offset:])
|
|
|
|
return nil
|
|
}
|
|
|
|
func (packet *Packet) Hash() []byte {
|
|
h := sha256.New()
|
|
h.Write([]byte{byte(packet.PayloadType)})
|
|
if packet.PayloadType == TypeTrace {
|
|
h.Write(packet.Path)
|
|
}
|
|
h.Write(packet.Payload)
|
|
return h.Sum(nil)[:8]
|
|
}
|
|
|
|
func (packet *Packet) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(struct {
|
|
SNR float64 `json:"snr"`
|
|
RSSI int8 `json:"rssi"`
|
|
RouteType RouteType `json:"route_type"`
|
|
PayloadType PayloadType `json:"payload_type"`
|
|
TransportCodes []uint16 `json:"transport_codes,omitempty"`
|
|
Path string `json:"path"`
|
|
Payload string `json:"payload,omitempty"`
|
|
Hash string `json:"hash"`
|
|
}{
|
|
packet.SNR,
|
|
packet.RSSI,
|
|
packet.RouteType,
|
|
packet.PayloadType,
|
|
packet.TransportCodes,
|
|
hex.EncodeToString(packet.Path),
|
|
hex.EncodeToString(packet.Payload),
|
|
hex.EncodeToString(packet.Hash()),
|
|
})
|
|
}
|
|
|
|
type RouteType byte
|
|
|
|
// Route types.
|
|
const (
|
|
TransportFlood RouteType = iota
|
|
Flood
|
|
Direct
|
|
TransportDirect
|
|
)
|
|
|
|
// HasTransportCodes indicates if this route type has transport codes in the packet.
|
|
func (rt RouteType) HasTransportCodes() bool {
|
|
return rt == TransportFlood || rt == TransportDirect
|
|
}
|
|
|
|
// IsDirect is a direct routing type.
|
|
func (rt RouteType) IsDirect() bool {
|
|
return rt == Direct || rt == TransportDirect
|
|
}
|
|
|
|
// IsFlood is a flood routing type.
|
|
func (rt RouteType) IsFlood() bool {
|
|
return rt == Flood || rt == TransportFlood
|
|
}
|
|
|
|
func (rt RouteType) String() string {
|
|
switch rt {
|
|
case TransportFlood:
|
|
return "F[T]"
|
|
case Flood:
|
|
return "F"
|
|
case Direct:
|
|
return "D"
|
|
case TransportDirect:
|
|
return "D[T]"
|
|
default:
|
|
return "?"
|
|
}
|
|
}
|
|
|
|
type PayloadType byte
|
|
|
|
// Payload types.
|
|
const (
|
|
TypeRequest PayloadType = iota
|
|
TypeResponse
|
|
TypeText
|
|
TypeAck
|
|
TypeAdvert
|
|
TypeGroupText
|
|
TypeGroupData
|
|
TypeAnonRequest
|
|
TypePath
|
|
TypeTrace
|
|
TypeMultipart
|
|
TypeControl
|
|
_ // reserved
|
|
_ // reserved
|
|
_ // reserved
|
|
TypeRawCustom
|
|
)
|
|
|
|
func (pt PayloadType) String() string {
|
|
switch pt {
|
|
case TypeRequest:
|
|
return "request"
|
|
case TypeResponse:
|
|
return "response"
|
|
case TypeText:
|
|
return "text"
|
|
case TypeAck:
|
|
return "ack"
|
|
case TypeAdvert:
|
|
return "advert"
|
|
case TypeGroupText:
|
|
return "group text"
|
|
case TypeGroupData:
|
|
return "group data"
|
|
case TypeAnonRequest:
|
|
return "anon request"
|
|
case TypePath:
|
|
return "path"
|
|
case TypeTrace:
|
|
return "trace"
|
|
case TypeMultipart:
|
|
return "multipart"
|
|
case TypeControl:
|
|
return "control"
|
|
case TypeRawCustom:
|
|
return "raw custom"
|
|
default:
|
|
return fmt.Sprintf("invalid %02x", byte(pt))
|
|
}
|
|
}
|