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