# How BitChat's BLEAnnounceHandler Processes Incoming Announcements

> Learn how BitChat's BLEAnnounceHandler processes incoming announcements in 14 steps. Discover cryptographic validation, trust policies, peer registry updates, and side effect management.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: internals
- Published: 2026-08-08

---

**BitChat's BLEAnnounceHandler processes incoming BLE announcements through a deterministic 14-step pipeline that validates packet integrity cryptographically, evaluates trust policies, atomically updates the peer registry, and manages all side effects through an injectable environment pattern.**

BitChat, an open-source peer-to-peer messaging platform maintained in the `permissionlesstech/bitchat` repository, discovers nearby peers using Bluetooth Low Energy (BLE) announcement packets. The core logic that ingests these packets resides in `BLEAnnounceHandler`, located at [`bitchat/Services/BLE/BLEAnnounceHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEAnnounceHandler.swift). This handler is deliberately designed to be queue-agnostic and synchronously testable, delegating all side effects—such as registry writes, UI notifications, and topology updates—to closure-based dependencies supplied by `BLEAnnounceHandlerEnvironment`.

## The 14-Step Processing Pipeline

When `BLEAnnounceHandler.handle(_:from:)` receives a packet, it executes a strict sequence of validation, verification, and state updates. Each step is implemented as a discrete operation with specific source file references.

### Step 1: Pre-Flight Validation

The pipeline begins with `BLEAnnouncePreflightPolicy.evaluate` in [`BLEAnnounceHandlingPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandlingPolicy.swift) (lines 21-48). This stage decodes the raw payload, validates that the derived peer ID matches the packet’s sender, rejects self-announces, and discards stale packets before any cryptographic work begins.

### Steps 2-4: Cryptographic Verification and Trust

Once pre-flight passes, the handler prepares signing keys and verifies authenticity:

- **Signature Preparation**: The handler retrieves `existingPeerKeys` from the registry, falling back to the `persistedSigningPublicKey` if the entry was lost (referenced at [`BLEAnnounceHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandler.swift) lines 29-38).
- **Signature Verification**: If the packet contains a signature, `env.verifySignature` validates it against the announced signing key, storing the boolean result in `signatureValid` (lines 41-46).
- **Trust Evaluation**: `BLEAnnounceTrustPolicy.evaluate` (defined in [`BLEAnnounceHandlingPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandlingPolicy.swift) lines 71-114) makes the final trust decision. It rejects announcements with missing signatures, invalid cryptographic proofs, or key mismatches, returning a verification status used for all subsequent steps.

### Steps 5-6: Direct Announce Detection and Registry Barrier

The handler determines if the packet is a **direct announce** by checking `packet.ttl == env.messageTTL` and verifies whether the inbound link is already bound to a different peer via `env.linkBoundToOtherPeer` (lines 76-80).

All registry mutations occur inside `env.withRegistryBarrier` (lines 96-125). If the trust evaluation returns unverified, the barrier exits early. Otherwise, `env.upsertVerifiedAnnounce` atomically inserts or updates the peer entry within the barrier block.

### Steps 7-12: Topology, Persistence, and UI Side Effects

For verified announcements, the handler triggers multiple side effects through the environment:

- **Connection Logging**: Debounced logs for new or re-connected peers are emitted via `env.shouldEmitReconnectLog` (lines 28-40).
- **Topology Updates**: Verified neighbor claims are handed to `env.updateTopology` (lines 43-46).
- **Identity Persistence**: The entire `AnnouncementPacket` is persisted using `env.persistIdentity` so the signing key survives application restarts (lines 48-53).
- **Deduplication and Response**: A deterministic deduplication ID (`announce-back-<peerID>`) prevents duplicate processing. If the packet is new, `env.sendAnnounceBack()` initiates bidirectional discovery (lines 55-68).
- **UI Notification**: `env.deliverAnnounceUIEvents` emits `.peerConnected` events and schedules the initial gossip sync (lines 70-74).
- **Packet Tracking**: The packet is recorded via `env.trackPacketSeen` for later gossip synchronization (lines 76-78).

### Steps 13-14: Afterglow and Result

For brand-new peers, the handler schedules an **afterglow re-announce**—a short random delay that pushes presence one hop further into the mesh—using `env.scheduleAfterglow` (lines 84-89).

Finally, the method returns a `BLEAnnounceHandlingResult` struct (lines 78-86) containing the peer ID, decoded announcement, and boolean flags indicating `isDirect` and `isVerified`.

## The Environment-Based Architecture

