mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 18:05:20 +00:00
Merge pull request #48 from ShilohEye/fix/android-ios-fragmentation-compatibility
Fix Android Message fragmentation for iOS compatibility
This commit is contained in:
@@ -14,7 +14,8 @@ import kotlinx.coroutines.*
|
|||||||
*/
|
*/
|
||||||
class BluetoothConnectionManager(
|
class BluetoothConnectionManager(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val myPeerID: String
|
private val myPeerID: String,
|
||||||
|
private val fragmentManager: FragmentManager? = null
|
||||||
) : PowerManagerDelegate {
|
) : PowerManagerDelegate {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -35,7 +36,7 @@ class BluetoothConnectionManager(
|
|||||||
// Component managers
|
// Component managers
|
||||||
private val permissionManager = BluetoothPermissionManager(context)
|
private val permissionManager = BluetoothPermissionManager(context)
|
||||||
private val connectionTracker = BluetoothConnectionTracker(connectionScope, powerManager)
|
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
|
// Delegate for component managers to call back to main manager
|
||||||
private val componentDelegate = object : BluetoothConnectionManagerDelegate {
|
private val componentDelegate = object : BluetoothConnectionManagerDelegate {
|
||||||
@@ -156,6 +157,7 @@ class BluetoothConnectionManager(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast packet to connected devices with connection limit enforcement
|
* Broadcast packet to connected devices with connection limit enforcement
|
||||||
|
* Automatically fragments large packets to fit within BLE MTU limits
|
||||||
*/
|
*/
|
||||||
fun broadcastPacket(routed: RoutedPacket) {
|
fun broadcastPacket(routed: RoutedPacket) {
|
||||||
if (!isActive) return
|
if (!isActive) return
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
private val securityManager = SecurityManager(encryptionService, myPeerID)
|
private val securityManager = SecurityManager(encryptionService, myPeerID)
|
||||||
private val storeForwardManager = StoreForwardManager()
|
private val storeForwardManager = StoreForwardManager()
|
||||||
private val messageHandler = MessageHandler(myPeerID)
|
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)
|
private val packetProcessor = PacketProcessor(myPeerID)
|
||||||
|
|
||||||
// Service state management
|
// Service state management
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import kotlinx.coroutines.launch
|
|||||||
*/
|
*/
|
||||||
class BluetoothPacketBroadcaster(
|
class BluetoothPacketBroadcaster(
|
||||||
private val connectionScope: CoroutineScope,
|
private val connectionScope: CoroutineScope,
|
||||||
private val connectionTracker: BluetoothConnectionTracker
|
private val connectionTracker: BluetoothConnectionTracker,
|
||||||
|
private val fragmentManager: FragmentManager?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -24,9 +25,6 @@ class BluetoothPacketBroadcaster(
|
|||||||
private const val CLEANUP_DELAY = 500L
|
private const val CLEANUP_DELAY = 500L
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast packet to connected devices with connection limit enforcement
|
|
||||||
*/
|
|
||||||
fun broadcastPacket(
|
fun broadcastPacket(
|
||||||
routed: RoutedPacket,
|
routed: RoutedPacket,
|
||||||
gattServer: BluetoothGattServer?,
|
gattServer: BluetoothGattServer?,
|
||||||
@@ -34,6 +32,37 @@ class BluetoothPacketBroadcaster(
|
|||||||
) {
|
) {
|
||||||
val packet = routed.packet
|
val packet = routed.packet
|
||||||
val data = packet.toBinaryData() ?: return
|
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) {
|
if (packet.recipientID != SpecialRecipients.BROADCAST) {
|
||||||
val recipientID = packet.recipientID?.let {
|
val recipientID = packet.recipientID?.let {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class FragmentManager {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "FragmentManager"
|
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 FRAGMENT_TIMEOUT = 30000L // 30 seconds
|
||||||
private const val CLEANUP_INTERVAL = 10000L // 10 seconds
|
private const val CLEANUP_INTERVAL = 10000L // 10 seconds
|
||||||
}
|
}
|
||||||
@@ -46,16 +46,15 @@ class FragmentManager {
|
|||||||
val fragments = mutableListOf<BitchatPacket>()
|
val fragments = mutableListOf<BitchatPacket>()
|
||||||
val fragmentID = generateFragmentID()
|
val fragmentID = generateFragmentID()
|
||||||
|
|
||||||
// Calculate header size (13 bytes for fragment metadata)
|
// Fragment overhead: 13 bytes (fragment metadata) + 21 bytes (packet header) = 34 bytes total
|
||||||
val headerSize = 13
|
// With 150 byte fragments, total packet = ~184 bytes (within iOS 185 byte MTU)
|
||||||
val dataPerFragment = MAX_FRAGMENT_SIZE - headerSize
|
val totalFragments = (data.size + MAX_FRAGMENT_SIZE - 1) / MAX_FRAGMENT_SIZE
|
||||||
val totalFragments = (data.size + dataPerFragment - 1) / dataPerFragment
|
|
||||||
|
|
||||||
Log.d(TAG, "Creating ${totalFragments} fragments for ${data.size} byte packet")
|
Log.d(TAG, "Creating ${totalFragments} fragments for ${data.size} byte packet")
|
||||||
|
|
||||||
for (i in 0 until totalFragments) {
|
for (i in 0 until totalFragments) {
|
||||||
val start = i * dataPerFragment
|
val start = i * MAX_FRAGMENT_SIZE
|
||||||
val end = minOf(start + dataPerFragment, data.size)
|
val end = minOf(start + MAX_FRAGMENT_SIZE, data.size)
|
||||||
val fragmentData = data.sliceArray(start until end)
|
val fragmentData = data.sliceArray(start until end)
|
||||||
|
|
||||||
val fragmentPayload = createFragmentPayload(
|
val fragmentPayload = createFragmentPayload(
|
||||||
@@ -158,7 +157,7 @@ class FragmentManager {
|
|||||||
* Create fragment payload with metadata
|
* Create fragment payload with metadata
|
||||||
*/
|
*/
|
||||||
private fun createFragmentPayload(
|
private fun createFragmentPayload(
|
||||||
fragmentID: String,
|
fragmentID: ByteArray,
|
||||||
index: Int,
|
index: Int,
|
||||||
total: Int,
|
total: Int,
|
||||||
originalType: UByte,
|
originalType: UByte,
|
||||||
@@ -167,8 +166,7 @@ class FragmentManager {
|
|||||||
val payload = ByteArray(13 + data.size)
|
val payload = ByteArray(13 + data.size)
|
||||||
|
|
||||||
// Fragment ID (8 bytes)
|
// Fragment ID (8 bytes)
|
||||||
val idBytes = fragmentID.toByteArray()
|
System.arraycopy(fragmentID, 0, payload, 0, 8)
|
||||||
System.arraycopy(idBytes, 0, payload, 0, minOf(8, idBytes.size))
|
|
||||||
|
|
||||||
// Index (2 bytes, big-endian)
|
// Index (2 bytes, big-endian)
|
||||||
payload[8] = ((index shr 8) and 0xFF).toByte()
|
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 {
|
private fun generateFragmentID(): ByteArray {
|
||||||
return "${System.currentTimeMillis()}-${kotlin.random.Random.nextInt()}"
|
val fragmentID = ByteArray(8)
|
||||||
|
kotlin.random.Random.nextBytes(fragmentID)
|
||||||
|
return fragmentID
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user