Merge 38bc03fb2dd1c7c298cc08b07fddcc145716782d into c127eb83ab94c069c32d37530d2faecd381cd2a8

This commit is contained in:
Taksh Kothari 2026-09-14 09:43:25 +05:30 committed by GitHub
commit 08ce879a28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 72 additions and 11 deletions

View File

@ -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

View File

@ -3,6 +3,7 @@ package com.bitchat.android.mesh
import com.bitchat.android.protocol.BitchatPacket
import com.bitchat.android.protocol.MessageType
import com.bitchat.android.model.FragmentPayload
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
@ -244,6 +245,68 @@ class FragmentManagerTest {
assertEquals(257, plan(low).size)
}
/**
* pad() declines any 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 are emitted unpadded. The frame
* then ends with the last byte of the signature, which is uniformly random,
* and a value of 0x01 reads as a one-byte PKCS#7 pad.
*/
@Test
fun `an unpadded frame keeps every byte through fragmentation`() {
for (payloadSize in listOf(600, 1500, 2400)) {
val packet = BitchatPacket(
version = 1u,
type = MessageType.MESSAGE.value,
senderID = hexStringToByteArray(senderID),
recipientID = hexStringToByteArray(recipientID),
timestamp = 1u,
payload = ByteArray(payloadSize).also { Random(payloadSize.toLong()).nextBytes(it) },
signature = ByteArray(64) { index -> if (index == 63) 0x01 else 0x7F },
ttl = 7u
)
val frame = packet.toBinaryData(padding = false)!!
val padded = packet.toBinaryData()!!
assertEquals("frame of $payloadSize should not be padded", frame.size, padded.size)
val fragments = fragmentManager.createFragments(packet)
assertTrue("frame of $payloadSize should fragment", fragments.size > 1)
val reassembled = fragments
.map { FragmentPayload.decode(it.payload)!!.data }
.reduce { acc, next -> acc + next }
assertArrayEquals(
"fragments of a $payloadSize-byte payload must carry the whole frame",
frame,
reassembled
)
}
}
/** A frame that really was padded must still fragment unpadded. */
@Test
fun `a padded frame is fragmented without its padding`() {
val packet = BitchatPacket(
version = 1u,
type = MessageType.MESSAGE.value,
senderID = hexStringToByteArray(senderID),
recipientID = hexStringToByteArray(recipientID),
timestamp = 1u,
payload = ByteArray(900).also { Random(900).nextBytes(it) },
signature = ByteArray(64) { 0x7F },
ttl = 7u
)
val frame = packet.toBinaryData(padding = false)!!
assertTrue("frame should be padded", packet.toBinaryData()!!.size > frame.size)
val reassembled = fragmentManager.createFragments(packet)
.map { FragmentPayload.decode(it.payload)!!.data }
.reduce { acc, next -> acc + next }
assertArrayEquals(frame, reassembled)
}
private fun hexStringToByteArray(hexString: String): ByteArray {
val result = ByteArray(8)
for (i in 0 until 8) {