How Bitchat Android Manages Peer-to-Peer File Sharing: A Deep Dive into the Mesh Networking Implementation
Bitchat Android implements end-to-end encrypted peer-to-peer file sharing through a three-layer architecture that separates UI coordination, mesh core logic, and transport-specific delivery over BLE or Wi-Fi Aware.
According to the permissionlesstech/bitchat-android source code, peer-to-peer file sharing is built on top of a unified mesh networking stack that automatically handles fragmentation, encryption, and transport selection. This article examines how the components work together to deliver files without centralized servers.
The Three-Layer Architecture
Bitchat Android splits file sharing into distinct responsibilities:
| Layer | Primary Class | Responsibility |
|---|---|---|
| UI Layer | MediaSendingManager.kt |
Collects file data, initiates preparation, commits transfers |
| Mesh Core | UnifiedMeshService.kt / MeshCore.kt |
Selects transport, fragments files, manages encryption |
| Transport | BluetoothMeshService.kt / WifiAwareMeshService.kt |
Delivers packets over radio and handles cancellation |
This separation allows the app to support multiple transports while presenting a unified API to the UI layer.
File Payload Representation with TLV Encoding
Files are encoded using BitchatFilePacket, which structures metadata and content into a Type-Length-Value (TLV) format defined in [/app/src/main/java/com/bitchat/android/model/BitchatFilePacket.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/model/BitchatFilePacket.kt).
The encoding scheme uses variable-length fields:
- Standard TLVs: 2-byte length fields for metadata (filename, MIME type, size)
- CONTENT TLV: 4-byte length field to support large file chunking (up to 65,535 bytes per chunk)
The decoder tolerates unknown TLV types and concatenates multiple CONTENT TLVs, ensuring forward compatibility with future protocol versions.
Preparing a Private File Transfer
When a user selects a file, MediaSendingManager initiates preparation through the mesh service:
val preparation = meshService.prepareFilePrivate(
recipientPeerID = peerId,
file = filePacket,
transferId = UUID.randomUUID().toString(),
allowLegacyFallback = false
)
Underneath this call, MeshCore.prepareFilePrivate (accessed via UnifiedMeshService) delegates to PrivateMediaTransferPreparer. This preparer:
- Builds TLV fragments from the file packet
- Signs each packet for authenticity
- Returns a
PrivateMediaPreparation.Readycontaining acommit()lambda
The commit() lambda dispatches routed packets through MeshTransport, which handles any additional fragmentation required by the underlying BLE or Wi-Fi link. The implementation in MeshCore.kt#L61-L84 ensures packets are ready before any radio transmission begins.
Transport Selection and Commit
UnifiedMeshService determines whether to use Bluetooth Low Energy or Wi-Fi Aware based on peer readiness, as shown in UnifiedMeshService.kt#L87-L106:
// Simplified logic from the source
if (bluetooth.isPeerReady(recipientPeerID)) {
bluetooth.prepareFilePrivate(...)
} else if (wifiService?.isPeerReady(recipientPeerID) == true) {
wifiService.prepareFilePrivate(...)
}
Once the UI receives a Ready object, MediaSendingManager calls commitPreparedPrivateFile. This function:
- Creates a local echo message for UI feedback
- Stores the
transferId ↔ messageIdmapping - Invokes
preparation.transfer.commit()to begin actual transmission
The UI updates to "sent" status on success, or marks the echo failed if the commit errors out (MediaSendingManager.kt#L50-L68).
End-to-End Encryption Requirements
All file fragments are encrypted with the Noise protocol. MeshCore enforces that a Noise session exists before allowing prepareFilePrivate to proceed:
// From MeshCore.kt#L460-L470
require(hasEstablishedSession(peerId)) {
"No Noise session established with peer $peerId"
}
If missing, the UI layer automatically triggers meshService.initiateNoiseHandshake(peerId) before retrying the file preparation. This ensures no unencrypted file data ever reaches the transport layer.
Cancellation Flow
Users can abort transfers through a unified cancellation API:
val cancelled = meshService.cancelFileTransfer(transferId)
The request propagates:
UnifiedMeshService.cancelFileTransfer→ both BLE and Wi-Fi services- Each transport removes pending fragments from its send queue
- Success is reported if either transport acknowledges cancellation
Both BluetoothMeshService and WifiAwareMeshService implement cancelFileTransfer(transferId) by forwarding to their underlying MeshTransport implementations (BluetoothMeshService.kt#L942-L1000).
Complete File Sharing Example
Here's the full workflow combining all layers:
// 1. Build file packet from raw bytes
val fileBytes = Files.readAllBytes(Paths.get("/sdcard/Download/photo.jpg"))
val filePacket = BitchatFilePacket(
fileName = "photo.jpg",
fileSize = fileBytes.size.toLong(),
mimeType = "image/jpeg",
content = fileBytes
)
// 2. Request encrypted private transfer preparation
val transferId = UUID.randomUUID().toString()
val prep = meshService.prepareFilePrivate(
recipientPeerID = peerId,
file = filePacket,
transferId = transferId,
allowLegacyFallback = false
)
// 3. Commit when ready (typically inside a coroutine)
when (prep) {
is PrivateMediaPreparation.Ready -> {
// Creates local echo, then transmits
mediaSendingManager.commitPreparedPrivateFile(prep, transferId)
}
is PrivateMediaPreparation.NeedsHandshake -> {
meshService.initiateNoiseHandshake(peerId)
// Retry after handshake completes
}
}
// 4. Cancel if user aborts
val cancelled = meshService.cancelFileTransfer(transferId)
Key Implementation Files
Summary
- Bitchat Android uses a layered architecture for peer-to-peer file sharing: UI (
MediaSendingManager), mesh core (UnifiedMeshService/MeshCore), and transport (BluetoothMeshService/WifiAwareMeshService). - TLV encoding in
BitchatFilePackethandles metadata and chunked content with forward-compatible parsing. - Noise protocol encryption is mandatory—
prepareFilePrivaterequires an established session or triggers automatic handshake. - Transport-agnostic preparation allows seamless fallback between BLE and Wi-Fi Aware without UI changes.
- Unified cancellation propagates through all layers to stop transfers cleanly.
Frequently Asked Questions
What transport protocols does Bitchat Android use for file sharing?
Bitchat Android supports Bluetooth Low Energy (BLE) and Wi-Fi Aware as transport protocols. The UnifiedMeshService automatically selects between them based on peer readiness, falling back to whichever connection is available. Both transports implement the same interface for fragmentation, sending, and cancellation.
How large can files be in Bitchat's peer-to-peer sharing?
The TLV encoding supports large files through chunked CONTENT fields with 4-byte length headers (max 65,535 bytes per chunk). Multiple CONTENT TLVs are concatenated during decoding, so practical limits depend on available storage and transfer time rather than protocol constraints.
Is file sharing in Bitchat Android encrypted?
Yes—all file transfers use end-to-end encryption via the Noise protocol. MeshCore.kt enforces that a Noise session exists before prepareFilePrivate succeeds. If no session exists, the UI automatically initiates a handshake. No file data is transmitted without encryption.
Can users cancel file transfers mid-send?
Yes. Calling meshService.cancelFileTransfer(transferId) propagates to both BLE and Wi-Fi Aware transports, which remove pending fragments from their send queues. The cancellation succeeds if either transport acknowledges it, ensuring no orphaned transmissions continue.
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 →