How BLE Peer Discovery and Connection Management Work in BitChat
BitChat implements a custom Bluetooth Low Energy mesh layer that alternates between scanning and advertising states to discover nearby devices, maintains a registry of peers with signal strength metrics, schedules connections respecting iOS limits, negotiates Noise-IK secure sessions, and fragments messages to comply with BLE's 512-byte MTU.
BitChat's peer-to-peer messaging architecture operates independently of internet infrastructure by leveraging a sophisticated BLE mesh implementation. The permissionlesstech/bitchat repository contains a complete Bluetooth Low Energy stack in bitchat/Services/BLE/ that handles radio duty cycles, cryptographic handshakes, and message routing. Understanding how BLE peer discovery and connection management function is critical for developers building offline-first communication systems.
Scanning and Advertising: The Discovery Phase
The discovery process relies on a duty-cycled approach to balance battery life against peer visibility.
BLEScanDutyPolicy (BLEScanDutyPolicy.swift) determines when the radio activates to conserve power. This policy returns schedules such as "scan for 5 seconds, idle for 30 seconds" and adapts dynamically based on battery level and known peer count. When scanning is active, BLEService initializes a CBCentralManager that forwards every didDiscover peripheral event to the registry.
Simultaneously, the device advertises its presence via CBPeripheralManager.startAdvertising using the custom service UUID kBitchatBLEService. The advertisement payload contains the device's Ed25519 public key, a capabilities bitmap indicating protocol version support, and an optional device name hash. This enables passive discovery by other BitChat instances without requiring active connections.
// Start the BLE subsystem from your AppDelegate or ChatViewModel
let bleService = BLEService()
bleService.start() // Initiates both scanning and advertising
Peer Registry and Deduplication
Every discovered peripheral enters BLEPeerRegistry (BLEPeerRegistry.swift), which stores a BLEPeerInfo struct for each device. Each registry entry tracks:
peerID: A deterministic identifier derived from the device's Ed25519 public keylastSeentimestamp andrssisignal strengthisConnectedandhasWrittenCharacteristicstate flags- Supported capabilities from the advertisement payload
The registry implements BLERedundantLinkPolicy to prevent duplicate entries when the same physical device appears under multiple BLE addresses. This deduplication ensures that connection scheduling treats each unique peer only once, avoiding redundant link attempts that would waste battery and radio resources.
Intelligent Connection Scheduling
The BLEConnectionScheduler (BLEConnectionScheduler.swift) operates on a background queue, executing every few seconds to evaluate which peers merit active connections. The scheduler sorts the registry by RSSI, recent activity, and existing secure session status, then applies the maxConcurrentConnections limit (currently set to 4) while respecting iOS-imposed Bluetooth constraints.
When selecting candidates, the scheduler prioritizes peers with strong signal strength that lack established Noise sessions. Once selected, BLEService initiates the connection via CBCentralManager.connect(peripheral).
// Manually trigger connection to a specific peer for debugging
if let peer = BLEPeerRegistry.shared.peer(with: somePeerID) {
BLEConnectionScheduler.shared.connect(to: peer)
}
Cryptographic Session Establishment
After the BLE link establishes, BLEService creates a BLENoiseSession for the peer. The handshake implementation resides in BLENoiseSessionQueues (BLENoiseSessionQueues.swift), which executes the Noise-IK protocol over BLE characteristics.
The outbound handshake packet is constructed by BLENoisePayloadFactory.privateFile(_:), while inbound packets are parsed by BLENoisePacketHandler. Successful authentication registers the peer in BLEIngressLinkRegistry (BLEIngressLinkRegistry.swift), creating a mapping between the CBPeripheral instance and the authenticated PeerID. This registry distinguishes between merely discovered peers and those with verified cryptographic identities.
Message Routing and Fragmentation
Authenticated peers participate in the mesh routing layer, which handles packet delivery and broadcast suppression.
BLEOutboundFragmentPlanner (BLEOutboundFragmentPlanner.swift) evaluates packets against the 512-byte BLE MTU limit. Oversized messages are split into sequenced chunks and queued in BLEOutboundWriteBuffer. The BLEOutboundFragmentTransferScheduler then drives actual CBPeripheral.writeValue calls, respecting iOS write-without-response rate limits to prevent controller saturation.
For broadcast decisions, BLEFanoutSelector (BLEFanoutSelector.swift) determines which subset of connected peers should receive a given packet. To prevent broadcast storms, BLEAnnounceThrottle (BLEAnnounceThrottle.swift) limits announcement frequency, while BLESubscriptionAnnounceLimiter caps total announcements within time windows.
// Send a protobuf-encoded packet through the mesh
let payload = BitchatPacket(message: .text("Hello BLE mesh!"))
BLEService.shared.send(payload) // Automatic fragmentation and routing
Summary
- BLEScanDutyPolicy implements adaptive scanning schedules to optimize battery usage while maintaining peer visibility
- BLEPeerRegistry maintains state for all discovered devices with deduplication via
BLERedundantLinkPolicy - BLEConnectionScheduler selects optimal connection candidates based on signal strength and connection limits (maximum 4 concurrent)
- BLENoiseSessionQueues handles Noise-IK cryptographic handshakes to establish secure sessions before message exchange
- BLEIngressLinkRegistry tracks authenticated peers separately from discovered but unverified devices
- BLEOutboundFragmentPlanner and BLEOutboundFragmentTransferScheduler manage the 512-byte MTU fragmentation and transmission pacing
- BLEFanoutSelector and throttling mechanisms prevent network flooding while ensuring message propagation
Frequently Asked Questions
How many simultaneous BLE connections can BitChat maintain?
BitChat enforces a maxConcurrentConnections limit of 4 simultaneous connections in BLEConnectionScheduler.swift. This constraint balances mesh connectivity against iOS Bluetooth controller limitations and battery consumption, ensuring stable operation without overwhelming the device's radio resources.
What cryptographic protocol secures BLE connections in BitChat?
BitChat uses the Noise-IK handshake pattern implemented in BLENoiseSessionQueues.swift. This modern cryptographic protocol provides mutual authentication and forward secrecy. The handshake occurs immediately after the BLE link establishes, with BLENoisePayloadFactory constructing packets and BLENoisePacketHandler validating responses before adding peers to BLEIngressLinkRegistry.
How does BitChat handle messages larger than BLE's packet size?
Messages exceeding the 512-byte MTU are automatically fragmented by BLEOutboundFragmentPlanner, which splits payloads into sequenced chunks. BLEOutboundFragmentTransferScheduler manages the transmission pacing to respect iOS write rate limits. On the receiving end, the reassembly process reconstructs the original message before passing it to higher-level protocol handlers.
What prevents BitChat from constantly draining the battery while scanning?
The BLEScanDutyPolicy implements adaptive duty cycling that adjusts scan duration and idle periods based on battery level and peer density. Rather than continuous scanning, the policy schedules brief scan windows (e.g., 5 seconds) followed by longer idle periods (e.g., 30 seconds). This approach, combined with passive advertising detection, allows peer discovery while maintaining acceptable battery life for mobile devices.
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 →