feat: Implement iOS-compatible selective padding for BLE messages (#501)

* feat: Implement iOS-compatible selective padding for BLE messages

* regression tests for padding

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
Hamza Öztürk
2026-06-25 22:45:03 +02:00
committed by GitHub
co-authored by callebtc
parent 793b215457
commit c26460ae02
5 changed files with 224 additions and 9 deletions
@@ -0,0 +1,18 @@
package com.bitchat.android.mesh
import com.bitchat.android.protocol.MessageType
/**
* iOS-compatible BLE padding policy.
*
* Keep this aligned with iOS BLEOutboundPacketPolicy.padsBLEFrame(for:):
* only Noise frames are padded over BLE.
*/
object BLEPacketPaddingPolicy {
fun shouldPadForBLE(type: UByte): Boolean {
return when (MessageType.fromValue(type)) {
MessageType.NOISE_ENCRYPTED, MessageType.NOISE_HANDSHAKE -> true
else -> false
}
}
}
@@ -170,7 +170,9 @@ class BluetoothPacketBroadcaster(
characteristic: BluetoothGattCharacteristic?
): Boolean {
val packet = routed.packet
val data = packet.toBinaryData() ?: return false
// iOS-compatible: Use selective padding policy for BLE
val padForBLE = BLEPacketPaddingPolicy.shouldPadForBLE(packet.type)
val data = packet.toBinaryData(padding = padForBLE) ?: return false
val isFile = packet.type == MessageType.FILE_TRANSFER.value
if (isFile) {
Log.d(TAG, "📤 Broadcasting FILE_TRANSFER: ${packet.payload.size} bytes")
@@ -261,7 +263,9 @@ class BluetoothPacketBroadcaster(
characteristic: BluetoothGattCharacteristic?
) {
val packet = routed.packet
val data = packet.toBinaryData() ?: return
// iOS-compatible: Use selective padding policy for BLE
val padForBLE = BLEPacketPaddingPolicy.shouldPadForBLE(packet.type)
val data = packet.toBinaryData(padding = padForBLE) ?: return
val typeName = MessageType.fromValue(packet.type)?.name ?: packet.type.toString()
val senderPeerID = routed.peerID ?: packet.senderID.toHexString()
val incomingAddr = routed.relayAddress
@@ -79,8 +79,8 @@ data class BitchatPacket(
ttl = ttl
)
fun toBinaryData(): ByteArray? {
return BinaryProtocol.encode(this)
fun toBinaryData(padding: Boolean = true): ByteArray? {
return BinaryProtocol.encode(this, padding = padding)
}
/**
@@ -198,7 +198,7 @@ object BinaryProtocol {
}
}
fun encode(packet: BitchatPacket): ByteArray? {
fun encode(packet: BitchatPacket, padding: Boolean = true): ByteArray? {
try {
// Try to compress payload if beneficial
var payload = packet.payload
@@ -310,11 +310,13 @@ object BinaryProtocol {
buffer.rewind()
buffer.get(result)
// Apply padding to standard block sizes for traffic analysis resistance
val optimalSize = MessagePadding.optimalBlockSize(result.size)
val paddedData = MessagePadding.pad(result, optimalSize)
// Apply padding if requested (iOS-compatible: selective padding for privacy)
if (padding) {
val optimalSize = MessagePadding.optimalBlockSize(result.size)
return MessagePadding.pad(result, optimalSize)
}
return paddedData
return result
} catch (e: Exception) {
Log.e("BinaryProtocol", "Error encoding packet type ${packet.type}: ${e.message}")