How Bitchat's File Transfer Protocol Manages Private Media Transfers Over BLE
The bitchat protocol uses a three-layer architecture—comprising BLEFileTransferPolicy for routing, BLEFileTransferHandler for authenticated storage, and BLEPrivateMediaReceiptStore for durable exactly-once delivery—to securely manage private media transfers over Bluetooth Low Energy.
Bitchat is an open-source, offline-first messaging stack that implements a specialized file transfer protocol for private media transfers over BLE. The system guarantees cryptographic authenticity, storage quota enforcement, and persistent receipt tracking to ensure that sensitive media arrives exactly once even across app restarts or intermittent connectivity.
Core Architecture of the BLE File Transfer Stack
The protocol delegates responsibility across three tightly-coupled components:
-
BLEFileTransferPolicy – Determines routing logic, filters self-echoes, and distinguishes private messages from broadcasts. Implemented in [
bitchat/Services/BLE/BLEFileTransferPolicy.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFileTransferPolicy.swift#L9-L28). -
BLEFileTransferHandler – The central coordinator that authenticates senders, enforces quotas, writes files, and manages durable receipt commits. Located at [
bitchat/Services/BLE/BLEFileTransferHandler.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFileTransferHandler.swift#L19-L88). -
BLEPrivateMediaReceiptStore – Maintains a persistent ledger mapping stable message IDs to file URLs, enabling exactly-once delivery guarantees. Found in [
bitchat/Services/BLE/BLEPrivateMediaReceiptStore.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEPrivateMediaReceiptStore.swift#L127-L215) alongside thePrivateMediaMessageIdentitygenerator in [BitchatFilePacket.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/BitchatFilePacket.swift#L168).
Routing and Self-Echo Prevention
When a BLE packet arrives, BLEFileTransferHandler.handle(_:from:) immediately consults the policy layer to prevent processing self-generated echoes. If the packet originated from the local peer and carries a non-zero TTL, the handler drops it:
if BLEFileTransferPolicy.isSelfEcho(packet: packet,
from: peerID,
localPeerID: localPeerID) {
return false // ← drop self-echo
}
This guard runs at lines 47‑49 of [BLEFileTransferHandler.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFileTransferHandler.swift#L47-L49). Only packets where deliveryPlan.isPrivateMessage == true proceed to authentication; broadcasts or irrelevant traffic is ignored.
Authentication and Sender Verification
Before writing any payload, the handler validates the sender’s cryptographic identity. The authenticatedRawSenderNickname function verifies packet signatures (or falls back to signed display names) to ensure only authentic peers can trigger file writes:
guard let senderNickname = authenticatedRawSenderNickname(
packet: packet,
from: peerID,
peers: peersSnapshot,
env: env) else {
// Drop unauthenticated packets
return false
}
See implementation at lines 37‑45. This authentication step is mandatory for both public and private media paths.
The Private Media Transfer Workflow
Private media handling diverges from public file broadcasts through four specialized stages:
1. Stable ID Generation
For private transfers, the protocol generates a deterministic messageID using PrivateMediaMessageIdentity.stableID, which hashes the sender, recipient, and file packet metadata. This ID serves as the canonical reference for deduplication and receipt tracking:
let messageID = usesDurableReceipts
? PrivateMediaMessageIdentity.stableID(
for: filePacket,
senderPeerID: peerID,
recipientPeerID: localPeerID)
: nil
Reference implementation at lines 44‑50 in [BitchatFilePacket.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/BitchatFilePacket.swift#L168).
2. Deduplication and Receipt Reservation
The PrivateMediaArrivalDeduplicator coalesces concurrent arrivals of the same ID and queries the receipt store for current state (accepted, tombstoned, or unavailable):
if let messageID {
switch privateMediaArrivals.reserve(messageID,
receiptState: { env.privateMediaReceiptState(messageID) }) {
case .reserved: break // first arrival
case .pending: return true // coalesce retries
case .accepted(let existingFile): … // duplicate handling
case .tombstoned: … // deleted media
case .unavailable: … // receipt store offline
}
}
This reservation logic appears at lines 44‑55 and 71‑85.
3. Atomic Commit and Storage
After persisting the file bytes, the handler attempts a durable commit. If commitPrivateMediaFile fails, the protocol rolls back the write to prevent orphaned duplicates:
if let messageID,
!env.commitPrivateMediaFile(messageID, destination) {
// Commit failed → roll back the file
env.removeIncomingFile(destination)
return false
}
See the commit and cleanup logic at lines 34‑40.
4. Delivery Verification
Final UI delivery occurs only after verifying the receipt store still acknowledges the file. The deliverStableMessage closure compares the expected URL against the resolved URL in the persistent ledger:
env.deliverMessage(message,
{
guard case .accepted(let resolvedURL) =
env.privateMediaReceiptState(messageID) else { return false }
return resolvedURL.standardizedFileURL == expectedURL.standardizedFileURL
},
{ env.acknowledgePrivateMedia(messageID, peerID) },
{ _ in env.finishIncomingFileDelivery(expectedURL) })
This guard executes at lines 90‑106, ensuring the UI never displays files that failed durable persistence.
Storage Quota Enforcement
To prevent malicious peers from exhausting disk space, the handler enforces per-device quotas (specification BCH‑01‑002) before writing payload bytes:
env.enforceStorageQuota(filePacket.content.count)
See line 21 of [BLEFileTransferHandler.swift](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/BLE/BLEFileTransferHandler.swift#L21). Exceeded quotas result in immediate rejection without affecting downstream gossip.
Persistence and Recovery Across Restarts
The BLEPrivateMediaReceiptStore maintains a persistent mapping of messageID → file URL (lines 127‑215). On app relaunch, any in-flight private media already committed are re-assembled via this store, allowing senders to safely retry until the durable receipt is recorded. The handler finalizes each reservation with privateMediaArrivals.finish(messageID) (lines 16‑19) to release memory while preserving the persistent state.
Integration with the BLE Service
BLEFileTransferHandler is instantiated lazily within BLEService at line 457:
private lazy var fileTransferHandler =
BLEFileTransferHandler(environment: makeFileTransferHandlerEnvironment())
All heavyweight operations—signature verification, quota checks, and file I/O—execute off the CoreBluetooth queue, making the handler queue-agnostic and fully testable. The surrounding BLE service merely forwards packets and provides environment closures.
Summary
- Three-layer separation: Routing policy, transfer handler, and receipt store collaborate to isolate concerns.
- Exactly-once delivery: Deterministic
stableIDgeneration and durable receipt commits prevent duplicate private media. - Cryptographic authentication:
authenticatedRawSenderNicknamevalidates every packet before storage. - Storage protection: Quota enforcement (BCH‑01‑002) and atomic commit/rollback prevent resource exhaustion and orphaned files.
- Crash resilience: Persistent receipt state allows recovery and idempotent retries across app restarts.
Frequently Asked Questions
How does bitchat prevent duplicate private media transfers?
The protocol generates a deterministic messageID using PrivateMediaMessageIdentity.stableID and routes it through PrivateMediaArrivalDeduplicator.reserve. This ensures concurrent arrivals of identical content coalesce into a single write, while the BLEPrivateMediaReceiptStore persists the accepted state to block replays after delivery.
What happens if the app restarts during a private media transfer?
Because BLEPrivateMediaReceiptStore maintains a persistent ledger mapping message IDs to file URLs, committed files survive restarts. On relaunch, the store re-hydrates the receipt state, allowing the protocol to recognize already-received media and reject retries without re-writing files.
How does the protocol protect against storage exhaustion attacks?
Before allocating disk space, BLEFileTransferHandler invokes enforceStorageQuota (BCH‑01‑002) to verify the incoming payload size against per-device limits. If the quota is exceeded, the handler drops the packet immediately without triggering downstream storage operations.
What distinguishes private media handling from public file broadcasts?
Private media activates usesDurableReceipts, triggering stable ID generation, deduplication via PrivateMediaArrivalDeduplicator, and atomic commit semantics with commitPrivateMediaFile. Public files bypass these steps, proceeding directly to storage without the persistent receipt guarantees or sender-recipient binding enforced for private transfers.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →