RequestSyncManager in Bitchat: How Targeted Fragment Recovery Works

The RequestSyncManager is a Swift component that tracks outgoing sync requests and validates incoming Request-Sync-Response (RSR) packets, enabling precise recovery of specific fragment streams without waiting for periodic gossip rounds.

In the permissionless Bitchat protocol, the RequestSyncManager (bitchat/Sync/RequestSyncManager.swift) solves a critical problem: how to request missing data from specific peers without flooding the network or accepting unsolicited responses. This article explains its core mechanisms and how it powers targeted fragment recovery when message streams stall.

Core Responsibilities of the RequestSyncManager

The manager maintains lightweight, thread-safe state across three primary duties.

Tracking Outgoing Sync Requests

When the sync layer sends a REQUEST_SYNC packet to a specific peer, that solicitation must be recorded. The registerRequest(to:) method stores a mapping of peerID → timestamp in an internal dictionary.

// RequestSyncManager.swift L32-L40
func registerRequest(to peerID: PeerID) {
    pendingRequests[peerID] = DispatchTime.now()
}

This registration creates the attribution that later distinguishes legitimate responses from unsolicited traffic.

Validating Incoming RSR Packets

Packets carrying the IS_RSR flag bypass the normal 2-minute timestamp freshness window—but only if they match a pending request. The isValidResponse(from:isRSR:) method enforces this:

// RequestSyncManager.swift L42-L66
func isValidResponse(from peerID: PeerID, isRSR: Bool) -> Bool {
    guard isRSR else { return false }
    guard let requestTime = pendingRequests[peerID] else { return false }
    let elapsed = DispatchTime.now().uptimeNanoseconds - requestTime.uptimeNanoseconds
    return elapsed < responseWindowNanos // default 30 seconds
}

If validation fails, the packet is dropped before processing.

Cleaning Up Expired Requests

Stale entries accumulate memory and create attack surface. The cleanup() method filters out requests older than the configured response window:

// RequestSyncManager.swift L68-L81
func cleanup() {
    let now = DispatchTime.now()
    pendingRequests = pendingRequests.filter { _, time in
        now.uptimeNanoseconds - time.uptimeNanoseconds < responseWindowNanos
    }
}

How RequestSyncManager Integrates with Sync Flow

The manager sits at the intersection of three operational stages: request generation, security validation, and response marking.

Outgoing REQUEST_SYNC Registration

Before any unicast sync packet is transmitted, GossipSyncManager registers it:

// GossipSyncManager.swift L60-L63
func sendRequestSync(to peerID: PeerID, types: SyncTypeFlags, fragmentIdFilter: String? = nil) {
    requestSyncManager.registerRequest(to: peerID)
    // ... packet construction and transmission
}

This marks the forthcoming response as solicited rather than spontaneous gossip.

Security Layer Validation

BLEService acts as the gatekeeper for all incoming packets. Its logic branches on the RSR flag:

// Conceptual flow per REQUEST_SYNC_MANAGER.md L42-L46
func handleIncoming(_ packet: BitchatPacket, from peerID: PeerID) {
    if packet.isRSR {
        // RSR packets: validate against tracked requests, skip timestamp check
        guard requestSyncManager.isValidResponse(from: peerID, isRSR: true) else { return }
    } else {
        // Normal gossip: enforce 2-minute freshness window
        guard abs(now - packet.timestamp) < 120 else { return }
    }
}

Response Marking

When constructing replies to REQUEST_SYNC, the sync layer explicitly marks packets:

// GossipSyncManager.swift L19-L25, L33-L36
var responsePacket = BitchatPacket(...)
responsePacket.isRSR = true   // Receiver will validate against pending request
responsePacket.ttl = 0        // Prevents further gossip propagation

These markings ensure the receiver can match responses to their original requests while containing response spread.

Targeted Fragment Recovery Mechanism

Broadcast fragments are normally reassembled from promiscuously received traffic. When a fragment stream stalls—detected when no new fragment arrives for approximately 5 seconds—the reassembler triggers targeted recovery.

Building the Fragment Filter

The sync manager creates a fragmentIdFilter containing comma-separated 16-character hex stream IDs:

