From 6e4bb8daca0321de9ec7dd7a527f74383f2423f4 Mon Sep 17 00:00:00 2001 From: a1denvalu3 Date: Sat, 25 Jul 2026 08:26:28 +0200 Subject: [PATCH] Preserve ambiguous raw deflate compatibility --- .../com/bitchat/android/protocol/CompressionUtil.kt | 13 +++++++++---- .../bitchat/android/protocol/BinaryProtocolTest.kt | 12 ++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/protocol/CompressionUtil.kt b/app/src/main/java/com/bitchat/android/protocol/CompressionUtil.kt index 230b9c7a..aa038abf 100644 --- a/app/src/main/java/com/bitchat/android/protocol/CompressionUtil.kt +++ b/app/src/main/java/com/bitchat/android/protocol/CompressionUtil.kt @@ -93,12 +93,17 @@ object CompressionUtil { return synchronized(decompressionLock) { if (looksLikeZlib(compressedData)) { - // A structurally valid zlib header selects the legacy wrapped format. Do not - // retry size/completion failures as raw deflate: that only doubles attacker work. - try { + // A raw stream can coincidentally begin with a valid-looking zlib header. The + // header therefore only determines which format to try first; any non-exact zlib + // result must still fall back to raw under the same size/completion bounds. + val zlibResult = try { inflateExact(compressedData, originalSize, nowrap = false) } catch (zlibException: DataFormatException) { - // A raw stream can coincidentally begin with a valid-looking zlib header. + null + } + if (zlibResult != null) { + zlibResult + } else { try { inflateExact(compressedData, originalSize, nowrap = true) } catch (rawException: DataFormatException) { diff --git a/app/src/test/java/com/bitchat/android/protocol/BinaryProtocolTest.kt b/app/src/test/java/com/bitchat/android/protocol/BinaryProtocolTest.kt index 6ea31a7e..f6a1042d 100644 --- a/app/src/test/java/com/bitchat/android/protocol/BinaryProtocolTest.kt +++ b/app/src/test/java/com/bitchat/android/protocol/BinaryProtocolTest.kt @@ -1139,6 +1139,18 @@ class BinaryProtocolTest { assertArrayEquals(payload, decoded!!.payload) } + @Test + fun `raw deflate with zlib-looking prefix falls back after non-exact zlib parse`() { + val payload = ByteArray(29) { index -> (index + 1).toByte() } + val compressed = byteArrayOf( + 0x08, // non-final raw stored block; also zlib CMF + 0x1d, 0x00, // LEN = 29; 0x08 0x1d passes the RFC 1950 header check + 0xe2.toByte(), 0xff.toByte() // one's complement of LEN + ) + payload + byteArrayOf(0x03, 0x00) // final empty fixed-Huffman block + + assertArrayEquals(payload, CompressionUtil.decompress(compressed, payload.size)) + } + @Test fun `under-declared zlib expansion is rejected by fallback`() { val payload = ByteArray(4_096) { 0x51 }