More protocols

This commit is contained in:
2026-02-17 23:30:49 +01:00
parent 62a90a468d
commit 74a517a22a
15 changed files with 2268 additions and 21 deletions

302
protocol/meshcore/packet.go Normal file
View File

@@ -0,0 +1,302 @@
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))
}
}