// Called when fragment assembly stalls
func requestMissingFragments(fragmentIDs: [Data]) {
    let filter = RequestSyncPacket.encodeFragmentIdFilter(fragmentIDs)
    // filter example: "a3f9b2c8d1e5f7a0,b4c1d2e3f4a5b6c7"
    for peer in delegate?.getConnectedPeers() ?? [] {
        sendRequestSync(to: peer, types: .fragment, fragmentIdFilter: filter)
    }
}

Filter Processing on the Responder

Receiving peers decode the filter and restrict their response:

// RequestSyncPacket.decodeFragmentIdFilter extracts stream IDs
// GossipSyncManager.swift L39-L45, L48-L60
func handleRequestSync(_ request: BitchatPacket, from peerID: PeerID) {
    let streamIDs = request.decodeFragmentIdFilter()
    for fragment in localFragments {
        let fragmentStreamID = fragment.payload.prefix(8) // First 8 bytes = stream ID
        guard streamIDs.contains(fragmentStreamID) else { continue }
        // Send matching fragment with isRSR=true, ignoring sinceTimestamp cursor
        sendRSR(fragment, to: peerID)
    }
}

Why This Works

  • Attribution: RequestSyncManager verifies that RSR packets come from requested peers
  • Precision: fragmentIdFilter limits responses to exactly the stalled streams
  • Freshness exemption: RSR packets bypass sinceTimestamp cursors, filling gaps without historical constraints

The result: stalled message streams recover in seconds rather than waiting for the next periodic GCS (Galois/Counter Mode Synchronization) round.

Practical Implementation Example

Here's how the complete flow coordinates between components:

// FragmentReassembler detects stall and calls:
GossipSyncManager.shared.requestMissingFragments(fragmentIDs: stalledIDs)

// Inside GossipSyncManager.swift L41-L56
func requestMissingFragments(_ fragmentIDs: [Data]) {
    let filter = RequestSyncPacket.encodeFragmentIdFilter(fragmentIDs)
    for peer in connectedPeers {
        // Registers request for validation
        requestSyncManager.registerRequest(to: peer.id)
        
        // Builds targeted REQUEST_SYNC
        let packet = buildRequestSync(types: .fragment, fragmentIdFilter: filter)
        send(packet, to: peer)
    }
}

// BLEService validates the response
func receivePacket(_ packet: BitchatPacket, from peer: PeerID) {
    if packet.isRSR {
        // Only passes if we requested from this peer
        guard requestSyncManager.isValidResponse(from: peer, isRSR: true) else {
            logger.warning("Dropping unsolicited RSR from \(peer)")
            return
        }
        processRSR(packet)
    }
}

Summary

  • RequestSyncManager maintains thread-safe state mapping peer IDs to request timestamps
  • RSR validation (isValidResponse) prevents unsolicited responses from entering the sync pipeline
  • Targeted fragment recovery combines the manager's attribution with fragmentIdFilter to request specific stalled streams
  • Security integration in BLEService applies different validation rules for solicited (RSR) versus spontaneous traffic
  • Cleanup logic bounds memory usage and prevents replay attacks beyond the 30-second response window

Frequently Asked Questions

What happens if an RSR packet arrives without a matching registered request?

The isValidResponse method returns false, causing BLEService to drop the packet. This prevents unsolicited data injection and replay attacks using old RSR packets.

How does the fragment ID filter format work?

Stream IDs are 8-byte (16 hex character) values. The encodeFragmentIdFilter method joins multiple IDs with commas into a UTF-8 string. The responder's decodeFragmentIdFilter parses this string and matches against the first 8 bytes of each fragment payload.

Can the response window be adjusted for different network conditions?

Yes. The responseWindowNanos property is configurable at initialization. The default 30 seconds balances latency tolerance against memory usage and attack surface, but slower networks could extend this value.

Does targeted fragment recovery work across multiple hops?

No. RSR packets are marked with ttl = 0, making them unicast-only. Targeted recovery requires direct peer connections, which is intentional—fragment streams are reassembled from direct broadcast traffic, and stall detection operates on the local assembly state.

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 →