From 9794fe0786c3339a893360ac8116f43e9027b33a Mon Sep 17 00:00:00 2001 From: michel Date: Sun, 19 Jul 2026 18:28:37 +0200 Subject: [PATCH] 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. --- soulseek-rs-lib/src/utils/zlib.rs | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/soulseek-rs-lib/src/utils/zlib.rs b/soulseek-rs-lib/src/utils/zlib.rs index 4ba7ef4..a434602 100644 --- a/soulseek-rs-lib/src/utils/zlib.rs +++ b/soulseek-rs-lib/src/utils/zlib.rs @@ -88,6 +88,43 @@ pub fn deflate(input: &[u8]) -> Result> { 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 { + 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, 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 = (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];