How BLEPeerRegistry in BitChat Tracks and Manages Discovered Peers

BitChat's BLEPeerRegistry maintains a lightweight in-memory dictionary of BLE peers, providing atomic upserts, cryptographic key pinning, connection state tracking, and automatic pruning of stale entries based on configurable retention rules.

The BLEPeerRegistry struct in the permissionlesstech/bitchat repository serves as the authoritative ledger for Bluetooth Low Energy peer discovery. It tracks each node's identity, cryptographic keys, capabilities, and reachability status, enabling the mesh network to maintain an accurate real-time view of nearby devices without persistent database overhead.

Core Data Structure and Storage

At the heart of the registry lies a type-safe dictionary that maps peer identifiers to comprehensive metadata records. In bitchat/Services/BLE/BLEPeerRegistry.swift (lines 42-43), the storage is declared as:

private var peers: [PeerID: BLEPeerInfo] = [:]

Each BLEPeerInfo value captures critical peer attributes including the nickname, Noise protocol public key, signing public key, connection status, last-seen timestamp, capabilities (such as bridge mode), and optional bridge geohash for location-based rendezvous.

The registry exposes basic query helpers (lines 44-71) for common operations:

  • isEmpty and count for inventory statistics
  • connectedCount for active link metrics
  • snapshotByID for immutable peer views
  • nickname(for:connectedOnly:) for UI display name resolution

Adding and Updating Peer Records

The registry provides two distinct pathways for ingesting peer announcements, handling both unverified discovery and cryptographically verified identity assertions.

Standard Upsert Operations

For basic peer discovery, the upsert(_:) method (lines 80-88) provides idempotent insertion:

mutating func upsert(_ info: BLEPeerInfo)

This overwrites existing entries entirely, making it suitable for initial discovery when processing raw BLE advertisements in BLEService.swift (line 3578).

Verified Announce with Trust-on-First-Use (TOFU)

When a peer presents a cryptographically signed announcement, upsertVerifiedAnnounce (lines 92-103) performs sophisticated key management:

mutating func upsertVerifiedAnnounce(
    peerID: PeerID,
    nickname: String,
    noisePublicKey: Data,
    signingPublicKey: Data,
    isConnected: Bool,
    now: Date,
    capabilities: Set<PeerCapability>,
    bridgeGeohash: String?
) -> BLEPeerAnnounceUpdate?

This method implements TOFU (Trust on First Use) semantics by preserving existing signing keys if present, preventing key downgrade attacks while allowing legitimate metadata updates. It returns an optional BLEPeerAnnounceUpdate struct indicating whether the peer is newly discovered or updated.

Connection State Management

The registry isolates connection lifecycle events from metadata updates through dedicated atomic toggles. In BLEPeerRegistry.swift (lines 62-76), state transitions are handled by:

mutating func markConnected(_ peerID: PeerID)
mutating func markDisconnected(_ peerID: PeerID)

These methods modify only the isConnected boolean within the stored BLEPeerInfo, leaving cryptographic material and capability advertisements intact. This separation allows the transport layer to rapidly fluctuate connection states without triggering full record re-validation or redundant cryptographic checks.

Reachability and Automatic Pruning

To prevent stale peer accumulation in long-running mesh sessions, the registry implements differentiated time-based retention policies.

Reachability Validation

The isReachable(_:now:) method (lines 93-104) evaluates timeout thresholds based on verification status:

  • Verified nicknames: Extended retention periods for cryptographically trusted peers
  • Unverified nicknames: Aggressive timeouts for anonymous devices

Bulk Reconciliation

The reconcileConnectivity(now:linkStates:) method (lines 141-170) performs periodic garbage collection:

mutating func reconcileConnectivity(
    now: Date,
    linkStates: [PeerID: LinkState]
) -> BLEPeerRegistryChanges

This function compares registry contents against provided link states, disconnecting peers that exceed their time-to-live thresholds and removing entries that have become permanently unreachable. It returns a BLEPeerRegistryChanges structure detailing which peers were disconnected or purged, which BLEService uses to emit appropriate delegate callbacks.

Capability Discovery and Bridge Rendezvous

Beyond basic presence tracking, the registry indexes peer capabilities to support intelligent mesh routing decisions.

Capability-Based Filtering

The peers(advertising:) method (lines 24-28) returns all peers advertising specific capabilities:

func peers(advertising capability: PeerCapability) -> [BLEPeerInfo]

This enables the protocol to locate bridge nodes (.bridge capability) that connect the BLE mesh to internet-connected infrastructure.

Geographic Bridge Discovery

For location-aware mesh joining, advertisedBridgeGeohash() (lines 29-36) extracts the first known bridge-geohash from capability-advertising peers:

func advertisedBridgeGeohash() -> String?

This allows GPS-less devices to discover geographic anchoring points when entering the mesh by referencing nearby bridge nodes that have advertised geohash coordinates.

