Add capability bits to announce TLV (#1375)

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: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack
2026-07-07 14:07:52 +02:00
committed by GitHub
co-authored by jack Claude Fable 5
parent c2a5668569
commit 5fdc15d5af
7 changed files with 182 additions and 5 deletions
@@ -0,0 +1,45 @@
import Foundation
/// Feature capabilities a peer advertises in its announce packet.
///
/// Encoded as a little-endian bitfield with trailing zero bytes dropped, so the
/// wire form grows only when high bits are assigned. Decoders keep the low 64
/// bits and ignore any longer field, and unknown bits are preserved verbatim
/// old clients skip the TLV entirely, new clients degrade per-feature.
public struct PeerCapabilities: OptionSet, Equatable, Hashable, Sendable {
public let rawValue: UInt64
public init(rawValue: UInt64) {
self.rawValue = rawValue
}
public static let prekeys = PeerCapabilities(rawValue: 1 << 0)
public static let wifiBulk = PeerCapabilities(rawValue: 1 << 1)
public static let gateway = PeerCapabilities(rawValue: 1 << 2)
public static let groups = PeerCapabilities(rawValue: 1 << 3)
public static let board = PeerCapabilities(rawValue: 1 << 4)
public static let vouch = PeerCapabilities(rawValue: 1 << 5)
public static let meshDiagnostics = PeerCapabilities(rawValue: 1 << 6)
/// Minimal little-endian byte encoding; always at least one byte so an
/// empty set is distinguishable from an absent TLV.
public func encoded() -> Data {
var value = rawValue
var bytes = Data()
repeat {
bytes.append(UInt8(truncatingIfNeeded: value))
value >>= 8
} while value != 0
return bytes
}
/// Accepts any length; bytes beyond the low 64 bits are ignored for
/// forward compatibility.
public init(encoded data: Data) {
var value: UInt64 = 0
for (index, byte) in data.prefix(8).enumerated() {
value |= UInt64(byte) << (8 * index)
}
self.init(rawValue: value)
}
}
@@ -0,0 +1,43 @@
//
// PeerCapabilitiesTests.swift
// bitchatTests
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
import Testing
import Foundation
@testable import BitFoundation
struct PeerCapabilitiesTests {
@Test
func encodingIsMinimalAndRoundTrips() {
#expect(PeerCapabilities([]).encoded() == Data([0x00]))
#expect(PeerCapabilities.prekeys.encoded() == Data([0x01]))
#expect(PeerCapabilities.meshDiagnostics.encoded() == Data([0x40]))
let high = PeerCapabilities(rawValue: 1 << 9)
#expect(high.encoded() == Data([0x00, 0x02]))
let all: PeerCapabilities = [.prekeys, .wifiBulk, .gateway, .groups, .board, .vouch, .meshDiagnostics]
#expect(PeerCapabilities(encoded: all.encoded()) == all)
#expect(PeerCapabilities(encoded: high.encoded()) == high)
#expect(PeerCapabilities(encoded: PeerCapabilities([]).encoded()) == [])
}
@Test
func decodingToleratesUnknownBitsAndOversizedFields() {
// Unknown bits survive a round-trip untouched.
let unknown = PeerCapabilities(encoded: Data([0xFF, 0xFF]))
#expect(unknown.rawValue == 0xFFFF)
#expect(unknown.contains(.gateway))
// Fields longer than 8 bytes keep the low 64 bits and ignore the rest.
let oversized = Data([0x01] + [UInt8](repeating: 0x00, count: 7) + [0xAA, 0xBB])
#expect(PeerCapabilities(encoded: oversized) == .prekeys)
// Empty value decodes to no capabilities.
#expect(PeerCapabilities(encoded: Data()) == [])
}
}