# How BitChat's Gossip Sync Protocol Maintains Message History Across Device Restarts and Network Partitions

> Discover how BitChat's gossip sync protocol preserves message history through device restarts and network partitions using in-memory stores, filtering, and JSON archives for seamless communication.

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

---

**BitChat uses a gossip-based sync manager (`GossipSyncManager`) that combines bounded in-memory packet stores, freshness-based filtering, and persistent JSON archives to maintain a rolling window of recent public messages that survives restarts and enables back-filling for offline peers.**

The **gossip sync protocol** in BitChat (implemented in the permissionlesstech/bitchat repository) solves a critical problem for decentralized mesh messaging: how do devices recover messages they missed while offline, without requiring always-on servers? The answer lies in a carefully engineered hybrid of memory-efficient caching, age-based expiration, and disk persistence that keeps recent public chat history available for re-gossiping to rejoining peers.

## Core Architecture: Packet Stores and Bounded Caches

At the heart of the protocol is `GossipSyncManager`, defined in [`bitchat/Sync/GossipSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Sync/GossipSyncManager.swift). The manager maintains a dedicated `PacketStore` for each sync type—including messages, fragments, file transfers, group messages, announcements, and pre-key bundles. Each store enforces a strict capacity limit and maintains packet IDs in insertion order, ensuring **FIFO eviction** when thresholds are exceeded.

```swift
// From GossipSyncManager.swift - packet store initialization
private let messageStore: PacketStore<PublicMessagePacket>
private let fragmentStore: PacketStore<FileTransferFragmentPacket>
// ... additional stores for each sync type

```

The store's `capacity` parameter (configurable per `GossipSyncManager.Config`) determines how many packets remain in memory. This design prevents unbounded memory growth while preserving enough history for effective gossip synchronization.

## Freshness Filtering: Age-Based Message Validation

Every incoming packet passes through `_onPublicPacketSeen`, which enforces **temporal boundaries** before storage. The manager consults three configuration parameters:

- `maxMessageAgeSeconds` — general message lifetime
- `publicMessageMaxAgeSeconds` — public chat specific window
- `prekeyBundleMaxAgeSeconds` — cryptographic key material freshness

Only packets falling within these windows are admitted to stores. This `freshness filtering` occurs in [`GossipSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/GossipSyncManager.swift) lines 45-61, ensuring stale traffic never pollutes caches or propagates to peers.

```swift
func onPublicPacketSeen(_ packet: GossipPacket) {
    // Age validation happens here before store insertion
    guard packet.isWithin(freshnessWindow) else { return }
    messageStore.insert(packet)
    dirtyFlags.insert(.messages)
}

```

## Persistent Archive: Surviving Application Restarts

Memory caches alone would lose data on termination. BitChat solves this through `GossipMessageArchive`, which maintains a JSON-backed copy of public messages in the device's **Application Support directory**.

When `GossipSyncManager` initializes with an archive instance (lines 123-138), it asynchronously executes `restoreArchivedMessages()`:

```swift
// Archive restoration on startup
init(archive: GossipMessageArchive?, ...) {
    self.archive = archive
    restoreArchivedMessages() // Loads fresh messages from disk
}

```

The archive uses lightweight JSON serialization, with **dirty-flag tracking** to minimize write amplification—only modified data flushes to disk.

## Periodic Maintenance: Automated Cleanup and Persistence

A `DispatchSourceTimer` drives background housekeeping every `maintenanceIntervalSeconds` (default: 30 seconds). This timer coordinates three critical tasks defined in lines 68-77 and 98-105:

1. **`cleanupExpiredMessages()`** — removes entries exceeding age thresholds
2. **Stale announcement pruning** — purges outdated network presence data
3. **`persistArchiveIfDirty()`** — commits memory state to disk when flagged

```swift
private func startMaintenanceTimer() {
    timer.schedule(deadline: .now(), repeating: .seconds(config.maintenanceIntervalSeconds))
    timer.setEventHandler { [weak self] in
        self?.cleanupExpiredMessages()
        self?.persistArchiveIfDirty()
    }
}

```

## Sync Rounds: Golomb-Coded Set Exchange for Gap Recovery

The protocol's **gossip synchronization mechanism** uses periodic **GCS (Golomb-Coded Set)** exchanges to detect missing packets. Each sync type operates on its own schedule:

- `messageSyncIntervalSeconds`
- `fragmentSyncIntervalSeconds`
- `groupMessageSyncIntervalSeconds`