Integration with BLEService and Message Handlers

The registry serves as the ground truth for several higher-level BLE services through snapshot-based decoupling.

BLEService.swift maintains a private peerRegistry instance and updates it when processing incoming announce packets or connection state changes. Rather than exposing mutable state directly, handlers such as BLEPublicMessageHandler.swift (lines 18-19) receive a closure:

peersSnapshot: () -> [PeerID: BLEPeerInfo]

This pattern allows message handlers to resolve sender nicknames using current registry state without direct coupling to the mutable registry instance. Similarly, BLEPeerSenderDisplayName.swift (lines 4-11) consumes these snapshots to provide human-readable identifiers, falling back to anonymous identifiers when peers lack verified nicknames.

Practical Implementation Examples

The following patterns demonstrate typical registry interactions within BitChat's architecture:

// Initialize empty registry
var registry = BLEPeerRegistry()

// Record unverified discovery from BLE scan
let discovery = BLEPeerInfo(
    peerID: peerUUID,
    nickname: "Alice",
    isConnected: false,
    noisePublicKey: noiseKey,
    signingPublicKey: nil,
    isVerifiedNickname: false,
    lastSeen: Date(),
    capabilities: [.bridge],
    capabilitiesWereExplicitlyAdvertised: true,
    bridgeGeohash: nil
)
registry.upsert(discovery)

// Process verified announce with cryptographic proof
if let update = registry.upsertVerifiedAnnounce(
    peerID: peerUUID,
    nickname: "Alice",
    noisePublicKey: noiseKey,
    signingPublicKey: signingKey,
    isConnected: true,
    now: Date(),
    capabilities: [.bridge],
    bridgeGeohash: "u4pruydqqvj") {
    
    if update.isNewPeer {
        print("New verified peer joined: \(update.nickname)")
    }
}

// Mark connection established after GATT negotiation
registry.markConnected(peerUUID)

// Periodic maintenance (called every few seconds by BLEService)
let changes = registry.reconcileConnectivity(
    now: Date(),
    linkStates: currentLinkStates
)

// Query for bridge-capable peers to route messages
let bridges = registry.peers(advertising: .bridge)

// Build transport snapshots for outgoing announce packets
let snapshots = registry.transportSnapshots(selfNickname: "MyNode")

Summary

  • In-memory storage: Uses a [PeerID: BLEPeerInfo] dictionary in BLEPeerRegistry.swift to maintain O(1) peer lookups without database persistence overhead.
  • Safe updates: Implements TOFU key pinning via upsertVerifiedAnnounce while allowing simple overwrites via upsert for unverified discovery.
  • Lifecycle management: Separates connection state (markConnected/markDisconnected) from peer metadata, with automatic pruning via reconcileConnectivity based on verification status.
  • Capability indexing: Supports mesh routing through peers(advertising:) and geographic bridge discovery via advertisedBridgeGeohash().
  • Service integration: Acts as the source of truth for BLEService, message handlers, and UI components through immutable snapshot patterns.

Frequently Asked Questions

How does BLEPeerRegistry prevent malicious key substitution attacks?

The upsertVerifiedAnnounce method implements TOFU (Trust on First Use) semantics as defined in BLEPeerRegistry.swift (lines 92-103). When processing a verified announce, the method checks for existing signing keys in the registry. If a key already exists for that PeerID, it preserves the existing key rather than overwriting it, preventing attackers from substituting keys through new announcements while allowing the legitimate key holder to update other metadata such as capabilities or connection status.

What distinguishes upsert from upsertVerifiedAnnounce?

upsert(_:) performs a simple dictionary insertion or replacement of the entire BLEPeerInfo record (lines 80-88), suitable for initial unverified discovery when processing raw BLE advertisements. In contrast, upsertVerifiedAnnounce performs cryptographic validation, preserves existing signing keys to prevent downgrade attacks, tracks verification status separately, and returns metadata about whether the peer is new or updated. Use upsert for raw BLE scans; use upsertVerifiedAnnounce only for cryptographically authenticated announcements.

How does the registry prevent memory exhaustion from abandoned devices?

The reconcileConnectivity(now:linkStates:) method (lines 141-170) applies time-based retention policies that automatically disconnect and eventually remove stale entries. Verified peers (those with cryptographically confirmed nicknames) survive longer than unverified anonymous peers. BLEService invokes this method periodically to prune the mesh view of unreachable devices, returning a BLEPeerRegistryChanges structure that tracks exactly which peers were affected.

Can BLEPeerRegistry filter peers by supported mesh capabilities?

Yes. The peers(advertising:) method (lines 24-28) filters the registry by PeerCapability values such as .bridge, returning only peers that explicitly advertised support for that capability. Additionally, advertisedBridgeGeohash() (lines 29-36) locates bridge peers with geographic anchoring data, enabling location-based mesh joining strategies without requiring GPS on all participating 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →