# How Bitchat Android Manages Peer Discovery Over Bluetooth Low Energy: A Technical Deep Dive

> Explore how Bitchat Android achieves BLE peer discovery with its layered architecture. Learn about BluetoothMeshService, BluetoothConnectionManager, and PeerManager in this technical deep dive.

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

---

**Bitchat Android discovers BLE peers through a layered architecture where `BluetoothMeshService` orchestrates advertising and scanning via `BluetoothConnectionManager`, maps MAC addresses to peer IDs using `BluetoothConnectionTracker`, and maintains reachability status through `PeerManager` updates triggered by ANNOUNCE packets.**

The Bitchat Android mesh networking stack implements peer discovery over Bluetooth Low Energy using a modular, transport-agnostic architecture that mirrors its iOS implementation while leveraging Android’s native BLE APIs. This design separates concerns between transport coordination, GATT server/client management, and peer identity tracking to enable reliable off-grid communication without centralized infrastructure.

## Transport-Level Orchestration with BluetoothMeshService

The discovery process begins in [`BluetoothMeshService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/BluetoothMeshService.kt), which acts as the top-level BLE coordinator. This service instantiates the core BLE transport and registers it with the broader mesh system.

When initialized, `BluetoothMeshService` creates a `BluetoothConnectionManager` instance—the BLE "core"—and registers it with `TransportBridgeService` under the transport ID **"BLE."** This registration allows the mesh router to treat BLE as a first-class transport alongside other potential interfaces.

```kotlin
// From BluetoothMeshService.kt - initialization sequence
val connectionManager = BluetoothConnectionManager(context, this)
// Register with the transport bridge for mesh routing
transportBridgeService.registerTransport("BLE", connectionManager)

```

The service also injects a critical closure called `isPeerDirectlyConnected` that reads the live `addressPeerMap` from the connection tracker, enabling the UI to display real-time direct-connection status without polling.

## Advertising and Scanning via Dual GATT Managers

Inside [`BluetoothConnectionManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/BluetoothConnectionManager.kt), the heavy lifting of peer discovery over Bluetooth Low Energy is handled by two specialized managers that run concurrently:

**BluetoothGattServerManager** handles BLE advertising, exposing the local device to remote scanners, and accepts inbound GATT connections from peers who discover the advertisement.

**BluetoothGattClientManager** performs continuous scanning for remote advertisements and initiates outbound GATT connections when compatible peers are detected.

Both managers are instantiated during `BluetoothConnectionManager` construction and activated via `startServices()`:

```kotlin
// From BluetoothConnectionManager.kt
fun startServices() {
    if (debugSettings.gattServerEnabled.value) {
        gattServerManager.start()
    }
    if (debugSettings.gattClientEnabled.value) {
        gattClientManager.startScanning()
    }
}

```

The entire discovery mechanism is gated by debug-controlled flags—`bleEnabled`, `gattServerEnabled`, and `gattClientEnabled`—allowing developers to toggle transport functionality without reconstructing the service instance.

## Mapping BLE Addresses to Peer Identities

Once physical connections establish, `BluetoothConnectionTracker` maintains the critical bi-directional mapping between network-layer identities and link-layer addresses. It stores this data in `addressPeerMap`, which associates a remote device’s MAC address with the peer-ID derived from the cryptographic Noise handshake.

When `BluetoothConnectionManager` receives a packet from the underlying BLE stack, it performs the following sequence:

1. Extracts the RSSI value from the connection metadata.
2. Updates the `PeerManager` via `delegate?.onRSSIUpdated()` to signal link quality.
3. Forwards the packet to the central `PacketProcessor` for message handling.
4. Updates `addressPeerMap` if this is a new or returning peer.

```kotlin
// Packet receipt handling in BluetoothConnectionManager.kt
override fun onPacketReceived(device: BluetoothDevice, data: ByteArray, rssi: Int) {
    val peerId = connectionTracker.getPeerIdForAddress(device.address)
    delegate?.onRSSIUpdated(peerId, rssi)
    packetProcessor.processPacket(data)
}

```

## Peer Lifecycle Management and Direct Link Detection

Discovery extends beyond mere connectivity into application-level peer management through `PeerManager`, which maintains a `ConcurrentHashMap<String, PeerInfo>` storing every announced peer in the mesh.

### Processing ANNOUNCE Packets

When the mesh receives an **ANNOUNCE** packet (processed by `DirectLinkAnnouncementPolicy`), the system evaluates whether the advertised relay address represents a direct BLE link. If the relay address matches a currently connected MAC address in `addressPeerMap`, `PeerManager` invokes `connectionManager.observePeerIfCurrent()` to log the direct route and trigger immediate synchronization.

