feat(shares): add a std-only zlib stored-block encoder

Soulseek peer messages (SharedFileListResponse, FileSearchResponse) are
zlib-compressed on the wire, but the library only had a decoder. Add
compress_stored(): a valid zlib stream built entirely from uncompressed
DEFLATE 'stored' blocks (0x78 0x01 header, blocks of <= 0xFFFF bytes, adler32),
which needs no real compressor and is accepted by standard decoders and by the
existing inflate path. Roundtrip-tested through the decoder at block
boundaries, plus header/adler32 vectors.
This commit is contained in:
michel
2026-07-19 18:28:56 +02:00
parent 3908dceb2f
commit 9794fe0786
+59
View File
@@ -88,6 +88,43 @@ pub fn deflate(input: &[u8]) -> Result<Vec<u8>> {
Ok(out)
}
/// Compress `data` into a valid zlib stream using only STORED blocks.
///
/// No real compressor is needed: standard zlib decoders (and [`deflate`] above)
/// accept a `0x78 0x01` header, one or more uncompressed DEFLATE "stored" blocks
/// of at most `0xFFFF` bytes, then a big-endian adler32 checksum.
#[must_use]
pub fn compress_stored(data: &[u8]) -> Vec<u8> {
let mut out = vec![0x78, 0x01];
if data.is_empty() {
// A single empty, final stored block: BFINAL=1, LEN=0, NLEN=0xFFFF.
out.extend_from_slice(&[0x01, 0x00, 0x00, 0xFF, 0xFF]);
} else {
let mut chunks = data.chunks(0xFFFF).peekable();
while let Some(chunk) = chunks.next() {
// BFINAL in bit 0, BTYPE=00 (stored) in bits 1-2.
out.push(u8::from(chunks.peek().is_none()));
let len = chunk.len() as u16;
out.extend_from_slice(&len.to_le_bytes());
out.extend_from_slice(&(!len).to_le_bytes());
out.extend_from_slice(chunk);
}
}
out.extend_from_slice(&adler32(data).to_be_bytes());
out
}
/// RFC 1950 adler32 checksum.
fn adler32(data: &[u8]) -> u32 {
let mut a: u32 = 1;
let mut b: u32 = 0;
for &byte in data {
a = (a + u32::from(byte)) % 65521;
b = (b + a) % 65521;
}
(b << 16) | a
}
fn inflate(r: &mut BitReader) -> std::result::Result<Vec<u8>, String> {
let mut bfinal = 0;
let mut out = Vec::new();
@@ -377,6 +414,28 @@ mod tests {
assert!(decode_symbol(&mut reader, &tree).is_err());
}
#[test]
fn compress_stored_roundtrips_through_the_decoder() {
// Cover the block boundaries: empty, tiny, exactly 0xFFFF, and multi-block.
for size in [0usize, 1, 11, 65_534, 65_535, 65_536, 131_072, 200_000] {
let data: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let compressed = compress_stored(&data);
assert_eq!(
deflate(&compressed).unwrap(),
data,
"roundtrip failed at size {size}"
);
}
}
#[test]
fn compress_stored_header_and_adler32_are_correct() {
assert_eq!(&compress_stored(b"")[0..2], &[120, 1]);
assert_eq!(adler32(b""), 1);
// a = 1+97+98+99 = 295 = 0x127; b = 98+294+589 = 981 = 0x24D.
assert_eq!(adler32(b"abc"), 0x024D_0127);
}
#[test]
fn test_bitreader_read_bits() {
let data = vec![0b11010010, 0b10110101];