#1618

iOS/Android disagree on max expanded (decompressed) payload size — 1.13 MiB vs 10 MiB, silent drop on iOS

Openrvg44wczyw-coderopened 2mo ago

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

  1. Android sender constructs and sends a compressed message with a declared originalSize between ~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).
  2. The packet decodes fine on the Android sender's own re-parse and on any Android recipient.
  3. An iOS recipient's BinaryProtocol.decode rejects it silently at the originalSize bounds 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.

Activity

1
ecgang2mo ago

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.decodeCore bails at guard 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.decompress allocates originalSize bytes 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 nil with 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.