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 802c804e..dd60ea1b 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt @@ -14,7 +14,8 @@ import kotlinx.coroutines.* */ class BluetoothConnectionManager( private val context: Context, - private val myPeerID: String + private val myPeerID: String, + private val fragmentManager: FragmentManager? = null ) : PowerManagerDelegate { companion object { @@ -35,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 { @@ -156,6 +157,7 @@ class BluetoothConnectionManager( /** * Broadcast packet to connected devices with connection limit enforcement + * Automatically fragments large packets to fit within BLE MTU limits */ fun broadcastPacket(routed: RoutedPacket) { if (!isActive) return 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 775ce2a8..803d65dd 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -45,7 +45,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) // Service state management 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 14d0ff07..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 { @@ -24,9 +25,6 @@ class BluetoothPacketBroadcaster( private const val CLEANUP_DELAY = 500L } - /** - * Broadcast packet to connected devices with connection limit enforcement - */ fun broadcastPacket( routed: RoutedPacket, gattServer: BluetoothGattServer?, @@ -34,6 +32,37 @@ class BluetoothPacketBroadcaster( ) { val packet = routed.packet val data = packet.toBinaryData() ?: 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 -> + broadcastSinglePacket(RoutedPacket(fragment), gattServer, characteristic) + // 20ms delay between fragments (matching iOS/Rust) + delay(20) + } + } + return + } + } + + // Send single packet if no fragmentation needed + broadcastSinglePacket(routed, gattServer, characteristic) + } + + + /** + * Broadcast single packet to connected devices with connection limit enforcement + */ + fun broadcastSinglePacket( + routed: RoutedPacket, + gattServer: BluetoothGattServer?, + characteristic: BluetoothGattCharacteristic? + ) { + val packet = routed.packet + val data = packet.toBinaryData() ?: return if (packet.recipientID != SpecialRecipients.BROADCAST) { val recipientID = packet.recipientID?.let { 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 } /**