# How the Gossip Protocol in BitChat Constructs the Mesh Topology Graph

> Discover how BitChat's gossip protocol constructs its mesh topology graph. Learn about neighbor claims, claim maps, and BFS routing for efficient network visualization.

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

---

**BitChat's gossip protocol builds a mesh topology by having peers periodically broadcast signed announce packets containing up to ten direct neighbor claims, which are sanitized and stored in a concurrent claims map, with edges confirmed only when both endpoints reciprocate the claim before being used for BFS routing or UI visualization.**

The gossip protocol in BitChat powers the decentralized peer discovery and routing layer of the `permissionlesstech/bitchat` repository. Through periodic announce broadcasts, nodes exchange 8-byte peer IDs representing reachable direct neighbors, constructing a directed adjacency graph that the `MeshTopologyTracker` validates and prunes to ensure only fresh, bidirectional connections form the mesh backbone.

## The Gossip-Sync Round: Broadcasting Topology Claims

BitChat uses a **gossip-sync** round where each peer periodically broadcasts an **announce** packet. According to the source code in [`bitchat/Protocols/Packets.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/Packets.swift), this packet contains a `directNeighbors` field encoding up to ten neighbor peer IDs, each represented as 8-byte values.

When a signed announce is received, the `GossipSyncManager._onPublicPacketSeen` method in [`bitchat/Sync/GossipSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Sync/GossipSyncManager.swift) validates the packet signature and freshness before storing it in the `latestAnnouncementByPeer` dictionary:

```swift
func _onPublicPacketSeen(_ packet: BitchatPacket) {
    // ...
    case .announce:
        guard isPacketFresh(packet), isAnnouncementFresh(packet) else { return }
        let sender = PeerID(hexData: packet.senderID)
        latestAnnouncementByPeer[sender] = packet
        // BLEAnnounceHandler extracts directNeighbors and calls tracker.updateNeighbors
}

```

## Processing Neighbor Claims in MeshTopologyTracker

The `BLEAnnounceHandler` decodes the announcement payload and extracts the `directNeighbors` array. It then calls `MeshTopologyTracker.updateNeighbors(for:neighbors:)` to record the topology claim.

### Sanitizing Peer IDs and Removing Self-Loops

Located in [`bitchat/Services/MeshTopologyTracker.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MeshTopologyTracker.swift), the `updateNeighbors` method sanitizes input by truncating or padding peer IDs to the required 8-byte hop size and filtering out self-referential claims:

```swift
func updateNeighbors(for sourceData: Data?, neighbors: [Data], at now: Date = Date()) {
    guard let source = sanitize(sourceData) else { return }
    // Remove self-loops and truncate/pad IDs to 8 bytes
    let validNeighbors = Set(neighbors.compactMap { sanitize($0) }).subtracting([source])
    queue.sync(flags: .barrier) {
        self.claims[source] = validNeighbors
        self.lastSeen[source] = now
    }
}

```

The method writes to a thread-safe `claims` dictionary mapping `RoutingID` to a `Set<RoutingID>` of purported neighbors.

## Confirming Bidirectional Edges and Pruning Stale Data

BitChat treats the topology as a **directed graph** where an edge exists only upon confirmation. An edge between two peers is considered valid only when **both** endpoints have reported each other in their respective announcements, as implemented in [`bitchat/Services/MeshTopologyTracker.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MeshTopologyTracker.swift).

### Staleness Detection

The topology tracker enforces freshness through `config.stalePeerTimeoutSeconds`. The `MeshTopologyTracker.prune` method and `GossipSyncManager.cleanupStaleAnnouncements` remove nodes and claims that exceed the configured timeout, ensuring the mesh reflects only recently reachable peers.

### Adjacency Snapshot for Routing

When computing routes or rendering the UI, the system queries `adjacencySnapshot()` to retrieve the current confirmed graph. The `computeRoute` method performs a BFS traversal over fresh, confirmed edges:

```swift
func computeRoute(from start: Data?, to goal: Data?, ...) -> [Data]? {
    // ...
    guard let neighbors = claims[last] else { continue }
    guard let lastSeenTime = lastSeen[last], lastSeenTime > freshnessDeadline else { continue }
    // Confirmed edge: both peers must claim each other
    guard let neighborClaims = claims[neighbor],
          neighborClaims.contains(last) else { continue }
    // ...
}

```

This implementation optionally enforces minimum protocol version requirements for every hop.

## Visualizing the Mesh Topology

The UI layer consumes the same adjacency data through `MeshTopologyView` in [`bitchat/Views/MeshTopologyView.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Views/MeshTopologyView.swift). The view receives a provider closure that constructs a `MeshTopologyDisplayModel` by calling `tracker.adjacencySnapshot()`.

The snapshot translates claims into `Node` objects and edge tuples, which the view renders in a circular layout with the local peer positioned centrally. This provides a real-time visualization of the gossip-constructed mesh.

## Summary

- BitChat's gossip protocol broadcasts signed announce packets containing up to ten 8-byte direct neighbor IDs
- `GossipSyncManager._onPublicPacketSeen` stores valid announces in `latestAnnouncementByPeer`
- `MeshTopologyTracker.updateNeighbors` sanitizes IDs to 8-byte hop size and removes self-loops before storing in the thread-safe `claims` dictionary
- Edges require **bidirectional confirmation**—both peers must claim each other—to be considered valid for routing
- `prune` and `cleanupStaleAnnouncements` remove nodes exceeding `stalePeerTimeoutSeconds` to maintain graph freshness
- `computeRoute` executes BFS over confirmed, fresh edges while `MeshTopologyView` visualizes the snapshot via `adjacencySnapshot()`

## Frequently Asked Questions

### How does BitChat prevent stale nodes from polluting the topology graph?

BitChat implements a timeout-based pruning mechanism. Nodes that fail to refresh their announce within `config.stalePeerTimeoutSeconds` are automatically removed by `MeshTopologyTracker.prune` and `GossipSyncManager.cleanupStaleAnnouncements`. This ensures that only recently active peers participate in the mesh topology, preventing routing attempts through disconnected nodes.

### Why does BitChat require bidirectional claims to establish a mesh edge?

The protocol stores directed claims in the `claims` dictionary but only treats an edge as valid when both endpoints list each other. This bidirectional confirmation prevents unilateral spoofing and ensures that routing paths traverse actually reachable peers, as verified by reciprocal gossip announcements in `MeshTopologyTracker.computeRoute`.

### What limits the number of neighbors announced in each gossip packet?

The announce packet structure defined in [`bitchat/Protocols/Packets.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/Packets.swift) limits the `directNeighbors` array to ten entries. This constraint bounds packet size for BLE transmission while providing sufficient connectivity data to build an effective mesh topology through the gossip-sync round.

### How does the gossip protocol handle malicious or spoofed topology claims?

While the announce packets are signed, the topology tracker primarily relies on **freshness checks** and **bidirectional edge confirmation** as tamper-resistance mechanisms. A malicious node cannot force a connection to an unwilling peer because edges require both parties to claim each other, and stale claims are pruned. Additionally, `MeshTopologyTracker.updateNeighbors` sanitizes all peer IDs to prevent malformed data from corrupting the graph.