package packet import ( "crypto/rand" "crypto/sha256" "encoding/binary" "errors" "fmt" "time" "git.quad4.io/Networks/Reticulum-Go/pkg/debug" "git.quad4.io/Networks/Reticulum-Go/pkg/identity" ) const ( // Packet Types PacketTypeData = 0x00 PacketTypeAnnounce = 0x01 PacketTypeLinkReq = 0x02 PacketTypeProof = 0x03 // Header Types HeaderType1 = 0x00 HeaderType2 = 0x01 // Context Types ContextNone = 0x00 ContextResource = 0x01 ContextResourceAdv = 0x02 ContextResourceReq = 0x03 ContextResourceHMU = 0x04 ContextResourcePRF = 0x05 ContextResourceICL = 0x06 ContextResourceRCL = 0x07 ContextCacheReq = 0x08 ContextRequest = 0x09 ContextResponse = 0x0A ContextPathResponse = 0x0B ContextCommand = 0x0C ContextCmdStatus = 0x0D ContextChannel = 0x0E ContextKeepalive = 0xFA ContextLinkIdentify = 0xFB ContextLinkClose = 0xFC ContextLinkProof = 0xFD ContextLRRTT = 0xFE ContextLRProof = 0xFF // Flag Values FlagSet = 0x01 FlagUnset = 0x00 // Header sizes HeaderMaxSize = 64 MTU = 500 AddressSize = 32 // Size of address/hash fields in bytes ) type Packet struct { HeaderType byte PacketType byte TransportType byte Context byte ContextFlag byte Hops byte DestinationType byte DestinationHash []byte Destination interface{} TransportID []byte Data []byte Raw []byte Packed bool Sent bool CreateReceipt bool FromPacked bool SentAt time.Time PacketHash []byte RatchetID []byte RSSI *float64 SNR *float64 Q *float64 Addresses []byte Link interface{} receipt *PacketReceipt } type PacketConfig struct { DestType byte Data []byte PacketType byte Context byte TransportType byte HeaderType byte TransportID []byte CreateReceipt bool ContextFlag byte } func NewPacket(destType byte, data []byte, packetType byte, context byte, transportType byte, headerType byte, transportID []byte, createReceipt bool, contextFlag byte) *Packet { return &Packet{ HeaderType: headerType, PacketType: packetType, TransportType: transportType, Context: context, ContextFlag: contextFlag, Hops: 0, DestinationType: destType, Data: data, TransportID: transportID, CreateReceipt: createReceipt, Packed: false, Sent: false, FromPacked: false, } } func (p *Packet) Pack() error { if p.Packed { return nil } debug.Log(debug.DEBUG_PACKETS, "Packing packet", "type", p.PacketType, "header", p.HeaderType) // Create header byte (Corrected order) flags := byte(0) flags |= (p.HeaderType << 6) & 0b01000000 flags |= (p.ContextFlag << 5) & 0b00100000 flags |= (p.TransportType << 4) & 0b00010000 flags |= (p.DestinationType << 2) & 0b00001100 flags |= p.PacketType & 0b00000011 header := []byte{flags, p.Hops} debug.Log(debug.DEBUG_TRACE, "Created packet header", "flags", fmt.Sprintf("%08b", flags), "hops", p.Hops) header = append(header, p.DestinationHash...) if p.HeaderType == HeaderType2 { if p.TransportID == nil { return errors.New("transport ID required for header type 2") } header = append(header, p.TransportID...) debug.Log(debug.DEBUG_ALL, "Added transport ID to header", "transport_id", fmt.Sprintf("%x", p.TransportID)) } header = append(header, p.Context) debug.Log(debug.DEBUG_PACKETS, "Final header length", "bytes", len(header)) p.Raw = append(header, p.Data...) debug.Log(debug.DEBUG_TRACE, "Final packet size", "bytes", len(p.Raw)) if len(p.Raw) > MTU { return errors.New("packet size exceeds MTU") } p.Packed = true p.updateHash() debug.Log(debug.DEBUG_ALL, "Packet hash", "hash", fmt.Sprintf("%x", p.PacketHash)) return nil } func (p *Packet) Unpack() error { if len(p.Raw) < 3 { return errors.New("packet too short") } flags := p.Raw[0] p.Hops = p.Raw[1] p.HeaderType = (flags & 0b01000000) >> 6 p.ContextFlag = (flags & 0b00100000) >> 5 p.TransportType = (flags & 0b00010000) >> 4 p.DestinationType = (flags & 0b00001100) >> 2 p.PacketType = flags & 0b00000011 dstLen := 16 // Truncated hash length if p.HeaderType == HeaderType2 { // Header Type 2: Header(2) + DestHash(16) + TransportID(16) + Context(1) + Data if len(p.Raw) < 2*dstLen+3 { return errors.New("packet too short for header type 2") } p.DestinationHash = p.Raw[2 : dstLen+2] // Destination hash first p.TransportID = p.Raw[dstLen+2 : 2*dstLen+2] // Transport ID second p.Context = p.Raw[2*dstLen+2] p.Data = p.Raw[2*dstLen+3:] } else { // Header Type 1: Header(2) + DestHash(16) + Context(1) + Data if len(p.Raw) < dstLen+3 { return errors.New("packet too short for header type 1") } p.TransportID = nil p.DestinationHash = p.Raw[2 : dstLen+2] p.Context = p.Raw[dstLen+2] p.Data = p.Raw[dstLen+3:] } p.Packed = false p.updateHash() return nil } func (p *Packet) GetHash() []byte { hashable := p.getHashablePart() hash := sha256.Sum256(hashable) return hash[:] } func (p *Packet) getHashablePart() []byte { hashable := []byte{p.Raw[0] & 0b00001111} // Lower 4 bits of flags if p.HeaderType == HeaderType2 { // Start hash from DestHash (index 18), skipping TransportID dstLen := 16 // RNS.Identity.TRUNCATED_HASHLENGTH / 8 startIndex := dstLen + 2 if len(p.Raw) > startIndex { hashable = append(hashable, p.Raw[startIndex:]...) } } else { // Start hash from DestHash (index 2) if len(p.Raw) > 2 { hashable = append(hashable, p.Raw[2:]...) } } return hashable } func (p *Packet) updateHash() { p.PacketHash = p.GetHash() } func (p *Packet) Hash() []byte { return p.GetHash() } func (p *Packet) TruncatedHash() []byte { hash := p.GetHash() if len(hash) >= 16 { return hash[:16] } return hash } func (p *Packet) Serialize() ([]byte, error) { if !p.Packed { if err := p.Pack(); err != nil { return nil, fmt.Errorf("failed to pack packet: %w", err) } } p.Addresses = p.DestinationHash return p.Raw, nil } func NewAnnouncePacket(destHash []byte, identity *identity.Identity, appData []byte, transportID []byte) (*Packet, error) { debug.Log(debug.DEBUG_ALL, "Creating new announce packet", "dest_hash", fmt.Sprintf("%x", destHash), "app_data", fmt.Sprintf("%x", appData)) // Get public key separated into encryption and signing keys pubKey := identity.GetPublicKey() encKey := pubKey[:32] signKey := pubKey[32:] debug.Log(debug.DEBUG_PACKETS, "Using public keys", "enc_key", fmt.Sprintf("%x", encKey), "sign_key", fmt.Sprintf("%x", signKey)) // Parse app name from first msgpack element if possible // For nodes, we'll use "reticulum.node" as the name hash var appName string if len(appData) > 2 && appData[0] == 0x93 { // This is a node announce, use standard node name appName = "reticulum.node" } else if len(appData) > 3 && appData[0] == 0x92 && appData[1] == 0xc4 { // Try to extract name from peer announce appData nameLen := int(appData[2]) if 3+nameLen <= len(appData) { appName = string(appData[3 : 3+nameLen]) } else { // Default fallback appName = "reticulum-go.node" } } else { // Default fallback appName = "reticulum-go.node" } // Create name hash (10 bytes) nameHash := sha256.Sum256([]byte(appName)) nameHash10 := nameHash[:10] debug.Log(debug.DEBUG_PACKETS, "Using name hash", "name", appName, "hash", fmt.Sprintf("%x", nameHash10)) // Create random hash (10 bytes) - 5 bytes random + 5 bytes time randomHash := make([]byte, 10) _, err := rand.Read(randomHash[:5]) // #nosec G104 if err != nil { debug.Log(debug.DEBUG_PACKETS, "Failed to read random bytes for hash", "error", err) return nil, err // Or handle the error appropriately } timeBytes := make([]byte, 8) binary.BigEndian.PutUint64(timeBytes, uint64(time.Now().Unix())) // #nosec G115 copy(randomHash[5:], timeBytes[:5]) debug.Log(debug.DEBUG_PACKETS, "Generated random hash", "hash", fmt.Sprintf("%x", randomHash)) // Prepare ratchet ID if available (not yet implemented) var ratchetID []byte // Prepare data for signature // Signature consists of destination hash, public keys, name hash, random hash, and app data signedData := make([]byte, 0, len(destHash)+len(encKey)+len(signKey)+len(nameHash10)+len(randomHash)+len(appData)) signedData = append(signedData, destHash...) signedData = append(signedData, encKey...) signedData = append(signedData, signKey...) signedData = append(signedData, nameHash10...) signedData = append(signedData, randomHash...) signedData = append(signedData, appData...) debug.Log(debug.DEBUG_TRACE, "Created signed data", "bytes", len(signedData)) // Sign the data signature := identity.Sign(signedData) debug.Log(debug.DEBUG_PACKETS, "Generated signature", "signature", fmt.Sprintf("%x", signature)) // Combine all fields according to spec // Data structure: Public Key (32) + Signing Key (32) + Name Hash (10) + Random Hash (10) + Ratchet (optional) + Signature (64) + App Data data := make([]byte, 0, 32+32+10+10+64+len(appData)) data = append(data, encKey...) // Encryption key (32 bytes) data = append(data, signKey...) // Signing key (32 bytes) data = append(data, nameHash10...) // Name hash (10 bytes) data = append(data, randomHash...) // Random hash (10 bytes) if ratchetID != nil { data = append(data, ratchetID...) // Ratchet ID (32 bytes if present) } data = append(data, signature...) // Signature (64 bytes) data = append(data, appData...) // Application data (variable) debug.Log(debug.DEBUG_TRACE, "Combined packet data", "bytes", len(data)) // Create the packet with header type 2 (two address fields) p := &Packet{ HeaderType: HeaderType2, PacketType: PacketTypeAnnounce, TransportID: transportID, DestinationHash: destHash, Data: data, } debug.Log(debug.DEBUG_VERBOSE, "Created announce packet", "type", p.PacketType, "header", p.HeaderType) return p, nil }