# How BitChat Handles Message Synchronization Across Mesh Peers: Gossip-Sync Protocol Explained

> Discover how BitChat's gossip-sync protocol with GCS filters efficiently synchronizes messages across BLE mesh peers, ensuring secure data propagation without central servers.

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

---

**BitChat synchronizes public chat history, file fragments, and group messages across BLE mesh peers using a gossip-sync protocol built on Golomb-Coded Set (GCS) filters, with dedicated managers tracking outbound requests and validating responses to ensure secure, efficient data propagation.**

BitChat implements **message synchronization across mesh peers** through a decentralized gossip protocol that operates without centralized servers. According to the `permissionlesstech/bitchat` source code, the system uses compressed probabilistic filters to minimize bandwidth while ensuring eventual consistency across highly partitioned networks.

## Core Architecture

The synchronization system centers on three coordinated components:

- **`GossipSyncManager`** ([`bitchat/Sync/GossipSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Sync/GossipSyncManager.swift)): Maintains bounded stores of recent packets and orchestrates periodic synchronization.
- **`RequestSyncManager`** ([`bitchat/Sync/RequestSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Sync/RequestSyncManager.swift)): Tracks pending outbound requests and validates incoming Request-Sync-Response (RSR) packets.
- **`RequestSyncPacket`** ([`bitchat/Models/RequestSyncPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Models/RequestSyncPacket.swift)): Defines the binary wire format for sync requests, including GCS filters and optional fragment-ID targeting.

## The Synchronization Protocol

### Storing Recent Packets

`GossipSyncManager` maintains separate bounded stores for each data type—`messages`, `fragments`, `fileTransfers`, `groupMessages`, and `latestPrekeyBundleByPeer`—enforcing freshness windows (default 15 minutes for most payloads, up to 6 hours for whole public messages).

When a new public message arrives, the manager computes its packet ID and inserts it into the local store:

```swift
// bitchat/Sync/GossipSyncManager.swift
let idHex = PacketIdUtil.computeId(packet).hexEncodedString()
messages.insert(idHex: idHex, packet: packet, capacity: max(1, config.seenCapacity))

```

This retention policy ensures that sync operations only propagate recent, relevant data across the mesh.

### Periodic Sync Schedule

Synchronization is driven by `SyncSchedule` objects that define cadence per packet type (e.g., every 15 seconds for messages, 30 seconds for fragments). A `DispatchSourceTimer` triggers `performPeriodicMaintenance()`, which inspects schedules and invokes `sendPeriodicSync(for:)`:

```swift
// bitchat/Sync/GossipSyncManager.swift
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now() + interval, repeating: interval)
timer.setEventHandler { self?.performPeriodicMaintenance() }

```

This design ensures passive, background synchronization without blocking user interactions.

### Building Sync Requests with GCS Filters

When a schedule fires, `sendPeriodicSync(for:)` constructs a Golomb-Coded Set (GCS) filter from the packet IDs of the requested types. The manager unicast-sends requests to directly connected peers when available, falling back to broadcast during discovery phases:

```swift
// bitchat/Sync/GossipSyncManager.swift
if let connectedPeers = delegate?.getConnectedPeers(), !connectedPeers.isEmpty {
    for peerID in connectedPeers { sendRequestSync(to: peerID, types: types) }
} else {
    sendRequestSync(for: types)
}

```

The request payload encodes the GCS filter parameters (false-positive rate `p`, modulus `m`, and compressed data) alongside optional cursors:

```swift
// bitchat/Sync/GossipSyncManager.swift
let p = GCSFilter.deriveP(targetFpr: config.gcsTargetFpr)
let ids = included.map { PacketIdUtil.computeId($0) }
let params = GCSFilter.buildFilter(ids: ids, maxBytes: config.gcsMaxBytes, targetFpr: config.gcsTargetFpr)
let req = RequestSyncPacket(p: params.p, m: params.m, data: params.data,
                            types: types, sinceTimestamp: sinceTimestamp,
                            fragmentIdFilter: fragmentIdFilter)
return req.encode()

```

### Registering Outbound Requests

Before transmission, `GossipSyncManager` registers the peer with `RequestSyncManager` to enable response attribution:

```swift
// bitchat/Sync/GossipSyncManager.swift
requestSyncManager.registerRequest(to: peerID)

```

`RequestSyncManager` stores the request timestamp in a thread-safe dictionary:

```swift
// bitchat/Sync/RequestSyncManager.swift
queue.async(flags: .barrier) { self.pendingRequests[peerID] = now }

```

This registration prevents processing of unsolicited sync responses.

### Validating Incoming RSR Packets

When a peer receives a packet with the `isRSR` flag set, `BLEService` forwards it to `GossipSyncManager`. The manager validates the response through `RequestSyncManager` before acceptance:

```swift
// bitchat/Services/BLE/BLEService.swift
guard requestSyncManager.isValidResponse(from: peerID, isRSR: true) else { return }

```

The validation ensures:
- The packet is explicitly marked as **RSR** (`isRSR == true`)
- A corresponding pending request exists for that `peerID`
- The response arrived within the configured response window (default 30 seconds)

```swift
// bitchat/Sync/RequestSyncManager.swift
guard let requestTime = pendingRequests[peerID] else { return false }
let now = self.now()
if now - requestTime > responseWindow { return false }

```

Valid RSR packets bypass normal timestamp validation, allowing historic messages to be synced safely without rejection based on age.

### Responding to Sync Requests

Upon receiving a `REQUEST_SYNC`, `GossipSyncManager` builds a diff by comparing the requester's GCS filter against local stores:

1. Decode the GCS filter from `RequestSyncPacket.decode`
2. Iterate over local packets of the requested types
3. Compute each packet ID using `PacketIdUtil.computeId`
4. Check `mightContain(id)` against the filter
5. Transmit packets not present in the filter with `isRSR = true`

```swift
// bitchat/Sync/GossipSyncManager.swift
var toSend = pkt
toSend.ttl = 0
toSend.isRSR = true
delegate?.sendPacket(to: peerID, packet: toSend)

```

This differential approach minimizes redundant transmissions across the mesh.

## Targeted Fragment Recovery

When file transfer fragments stall, BitChat implements targeted recovery by encoding specific fragment IDs into a dedicated filter:

```swift
// bitchat/Sync/GossipSyncManager.swift
guard let filter = RequestSyncPacket.encodeFragmentIdFilter(fragmentIDs) else { return }
sendRequestSync(to: peerID, types: .fragment, fragmentIdFilter: filter)

```

The responder applies this filter when selecting packets, ensuring only missing fragments are transmitted rather than the entire fragment store.

## Security and Cleanup Mechanisms

Both managers implement automatic pruning to prevent memory exhaustion and stale request accumulation:

- **`GossipSyncManager.performPeriodicMaintenance()`**: Removes expired packets and stale announcements, then invokes `requestSyncManager.cleanup()`.
- **`RequestSyncManager.cleanup()`**: Filters `pendingRequests` to retain only entries within the response window:

```swift
// bitchat/Sync/RequestSyncManager.swift
self.pendingRequests = self.pendingRequests.filter { now - $0.value <= responseWindow }

```

Additionally, `SyncResponseRateLimiter` prevents peers from flooding the device with excessive sync requests, protecting against resource exhaustion attacks.

## Summary

- **BitChat** uses a **gossip-sync protocol** with **GCS filters** to efficiently synchronize data across BLE mesh peers without centralized infrastructure.
- **`GossipSyncManager`** maintains bounded, time-limited stores of packets and drives periodic synchronization via configurable schedules.
- **`RequestSyncManager`** provides request attribution and validates **RSR packets** against pending requests to prevent spoofing and replay attacks.
- The system supports **targeted fragment recovery** through specialized filters and implements automatic cleanup of stale data and expired requests.
- All synchronization logic is implemented in [`bitchat/Sync/GossipSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Sync/GossipSyncManager.swift), [`bitchat/Sync/RequestSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Sync/RequestSyncManager.swift), and [`bitchat/Models/RequestSyncPacket.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Models/RequestSyncPacket.swift).

