From 33d273f2680d441cf925aaf584789efc2cff5fe3 Mon Sep 17 00:00:00 2001 From: ShilohEye Date: Thu, 10 Jul 2025 19:25:52 -0400 Subject: [PATCH] Fix Android fragmentation for iOS/Rust compatibility - Changed MAX_FRAGMENT_SIZE from 500 to 150 bytes to match iOS/Rust - Fixed fragment ID generation to use 8 random bytes instead of string - Implemented automatic fragmentation in BluetoothConnectionManager - Added 20ms delay between fragments to match iOS/Rust implementation - Updated fragment calculation to account for full packet overhead (34 bytes) This ensures Android-generated fragments fit within iOS's default 185-byte BLE MTU, fixing issues with commands like ?ls -la and ?ifconfig failing on iOS devices. --- .../mesh/BluetoothConnectionManager.kt | 30 +++++++++++++++++-- .../android/mesh/BluetoothMeshService.kt | 2 +- .../bitchat/android/mesh/FragmentManager.kt | 26 ++++++++-------- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt index faaa3a45..685219f0 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt @@ -20,7 +20,8 @@ import java.util.concurrent.CopyOnWriteArrayList */ class BluetoothConnectionManager( private val context: Context, - private val myPeerID: String + private val myPeerID: String, + private val fragmentManager: FragmentManager? = null ) : PowerManagerDelegate { companion object { @@ -198,13 +199,38 @@ class BluetoothConnectionManager( /** * Broadcast packet to connected devices with connection limit enforcement + * Automatically fragments large packets to fit within BLE MTU limits */ fun broadcastPacket(packet: BitchatPacket) { if (!isActive) return + // Check if we need to fragment + if (fragmentManager != null) { + val fragments = fragmentManager.createFragments(packet) + if (fragments.size > 1) { + Log.d(TAG, "Fragmenting packet into ${fragments.size} fragments") + connectionScope.launch { + fragments.forEach { fragment -> + sendSinglePacket(fragment) + // 20ms delay between fragments (matching iOS/Rust) + delay(20) + } + } + return + } + } + + // Send single packet if no fragmentation needed + sendSinglePacket(packet) + } + + /** + * Send a single packet (fragment or whole) to all connected devices + */ + private fun sendSinglePacket(packet: BitchatPacket) { val data = packet.toBinaryData() ?: return - Log.d(TAG, "Broadcasting packet type ${packet.type} to ${subscribedDevices.size} server + ${connectedDevices.size} client connections") + Log.d(TAG, "Sending packet type ${packet.type} (${data.size} bytes) to ${subscribedDevices.size} server + ${connectedDevices.size} client connections") // Send to server connections (devices connected to our GATT server) subscribedDevices.forEach { device -> diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt index 0a66bcf8..cfa2127e 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -44,7 +44,7 @@ class BluetoothMeshService(private val context: Context) { private val securityManager = SecurityManager(encryptionService, myPeerID) private val storeForwardManager = StoreForwardManager() private val messageHandler = MessageHandler(myPeerID) - internal val connectionManager = BluetoothConnectionManager(context, myPeerID) // Made internal for access + internal val connectionManager = BluetoothConnectionManager(context, myPeerID, fragmentManager) // Made internal for access private val packetProcessor = PacketProcessor(myPeerID) // Delegate for message callbacks (maintains same interface) 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 4f21e295..eb73bffc 100644 --- a/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/FragmentManager.kt @@ -14,7 +14,7 @@ class FragmentManager { companion object { private const val TAG = "FragmentManager" - private const val MAX_FRAGMENT_SIZE = 500 + private const val MAX_FRAGMENT_SIZE = 150 // Match iOS/Rust for BLE compatibility (185 byte MTU limit) private const val FRAGMENT_TIMEOUT = 30000L // 30 seconds private const val CLEANUP_INTERVAL = 10000L // 10 seconds } @@ -46,16 +46,15 @@ class FragmentManager { val fragments = mutableListOf() val fragmentID = generateFragmentID() - // Calculate header size (13 bytes for fragment metadata) - val headerSize = 13 - val dataPerFragment = MAX_FRAGMENT_SIZE - headerSize - val totalFragments = (data.size + dataPerFragment - 1) / dataPerFragment + // Fragment overhead: 13 bytes (fragment metadata) + 21 bytes (packet header) = 34 bytes total + // With 150 byte fragments, total packet = ~184 bytes (within iOS 185 byte MTU) + val totalFragments = (data.size + MAX_FRAGMENT_SIZE - 1) / MAX_FRAGMENT_SIZE Log.d(TAG, "Creating ${totalFragments} fragments for ${data.size} byte packet") for (i in 0 until totalFragments) { - val start = i * dataPerFragment - val end = minOf(start + dataPerFragment, data.size) + val start = i * MAX_FRAGMENT_SIZE + val end = minOf(start + MAX_FRAGMENT_SIZE, data.size) val fragmentData = data.sliceArray(start until end) val fragmentPayload = createFragmentPayload( @@ -158,7 +157,7 @@ class FragmentManager { * Create fragment payload with metadata */ private fun createFragmentPayload( - fragmentID: String, + fragmentID: ByteArray, index: Int, total: Int, originalType: UByte, @@ -167,8 +166,7 @@ class FragmentManager { val payload = ByteArray(13 + data.size) // Fragment ID (8 bytes) - val idBytes = fragmentID.toByteArray() - System.arraycopy(idBytes, 0, payload, 0, minOf(8, idBytes.size)) + System.arraycopy(fragmentID, 0, payload, 0, 8) // Index (2 bytes, big-endian) payload[8] = ((index shr 8) and 0xFF).toByte() @@ -188,10 +186,12 @@ class FragmentManager { } /** - * Generate unique fragment ID + * Generate unique fragment ID (8 random bytes to match iOS/Rust) */ - private fun generateFragmentID(): String { - return "${System.currentTimeMillis()}-${kotlin.random.Random.nextInt()}" + private fun generateFragmentID(): ByteArray { + val fragmentID = ByteArray(8) + kotlin.random.Random.nextBytes(fragmentID) + return fragmentID } /**