LibDEFLATE: Add GZip support

This allows compressing and decompressing with data using GZip headers
and footers
This commit is contained in:
Oskari Alaranta 2026-02-20 00:00:32 +02:00
parent 632787b142
commit 7c1b403e05
4 changed files with 125 additions and 0 deletions

View File

@ -580,6 +580,8 @@ namespace LibDEFLATE
BAN::ErrorOr<BAN::Vector<uint8_t>> Compressor::compress() BAN::ErrorOr<BAN::Vector<uint8_t>> Compressor::compress()
{ {
const uint32_t data_size = m_data.size();
uint32_t checksum = 0; uint32_t checksum = 0;
switch (m_type) switch (m_type)
{ {
@ -590,6 +592,21 @@ namespace LibDEFLATE
TRY(m_stream.write_bits(0x9C, 8)); // default compression TRY(m_stream.write_bits(0x9C, 8)); // default compression
checksum = calculate_adler32(m_data); checksum = calculate_adler32(m_data);
break; break;
case StreamType::GZip:
{
const time_t current_time = time(nullptr);
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; constexpr size_t max_block_size = 16 * 1024;
@ -612,6 +629,16 @@ namespace LibDEFLATE
TRY(m_stream.write_bits(checksum >> 8, 8)); TRY(m_stream.write_bits(checksum >> 8, 8));
TRY(m_stream.write_bits(checksum >> 0, 8)); TRY(m_stream.write_bits(checksum >> 0, 8));
break; 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(); return m_stream.take_buffer();

View File

@ -127,6 +127,58 @@ namespace LibDEFLATE
TRY(m_stream.take_bits(16)); 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 {}; return {};
} }
} }
@ -154,6 +206,32 @@ namespace LibDEFLATE
return BAN::Error::from_errno(EINVAL); return BAN::Error::from_errno(EINVAL);
} }
return {};
}
case StreamType::GZip:
{
m_stream.skip_to_byte_boundary();
const uint32_t crc32 =
static_cast<uint32_t>(TRY(m_stream.take_bits(16))) |
static_cast<uint32_t>(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<uint32_t>(TRY(m_stream.take_bits(16))) |
static_cast<uint32_t>(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 {}; return {};
} }
} }

View File

@ -7,6 +7,7 @@ namespace LibDEFLATE
{ {
Raw, Raw,
Zlib, Zlib,
GZip,
}; };
} }

View File

@ -19,6 +19,25 @@ namespace LibDEFLATE
return (s2 << 16) | s1; 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) inline constexpr uint16_t reverse_bits(uint16_t value, size_t count)
{ {
uint16_t reverse = 0; uint16_t reverse = 0;