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

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:

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:

// 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:):

// 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:

// 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:

// 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:

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

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

// 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:

// 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)
// 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
// 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:

// 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:
// 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, bitchat/Sync/RequestSyncManager.swift, and 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.

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 →