diff --git a/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt b/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt index 5b1d7287..47ee8d75 100644 --- a/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt +++ b/app/src/main/java/com/bitchat/android/protocol/BinaryProtocol.kt @@ -522,7 +522,7 @@ object BinaryProtocol { route = route ) - } catch (e: Throwable) { + } catch (e: Exception) { Log.e("BinaryProtocol", "Error decoding packet: ${e.message}") return null } diff --git a/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt b/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt index 0b8f2526..41431cd1 100644 --- a/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt +++ b/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt @@ -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 } diff --git a/docs/file_transfer.md b/docs/file_transfer.md index 0d0274d4..8422b77a 100644 --- a/docs/file_transfer.md +++ b/docs/file_transfer.md @@ -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.