```kotlin
// From BluetoothMeshService.kt - handling incoming announcements
fun handleAnnounce(packet: AnnouncePacket) {
    if (connectionManager.isDirectConnection(packet.relayAddress)) {
        peerManager.observePeerIfCurrent(packet.peerId, packet.relayAddress)
        triggerSync(packet.peerId)
    }
}

```

### Real-Time Connection Status

To support UI indicators showing whether a peer is reachable via direct BLE versus multi-hop routing, `BluetoothMeshService` injects a closure into `PeerManager` during initialization. This closure reads the live `addressPeerMap`, ensuring the displayed connection status always reflects the current link state without requiring additional network queries.

## Practical Implementation: Starting Discovery and Broadcasting

To activate peer discovery over Bluetooth Low Energy in your own build, interact with the public API surface of `BluetoothMeshService`:

```kotlin
// 1️⃣ Start the BLE transport (normally called from the Activity lifecycle)
val meshService = BluetoothMeshService(context)
meshService.startServices()  // Launches GATT server/client and registers transport

// 2️⃣ Toggle BLE discovery via debug settings without service recreation
com.bitchat.android.ui.debug.DebugSettingsManager
    .getInstance()
    .bleEnabled.value = true  // Set false to pause scanning/advertising

// 3️⃣ Manually initiate connection to a specific device (debugging utility)
meshService.connectionManager.connectToAddress("AA:BB:CC:DD:EE:FF")

// 4️⃣ Broadcast a message to all discovered peers
val packet = BitchatPacket(
    version = 1u,
    type = MessageType.MESSAGE.value,
    senderID = hexStringToByteArray(myPeerID),
    recipientID = SpecialRecipients.BROADCAST,
    timestamp = System.currentTimeMillis().toULong(),
    payload = "Hello, mesh!".toByteArray(),
    ttl = MAX_TTL
)
meshService.broadcastRoutedPacket(RoutedPacket(packet))

```

## Summary

- **BluetoothMeshService** coordinates the BLE transport by creating `BluetoothConnectionManager` and registering it with the mesh bridge under the ID **"BLE."**
- **Dual GATT architecture** separates advertising (`BluetoothGattServerManager`) from scanning (`BluetoothGattClientManager`), both controlled via debug flags in `BluetoothConnectionManager.startServices()`.
- **Address-to-identity mapping** occurs in `BluetoothConnectionTracker` through `addressPeerMap`, which links MAC addresses to Noise-derived peer IDs and tracks RSSI values.
- **Peer state management** uses `PeerManager` with a `ConcurrentHashMap` to store discovered nodes, processing ANNOUNCE packets via `DirectLinkAnnouncementPolicy` to distinguish direct BLE links from routed paths.
- **Dynamic status updates** are enabled by a closure injected into `PeerManager` that reads the live connection map, allowing the UI to reflect real-time reachability without network overhead.

## Frequently Asked Questions

### How does Bitchat Android distinguish between direct BLE links and routed connections?

The system checks the `addressPeerMap` maintained by `BluetoothConnectionTracker`. When processing an ANNOUNCE packet, `DirectLinkAnnouncementPolicy` verifies if the advertised relay address exists as a connected MAC address. If present, `PeerManager` marks the peer as directly connected via the closure injected by `BluetoothMeshService`; otherwise, the traffic routes through intermediate mesh hops.

### What controls whether the app advertises or scans for peers?

Debug-controlled boolean flags—`bleEnabled`, `gattServerEnabled`, and `gattClientEnabled`—govern the discovery state. These are checked inside `BluetoothConnectionManager.startServices()`, allowing developers to toggle advertising, scanning, or the entire transport without destroying the `BluetoothMeshService` instance.

### How does the app handle RSSI updates for discovered peers?

When `BluetoothConnectionManager` receives a packet from the BLE stack, it extracts the RSSI value and immediately notifies `PeerManager` via the `delegate?.onRSSIUpdated()` callback. This updates the link quality metric stored in the peer’s `PeerInfo` record, which the UI can access to display signal strength indicators.

### Which source files contain the core BLE discovery logic?

The primary files are:
- [`BluetoothMeshService.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/BluetoothMeshService.kt) – Transport registration and ANNOUNCE handling.
- [`BluetoothConnectionManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/BluetoothConnectionManager.kt) – GATT manager orchestration and packet routing.
- [`BluetoothGattServerManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/BluetoothGattServerManager.kt) – BLE advertising and inbound connections.
- [`BluetoothGattClientManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/BluetoothGattClientManager.kt) – Scanning and outbound connections.
- [`BluetoothConnectionTracker.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/BluetoothConnectionTracker.kt) – MAC-to-peer-ID mapping via `addressPeerMap`.
- [`PeerManager.kt`](https://github.com/permissionlesstech/bitchat-android/blob/main/PeerManager.kt) – Central peer storage and direct-connection status.