import BitFoundation import Foundation // MARK: - Protocol TLV Packets struct AnnouncementPacket { let nickname: String let noisePublicKey: Data // Noise static public key (Curve25519.KeyAgreement) let signingPublicKey: Data // Ed25519 public key for signing let directNeighbors: [Data]? // 8-byte peer IDs let capabilities: PeerCapabilities? // advertised feature bits; nil when absent (old clients) /// Rendezvous geohash cell this peer bridges, when advertising `.bridge`. /// Coarse (cell-level) by design; lets mesh-only peers compose correctly /// tagged rendezvous events without their own location fix. let bridgeGeohash: String? init( nickname: String, noisePublicKey: Data, signingPublicKey: Data, directNeighbors: [Data]?, capabilities: PeerCapabilities? = nil, bridgeGeohash: String? = nil ) { self.nickname = nickname self.noisePublicKey = noisePublicKey self.signingPublicKey = signingPublicKey self.directNeighbors = directNeighbors self.capabilities = capabilities self.bridgeGeohash = bridgeGeohash } private enum TLVType: UInt8 { case nickname = 0x01 case noisePublicKey = 0x02 case signingPublicKey = 0x03 case directNeighbors = 0x04 case capabilities = 0x05 case bridgeGeohash = 0x06 } func encode() -> Data? { var data = Data() // Reserve: TLVs for nickname (2 + n), noise key (2 + 32), signing key (2 + 32) data.reserveCapacity(2 + min(nickname.count, 255) + 2 + noisePublicKey.count + 2 + signingPublicKey.count) // TLV for nickname guard let nicknameData = nickname.data(using: .utf8), nicknameData.count <= 255 else { return nil } data.append(TLVType.nickname.rawValue) data.append(UInt8(nicknameData.count)) data.append(nicknameData) // TLV for noise public key guard noisePublicKey.count <= 255 else { return nil } data.append(TLVType.noisePublicKey.rawValue) data.append(UInt8(noisePublicKey.count)) data.append(noisePublicKey) // TLV for signing public key guard signingPublicKey.count <= 255 else { return nil } data.append(TLVType.signingPublicKey.rawValue) data.append(UInt8(signingPublicKey.count)) data.append(signingPublicKey) // TLV for direct neighbors (optional) if let neighbors = directNeighbors, !neighbors.isEmpty { let neighborsData = neighbors.prefix(10).reduce(Data()) { $0 + $1 } if !neighborsData.isEmpty && neighborsData.count % 8 == 0 { data.append(TLVType.directNeighbors.rawValue) data.append(UInt8(neighborsData.count)) data.append(neighborsData) } } // TLV for capabilities (optional) if let capabilities = capabilities { let capabilityBytes = capabilities.encoded() guard capabilityBytes.count <= 255 else { return nil } data.append(TLVType.capabilities.rawValue) data.append(UInt8(capabilityBytes.count)) data.append(capabilityBytes) } // TLV for bridge rendezvous cell (optional; old clients skip it) if let bridgeGeohash = bridgeGeohash, let cellData = bridgeGeohash.data(using: .utf8), !cellData.isEmpty, cellData.count <= 12 { data.append(TLVType.bridgeGeohash.rawValue) data.append(UInt8(cellData.count)) data.append(cellData) } return data } static func decode(from data: Data) -> AnnouncementPacket? { var offset = 0 var nickname: String? var noisePublicKey: Data? var signingPublicKey: Data? var directNeighbors: [Data]? var capabilities: PeerCapabilities? var bridgeGeohash: String? while offset + 2 <= data.count { let typeRaw = data[offset] offset += 1 let length = Int(data[offset]) offset += 1 guard offset + length <= data.count else { return nil } let value = data[offset.. 0 && length % 8 == 0 { var neighbors = [Data]() let count = length / 8 for i in 0.. Data? { var data = Data() data.reserveCapacity(2 + min(messageID.count, 255) + 2 + min(content.count, 255)) // TLV for messageID guard let messageIDData = messageID.data(using: .utf8), messageIDData.count <= 255 else { return nil } data.append(TLVType.messageID.rawValue) data.append(UInt8(messageIDData.count)) data.append(messageIDData) // TLV for content guard let contentData = content.data(using: .utf8), contentData.count <= 255 else { return nil } data.append(TLVType.content.rawValue) data.append(UInt8(contentData.count)) data.append(contentData) return data } static func decode(from data: Data) -> PrivateMessagePacket? { var offset = 0 var messageID: String? var content: String? while offset + 2 <= data.count { guard let type = TLVType(rawValue: data[offset]) else { return nil } offset += 1 let length = Int(data[offset]) offset += 1 guard offset + length <= data.count else { return nil } let value = data[offset..