Files
bitchat/bitchat/Protocols/Packets.swift
T
jackandClaude Fable 5 688b954fb8 Add capability bits to announce TLV
Announces now carry an optional capabilities TLV (0x05): a little-endian
bitfield with named bits for upcoming features (prekeys, wifiBulk,
gateway, groups, board, vouch, meshDiagnostics). Old clients skip the
unknown TLV; peers without it decode as nil so features can distinguish
"legacy peer" from "advertises nothing".

PeerCapabilities lives in BitFoundation with a minimal-length encoding
that preserves unknown bits for forward compatibility. Peer capabilities
are stored in the BLE peer registry on verified announce and exposed via
BLEService.peerCapabilities(_:). The local advertisement set is empty
until each feature ships its bit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:44:55 +02:00

193 lines
6.7 KiB
Swift

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)
init(
nickname: String,
noisePublicKey: Data,
signingPublicKey: Data,
directNeighbors: [Data]?,
capabilities: PeerCapabilities? = nil
) {
self.nickname = nickname
self.noisePublicKey = noisePublicKey
self.signingPublicKey = signingPublicKey
self.directNeighbors = directNeighbors
self.capabilities = capabilities
}
private enum TLVType: UInt8 {
case nickname = 0x01
case noisePublicKey = 0x02
case signingPublicKey = 0x03
case directNeighbors = 0x04
case capabilities = 0x05
}
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)
}
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?
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..<offset + length]
offset += length
if let type = TLVType(rawValue: typeRaw) {
switch type {
case .nickname:
nickname = String(data: value, encoding: .utf8)
case .noisePublicKey:
noisePublicKey = Data(value)
case .signingPublicKey:
signingPublicKey = Data(value)
case .directNeighbors:
if length > 0 && length % 8 == 0 {
var neighbors = [Data]()
let count = length / 8
for i in 0..<count {
let start = value.startIndex + i * 8
let end = start + 8
neighbors.append(Data(value[start..<end]))
}
directNeighbors = neighbors
}
case .capabilities:
capabilities = PeerCapabilities(encoded: Data(value))
}
} else {
// Unknown TLV; skip (tolerant decoder for forward compatibility)
continue
}
}
guard let nickname = nickname, let noisePublicKey = noisePublicKey, let signingPublicKey = signingPublicKey else { return nil }
return AnnouncementPacket(
nickname: nickname,
noisePublicKey: noisePublicKey,
signingPublicKey: signingPublicKey,
directNeighbors: directNeighbors,
capabilities: capabilities
)
}
}
struct PrivateMessagePacket {
let messageID: String
let content: String
private enum TLVType: UInt8 {
case messageID = 0x00
case content = 0x01
}
func encode() -> 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..<offset + length]
offset += length
switch type {
case .messageID:
messageID = String(data: value, encoding: .utf8)
case .content:
content = String(data: value, encoding: .utf8)
}
}
guard let messageID = messageID, let content = content else { return nil }
return PrivateMessagePacket(messageID: messageID, content: content)
}
}