package announce import ( "crypto/ed25519" "crypto/rand" "crypto/sha256" "encoding/binary" "errors" "fmt" "log" "sync" "time" "github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/identity" ) const ( PACKET_TYPE_DATA = 0x00 PACKET_TYPE_ANNOUNCE = 0x01 PACKET_TYPE_LINK = 0x02 PACKET_TYPE_PROOF = 0x03 // Announce Types ANNOUNCE_NONE = 0x00 ANNOUNCE_PATH = 0x01 ANNOUNCE_IDENTITY = 0x02 // Header Types HEADER_TYPE_1 = 0x00 // One address field HEADER_TYPE_2 = 0x01 // Two address fields // Propagation Types PROP_TYPE_BROADCAST = 0x00 PROP_TYPE_TRANSPORT = 0x01 DEST_TYPE_SINGLE = 0x00 DEST_TYPE_GROUP = 0x01 DEST_TYPE_PLAIN = 0x02 DEST_TYPE_LINK = 0x03 // IFAC Flag IFAC_NONE = 0x00 IFAC_AUTH = 0x80 MAX_HOPS = 128 PROPAGATION_RATE = 0.02 // 2% of interface bandwidth RETRY_INTERVAL = 300 // 5 minutes MAX_RETRIES = 3 ) type AnnounceHandler interface { AspectFilter() []string ReceivedAnnounce(destinationHash []byte, announcedIdentity interface{}, appData []byte) error ReceivePathResponses() bool } type Announce struct { mutex *sync.RWMutex destinationHash []byte identity *identity.Identity appData []byte hops uint8 timestamp int64 signature []byte pathResponse bool retries int handlers []AnnounceHandler ratchetID []byte packet []byte hash []byte } func New(dest *identity.Identity, appData []byte, pathResponse bool) (*Announce, error) { if dest == nil { return nil, errors.New("destination identity required") } a := &Announce{ mutex: &sync.RWMutex{}, identity: dest, appData: appData, hops: 0, timestamp: time.Now().Unix(), pathResponse: pathResponse, retries: 0, handlers: make([]AnnounceHandler, 0), } // Generate truncated hash from public key pubKey := dest.GetPublicKey() hash := sha256.Sum256(pubKey) a.destinationHash = hash[:identity.TRUNCATED_HASHLENGTH/8] // Get current ratchet ID if enabled currentRatchet := dest.GetCurrentRatchetKey() if currentRatchet != nil { a.ratchetID = dest.GetRatchetID(currentRatchet) } // Sign announce data signData := append(a.destinationHash, a.appData...) if a.ratchetID != nil { signData = append(signData, a.ratchetID...) } a.signature = dest.Sign(signData) return a, nil } func (a *Announce) Propagate(interfaces []common.NetworkInterface) error { a.mutex.RLock() defer a.mutex.RUnlock() log.Printf("[DEBUG-7] Propagating announce across %d interfaces", len(interfaces)) var packet []byte if a.packet != nil { log.Printf("[DEBUG-7] Using cached packet (%d bytes)", len(a.packet)) packet = a.packet } else { log.Printf("[DEBUG-7] Creating new packet") packet = a.CreatePacket() a.packet = packet } for _, iface := range interfaces { if !iface.IsEnabled() { log.Printf("[DEBUG-7] Skipping disabled interface: %s", iface.GetName()) continue } if !iface.GetBandwidthAvailable() { log.Printf("[DEBUG-7] Skipping interface with insufficient bandwidth: %s", iface.GetName()) continue } log.Printf("[DEBUG-7] Sending announce on interface %s", iface.GetName()) if err := iface.Send(packet, ""); err != nil { log.Printf("[DEBUG-7] Failed to send on interface %s: %v", iface.GetName(), err) return fmt.Errorf("failed to propagate on interface %s: %w", iface.GetName(), err) } log.Printf("[DEBUG-7] Successfully sent announce on interface %s", iface.GetName()) } return nil } func (a *Announce) RegisterHandler(handler AnnounceHandler) { a.mutex.Lock() defer a.mutex.Unlock() a.handlers = append(a.handlers, handler) } func (a *Announce) DeregisterHandler(handler AnnounceHandler) { a.mutex.Lock() defer a.mutex.Unlock() for i, h := range a.handlers { if h == handler { a.handlers = append(a.handlers[:i], a.handlers[i+1:]...) break } } } func (a *Announce) HandleAnnounce(data []byte) error { a.mutex.Lock() defer a.mutex.Unlock() log.Printf("[DEBUG-7] Handling announce packet of %d bytes", len(data)) // Minimum packet size validation (2 header + 16 hash + 32 pubkey + 1 hops + 2 appdata len + 64 sig) if len(data) < 117 { log.Printf("[DEBUG-7] Invalid announce data length: %d bytes", len(data)) return errors.New("invalid announce data length") } // Parse header header := data[:2] hopCount := header[1] log.Printf("[DEBUG-7] Announce header: type=%x, hops=%d", header[0], hopCount) if hopCount > MAX_HOPS { log.Printf("[DEBUG-7] Announce exceeded max hops: %d", hopCount) return errors.New("announce exceeded maximum hop count") } // Extract fields with detailed logging destHash := data[2:18] publicKey := data[18:50] hopsByte := data[50] log.Printf("[DEBUG-7] Announce fields: destHash=%x, pubKeyLen=%d, hops=%d", destHash, len(publicKey), hopsByte) // Validate hop count matches header if hopsByte != hopCount { return errors.New("inconsistent hop count in packet") } // Extract app data length and content appDataLen := binary.BigEndian.Uint16(data[51:53]) appDataEnd := 53 + int(appDataLen) if appDataEnd > len(data) { return errors.New("invalid app data length") } appData := data[53:appDataEnd] // Handle ratchet ID if present var ratchetID []byte signatureStart := appDataEnd remainingBytes := len(data) - appDataEnd if remainingBytes > ed25519.SignatureSize { ratchetID = data[appDataEnd : len(data)-ed25519.SignatureSize] signatureStart = len(data) - ed25519.SignatureSize } if signatureStart+ed25519.SignatureSize > len(data) { return errors.New("invalid signature position") } signature := data[signatureStart:] // Create announced identity announcedIdentity := identity.FromPublicKey(publicKey) if announcedIdentity == nil { return errors.New("invalid identity public key") } // Verify signature signData := append(destHash, appData...) if ratchetID != nil { signData = append(signData, ratchetID...) } if !announcedIdentity.Verify(signData, signature) { return errors.New("invalid announce signature") } // Process with handlers for _, handler := range a.handlers { if handler.ReceivePathResponses() || !a.pathResponse { if err := handler.ReceivedAnnounce(destHash, announcedIdentity, appData); err != nil { return err } } } return nil } func (a *Announce) RequestPath(destHash []byte, onInterface common.NetworkInterface) error { a.mutex.Lock() defer a.mutex.Unlock() // Create path request packet packet := make([]byte, 0) packet = append(packet, destHash...) packet = append(packet, byte(0)) // Initial hop count // Send path request if err := onInterface.Send(packet, ""); err != nil { return err } return nil } // CreateHeader creates a Reticulum packet header according to spec func CreateHeader(ifacFlag byte, headerType byte, contextFlag byte, propType byte, destType byte, packetType byte, hops byte) []byte { header := make([]byte, 2) // First byte: [IFAC Flag], [Header Type], [Context Flag], [Propagation Type], [Destination Type] and [Packet Type] header[0] = ifacFlag | (headerType << 6) | (contextFlag << 5) | (propType << 4) | (destType << 2) | packetType // Second byte: Number of hops header[1] = hops return header } func (a *Announce) CreatePacket() []byte { log.Printf("[DEBUG-7] Creating announce packet") headerByte := byte( (IFAC_NONE) | (HEADER_TYPE_1 << 6) | (0 << 5) | (PROP_TYPE_BROADCAST << 4) | (DEST_TYPE_SINGLE << 2) | PACKET_TYPE_ANNOUNCE, ) log.Printf("[DEBUG-7] Created header byte: %02x, hops: %d", headerByte, a.hops) packet := []byte{headerByte, a.hops} // Add destination hash (16 bytes) log.Printf("[DEBUG-7] Adding destination hash (16 bytes): %x", a.destinationHash) packet = append(packet, a.destinationHash...) // Split public key into encryption and signing keys (32 bytes each) pubKey := a.identity.GetPublicKey() encKey := pubKey[:32] signKey := pubKey[32:] log.Printf("[DEBUG-7] Adding encryption key (32 bytes): %x", encKey) packet = append(packet, encKey...) log.Printf("[DEBUG-7] Adding signing key (32 bytes): %x", signKey) packet = append(packet, signKey...) // Add name hash (10 bytes) nameHash := a.identity.GetNameHash() log.Printf("[DEBUG-7] Adding name hash (10 bytes): %x", nameHash) packet = append(packet, nameHash...) // Add random hash (5 random + 5 timestamp bytes = 10 bytes) randomHash := make([]byte, 5) rand.Read(randomHash) timeBytes := make([]byte, 8) binary.BigEndian.PutUint64(timeBytes, uint64(time.Now().Unix())) log.Printf("[DEBUG-7] Adding random hash (10 bytes): %x%x", randomHash, timeBytes[:5]) packet = append(packet, randomHash...) packet = append(packet, timeBytes[:5]...) // Add ratchet if present (32 bytes) if a.ratchetID != nil { log.Printf("[DEBUG-7] Adding ratchet ID (32 bytes): %x", a.ratchetID) packet = append(packet, a.ratchetID...) } // Add app data log.Printf("[DEBUG-7] Adding app data (%d bytes): %x", len(a.appData), a.appData) packet = append(packet, a.appData...) // Add signature (64 bytes) signData := append(a.destinationHash, a.appData...) if a.ratchetID != nil { signData = append(signData, a.ratchetID...) } signature := a.identity.Sign(signData) log.Printf("[DEBUG-7] Adding signature (64 bytes): %x", signature) packet = append(packet, signature...) log.Printf("[DEBUG-7] Final packet size: %d bytes", len(packet)) a.packet = packet return packet } type AnnouncePacket struct { Data []byte } func NewAnnouncePacket(pubKey []byte, appData []byte, announceID []byte) *AnnouncePacket { packet := &AnnouncePacket{} // Build packet data packet.Data = make([]byte, 0, len(pubKey)+len(appData)+len(announceID)+4) // Add header packet.Data = append(packet.Data, PACKET_TYPE_ANNOUNCE) packet.Data = append(packet.Data, ANNOUNCE_IDENTITY) // Add public key packet.Data = append(packet.Data, pubKey...) // Add app data length and content appDataLen := make([]byte, 2) binary.BigEndian.PutUint16(appDataLen, uint16(len(appData))) packet.Data = append(packet.Data, appDataLen...) packet.Data = append(packet.Data, appData...) // Add announce ID packet.Data = append(packet.Data, announceID...) return packet } // NewAnnounce creates a new announce packet for a destination func NewAnnounce(identity *identity.Identity, appData []byte, ratchetID []byte, pathResponse bool) (*Announce, error) { log.Printf("[DEBUG-7] Creating new announce: appDataLen=%d, hasRatchet=%v, pathResponse=%v", len(appData), ratchetID != nil, pathResponse) if identity == nil { log.Printf("[DEBUG-7] Error: nil identity provided") return nil, errors.New("identity cannot be nil") } destHash := identity.Hash() log.Printf("[DEBUG-7] Generated destination hash: %x", destHash) a := &Announce{ identity: identity, appData: appData, ratchetID: ratchetID, pathResponse: pathResponse, destinationHash: destHash, hops: 0, mutex: &sync.RWMutex{}, handlers: make([]AnnounceHandler, 0), } log.Printf("[DEBUG-7] Created announce object: destHash=%x, hops=%d", a.destinationHash, a.hops) // Create initial packet packet := a.CreatePacket() a.packet = packet // Generate hash hash := a.Hash() log.Printf("[DEBUG-7] Generated announce hash: %x", hash) return a, nil } func (a *Announce) Hash() []byte { if a.hash == nil { // Generate hash from announce data h := sha256.New() h.Write(a.destinationHash) h.Write(a.identity.GetPublicKey()) h.Write([]byte{a.hops}) h.Write(a.appData) if a.ratchetID != nil { h.Write(a.ratchetID) } a.hash = h.Sum(nil) } return a.hash } func (a *Announce) GetPacket() []byte { a.mutex.Lock() defer a.mutex.Unlock() if a.packet == nil { // Generate hash from announce data h := sha256.New() h.Write(a.destinationHash) h.Write(a.identity.GetPublicKey()) h.Write([]byte{a.hops}) h.Write(a.appData) if a.ratchetID != nil { h.Write(a.ratchetID) } // Construct packet packet := make([]byte, 0) packet = append(packet, PACKET_TYPE_ANNOUNCE) packet = append(packet, a.destinationHash...) packet = append(packet, a.identity.GetPublicKey()...) packet = append(packet, a.hops) packet = append(packet, a.appData...) if a.ratchetID != nil { packet = append(packet, a.ratchetID...) } // Add signature signData := append(a.destinationHash, a.appData...) if a.ratchetID != nil { signData = append(signData, a.ratchetID...) } signature := a.identity.Sign(signData) packet = append(packet, signature...) a.packet = packet } return a.packet }