Finish rollout: user-visible size-cap failure, OOM-safe decode catch

- MediaSendingManager: surface a chat system message when a picked file
  exceeds the ~10 MiB send cap instead of silently dropping the send
  (voice/image/file paths), completing the 'user-visible failure'
  requirement of the rollout gate
- BinaryProtocol: catch Exception instead of Throwable in decodeCore so
  OutOfMemoryError is never swallowed and masked as a parse failure
- docs/file_transfer.md: mark the compressed-expansion rollout gate
  resolved; support for legacy >10 MiB compressed transfers is
  explicitly ended
This commit is contained in:
callebtc 2026-07-27 11:35:51 +02:00
parent b251812b9e
commit c3bc911bcd
3 changed files with 26 additions and 12 deletions

View File

@ -522,7 +522,7 @@ object BinaryProtocol {
route = route
)
} catch (e: Throwable) {
} catch (e: Exception) {
Log.e("BinaryProtocol", "Error decoding packet: ${e.message}")
return null
}

View File

@ -81,6 +81,22 @@ class MediaSendingManager(
private var automaticRetryRequestedFor: String? = null
private var pendingAutomaticTimeoutRequestId: String? = null
/**
* Enforce the send-size cap with a user-visible failure.
* Returns true if the file is oversized and the send was aborted.
*/
private fun rejectIfOversized(file: java.io.File): Boolean {
val size = file.length()
if (size <= MAX_FILE_SIZE) return false
Log.e(TAG, "❌ File too large: $size bytes (max: $MAX_FILE_SIZE)")
val sizeMb = size / (1024 * 1024)
val maxMb = MAX_FILE_SIZE / (1024 * 1024)
messageManager.addSystemMessage(
"cannot send ${file.name}: file is too large (${sizeMb} MB, max $maxMb MB)"
)
return true
}
/**
* Send a voice note (audio file)
*/
@ -103,8 +119,7 @@ class MediaSendingManager(
return@withContext null
}
if (file.length() > MAX_FILE_SIZE) {
Log.e(TAG, "File too large: ${file.length()} bytes (max: $MAX_FILE_SIZE)")
if (rejectIfOversized(file)) {
return@withContext null
}
@ -148,8 +163,7 @@ class MediaSendingManager(
return@withContext null
}
if (file.length() > MAX_FILE_SIZE) {
Log.e(TAG, "File too large: ${file.length()} bytes (max: $MAX_FILE_SIZE)")
if (rejectIfOversized(file)) {
return@withContext null
}
@ -193,8 +207,7 @@ class MediaSendingManager(
return@withContext null
}
if (file.length() > MAX_FILE_SIZE) {
Log.e(TAG, "File too large: ${file.length()} bytes (max: $MAX_FILE_SIZE)")
if (rejectIfOversized(file)) {
return@withContext null
}

View File

@ -107,7 +107,7 @@ source-route metadata. It does not imply multi-gigabyte mesh transfer support.
transport threshold; the data portion is at most 469 bytes and becomes
smaller when recipient or source-route overhead is present.
#### Compressed expansion rollout gate (draft/HOLD)
#### Compressed expansion rollout gate (resolved)
Android's bounded decoder applies the same 10 MiB expanded-payload ceiling to every outer
message type. It also requires a non-empty compressed body, an exact declared output size, and a
@ -116,10 +116,11 @@ attacker-controlled before packet signature verification, and the current receiv
inflate before it can perform that verification.
New Android senders cap files just below 10 MiB (reserving envelope overhead) and refuse to encode
any payload above the receiver ceiling.
Legacy Android senders can still produce a highly compressible public file between 10 MiB and their
50 MiB UI limit that the bounded decoder rejects. Grandfathering 50 MiB is not safe after only a type
check: inflation allocates the declared payload and
any payload above the receiver ceiling; exceeding the cap surfaces a user-visible error in chat.
Support for legacy >10 MiB compressed transfers is explicitly ended: legacy Android senders can
still produce a highly compressible public file between 10 MiB and their 50 MiB UI limit that the
bounded decoder rejects. Grandfathering 50 MiB is not safe after only a type check: inflation
allocates the declared payload and
`BitchatFilePacket.decode` currently copies the content again, creating a greater than 100 MiB peak
for a maximum-size transfer.