During a sync round, `buildGcsPayload()` constructs a compact probabilistic filter containing IDs of fresh packets. The manager transmits a `REQUEST_SYNC` packet to connected peers, who respond with any IDs absent from the filter—enabling **targeted back-filling** without full history transfer.

This mechanism, spanning lines 24-33 and 124-138, ensures efficient bandwidth usage even as mesh topology shifts.

## Pre-Key Bundle Security: Anti-Flooding Protection

Cryptographic key material receives special handling. Rather than a standard `PacketStore`, pre-key bundles reside in `latestPrekeyBundleByPeer`, a dictionary keyed by **noise static public key**. This structure:

- Retains only **one bundle per peer owner**
- Prevents attackers from exhausting cache capacity with forged bundles
- Integrates with the same freshness and persistence pipelines

The implementation appears in lines 86-106, demonstrating how security considerations influence storage architecture.

## Practical Integration: Using the Gossip Sync Manager

The following pattern illustrates typical manager lifecycle management within the BitChat Bluetooth transport layer:

```swift
// 1️⃣ Create a manager with persistence
let archive = GossipMessageArchive() // ~/Library/Application Support/sync/public-messages.json
let requestSyncMgr = RequestSyncManager()
let gossipMgr = GossipSyncManager(
    myPeerID: myPeerID,
    config: GossipSyncManager.Config(),
    requestSyncManager: requestSyncMgr,
    archive: archive
)

// 2️⃣ Start periodic maintenance
gossipMgr.start()

// 3️⃣ Feed a newly-received public message (e.g. from BLE)
gossipMgr.onPublicPacketSeen(incomingPacket)

// 4️⃣ When the app goes to background, force a flush
gossipMgr.persistNow()

// 5️⃣ Retrieve fresh public messages for UI "timeline echo"
gossipMgr.collectPublicMessagePackets { packets in
    // packets contains only messages within publicMessageMaxAgeSeconds
    display(packets)
}

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`bitchat/Sync/GossipSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Sync/GossipSyncManager.swift) | Core sync engine: packet stores, freshness checks, periodic sync, request handling, archive persistence |
| [`bitchat/Sync/GossipMessageArchive.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Sync/GossipMessageArchive.swift) | Disk-level JSON archive for restart survival |
| [`docs/REQUEST_SYNC_MANAGER.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/REQUEST_SYNC_MANAGER.md) | Protocol design documentation for GCS-based synchronization |
| [`bitchat/Services/BLE/BLEService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEService.swift) | Integration example showing manager instantiation in Bluetooth transport |

## Summary

BitChat's gossip sync protocol maintains message history through five coordinated mechanisms:

- **Bounded in-memory stores** with FIFO eviction prevent unbounded growth
- **Freshness filtering** enforces temporal windows on all stored packets
- **JSON archive persistence** enables recovery across application restarts
- **Periodic maintenance timers** automate cleanup and disk synchronization
- **GCS-based sync rounds** efficiently detect and fill gaps for offline peers

This architecture—implemented primarily in [`GossipSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/GossipSyncManager.swift)—delivers **serverless mesh messaging** with reliable history preservation despite device churn and network partitions.

## Frequently Asked Questions

### How does BitChat prevent old messages from filling up device storage?

BitChat employs **dual-layer expiration**: in-memory stores enforce capacity limits with FIFO eviction, while freshness filters reject packets exceeding age thresholds (`maxMessageAgeSeconds`, `publicMessageMaxAgeSeconds`). The periodic maintenance timer (default 30-second interval) actively purges expired entries from both memory and archive.

### What happens to message history when a device restarts?

The `GossipMessageArchive` persists fresh public messages to JSON in the Application Support directory. On startup, `GossipSyncManager.restoreArchivedMessages()` reloads any still-fresh messages into memory, restoring gossip capability without network dependency. Stale messages are discarded during restoration.

### How does BitChat synchronize missed messages without central servers?

Peers exchange **Golomb-Coded Sets (GCS)** containing packet IDs they currently hold. A receiving peer compares this filter against its own store and responds with missing packets. This `REQUEST_SYNC` protocol operates independently per packet type on configurable intervals, enabling targeted back-filling without full history broadcasts.

### Why are pre-key bundles stored separately from regular messages?

Pre-key bundles use a dedicated dictionary (`latestPrekeyBundleByPeer`) keyed by cryptographic identity to enforce **single-bundle-per-peer limits**. This prevents denial-of-service attacks that could otherwise exhaust store capacity with forged bundles, while still benefiting from the same freshness validation and persistence infrastructure.