`BLEAnnounceHandler` achieves testability by avoiding direct dependencies on BLE queues or UI frameworks. Instead, it accepts a `BLEAnnounceHandlerEnvironment` struct containing closures for all external operations. This design allows unit tests to inject deterministic mocks, as demonstrated in [`BLEAnnounceHandlerTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandlerTests.swift).

### Wiring the Environment

A concrete implementation (such as `BLEService`) constructs the environment as follows:

```swift
let env = BLEAnnounceHandlerEnvironment(
    localPeerID: { self.identityManager.localPeerID },
    messageTTL: 1,
    now: Date.init,
    existingPeerKeys: registry.existingKeys(for:),
    persistedSigningPublicKey: identityManager.persistedSigningKey,
    authenticatedSigningPublicKey: { noiseKey in 
        registry.authenticatedSigningKey(forNoiseKey: noiseKey) 
    },
    verifySignature: { packet, pubKey in
        packet.signature.map { 
            Crypto.verify($0, data: packet.payload, publicKey: pubKey) 
        } ?? false
    },
    linkState: { peerID in self.linkState(for: peerID) },
    linkBoundToOtherPeer: { packet, peerID in 
        self.isLinkBound(toOtherPeer: peerID, packet: packet) 
    },
    withRegistryBarrier: { work in registry.performBarrier(work) },
    upsertVerifiedAnnounce: registry.upsertVerifiedAnnounce,
    shouldEmitReconnectLog: { peerID, now in 
        self.reconnectLogDebouncer.shouldLog(peerID, now) 
    },
    updateTopology: topology.update,
    persistIdentity: { ann in self.identityStore.save(ann) },
    dedupContains: dedupCache.contains,
    dedupMarkProcessed: dedupCache.mark,
    deliverAnnounceUIEvents: { pid, notify, schedule in
        ui.notifyPeerConnected(pid)
        if schedule { sync.scheduleInitial(for: pid) }
        ui.refreshPeerList()
    },
    trackPacketSeen: { pkt in sync.track(pkt) },
    sendAnnounceBack: { self.sendOwnAnnounce() },
    scheduleAfterglow: { delay in self.scheduleAfterglow(delay) }
)

let handler = BLEAnnounceHandler(environment: env)
let result = handler.handle(incomingPacket, from: sourcePeerID)

```

### Unit Testing the Handler

Because the handler is pure logic wrapped around the environment, tests can verify the full pipeline with mocked dependencies:

```swift
func testHandlesValidAnnounce() {
    let env = makeEnvironment(...)
    let handler = BLEAnnounceHandler(environment: env)
    
    let packet = makeValidAnnouncePacket()
    let result = handler.handle(packet, from: somePeerID)
    
    XCTAssertNotNil(result)
    XCTAssertTrue(result!.isVerified)
    XCTAssertTrue(result!.isDirectAnnounce)
}

```

## Summary

- **BLEAnnounceHandler** ingests BLE packets through a strictly ordered 14-step pipeline defined in [`BLEAnnounceHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandler.swift).
- **Pre-flight validation** and **trust evaluation** occur in [`BLEAnnounceHandlingPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandlingPolicy.swift), separating policy from execution.
- **Registry updates** are atomic, executing within `withRegistryBarrier` only after cryptographic verification succeeds.
- **Side effects** (UI updates, topology changes, persistence) are injected via `BLEAnnounceHandlerEnvironment`, making the core logic testable without BLE hardware.
- **Bidirectional discovery** uses deduplication IDs to prevent redundant announce-backs, while **afterglow scheduling** propagates presence to multi-hop neighbors.

## Frequently Asked Questions

### How does BLEAnnounceHandler prevent processing of spoofed or replayed announcements?

The handler rejects spoofed packets during the **trust evaluation** step in `BLEAnnounceTrustPolicy.evaluate` ([`BLEAnnounceHandlingPolicy.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandlingPolicy.swift) lines 71-114), which requires valid cryptographic signatures matching the announced public key. Replay attacks are mitigated by the **deduplication** system (lines 55-68) that checks `dedupContains` before processing and marks packets via `dedupMarkProcessed`.

### What is the difference between a direct announce and a routed announce?

A **direct announce** is detected when `packet.ttl == env.messageTTL` ([`BLEAnnounceHandler.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandler.swift) lines 76-80), indicating the packet originated from the immediate neighbor. Routed announces have decremented TTL values and are processed differently for topology updates, though both types undergo the same cryptographic validation.

### Why does the handler use an environment struct instead of direct dependencies?

The **environment pattern** allows `BLEAnnounceHandler` to remain synchronously testable and queue-agnostic. By injecting closures like `withRegistryBarrier`, `verifySignature`, and `deliverAnnounceUIEvents`, the handler can run inside unit tests without requiring actual BLE hardware, database connections, or UI frameworks. This architecture is verified in [`BLEAnnounceHandlerTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BLEAnnounceHandlerTests.swift).

### How does the registry barrier ensure thread safety during peer updates?

The `withRegistryBarrier` closure (lines 96-125) serializes access to the peer registry. All mutations—including `upsertVerifiedAnnounce`—execute within this barrier, preventing race conditions when multiple BLE connections simultaneously announce peers or when the UI thread queries the registry during updates.