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(), }) }