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:

  1. Builds TLV fragments from the file packet
  2. Signs each packet for authenticity
  3. Returns a PrivateMediaPreparation.Ready containing a commit() 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 ↔ messageId mapping
  • 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:

  1. UnifiedMeshService.cancelFileTransfer → both BLE and Wi-Fi services
  2. Each transport removes pending fragments from its send queue
  3. 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

File GitHub Path Role
BitchatFilePacket.kt [model/BitchatFilePacket.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/model/BitchatFilePacket.kt) TLV serialization
MeshCore.kt [mesh/MeshCore.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/mesh/MeshCore.kt) Encryption, preparation orchestration
UnifiedMeshService.kt [mesh/UnifiedMeshService.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/mesh/UnifiedMeshService.kt) Public API, transport selection
BluetoothMeshService.kt [mesh/BluetoothMeshService.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt) BLE transport
WifiAwareMeshService.kt [wifi-aware/WifiAwareMeshService.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshService.kt) Wi-Fi Aware transport
MediaSendingManager.kt [ui/MediaSendingManager.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt) UI coordination

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 BitchatFilePacket handles metadata and chunked content with forward-compatible parsing.
  • Noise protocol encryption is mandatory—prepareFilePrivate requires 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:

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 →