From 2255e368102729576fa932dae50a6892a819080f Mon Sep 17 00:00:00 2001 From: Bananymous Date: Fri, 20 Feb 2026 00:03:18 +0200 Subject: [PATCH] LibDEFLATE: Add GZip support This allows compressing and decompressing with data using GZip headers and footers --- userspace/libraries/LibDEFLATE/Compressor.cpp | 33 ++++++++ .../libraries/LibDEFLATE/Decompressor.cpp | 78 +++++++++++++++++++ .../include/LibDEFLATE/StreamType.h | 1 + .../LibDEFLATE/include/LibDEFLATE/Utils.h | 19 +++++ 4 files changed, 131 insertions(+) diff --git a/userspace/libraries/LibDEFLATE/Compressor.cpp b/userspace/libraries/LibDEFLATE/Compressor.cpp index 4664d041..785bcba2 100644 --- a/userspace/libraries/LibDEFLATE/Compressor.cpp +++ b/userspace/libraries/LibDEFLATE/Compressor.cpp @@ -6,6 +6,8 @@ #include #include +#include + namespace LibDEFLATE { @@ -580,6 +582,8 @@ namespace LibDEFLATE BAN::ErrorOr> Compressor::compress() { + const uint32_t data_size = m_data.size(); + uint32_t checksum = 0; switch (m_type) { @@ -590,6 +594,25 @@ namespace LibDEFLATE TRY(m_stream.write_bits(0x9C, 8)); // default compression checksum = calculate_adler32(m_data); break; + case StreamType::GZip: + { +#if __is_kernel + const time_t current_time = 0; +#else + const time_t current_time = time(nullptr); +#endif + TRY(m_stream.write_bits(0x1F, 8)); // ID1 + TRY(m_stream.write_bits(0x8B, 8)); // ID2 + TRY(m_stream.write_bits(8, 8)); // CM (deflate) + TRY(m_stream.write_bits(0, 8)); // FLG + TRY(m_stream.write_bits(current_time >> 0, 8)); // MTIME + TRY(m_stream.write_bits(current_time >> 8, 8)); + TRY(m_stream.write_bits(current_time >> 16, 8)); + TRY(m_stream.write_bits(current_time >> 24, 8)); + TRY(m_stream.write_bits(0, 8)); // XFL + TRY(m_stream.write_bits(3, 8)); // OS (Unix) + checksum = calculate_crc32(m_data); + } } constexpr size_t max_block_size = 16 * 1024; @@ -612,6 +635,16 @@ namespace LibDEFLATE TRY(m_stream.write_bits(checksum >> 8, 8)); TRY(m_stream.write_bits(checksum >> 0, 8)); break; + case StreamType::GZip: + TRY(m_stream.write_bits(checksum >> 0, 8)); + TRY(m_stream.write_bits(checksum >> 8, 8)); + TRY(m_stream.write_bits(checksum >> 16, 8)); + TRY(m_stream.write_bits(checksum >> 24, 8)); + TRY(m_stream.write_bits(data_size >> 0, 8)); + TRY(m_stream.write_bits(data_size >> 8, 8)); + TRY(m_stream.write_bits(data_size >> 16, 8)); + TRY(m_stream.write_bits(data_size >> 24, 8)); + break; } return m_stream.take_buffer(); diff --git a/userspace/libraries/LibDEFLATE/Decompressor.cpp b/userspace/libraries/LibDEFLATE/Decompressor.cpp index c5d757bc..e6185070 100644 --- a/userspace/libraries/LibDEFLATE/Decompressor.cpp +++ b/userspace/libraries/LibDEFLATE/Decompressor.cpp @@ -127,6 +127,58 @@ namespace LibDEFLATE TRY(m_stream.take_bits(16)); } + return {}; + } + case StreamType::GZip: + { + const uint8_t id1 = TRY(m_stream.take_bits(8)); + const uint8_t id2 = TRY(m_stream.take_bits(8)); + if (id1 != 0x1F || id2 != 0x8B) + { + dwarnln("gzip header invalid identification"); + return BAN::Error::from_errno(EINVAL); + + } + + const uint8_t cm = TRY(m_stream.take_bits(8)); + if (cm != 8) + { + dwarnln("gzip does not use DEFLATE"); + return BAN::Error::from_errno(EINVAL); + } + + const uint8_t flg = TRY(m_stream.take_bits(8)); + + TRY(m_stream.take_bits(16)); // mtime + TRY(m_stream.take_bits(16)); + + TRY(m_stream.take_bits(8)); // xfl + + TRY(m_stream.take_bits(8)); // os + + // extra fields + if (flg & (1 << 2)) + { + const uint16_t xlen = TRY(m_stream.take_bits(16)); + for (size_t i = 0; i < xlen; i++) + TRY(m_stream.take_bits(8)); + } + + // file name + if (flg & (1 << 3)) + while (TRY(m_stream.take_bits(8)) != '\0') + continue; + + // file comment + if (flg & (1 << 4)) + while (TRY(m_stream.take_bits(8)) != '\0') + continue; + + // crc16 + // TODO: validate + if (flg & (1 << 1)) + TRY(m_stream.take_bits(16)); + return {}; } } @@ -154,6 +206,32 @@ namespace LibDEFLATE return BAN::Error::from_errno(EINVAL); } + return {}; + } + case StreamType::GZip: + { + m_stream.skip_to_byte_boundary(); + + const uint32_t crc32 = + static_cast(TRY(m_stream.take_bits(16))) | + static_cast(TRY(m_stream.take_bits(16))) << 16; + + if (crc32 != calculate_crc32(m_output.span())) + { + dwarnln("gzip final crc32 checksum failed"); + return BAN::Error::from_errno(EINVAL); + } + + const uint32_t isize = + static_cast(TRY(m_stream.take_bits(16))) | + static_cast(TRY(m_stream.take_bits(16))) << 16; + + if (isize != m_output.size() % UINT32_MAX) + { + dwarnln("gzip final isize does not match {} vs {}", isize, m_output.size()); + return BAN::Error::from_errno(EINVAL); + } + return {}; } } diff --git a/userspace/libraries/LibDEFLATE/include/LibDEFLATE/StreamType.h b/userspace/libraries/LibDEFLATE/include/LibDEFLATE/StreamType.h index 8ec35f2e..894bd589 100644 --- a/userspace/libraries/LibDEFLATE/include/LibDEFLATE/StreamType.h +++ b/userspace/libraries/LibDEFLATE/include/LibDEFLATE/StreamType.h @@ -7,6 +7,7 @@ namespace LibDEFLATE { Raw, Zlib, + GZip, }; } diff --git a/userspace/libraries/LibDEFLATE/include/LibDEFLATE/Utils.h b/userspace/libraries/LibDEFLATE/include/LibDEFLATE/Utils.h index 699550b1..ed4bef49 100644 --- a/userspace/libraries/LibDEFLATE/include/LibDEFLATE/Utils.h +++ b/userspace/libraries/LibDEFLATE/include/LibDEFLATE/Utils.h @@ -19,6 +19,25 @@ namespace LibDEFLATE return (s2 << 16) | s1; } + inline uint32_t calculate_crc32(BAN::ConstByteSpan data) + { + uint32_t crc32 = 0xFFFFFFFF; + uint32_t polynomial = 0xEDB88320; + + for (size_t i = 0; i < data.size(); i++) { + crc32 ^= data[i]; + + for (size_t j = 0; j < 8; j++) { + if (crc32 & 1) + crc32 = (crc32 >> 1) ^ polynomial; + else + crc32 >>= 1; + } + } + + return ~crc32; + } + inline constexpr uint16_t reverse_bits(uint16_t value, size_t count) { uint16_t reverse = 0;