From ab8c06613db84eda7716d3cb61bc225cca0cb598 Mon Sep 17 00:00:00 2001 From: Taksh Date: Sun, 23 Aug 2026 19:56:50 +0530 Subject: [PATCH] fix: stop fragmentation eating a signature byte off an unpadded frame createFragments encoded the packet with padding on and then called MessagePadding.unpad to strip it again. Across most of the fragmentation range no padding was ever applied: pad() declines a request needing more than 255 bytes, and optimalBlockSize() returns the input size unchanged above 2048, so frames of 513-768, 1009-1792 and >=2033 bytes come back untouched. An unpadded frame ends with the last byte of the signature, which is uniformly random. unpad reads it as a PKCS#7 pad length, and a value of 0x01 satisfies the tail check trivially, so a real signature byte is stripped. Every receiver reassembles the fragments faithfully and then fails the size check in decodeCore, which is why the message dies on all of them at once with nothing in the sender's log. Ask the encoder for the unpadded frame instead. Byte-identical for frames that really were padded, so nothing changes on the wire. --- .../bitchat/android/mesh/FragmentManager.kt | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt b/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt index 3a10f998..ada67f92 100644 --- a/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt @@ -3,7 +3,6 @@ package com.bitchat.android.mesh import android.util.Log import com.bitchat.android.protocol.BitchatPacket import com.bitchat.android.protocol.MessageType -import com.bitchat.android.protocol.MessagePadding import com.bitchat.android.model.FragmentPayload import kotlinx.coroutines.* import java.util.concurrent.ConcurrentHashMap @@ -66,20 +65,19 @@ class FragmentManager { Log.w(TAG, "Rejecting invalid outbound fragment limit: $maxFragments") return emptyList() } - val encoded = packet.toBinaryData() - if (encoded == null) { + // Fragment the unpadded frame; each fragment will be encoded (and padded) independently - iOS fix. + // Ask the encoder to skip padding rather than un-padding afterwards: + // pad() declines a request needing more than 255 bytes and + // optimalBlockSize() returns the input size above 2048, so frames of + // 513-768, 1009-1792 and >=2033 bytes are never padded. Those end + // with the last byte of the signature, and unpad() reads a value of + // 0x01 as a one-byte pad and strips a real signature byte. + val fullData = packet.toBinaryData(padding = false) + if (fullData == null) { Log.e(TAG, "Failed to encode packet to binary data") return emptyList() } - // Fragment the unpadded frame; each fragment will be encoded (and padded) independently - iOS fix - val fullData = try { - MessagePadding.unpad(encoded) - } catch (e: Exception) { - Log.e(TAG, "Failed to unpad data: ${e.message}", e) - return emptyList() - } - // iOS logic: if data.count > 512 && packet.type != MessageType.fragment.rawValue if (fullData.size <= FRAGMENT_SIZE_THRESHOLD) { return listOf(packet) // No fragmentation needed