package meshcore import ( "bytes" "encoding/binary" "fmt" "io" "time" "git.maze.io/go/ham/protocol/meshcore/crypto" ) var zeroPositionBytes = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} type Payload interface { fmt.Stringer // Packet returns the underlying raw packet (if available, can be nil). Packet() *Packet // Marhal encodes the payload to bytes. Marshal() []byte // Unmarshal decodes the payload from bytes. Unmarshal([]byte) error } type Acknowledgement struct { // Raw packet (optional). Raw *Packet `json:"-"` // Checksum of message timestamp, text and sender public key. Checksum uint32 `json:"checksum"` } func (ack *Acknowledgement) String() string { return fmt.Sprintf("checksum=%08X", ack.Checksum) } func (ack *Acknowledgement) Packet() *Packet { return ack.Raw } func (ack *Acknowledgement) Marshal() []byte { var b [4]byte binary.BigEndian.PutUint32(b[:], ack.Checksum) return b[:] } func (ack *Acknowledgement) Unmarshal(b []byte) error { if len(b) < 4 { return io.ErrUnexpectedEOF } ack.Checksum = binary.BigEndian.Uint32(b) return nil } type RequestType byte const ( GetStats RequestType = iota + 1 KeepAlive GetTelemetryData GetMinMaxAvgData GetAccessList GetNeighbors GetOwnerInfo ) func (rt RequestType) String() string { switch rt { case GetStats: return "get stats" case KeepAlive: return "keep alive" case GetTelemetryData: return "get telemetry data" case GetMinMaxAvgData: return "get min/max/avg data" case GetAccessList: return "get access list" case GetNeighbors: return "get neighbors" case GetOwnerInfo: return "get owner info" default: return fmt.Sprintf("invalid %#02x", byte(rt)) } } type Request struct { // Raw packet (optional). Raw *Packet `json:"-"` EncryptedData // Only available after successful decryption: Content *RequestContent `json:"content,omitempty"` } type RequestContent struct { // Time of sending the request. Time time.Time `json:"time"` // Type of request. Type RequestType `json:"type"` // Data is the request payload. Data []byte `json:"data"` } func (req *RequestContent) String() string { return fmt.Sprintf("time=%s, type=%s, data=(%d bytes)", req.Time, req.Type.String(), len(req.Data)) } func (req *Request) String() string { if req.Content == nil { return req.EncryptedData.String() } return req.Content.String() } func (req *Request) Packet() *Packet { return req.Raw } type Response struct { // Raw packet (optional). Raw *Packet `json:"-"` EncryptedData // Only available after successful decryption: Content *ResponseContent `json:"content,omitempty"` } type ResponseContent struct { // Tag. Tag uint32 `json:"tag"` // Content of the response. Content []byte `json:"content"` } func (res *ResponseContent) String() string { return fmt.Sprintf("tag=%08X content=%q", res.Tag, res.Content) } func (res *Response) String() string { if res.Content == nil { return res.EncryptedData.String() } return res.Content.String() } func (res *Response) Packet() *Packet { return res.Raw } type TextType byte const ( PlainText TextType = iota CLICommand SignedPlainText ) type Text struct { // Raw packet (optional). Raw *Packet `json:"-"` EncryptedData // Only available after successful decryption: Content *TextContent `json:"content,omitempty"` } type TextContent struct { // Time of sending the message. Time time.Time `json:"time"` // Type of text. Type TextType `json:"type"` // Attempt for thie packet. Attempt uint8 `json:"attempt"` // Message contains the text message. Message []byte `json:"message"` } func (text *Text) Packet() *Packet { return text.Raw } type EncryptedData struct { // Destination hash is the first byte of the recipient public key. Destination byte `json:"dst"` // Source hash is the first byte of the sender public key. Source byte `json:"src"` // CipherMAC is the message authenticator. CipherMAC uint16 `json:"cipher_mac"` // CipherText is the encrypted message. CipherText []byte `json:"cipher_text"` } func (enc *EncryptedData) String() string { return fmt.Sprintf("dst=%02X src=%02X mac=%04X text=", enc.Destination, enc.Source, enc.CipherMAC, len(enc.CipherText)) } func (enc *EncryptedData) Marshal() []byte { b := make([]byte, 4) b[0] = enc.Destination b[1] = enc.Source binary.BigEndian.PutUint16(b[2:], enc.CipherMAC) return append(b, enc.CipherText...) } func (enc *EncryptedData) Unmarshal(b []byte) error { if len(b) < 4 { return io.ErrUnexpectedEOF } enc.Destination = b[0] enc.Source = b[1] enc.CipherMAC = binary.BigEndian.Uint16(b[2:]) enc.CipherText = make([]byte, len(b)-4) copy(enc.CipherText, b[4:]) return nil } type GroupText struct { // Raw packet (optional). Raw *Packet `json:"-"` EncryptedGroupData // Only available after successful decryption: Content *GroupTextContent `json:"content,omitempty"` } type GroupTextContent struct { // Group this was sent on (not part of the packet). Group *Group `json:"group"` // Time of sending. Time time.Time `json:"time"` // Flags is generally 0x00 indicating a plain text message. Type TextType `json:"type"` // Attempt is the number of retries. Attempt uint8 `json:"attempt"` // Text sent to the group. Typically contains a ": " prefix. Text string `json:"text"` } func (text *GroupTextContent) String() string { return fmt.Sprintf("time=%s type=%02X attempt=%d text=%q", text.Time, text.Type, text.Attempt, text.Text) } func (text *GroupText) String() string { if text.Content == nil { return text.EncryptedGroupData.String() } return text.Content.String() } func (text *GroupText) Packet() *Packet { return text.Raw } func (text *GroupText) Decrypt(group *Group) error { b, err := group.Secret.MACThenDecrypt(text.CipherText, text.CipherMAC) if err != nil { return err } if len(b) < 5 { return io.ErrUnexpectedEOF } text.Content = &GroupTextContent{ Group: group, Time: decodeTime(b), Type: TextType(b[4] >> 2), Attempt: b[4] & 0x03, Text: decodeCString(b[5:]), } return nil } type GroupData struct { // Raw packet (optional). Raw *Packet `json:"-"` EncryptedGroupData // Content contains the group datagram. Content []byte `json:"content,omitempty"` } func (data *GroupData) String() string { if data.Content == nil { return data.EncryptedGroupData.String() } return fmt.Sprintf("data=%q", data.Content) } func (data *GroupData) Packet() *Packet { return data.Raw } func (data *GroupData) Decrypt(group *Group) error { b, err := group.Secret.MACThenDecrypt(data.CipherText, data.CipherMAC) if err != nil { return err } data.Content = b return nil } type EncryptedGroupData struct { // ChannelHash is the first byte of the channel public key. ChannelHash byte `json:"channel_hash"` // CipherMAC is the message authenticator. CipherMAC uint16 `json:"cipher_mac"` // CipherText is the encrypted message. CipherText []byte `json:"cipher_text"` } func (enc *EncryptedGroupData) String() string { return fmt.Sprintf("channel=%02X mac=%04X text=", enc.ChannelHash, enc.CipherMAC, len(enc.CipherText)) } func (enc *EncryptedGroupData) Marshal() []byte { b := make([]byte, 3) b[0] = enc.ChannelHash binary.BigEndian.PutUint16(b[1:], enc.CipherMAC) return append(b, enc.CipherText...) } func (enc *EncryptedGroupData) Unmarshal(b []byte) error { if len(b) < 3 { return io.ErrUnexpectedEOF } enc.ChannelHash = b[0] enc.CipherMAC = binary.BigEndian.Uint16(b[1:]) enc.CipherText = make([]byte, len(b)-3) copy(enc.CipherText, b[3:]) return nil } type NodeType byte const ( Chat NodeType = iota + 1 Repeater Room Sensor ) func (nt NodeType) String() string { switch nt { case Chat: return "chat" case Repeater: return "repeater" case Room: return "room" case Sensor: return "sensor" default: return fmt.Sprintf("", byte(nt)) } } const ( advertHasPosition = 0x10 advertHasFeature1 = 0x20 advertHasFeature2 = 0x40 advertHasName = 0x80 ) type Advert struct { // Raw packet (optional). Raw *Packet `json:"-"` PublicKey *crypto.PublicKey `json:"public_key"` Time time.Time `json:"time"` Signature crypto.Signature `json:"signature"` Type NodeType `json:"node_type"` Position *Position `json:"position,omitempty"` Feature1 *uint16 `json:"feature1,omitempty"` Feature2 *uint16 `json:"feature2,omitempty"` Name string `json:"name,omitempty"` } func (adv *Advert) String() string { return fmt.Sprintf("type=%s position=%s name=%q key=%s", adv.Type, adv.Position, adv.Name, formatPublicKey(adv.PublicKey.Bytes())) } func (adv *Advert) Packet() *Packet { return adv.Raw } func (adv *Advert) Marshal() []byte { b := make([]byte, crypto.PublicKeySize+4+64+1) copy(b, adv.PublicKey.Bytes()) encodeTime(b[crypto.PublicKeySize:], adv.Time) copy(b[crypto.PublicKeySize+4:], adv.Signature[:]) const flagsOffset = crypto.PublicKeySize + 4 + 64 b[flagsOffset] = byte(adv.Type) & 0x7 if adv.Position != nil { b[flagsOffset] |= advertHasPosition b = append(b, adv.Position.Marshal()...) } if adv.Feature1 != nil { b[flagsOffset] |= advertHasFeature1 b = append(b, byte(*adv.Feature1)) b = append(b, byte(*adv.Feature1>>8)) } if adv.Feature2 != nil { b[flagsOffset] |= advertHasFeature2 b = append(b, byte(*adv.Feature2)) b = append(b, byte(*adv.Feature2>>8)) } if len(adv.Name) > 0 { b[flagsOffset] |= advertHasName b = append(b, []byte(adv.Name)...) } return b } func (adv *Advert) Unmarshal(b []byte) error { if len(b) < crypto.PublicKeySize+4+64+1 { return io.ErrUnexpectedEOF } var ( n int err error ) // parse public key if adv.PublicKey, err = crypto.NewPublicKey(b[:crypto.PublicKeySize]); err != nil { return err } n += crypto.PublicKeySize // parse time adv.Time = decodeTime(b[n:]) n += 4 // parse signature n += copy(adv.Signature[:], b[n:]) // parse flags flags, rest := b[n], b[n+1:] adv.Type = NodeType(flags & 0x07) if flags&advertHasPosition != 0 { if len(rest) < 8 { return io.ErrUnexpectedEOF } // nb: some repeaters have no location and send 0,0; we're going to ignore these if !bytes.Equal(rest[:8], zeroPositionBytes) { adv.Position = new(Position) if err = adv.Position.Unmarshal(rest); err != nil { return err } } rest = rest[8:] } if flags&advertHasFeature1 != 0 { if len(rest) < 2 { return io.ErrUnexpectedEOF } adv.Feature1 = new(uint16) *adv.Feature1, rest = binary.LittleEndian.Uint16(rest), rest[2:] } if flags&advertHasFeature2 != 0 { if len(rest) < 2 { return io.ErrUnexpectedEOF } adv.Feature2 = new(uint16) *adv.Feature2, rest = binary.LittleEndian.Uint16(rest), rest[2:] } if flags&advertHasName != 0 { adv.Name = string(rest) } return nil } type AnonymousRequest struct { // Raw packet (optional). Raw *Packet `json:"-"` // Destination hash is the first byte of the recipient public key. Destination byte `json:"dst"` // PublicKey of the sender. PublicKey *crypto.PublicKey `json:"public_key"` // CipherMAC is the message authenticator. CipherMAC uint16 `json:"cipher_mac"` // CipherText is the encrypted message. CipherText []byte `json:"cipher_text"` } func (req *AnonymousRequest) String() string { return fmt.Sprintf("dst=%02X key=%s mac=%02X text=", req.Destination, formatPublicKey(req.PublicKey.Bytes()), req.CipherMAC, len(req.CipherText)) } func (req *AnonymousRequest) Packet() *Packet { return req.Raw } func (req *AnonymousRequest) Marshal() []byte { b := make([]byte, 1+crypto.PublicKeySize+2) b[0] = req.Destination n := 1 + copy(b[1:], req.PublicKey.Bytes()) binary.BigEndian.PutUint16(b[n:], req.CipherMAC) return append(b, req.CipherText...) } func (req *AnonymousRequest) Unmarshal(b []byte) error { if len(b) < 1+crypto.PublicKeySize+2 { return io.ErrUnexpectedEOF } var err error req.Destination = b[0] if req.PublicKey, err = crypto.NewPublicKey(b[1 : 1+crypto.PublicKeySize]); err != nil { return err } req.CipherMAC = binary.BigEndian.Uint16(b[1+crypto.PublicKeySize:]) req.CipherText = make([]byte, len(b)-1-crypto.PublicKeySize-2) copy(req.CipherText, b[1+crypto.PublicKeySize+2:]) return nil } type Path struct { // Raw packet (optional). Raw *Packet `json:"-"` EncryptedData } func (path *Path) Packet() *Packet { return path.Raw } type Trace struct { // Raw packet (optional). Raw *Packet `json:"-"` Tag uint32 `json:"tag"` AuthCode uint32 `json:"authcode"` Flags byte `json:"flags"` Path []byte `json:"path"` } func (trace *Trace) String() string { return fmt.Sprintf("tag=%08X authcode=%08X flags=%02X path=%s", trace.Tag, trace.AuthCode, trace.Flags, formatPath(trace.Path)) } func (trace *Trace) Packet() *Packet { return trace.Raw } func (trace *Trace) Marshal() []byte { b := make([]byte, 4+4+1) binary.LittleEndian.PutUint32(b[0:], trace.Tag) binary.LittleEndian.PutUint32(b[4:], trace.AuthCode) b[8] = trace.Flags return append(b, trace.Path...) } func (trace *Trace) Unmarshal(b []byte) error { if len(b) < 9 { return io.ErrUnexpectedEOF } trace.Tag = binary.LittleEndian.Uint32(b[0:]) trace.AuthCode = binary.LittleEndian.Uint32(b[4:]) trace.Flags = b[8] trace.Path = make([]byte, len(b)-9) copy(trace.Path, b[9:]) return nil } type Multipart struct { // Raw packet (optional). Raw *Packet `json:"-"` Remaining uint8 `json:"remaining"` Type PayloadType `json:"type"` Data []byte `json:"data"` } func (multi *Multipart) String() string { return fmt.Sprintf("remaining=%d type=%s data=%q", multi.Remaining, multi.Type.String(), multi.Data) } func (multi *Multipart) Packet() *Packet { return multi.Raw } func (multi *Multipart) Marshal() []byte { return append([]byte{ multi.Remaining<<4 | byte(multi.Type&0x0F), }, multi.Data...) } func (multi *Multipart) Unmarshal(b []byte) error { if len(b) < 1 { return io.ErrUnexpectedEOF } multi.Remaining = b[0] >> 4 multi.Type = PayloadType(b[0] & 0x0F) multi.Data = make([]byte, len(b)-1) copy(multi.Data, b[1:]) return nil } type ControlType byte const ( DiscoverRequest ControlType = 0x80 DiscoverResponse ControlType = 0x90 ) type Control struct { // Raw packet (optional). Raw *Packet `json:"-"` // Type of control packet. Type ControlType `json:"type"` // Request for discovery. Request *ControlDiscoverRequest `json:"request,omitempty"` // Response for discovery. Response *ControlDiscoverResponse `json:"response,omitempty"` // Data contains the data bytes for unknown/unparsed control types. Data []byte `json:"data"` } type ControlDiscoverRequest struct { Flags byte `json:"flags"` // upper 4 bits PrefixOnly bool `json:"prefix_only"` // lower 1 bit TypeFilter byte `json:"type_filter"` Tag uint32 `json:"tag"` Since *time.Time `json:"since,omitempty"` } func (req ControlDiscoverRequest) String() string { var since string if req.Since != nil { since = " " + req.Since.String() } return fmt.Sprintf("flags=%X prefixonly=%t filter=%08b tag=%08X"+since, req.Flags, req.PrefixOnly, req.TypeFilter, req.Tag) } type ControlDiscoverResponse struct { Flags byte `json:"flags"` // upper 4 bits NodeType NodeType `json:"node_type"` // lower 4 bits SNR float64 `json:"snr"` Tag uint32 `json:"tag"` PublicKey []byte `json:"public_key"` // 8 or 32 bytes } func (res ControlDiscoverResponse) String() string { return fmt.Sprintf("flags=%X node=%s snr=%g tag=%08X key=%s", res.Flags, res.NodeType.String(), res.SNR, res.Tag, formatPublicKey(res.PublicKey)) } func (control *Control) String() string { switch { case control.Request != nil: return "request " + control.Request.String() case control.Response != nil: return "response " + control.Response.String() default: return fmt.Sprintf("type=%02X data=%q", control.Type, control.Data) } } func (control *Control) Packet() *Packet { return control.Raw } func (control *Control) Marshal() []byte { b := []byte{byte(control.Type)} switch control.Type { case DiscoverRequest: if control.Request != nil { b = append(b, control.Request.Flags<<4, // 1 control.Request.TypeFilter, // 2 0x00, 0x00, 0x00, 0x00, // 3-6: tag 0x00, 0x00, 0x00, 0x00, // 7-10: since ) if control.Request.PrefixOnly { b[1] |= 0x01 } binary.LittleEndian.PutUint32(b[3:], control.Request.Tag) if control.Request.Since != nil { encodeTime(b[7:], *control.Request.Since) } } case DiscoverResponse: if control.Response != nil { b = append(b, control.Response.Flags<<4|byte(control.Response.NodeType&0x0F), // 1 byte(control.Response.SNR*4), // 2 0x00, 0x00, 0x00, 0x00, // 3-6: tag ) binary.LittleEndian.PutUint32(b[3:], control.Response.Tag) } b = append(b, control.Response.PublicKey...) default: b = append(b, control.Data...) } return b } func (control *Control) Unmarshal(b []byte) error { if len(b) < 1 { return io.ErrUnexpectedEOF } control.Type = ControlType(b[0] >> 4) switch control.Type { case DiscoverRequest: if len(b) < 7 { return io.ErrUnexpectedEOF } control.Request = &ControlDiscoverRequest{ Flags: b[1] >> 4, PrefixOnly: b[1]&0x01 != 0, TypeFilter: b[2], Tag: binary.LittleEndian.Uint32(b[3:]), } if len(b) >= 11 { since := decodeTime(b[7:]) control.Request.Since = &since } case DiscoverResponse: if len(b) < 15 { return io.ErrUnexpectedEOF } control.Response = &ControlDiscoverResponse{ Flags: b[1] >> 4, NodeType: NodeType(b[1] & 0x0F), SNR: float64(int8(b[2])) / 4, Tag: binary.LittleEndian.Uint32(b[3:]), } control.Response.PublicKey = make([]byte, len(b)-6) copy(control.Response.PublicKey, b[6:]) default: control.Data = make([]byte, len(b)-1) copy(control.Data, b[1:]) } return nil } type RawCustom struct { // Raw packet (optional). Raw *Packet `json:"-"` // Data in the payload. Data []byte `json:"data"` } func (raw *RawCustom) String() string { return fmt.Sprintf("data=%q", raw.Data) } func (raw *RawCustom) Packet() *Packet { return raw.Raw } func (raw *RawCustom) Marshal() []byte { return raw.Data } func (raw *RawCustom) Unmarshal(b []byte) error { raw.Data = make([]byte, len(b)) copy(raw.Data, b) return nil } var ( _ Payload = (*Acknowledgement)(nil) _ Payload = (*Request)(nil) _ Payload = (*Response)(nil) _ Payload = (*Text)(nil) _ Payload = (*GroupText)(nil) _ Payload = (*GroupData)(nil) )