160 lines
4.5 KiB
Go
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(),
|
|
})
|
|
}
|