## Frequently Asked Questions

### What is a GCS filter in BitChat's synchronization protocol?

A **Golomb-Coded Set (GCS) filter** is a space-efficient probabilistic data structure used to represent the set of packet IDs a peer possesses. When `GossipSyncManager` builds a sync request, it encodes recent packet IDs into a GCS filter with configurable false-positive rates (`targetFpr`) and size limits (`maxBytes`). Receiving peers use this filter to determine exactly which packets the requester is missing, transmitting only the differential data rather than full message histories.

### How does BitChat prevent spoofed or unsolicited sync responses?

BitChat prevents spoofed responses through **request attribution** managed by `RequestSyncManager`. When `GossipSyncManager` sends a sync request, it registers the peer and timestamp in `pendingRequests`. Incoming packets with the `isRSR` flag are rejected unless `isValidResponse(from:isRSR:)` confirms a matching pending request exists and the response arrived within the 30-second response window. This ensures peers cannot inject historic messages unsolicited or冒充 responses to requests they did not receive.

### What happens when a peer receives an RSR (Request-Sync-Response) packet?

When `BLEService` receives a packet with `isRSR = true`, it forwards the packet to `GossipSyncManager` for validation. The manager first queries `RequestSyncManager.isValidResponse(from:peerID, isRSR: true)` to verify the response corresponds to a recently sent request. If validation passes, the packet bypasses normal timestamp checks (allowing older messages) and is processed as a standard public packet through `onPublicPacketSeen(packet:)`, completing the synchronization cycle.

### How does BitChat handle incomplete file transfers across the mesh?

BitChat implements **targeted fragment recovery** when file reassembly detects missing chunks. The system encodes the specific missing fragment IDs into a fragment-ID filter using `RequestSyncPacket.encodeFragmentIdFilter(fragmentIDs)`, then unicasts a specialized sync request to the peer suspected of holding the data. The responder limits its search to only the requested fragment IDs, efficiently retrieving missing pieces without broadcasting large file segments across the entire mesh.