Files
ham/protocol/meshtastic/packet.go
maze e2b69d92fd
Some checks failed
Run tests / test (1.25) (push) Failing after 1m1s
Run tests / test (stable) (push) Failing after 1m0s
meshtastic: support
2026-03-06 09:24:56 +01:00

160 lines
4.5 KiB
Go

package meshtastic
import (
"encoding/binary"
"encoding/json"
"errors"
meshtasticpb "git.maze.io/go/ham/protocol/meshtastic/pb"
"google.golang.org/protobuf/proto"
)
const (
minPacketSize = 4 + 4 + 4 + 1 + 1 + 1 + 1
maxPayloadSize = 237
)
var (
// ErrInvalidPacket signals the source buffer does not contain a valid packet.
ErrInvalidPacket = errors.New("meshtastic: invalid packet")
)
type Packet struct {
Destination NodeID `json:"destination"`
Source NodeID `json:"source"`
ID uint32 `json:"id"`
Flags uint8 `json:"flags"`
ChannelHash uint8 `json:"channelHash"`
NextHop uint8 `json:"nextHop"`
RelayNode uint8 `json:"relayNode"`
PayloadLength int `json:"-"`
Payload [maxPayloadSize]byte `json:"-"`
Data *meshtasticpb.Data `json:"data,omitempty"`
DecodedPayload proto.Message `json:"decodedPayload,omitempty"`
TextPayload string `json:"textPayload,omitempty"`
}
func (packet *Packet) Decode(data []byte) error {
if len(data) < minPacketSize {
return ErrInvalidPacket
}
if len(data[16:]) > maxPayloadSize {
return ErrInvalidPacket
}
packet.Source = parseNodeID(data[0:])
packet.Destination = parseNodeID(data[4:])
packet.ID = binary.LittleEndian.Uint32(data[8:])
packet.Flags = data[12]
packet.ChannelHash = data[13]
packet.NextHop = data[14]
packet.RelayNode = data[15]
packet.PayloadLength = len(data[16:])
copy(packet.Payload[:], data[16:])
packet.Data = nil
packet.DecodedPayload = nil
packet.TextPayload = ""
packet.decodePayloadProtobufs()
return nil
}
func (packet *Packet) PayloadBytes() []byte {
return packet.Payload[:packet.PayloadLength]
}
func (packet *Packet) HopLimit() int {
return 7
}
func parseNodeID(data []byte) NodeID {
return NodeID(binary.LittleEndian.Uint32(data))
}
func (packet *Packet) decodePayloadProtobufs() {
if packet.PayloadLength == 0 {
return
}
decodedData := &meshtasticpb.Data{}
if err := proto.Unmarshal(packet.PayloadBytes(), decodedData); err != nil {
return
}
packet.Data = decodedData
decodedPayload, textPayload := decodeDataPayload(decodedData.GetPortnum(), decodedData.GetPayload())
packet.DecodedPayload = decodedPayload
packet.TextPayload = textPayload
}
func decodeDataPayload(portNum meshtasticpb.PortNum, payload []byte) (proto.Message, string) {
if len(payload) == 0 {
return nil, ""
}
textPortNum := map[meshtasticpb.PortNum]struct{}{
meshtasticpb.PortNum_TEXT_MESSAGE_APP: {},
meshtasticpb.PortNum_ALERT_APP: {},
meshtasticpb.PortNum_DETECTION_SENSOR_APP: {},
meshtasticpb.PortNum_REPLY_APP: {},
meshtasticpb.PortNum_RANGE_TEST_APP: {},
meshtasticpb.PortNum_TEXT_MESSAGE_COMPRESSED_APP: {},
}
if _, ok := textPortNum[portNum]; ok {
return nil, string(payload)
}
var message proto.Message
switch portNum {
case meshtasticpb.PortNum_REMOTE_HARDWARE_APP:
message = &meshtasticpb.HardwareMessage{}
case meshtasticpb.PortNum_POSITION_APP:
message = &meshtasticpb.Position{}
case meshtasticpb.PortNum_NODEINFO_APP:
message = &meshtasticpb.User{}
case meshtasticpb.PortNum_ROUTING_APP:
message = &meshtasticpb.Routing{}
case meshtasticpb.PortNum_ADMIN_APP:
message = &meshtasticpb.AdminMessage{}
case meshtasticpb.PortNum_WAYPOINT_APP:
message = &meshtasticpb.Waypoint{}
case meshtasticpb.PortNum_KEY_VERIFICATION_APP:
message = &meshtasticpb.KeyVerification{}
case meshtasticpb.PortNum_PAXCOUNTER_APP:
message = &meshtasticpb.Paxcount{}
case meshtasticpb.PortNum_STORE_FORWARD_PLUSPLUS_APP:
message = &meshtasticpb.StoreForwardPlusPlus{}
case meshtasticpb.PortNum_STORE_FORWARD_APP:
message = &meshtasticpb.StoreAndForward{}
case meshtasticpb.PortNum_TELEMETRY_APP:
message = &meshtasticpb.Telemetry{}
case meshtasticpb.PortNum_TRACEROUTE_APP:
message = &meshtasticpb.RouteDiscovery{}
case meshtasticpb.PortNum_NEIGHBORINFO_APP:
message = &meshtasticpb.NeighborInfo{}
case meshtasticpb.PortNum_NODE_STATUS_APP:
message = &meshtasticpb.StatusMessage{}
default:
return nil, ""
}
if err := proto.Unmarshal(payload, message); err != nil {
return nil, ""
}
return message, ""
}
// MarshalJSON implements custom JSON marshaling for Packet.
func (packet *Packet) MarshalJSON() ([]byte, error) {
type Alias Packet
return json.Marshal(&struct {
*Alias
Raw []byte `json:"raw"`
}{
Alias: (*Alias)(packet),
Raw: packet.PayloadBytes(),
})
}