How Peers Advertise and Discover Capabilities in BitChat: A Complete Technical Guide
BitChat uses a lightweight, forward-compatible bit-field encoded as a PeerCapabilities option set embedded directly in BLE advertisement packets to enable real-time peer capability exchange.
Peers in BitChat continuously broadcast what features they support—gateway mode, bridge relay, private media, and more—through Bluetooth Low Energy (BLE) mesh advertisements. This declarative capability system allows the mesh to dynamically route messages based on each peer's advertised abilities, without requiring separate handshake protocols or connection negotiation.
How BitChat Encodes Peer Capabilities
The foundation of BitChat's capability system is the PeerCapabilities bit-field defined in PeerCapabilities.swift. This Swift OptionSet packs multiple feature flags into a compact, variable-length byte sequence that minimizes BLE advertisement payload overhead.
Capability Bits Currently Defined
| Bit | Capability | Description |
|---|---|---|
prekeys |
Pre-key support | Enables X3DH-style key agreement |
wifiBulk |
WiFi bulk transfer | Supports large file transfers over local WiFi |
gateway |
Gateway mode | Relays messages to/from the internet |
groups |
Group messaging | Supports multi-party chat primitives |
board |
Public board | Publishes to decentralized bulletin boards |
vouch |
Vouching | Can attest to peer identity |
meshDiagnostics |
Diagnostics | Exposes mesh health metrics |
bridge |
Nostr bridge | Relays to Nostr network via geohash |
privateMedia |
Private media | Accepts encrypted media blobs |
privateMediaReceipts |
Media receipts | Sends delivery confirmations for private media |
The encoding is little-endian and automatically trimmed to the minimum bytes required. A peer advertising only basic capabilities might send 1-2 bytes, while a full-featured gateway node sends more—ensuring backward compatibility without bloating simple advertisements.
// Encoding example from PeerCapabilities.swift internals
let caps: PeerCapabilities = [.gateway, .bridge, .privateMedia]
let data = caps.encoded() // → Compact Data, 2 bytes for these three flags
let decoded = PeerCapabilities(encoded: data) // ← Receiver reconstructs the set
Advertising Capabilities in BLE Mesh Sessions
When a BitChat device starts broadcasting, BLERadioController.advertisementData() constructs the BLE payload. The actual injection of capability data happens in BLEService.swift, where the service assembles the TLV (Type-Length-Value) structure that carries the PeerCapabilities bit-field.
Runtime Advertisement Updates
Capabilities are not static. When users toggle services, the mesh adapts immediately:
// Enable bridge mode (exposes geohash cell to Nostr)
BridgeService.shared.isEnabled = true
BLEService.shared?.setLocalCapability(.bridge, enabled: true)
The setLocalCapability(_:enabled:) method triggers three operations:
- Updates the local identity's capability state
- Re-encodes the bit-field
- Re-advertises the new packet so nearby peers detect the change within seconds
This design enables hot-swapping roles—a phone can become a bridge when plugged in, then disable it to save battery, with zero mesh disruption.
Special Handling for Bridge Geohash
When .bridge is enabled, BLEService.swift additionally includes advertisedBridgeGeohash in the advertisement. This allows Nostr-connected bridges to declare their geographic coverage area, helping peers find optimal relay paths:
// From BLEService.swift advertisement construction
if BridgeService.shared.isEnabled, let geohash = bridgeGeohash {
advertisement.setBridgeGeohash(geohash) // Helps peers locate nearby Nostr exits
}
Discovery: Parsing Peer Capabilities from Advertisements
Incoming BLE advertisements flow through BLEService → BLEPeerRegistry. The registry maintains a real-time database of visible peers and their capabilities.
Extracting Capabilities from Announce Packets
// Query a discovered peer's capabilities
if let peer = BLEPeerRegistry.shared.peer(for: remoteID) {
let caps = peer.advertisedCapabilities
if caps.contains(.privateMedia) {
// Peer can receive encrypted private media
sendPrivateMedia(to: remoteID, data: encryptedBlob)
}
if caps.contains(.bridge) {
// Peer can route to Nostr
routeViaBridge(peer: remoteID, message: nostrPayload)
}
}
The BLEPeerRegistry.swift parser extracts the capabilities TLV from each announce packet, validates the bit-field length, and stores the result in a PeerCapabilities instance keyed by the peer's stable identifier. Old advertisements expire automatically, ensuring the capability view reflects currently broadcasted state.
Transport Layer Integration for Routing Decisions
Beyond simple discovery, advertised capabilities directly influence message routing. Transport.swift reads advertisedCapabilities from each peer's last verified announce when making delivery decisions.
Capability-Aware Routing Examples
| Scenario | Capability Check | Routing Choice |
|---|---|---|
| Send large file | .wifiBulk present? |
Upgrade to WiFi direct transfer |
| Message offline contact | .gateway present nearby? |
Queue for internet relay |
| Post to Nostr | .bridge with matching geohash? |
Route through bridge peer |
| Send sensitive photo | .privateMedia confirmed? |
Encrypt and send; else warn user |
This integration is zero-round-trip: the transport layer consults cached capability data from the registry, avoiding latency penalties for capability negotiation.
// From Transport.swift routing logic
let bestBridge = peerRegistry.peers(containing: .bridge)
.filter { $0.advertisedGeohash?.contains(myLocation) ?? false }
.min { $0.hopDistance < $1.hopDistance }
Forward Compatibility and Versioning
The bit-field design prioritizes forward compatibility. New capability bits occupy higher positions in the option set. Legacy receivers ignore unknown high bits (they truncate to their understood length), while new receivers parse the full field. This allows the BitChat protocol to evolve without coordinated upgrades.
Key implementation details from PeerCapabilities.swift:
- Unknown bits are preserved during decoding—peers can pass through capabilities they don't understand
- The
encoded()method computes the minimum bytes needed:max(1, (highestSetBit / 8) + 1) - No version number in the packet—implicit versioning via bit presence
Summary
- BitChat encodes capabilities as a compact, trimmed bit-field in
PeerCapabilities.swift, supporting flags for gateway, bridge, private media, and more - Advertising happens through BLE TLVs constructed in
BLEService.swift, with runtime updates viasetLocalCapability(_:enabled:) - Discovery parses announcements in
BLEPeerRegistry.swift, caching each peer's abilities for immediate query - Transport routing consumes capabilities from
Transport.swiftto select optimal paths without additional handshake overhead - Forward compatibility is built-in: unknown bits are ignored, new features only extend payload when used
Frequently Asked Questions
What is the maximum size of a capability advertisement in BitChat?
The PeerCapabilities bit-field scales automatically based on the highest set bit. Currently, with flags up to privateMediaReceipts, advertisements typically require 2 bytes. The implementation in PeerCapabilities.swift uses UInt64 internally, allowing up to 64 distinct capability flags before requiring architectural changes—far more than any near-term protocol needs.
Can a BitChat peer lie about its capabilities?
The current implementation trusts advertised capabilities without cryptographic attestation. However, the Transport.swift layer treats unverified capabilities as hints: actual protocol attempts fail gracefully if a peer doesn't implement claimed features. Future versions may sign capability announcements using the peer's identity key for stronger verification.
How quickly do capability changes propagate through the mesh?
Updates are near-instantaneous locally (BLE advertisement intervals are typically 100-300ms). The BLEService re-advertises immediately on setLocalCapability changes. Mesh-wide visibility depends on gossip routing—peers rebroadcast capability metadata when forwarding messages, so indirect peers learn capabilities within a few gossip hops.
Why use a bit-field instead of a list of strings or protocol version numbers?
Bit-fields minimize BLE payload size—critical for the 31-byte advertisement limit. They also enable fast set operations (contains(_:), intersection, union) without parsing overhead. The explicit design in PeerCapabilities.swift prioritizes runtime efficiency and forward compatibility over human readability, appropriate for a low-level mesh protocol.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →