How Bitchat Android Handles File Transfers Over the Mesh Network

TLDR: Bitchat Android wraps every file in a BitchatFilePacket, TLV-encodes the payload, routes it through UnifiedMeshService as a MessageType.FILE_TRANSFER packet over BLE or Wi-Fi Aware, and reconstructs it on the remote side via BitchatFilePacket.decode().

Bitchat Android enables decentralized peer-to-peer communication without relying on internet infrastructure. Its file transfer mechanism reuses the exact same mesh transport pipeline that carries text messages, ensuring consistency across the codebase. Understanding how Bitchat Android handles file transfers over the mesh network starts with tracing a file from the UI picker all the way to the remote device’s storage layer.

From URI to Packet: Creating a BitchatFilePacket

When a user selects a file via the system content picker, the UI layer—coordinated by MediaSendingManager in app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt—invokes FileSharingManager.createFilePacketFromUri(context, uri, optionalName). This method copies the content to a temporary location using FileUtils.copyFileForSending, reads the raw bytes, and assembles a BitchatFilePacket instance.

The source of truth for this logic lives in app/src/main/java/com/bitchat/android/model/FileSharingManager.kt.

val packet = FileSharingManager.createFilePacketFromUri(context, uri, optionalName)

This single call bridges the UI layer and the domain model, returning a packet ready for serialization.

TLV Encoding the File Payload

BitchatFilePacket.encode() converts the file metadata and content into a binary TLV byte array. Each field is prefixed with a 1-byte type identifier, followed by a length field—2 bytes for filename, filesize, and mime-type, and 4 bytes for the content payload.

Large files may generate multiple CONTENT TLVs, although the transport layer fragments packets at the MTU level when running over BLE. The encoder itself is implemented in app/src/main/java/com/bitchat/android/model/BitchatFilePacket.kt.

val encodedBytes = packet.encode()

The resulting byte array contains the fully self-describing structure needed for reconstruction on any mesh peer.

Routing the Packet Through UnifiedMeshService

Once encoded, the payload is injected into the mesh core as a standard message. The caller passes MessageType.FILE_TRANSFER so that downstream nodes recognize the packet type correctly. UnifiedMeshService.sendMessage(...) abstracts the concrete transport and forwards the call to whichever backend is currently active.

According to the Bitchat Android source code, the two primary transport implementations are:

  • BluetoothMeshService for BLE mesh links
  • WifiAwareMeshService for Wi-Fi Aware links

Both implementations are located under app/src/main/java/com/bitchat/android/mesh/, with the Wi-Fi Aware variant residing in app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshService.kt.

meshCore.sendMessage(
    payload = encodedFile,
    type = MessageType.FILE_TRANSFER
)

This design means file transfers benefit from the same retry, discovery, and routing logic used for regular chat messages.

Receiving and Decoding Files on the Remote Peer

Incoming packets land in MessageHandler, where the MessageType field is inspected. When the value equals MessageType.FILE_TRANSFER, the handler reconstructs the original packet with BitchatFilePacket.decode(packet.payload) and delegates file-system persistence to FileSharingManager.processReceivedFile(..).

The return value is a ReceivedFileInfo object that carries the original filename, total size, MIME type, and raw ByteArray. The UI layer then surfaces a save or open action inside the chat bubble.

The relevant files are app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt and app/src/main/java/com/bitchat/android/model/FileSharingManager.kt.

val filePacket = BitchatFilePacket.decode(packet.payload)
val fileInfo = FileSharingManager.processReceivedFile(filePacket)

Because decoding mirrors encoding exactly, the receiver rebuilds the identical structure that left the sender’s device.

Canceling an Active File Transfer

Either peer can abort a transfer mid-flight by calling meshService.cancelFileTransfer(transferId). UnifiedMeshService propagates the cancellation request to each concrete transport implementation, invoking BluetoothMeshService.cancelFileTransfer and WifiAwareMeshService.cancelFileTransfer as appropriate.

This ensures that buffers are released and partial files are discarded regardless of which radio path the packet was traversing.

val cancelled = meshService.cancelFileTransfer(transferId)
if (cancelled) Log.d("Transfer", "Cancelled $transferId")

The cancellation API lives alongside the send logic in app/src/main/java/com/bitchat/android/mesh/UnifiedMeshService.kt.

Summary

  • Bitchat Android reuses its text-message mesh pipeline for file transfers by tagging packets with MessageType.FILE_TRANSFER.
  • FileSharingManager.createFilePacketFromUri() and BitchatFilePacket.encode() handle packaging and TLV serialization in app/src/main/java/com/bitchat/android/model/.
  • UnifiedMeshService routes encoded payloads through BLE or Wi-Fi Aware without exposing transport details to the UI.
  • MessageHandler and BitchatFilePacket.decode() reconstruct the file into a ReceivedFileInfo object on the receiving peer.
  • Transfers can be aborted at any time via cancelFileTransfer(), which reaches both BluetoothMeshService and WifiAwareMeshService.

Frequently Asked Questions

How does Bitchat Android avoid duplicating transport code for files versus text?

It reuses the same UnifiedMeshService infrastructure. Files are simply BitchatPacket instances whose MessageType is set to FILE_TRANSFER. The BLE and Wi-Fi Aware layers remain unaware of the payload content, so no separate file transport stack is required.

What happens if a file is larger than the BLE MTU?

BitchatFilePacket.encode() structures the payload in TLV format, and the underlying transport—specifically BluetoothMeshService—fragments the byte stream at the MTU level. The code in BitchatFilePacket.kt can emit multiple CONTENT TLVs, but in practice the transport handles segmentation transparently.

Which class is responsible for converting a received byte array back into a usable file?

MessageHandler detects the FILE_TRANSFER type and calls BitchatFilePacket.decode(). It then passes the decoded packet to FileSharingManager.processReceivedFile(..), which returns a ReceivedFileInfo containing the filename, MIME type, and raw bytes for the UI to save or open.

Can a user cancel a file transfer after it has started?

Yes. The public API meshService.cancelFileTransfer(transferId) dispatches cancellation to the active transport implementations in BluetoothMeshService and WifiAwareMeshService, releasing resources and stopping the transfer on both ends.

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 →