Summary
The two clients disagree on the largest message the mesh will carry, which causes silent, unexplained delivery failures for otherwise-legitimate large compressed messages sent from Android to iOS.
Details
- iOS:
FileTransferLimits.maxFramedFileBytes(localPackages/BitFoundation/Sources/BitFoundation/FileTransferLimits.swift) = 1,179,760 bytes (~1.13 MiB) - Android:
AppConstants.Protocol.MAX_PAYLOAD_LENGTH(app/src/main/java/com/bitchat/android/util/AppConstants.kt:70) = 10,485,760 bytes (10 MiB)
Both platforms enforce the same 50,000:1 compression-ratio guard on decode, but Android allows a legitimately-compressed message to declare an expanded (originalSize) payload up to 10 MiB, while iOS caps the same field at ~1.13 MiB (BinaryProtocol.swift's decodeCore: guard originalSize >= 0 && originalSize <= FileTransferLimits.maxFramedFileBytes).
Repro scenario
- Android sender constructs and sends a compressed message with a declared
originalSizebetween ~1.13 MiB and 10 MiB (well within its own limit, and with a compressed on-wire size small enough to pass the shared ratio guard — typical for genuinely compressible content). - The packet decodes fine on the Android sender's own re-parse and on any Android recipient.
- An iOS recipient's
BinaryProtocol.decoderejects it silently at theoriginalSizebounds check — the message is simply dropped, with no error surfaced on either end.
Suggested fix
Reconcile the two constants (or otherwise agree on a shared, enforced expanded-payload ceiling) so cross-platform delivery of large compressed messages doesn't silently fail depending on which platform sent it. Happy to be pointed at the intended design if this is a known, accepted asymmetry rather than an oversight.
Scope note
Filing as a plain compatibility bug, not a security report — no attacker control involved, just legitimate content sent between two honest clients on different platforms.
Confirmed the iOS side against
main, and the numbers check out exactly:FileTransferLimits.maxFramedFileBytes= 1,179,760 —maxPayloadBytes(1 MiB) + 131,088 TLV/metadata + binary envelope.BinaryProtocol.decodeCorebails atguard originalSize >= 0 && originalSize <= FileTransferLimits.maxFramedFileBytes else { return nil }, with no log.One thing worth pinning down before picking a direction, because "reconcile the two constants" resolves two ways and only one is safe.
That cap is also the allocation bound.
CompressionUtil.decompressallocatesoriginalSizebytes straight from the wire-declared field, and the 50,000:1 ratio guard runs after the size check. At that ratio a declared 1.13 MiB needs only a ~24-byte compressed body to pass, so a remote peer can already make an iOS receiver allocate 1.13 MiB from a 24-byte packet. Raising iOS to 10 MiB makes that ~10 MiB from ~210 bytes — a 9x larger cheap allocation, on the more memory-constrained of the two platforms.Reconciling downward looks close to free by comparison. iOS caps its own sends at
maxPayloadBytes(1 MiB), so Android's extra headroom is unusable for anything iOS-bound regardless; and a 10 MiB expanded payload over BLE fragmentation isn't a transfer that completes in practice.Separately, the silence is an iOS-side bug independent of which constant wins. The ratio rejection logs a warning; the size rejection returns
nilwith nothing at all. Same class of drop, one diagnosable and one invisible — which is presumably why this took a code read to find rather than showing up in a log. Happy to put up a small PR adding a matching warning there, since that's useful whichever way the ceiling goes.