mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-24 23:25:19 +00:00
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.
This commit is contained in:
@@ -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 ->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<BitchatPacket>()
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user