From 33d273f2680d441cf925aaf584789efc2cff5fe3 Mon Sep 17 00:00:00 2001 From: ShilohEye Date: Thu, 10 Jul 2025 19:25:52 -0400 Subject: [PATCH 1/2] 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 } /** From f909f01fec8e7f6da3c7e9d1553b54fe4d00deef Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 13 Jul 2025 23:23:15 +0200 Subject: [PATCH 2/2] integrate into main --- .../com/bitchat/android/mesh/BluetoothConnectionManager.kt | 2 +- .../com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 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 83bb2e8b..dd60ea1b 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt @@ -36,7 +36,7 @@ class BluetoothConnectionManager( // Component managers private val permissionManager = BluetoothPermissionManager(context) private val connectionTracker = BluetoothConnectionTracker(connectionScope, powerManager) - private val packetBroadcaster = BluetoothPacketBroadcaster(connectionScope, connectionTracker) + private val packetBroadcaster = BluetoothPacketBroadcaster(connectionScope, connectionTracker, fragmentManager) // Delegate for component managers to call back to main manager private val componentDelegate = object : BluetoothConnectionManagerDelegate { diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt index 206865dd..b0dd85a8 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothPacketBroadcaster.kt @@ -16,7 +16,8 @@ import kotlinx.coroutines.launch */ class BluetoothPacketBroadcaster( private val connectionScope: CoroutineScope, - private val connectionTracker: BluetoothConnectionTracker + private val connectionTracker: BluetoothConnectionTracker, + private val fragmentManager: FragmentManager? ) { companion object { @@ -38,7 +39,7 @@ class BluetoothPacketBroadcaster( Log.d(TAG, "Fragmenting packet into ${fragments.size} fragments") connectionScope.launch { fragments.forEach { fragment -> - broadcastSinglePacket(fragment, gattServer, characteristic) + broadcastSinglePacket(RoutedPacket(fragment), gattServer, characteristic) // 20ms delay between fragments (matching iOS/Rust) delay(20) }