# How BLE Peer Discovery and Connection Management Work in BitChat

> Discover how BitChat leverages BLE for peer discovery and connection management. Learn about its custom mesh layer, secure sessions, and efficient message handling for decentralized communication.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-07-27

---

**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`](https://github.com/permissionlesstech/bitchat/blob/main/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.

```swift
// 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`](https://github.com/permissionlesstech/bitchat/blob/main/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 key
- `lastSeen` timestamp and `rssi` signal strength
- `isConnected` and `hasWrittenCharacteristic` state 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`](https://github.com/permissionlesstech/bitchat/blob/main/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)`.

```swift
// 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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/BLEFanoutSelector.swift)) determines which subset of connected peers should receive a given packet. To prevent broadcast storms, **BLEAnnounceThrottle** ([`BLEAnnounceThrottle.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceThrottle.swift)) limits announcement frequency, while **BLESubscriptionAnnounceLimiter** caps total announcements within time windows.

```swift
// 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`](https://github.com/permissionlesstech/bitchat/blob/main/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`](https://github.com/permissionlesstech/bitchat/blob/main/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.