How Bitchat Android Reassembles Multi-Packet Messages: A Complete Technical Guide
Bitchat Android uses a FragmentManager class to split oversized packets into smaller fragments on the sending side and reassemble them on the receiving side by buffering fragments in memory, verifying metadata consistency, and reconstructing the original payload once all fragments arrive.
Multi-packet message reassembly is essential for any mesh messaging app that exchanges data larger than the Bluetooth MTU limit. In the permissionlesstech/bitchat-android repository, this capability is implemented through a fragmentation system that mirrors the iOS version for cross-platform compatibility. This article breaks down exactly how fragmented messages are reassembled in Bitchat Android, from outbound splitting to inbound reconstruction and cleanup.
Outbound Fragmentation in Bitchat Android
When a message exceeds the MTU threshold (approximately 512 bytes), Bitchat Android automatically fragments it before transmission.
The FragmentingPacketSender Entry Point
MeshCore creates a FragmentingPacketSender (located in [FragmentingPacketSender.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/mesh/FragmentingPacketSender.kt)) to handle large packets. The sender checks against AppConstants.Fragmentation.FRAGMENT_SIZE_THRESHOLD and invokes fragmentManager.createFragments(packet, maxFragments).
The FragmentManager instance lives in MeshCore and is shared across the mesh layer.
Creating Fragment Payloads
FragmentManager.createFragments performs three critical operations:
- Generates an 8-byte random fragment ID to identify the fragment set
- Splits the unpadded payload into chunks that fit within MTU-overhead limits
- Wraps each chunk in a
FragmentPayload(defined in [FragmentPayload.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/model/FragmentPayload.kt))
Each resulting BitchatPacket has:
type = MessageType.FRAGMENTpayload = FragmentPayload.encode()- Original route and TTL preserved
The FragmentingPacketSender then transmits these fragments sequentially, with optional delays between packets.
Inbound Reassembly: The Core Process
All incoming packets flow through PacketProcessor.processPacket → PacketProcessor.handleFragment in [PacketProcessor.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/mesh/PacketProcessor.kt). Fragment packets are delegated back to MeshCore:
override fun handleFragment(packet: BitchatPacket): BitchatPacket? {
return fragmentManager.handleFragment(packet)
}
FragmentManager.handleFragment Implementation
The reassembly routine in [FragmentManager.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt) (lines 70-140) executes the following steps:
Validity checks — Verifies payload contains at least the 13-byte FragmentPayload header.
Decoding — FragmentPayload.decode(packet.payload) extracts (fragmentID, index, total, originalType, data).
Metadata verification — Confirms all fragments in a set share identical originalType and total values.
Fragment storage — Stores fragments in incomingFragments[fragmentID] as a map of index → data. Two safeguards prevent memory exhaustion:
MAX_FRAGMENT_TOTAL_BYTES— per-set byte limitMAX_GLOBAL_FRAGMENT_TOTAL_BYTES— global buffer cap
Completion detection — When fragmentMap.size == total, reassembly proceeds:
val reassembledData = mutableListOf<Byte>()
for (i in 0 until total) {
fragmentMap[i]?.let { reassembledData.addAll(it.asIterable()) }
}
Packet reconstruction — BitchatPacket.fromBinaryData(reassembledData.toByteArray()) rebuilds the original. TTL is forced to zero (copy(ttl = 0u)) to prevent replay attacks.
Cleanup — The completed fragment set is immediately removed from internal maps.
The reassembled packet returns to PacketProcessor.handleFragment, which reinjects it into normal processing via handleReceivedPacket. From there, it routes to handleMessage, handleAnnounce, or other appropriate handlers.
Reassembly Safeguards and Housekeeping
Bitchat Android implements multiple protective mechanisms to ensure reliable multi-packet message reassembly:
Timeout cleanup — A coroutine in FragmentManager periodically invokes cleanupOldFragments, removing sets older than FRAGMENT_TIMEOUT (30 seconds).
Active-set limits — MAX_ACTIVE_FRAGMENT_SETS caps concurrent fragment collections, while MAX_GLOBAL_FRAGMENT_TOTAL_BYTES bounds total buffered memory.
These limits make fragmentation deterministic and safe across Android and iOS peers.
Practical Code Examples
Sending Large Messages (Automatic Fragmentation)
// Inside a component with MeshCore instance `mesh`
val bigText = "A".repeat(2000) // Exceeds 512-byte MTU
mesh.sendMessage(content = bigText) // Automatically fragmented
Behind the scenes, FragmentingPacketSender calls fragmentManager.createFragments and emits multiple MessageType.FRAGMENT packets.
Manual Fragmentation and Reassembly
val fm = FragmentManager()
val original = BitchatPacket(
version = 1u,
type = MessageType.MESSAGE.value,
senderID = MeshPacketUtils.hexStringToByteArray("deadbeef"),
recipientID = SpecialRecipients.BROADCAST,
timestamp = System.currentTimeMillis().toULong(),
payload = ByteArray(1500) { it.toByte() },
ttl = 5u
)
// 1. Create fragments
val fragments: List<BitchatPacket> = fm.createFragments(original)
// 2. Process fragments in any order
fragments.shuffled().forEach { fm.handleFragment(it) }
// 3. Final call returns reassembled packet
The repository's [FragmentManagerTest.kt](https://github.com/permissionlesstech/bitchat-android/blob/main/app/src/test/java/com/bitchat/android/mesh/FragmentManagerTest.kt) validates this exact flow.
Debugging Reassembly
Enable debug logging in FragmentManager.handleFragment. Successful reassembly produces:
D/FragmentManager: Reassembled packet type=0x01, payloadSize=1500
Key Source Files for Multi-Packet Reassembly
Summary
- Bitchat Android automatically fragments packets exceeding ~512 bytes using
FragmentManager.createFragments, generating 8-byte random fragment IDs and wrapping chunks inFragmentPayloadstructures. - Inbound reassembly occurs in
FragmentManager.handleFragment, which validates headers, verifies metadata consistency, buffers fragments in ordered maps, and reconstructs the original packet once all pieces arrive. - Safety mechanisms include per-set and global byte limits, maximum active fragment sets, 30-second timeouts, and TTL zeroing to prevent replay.
- Cross-platform compatibility is maintained by mirroring the iOS implementation's structure and behavior.
- Key classes:
FragmentManagerfor logic,FragmentPayloadfor binary format,PacketProcessorfor routing,FragmentingPacketSenderfor transmission.
Frequently Asked Questions
What triggers message fragmentation in Bitchat Android?
Fragmentation triggers when a BitchatPacket payload exceeds AppConstants.Fragmentation.FRAGMENT_SIZE_THRESHOLD, approximately 512 bytes to stay within Bluetooth MTU limits. The MeshCore automatically routes oversized messages through FragmentingPacketSender, which delegates to FragmentManager.createFragments.
Can fragments arrive out of order and still reassemble correctly?
Yes. FragmentManager.handleFragment stores fragments in incomingFragments[fragmentID] as a map of index → data, then reconstructs by iterating 0 until total in order. The unit test in FragmentManagerTest.kt explicitly shuffles fragments before processing to verify this behavior.
How does Bitchat Android prevent memory exhaustion from fragment attacks?
Three safeguards protect against malicious flooding: MAX_FRAGMENT_TOTAL_BYTES limits per-set buffering, MAX_GLOBAL_FRAGMENT_TOTAL_BYTES caps total memory across all sets, and MAX_ACTIVE_FRAGMENT_SETS restricts concurrent fragment collections. A 30-second timeout (FRAGMENT_TIMEOUT) additionally removes stale incomplete sets.
Why is TTL set to zero on reassembled packets?
FragmentManager calls copy(ttl = 0u) during reconstruction to prevent replay attacks. A reassembled packet with its original TTL could be captured and retransmitted by an attacker to propagate indefinitely through the mesh; zeroing TTL ensures the packet is processed locally by the receiving node only.
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 →