1use std::io::Write;
37
38use crate::error::{CompressError, DecompressError};
39use crate::{CHUNK_SIZE, STREAM_BUF_SIZE};
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum CompressionFormat {
44 None,
46 Zrip,
48}
49
50impl CompressionFormat {
51 pub fn as_str(self) -> &'static str {
54 match self {
55 CompressionFormat::None => "identity",
56 CompressionFormat::Zrip => "zrip",
57 }
58 }
59
60 pub fn parse_header(s: &str) -> Option<Self> {
62 match s.trim().to_ascii_lowercase().as_str() {
63 "" | "identity" | "none" => Some(CompressionFormat::None),
64 "zrip" | "zstd" => Some(CompressionFormat::Zrip),
65 _ => None,
66 }
67 }
68}
69
70pub const MAX_FRAME_OUTPUT: usize = CHUNK_SIZE as usize;
75
76pub const MAX_PENDING_FRAME: usize = CHUNK_SIZE as usize + STREAM_BUF_SIZE;
81
82pub const ZRIP_DEFAULT_LEVEL: i32 = 1;
84
85pub trait Compressor: Send {
88 fn format(&self) -> CompressionFormat;
90
91 fn compress(&mut self, input: &[u8], out: &mut Vec<u8>) -> Result<(), CompressError>;
97
98 fn finish(&mut self, out: &mut Vec<u8>) -> Result<(), CompressError>;
102}
103
104pub trait Decompressor: Send {
107 fn format(&self) -> CompressionFormat;
109
110 fn decompress(&mut self, input: &[u8], out: &mut Vec<u8>) -> Result<(), DecompressError>;
115
116 fn finish(&mut self, out: &mut Vec<u8>) -> Result<(), DecompressError>;
121}
122
123pub fn compressor(format: CompressionFormat) -> Result<Box<dyn Compressor>, CompressError> {
125 match format {
126 CompressionFormat::None => Ok(Box::new(PassthroughCompressor)),
127 CompressionFormat::Zrip => Ok(Box::new(ZripCompressor::new(ZRIP_DEFAULT_LEVEL)?)),
128 }
129}
130
131pub fn decompressor(format: CompressionFormat) -> Box<dyn Decompressor> {
133 match format {
134 CompressionFormat::None => Box::new(PassthroughDecompressor),
135 CompressionFormat::Zrip => Box::new(ZripDecompressor::new()),
136 }
137}
138
139struct PassthroughCompressor;
144
145impl Compressor for PassthroughCompressor {
146 fn format(&self) -> CompressionFormat {
147 CompressionFormat::None
148 }
149
150 fn compress(&mut self, input: &[u8], out: &mut Vec<u8>) -> Result<(), CompressError> {
151 out.extend_from_slice(input);
152 Ok(())
153 }
154
155 fn finish(&mut self, _out: &mut Vec<u8>) -> Result<(), CompressError> {
156 Ok(())
157 }
158}
159
160struct PassthroughDecompressor;
161
162impl Decompressor for PassthroughDecompressor {
163 fn format(&self) -> CompressionFormat {
164 CompressionFormat::None
165 }
166
167 fn decompress(&mut self, input: &[u8], out: &mut Vec<u8>) -> Result<(), DecompressError> {
168 out.extend_from_slice(input);
169 Ok(())
170 }
171
172 fn finish(&mut self, _out: &mut Vec<u8>) -> Result<(), DecompressError> {
173 Ok(())
174 }
175}
176
177pub struct ZripCompressor {
183 encoder: Option<zrip::FrameEncoder<Vec<u8>>>,
184 dirty: bool,
186}
187
188impl ZripCompressor {
189 pub fn new(level: i32) -> Result<Self, CompressError> {
191 Ok(ZripCompressor {
192 encoder: Some(zrip::FrameEncoder::new(Vec::new(), level)?),
193 dirty: false,
194 })
195 }
196}
197
198impl Compressor for ZripCompressor {
199 fn format(&self) -> CompressionFormat {
200 CompressionFormat::Zrip
201 }
202
203 fn compress(&mut self, input: &[u8], out: &mut Vec<u8>) -> Result<(), CompressError> {
204 if input.is_empty() {
205 return Ok(());
206 }
207 let encoder = self
208 .encoder
209 .as_mut()
210 .ok_or_else(|| std::io::Error::other("compressor already finished"))?;
211 if self.dirty {
213 let finished = encoder.reset(Vec::new())?;
214 out.extend_from_slice(&finished);
215 }
216 encoder.write_all(input)?;
217 self.dirty = true;
218 Ok(())
219 }
220
221 fn finish(&mut self, out: &mut Vec<u8>) -> Result<(), CompressError> {
222 let encoder = self
223 .encoder
224 .take()
225 .ok_or_else(|| std::io::Error::other("compressor already finished"))?;
226 let tail = encoder.finish()?;
227 out.extend_from_slice(&tail);
228 self.dirty = false;
229 Ok(())
230 }
231}
232
233pub struct ZripDecompressor {
235 pending: Vec<u8>,
237 finished: bool,
238}
239
240impl ZripDecompressor {
241 pub fn new() -> Self {
243 ZripDecompressor {
244 pending: Vec::with_capacity(STREAM_BUF_SIZE),
245 finished: false,
246 }
247 }
248
249 fn drain_frames(&mut self, out: &mut Vec<u8>) -> Result<(), DecompressError> {
251 loop {
252 let boundary = frame_boundary(&self.pending);
253 match boundary {
254 FrameBoundary::Complete { len, content_size } => {
255 if content_size.is_some_and(|cs| cs > MAX_FRAME_OUTPUT as u64) {
256 return Err(DecompressError::TooLarge {
257 limit: MAX_FRAME_OUTPUT,
258 });
259 }
260 let decoded = {
261 let frame = &self.pending[..len];
262 zrip::decompress_with_limit(frame, MAX_FRAME_OUTPUT).map_err(|e| {
263 DecompressError::Io(std::io::Error::new(
264 std::io::ErrorKind::InvalidData,
265 e,
266 ))
267 })?
268 };
269 out.extend_from_slice(&decoded);
270 self.pending.drain(..len);
271 }
272 FrameBoundary::Incomplete => return Ok(()),
273 FrameBoundary::Invalid => {
274 return Err(DecompressError::Io(std::io::Error::new(
275 std::io::ErrorKind::InvalidData,
276 "invalid zstd frame data",
277 )))
278 }
279 }
280 }
281 }
282}
283
284impl Default for ZripDecompressor {
285 fn default() -> Self {
286 ZripDecompressor::new()
287 }
288}
289
290impl Decompressor for ZripDecompressor {
291 fn format(&self) -> CompressionFormat {
292 CompressionFormat::Zrip
293 }
294
295 fn decompress(&mut self, input: &[u8], out: &mut Vec<u8>) -> Result<(), DecompressError> {
296 if self.finished {
297 return Err(DecompressError::Io(std::io::Error::other(
298 "decompressor already finished",
299 )));
300 }
301 if !input.is_empty() {
302 self.pending.extend_from_slice(input);
303 }
304 self.drain_frames(out)?;
305 if self.pending.len() > MAX_PENDING_FRAME {
306 return Err(DecompressError::TooLarge {
307 limit: MAX_PENDING_FRAME,
308 });
309 }
310 Ok(())
311 }
312
313 fn finish(&mut self, out: &mut Vec<u8>) -> Result<(), DecompressError> {
314 if self.finished {
315 return Ok(());
316 }
317 self.finished = true;
318 self.drain_frames(out)?;
319 if !self.pending.is_empty() {
320 return Err(DecompressError::Truncated(std::io::Error::new(
321 std::io::ErrorKind::UnexpectedEof,
322 format!("{} trailing compressed bytes", self.pending.len()),
323 )));
324 }
325 Ok(())
326 }
327}
328
329const ZSTD_MAGIC: u32 = 0xFD2F_B528;
334const SKIPPABLE_MASK: u32 = 0xFFFF_FFF0;
335const SKIPPABLE_MAGIC: u32 = 0x184D_2A50;
336
337enum FrameBoundary {
338 Complete { len: usize, content_size: Option<u64> },
341 Incomplete,
343 Invalid,
345}
346
347fn frame_boundary(buf: &[u8]) -> FrameBoundary {
349 if buf.len() < 4 {
350 return FrameBoundary::Incomplete;
351 }
352 let magic = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
353 if magic == ZSTD_MAGIC {
354 zstd_frame_boundary(buf)
355 } else if (magic & SKIPPABLE_MASK) == SKIPPABLE_MAGIC {
356 skippable_frame_boundary(buf)
357 } else {
358 FrameBoundary::Invalid
359 }
360}
361
362fn skippable_frame_boundary(buf: &[u8]) -> FrameBoundary {
364 if buf.len() < 8 {
365 return FrameBoundary::Incomplete;
366 }
367 let skip = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]) as usize;
368 let total = 8usize.saturating_add(skip);
369 if buf.len() >= total {
370 FrameBoundary::Complete {
371 len: total,
372 content_size: Some(0),
373 }
374 } else {
375 FrameBoundary::Incomplete
376 }
377}
378
379fn zstd_frame_boundary(buf: &[u8]) -> FrameBoundary {
384 if buf.len() < 5 {
385 return FrameBoundary::Incomplete;
386 }
387 let descriptor = buf[4];
388 if descriptor & 0x18 != 0 {
390 return FrameBoundary::Invalid;
391 }
392 let single_segment = descriptor & 0x20 != 0;
393 let checksum = descriptor & 0x04 != 0;
394 let dict_id_flag = descriptor & 0x03;
395 let fcs_flag = (descriptor >> 6) & 0x03;
396
397 let mut hdr_len = 5usize;
398 if !single_segment {
399 hdr_len += 1; }
401 hdr_len += match dict_id_flag {
402 0 => 0,
403 1 => 1,
404 2 => 2,
405 3 => 4,
406 _ => unreachable!(),
407 };
408 let fcs_size: usize = match fcs_flag {
409 0 if single_segment => 1,
410 0 => 0,
411 1 => 2,
412 2 => 4,
413 3 => 8,
414 _ => unreachable!(),
415 };
416 hdr_len += fcs_size;
417
418 if buf.len() < hdr_len {
419 return FrameBoundary::Incomplete;
420 }
421 let content_size = if fcs_size > 0 {
422 let mut v = 0u64;
423 for (i, &b) in buf[5..5 + fcs_size].iter().enumerate() {
424 v |= (b as u64) << (8 * i);
425 }
426 Some(v)
427 } else {
428 None
429 };
430
431 let mut off = hdr_len;
433 loop {
434 if buf.len() < off + 3 {
435 return FrameBoundary::Incomplete;
436 }
437 let block_header = u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], 0]);
440 let last = block_header & 0x01 != 0;
441 let block_type = (block_header >> 1) & 0x03;
442 let block_size = (block_header >> 3) as usize;
443 if block_type == 3 {
444 return FrameBoundary::Invalid;
446 }
447 off += 3 + block_size;
448 if off > MAX_PENDING_FRAME {
449 return FrameBoundary::Invalid;
450 }
451 if last {
452 break;
453 }
454 }
455 if checksum {
456 off += 4;
457 }
458 if buf.len() < off {
459 return FrameBoundary::Incomplete;
460 }
461 FrameBoundary::Complete {
462 len: off,
463 content_size,
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 fn roundtrip_chunks(data: &[u8], feed: &[usize]) {
472 let mut c = ZripCompressor::new(ZRIP_DEFAULT_LEVEL).unwrap();
473 let mut compressed = Vec::new();
474 let mut off = 0;
475 for &n in feed {
476 let end = (off + n).min(data.len());
477 if end > off {
478 c.compress(&data[off..end], &mut compressed).unwrap();
479 }
480 off = end;
481 }
482 c.finish(&mut compressed).unwrap();
483 assert!(!compressed.is_empty());
484
485 let mut d = ZripDecompressor::new();
487 let mut plain = Vec::new();
488 let mut step = 1;
489 let mut i = 0;
490 while i < compressed.len() {
491 let end = (i + step).min(compressed.len());
492 d.decompress(&compressed[i..end], &mut plain).unwrap();
493 i = end;
494 step = step % 5 + 1; }
496 d.finish(&mut plain).unwrap();
497 assert_eq!(plain, data, "roundtrip mismatch with feed {feed:?}");
498 }
499
500 #[test]
501 fn roundtrip_single_chunk() {
502 let data: Vec<u8> = (0..100_000u32).map(|i| (i % 251) as u8).collect();
503 roundtrip_chunks(&data, &[data.len()]);
504 }
505
506 #[test]
507 fn roundtrip_multi_chunk_64k_windows() {
508 let data: Vec<u8> = (0..300_000u32).map(|i| (i / 7) as u8).collect();
510 let feed: Vec<usize> = std::iter::repeat(STREAM_BUF_SIZE).take(5).collect();
511 roundtrip_chunks(&data, &feed);
512 }
513
514 #[test]
515 fn roundtrip_highly_compressible() {
516 let data = b"libfw streaming compression test. ".repeat(10_000);
517 let mut feed = Vec::new();
519 let mut consumed = 0;
520 for (_, &size) in [1024usize, 2048, 4096, 8192, 16384].iter().cycle().enumerate() {
521 feed.push(size);
522 consumed += size;
523 if consumed >= data.len() {
524 break;
525 }
526 }
527 roundtrip_chunks(&data, &feed);
528 }
529
530 #[test]
531 fn roundtrip_empty_stream() {
532 let mut c = ZripCompressor::new(ZRIP_DEFAULT_LEVEL).unwrap();
533 let mut compressed = Vec::new();
534 c.finish(&mut compressed).unwrap();
535
536 let mut d = ZripDecompressor::new();
537 let mut plain = Vec::new();
538 d.decompress(&compressed, &mut plain).unwrap();
539 d.finish(&mut plain).unwrap();
540 assert!(plain.is_empty());
541 }
542
543 #[test]
544 fn empty_input_chunks_are_noops() {
545 let mut c = ZripCompressor::new(ZRIP_DEFAULT_LEVEL).unwrap();
546 let mut out = Vec::new();
547 c.compress(&[], &mut out).unwrap();
548 c.compress(b"hello", &mut out).unwrap();
549 c.finish(&mut out).unwrap();
550
551 let mut d = ZripDecompressor::new();
552 let mut plain = Vec::new();
553 d.decompress(&out, &mut plain).unwrap();
554 d.finish(&mut plain).unwrap();
555 assert_eq!(plain, b"hello");
556 }
557
558 #[test]
559 fn format_header_roundtrip() {
560 assert_eq!(CompressionFormat::parse_header("zrip"), Some(CompressionFormat::Zrip));
561 assert_eq!(CompressionFormat::parse_header("ZSTD"), Some(CompressionFormat::Zrip));
562 assert_eq!(CompressionFormat::parse_header("identity"), Some(CompressionFormat::None));
563 assert_eq!(CompressionFormat::parse_header(""), Some(CompressionFormat::None));
564 assert_eq!(CompressionFormat::parse_header("br"), None);
565 assert_eq!(CompressionFormat::Zrip.as_str(), "zrip");
566 assert_eq!(CompressionFormat::None.as_str(), "identity");
567 }
568
569 #[test]
570 fn passthrough_roundtrip() {
571 let mut c = compressor(CompressionFormat::None).unwrap();
572 let mut d = decompressor(CompressionFormat::None);
573 let mut compressed = Vec::new();
574 let mut plain = Vec::new();
575 c.compress(b"abc", &mut compressed).unwrap();
576 c.finish(&mut compressed).unwrap();
577 d.decompress(&compressed, &mut plain).unwrap();
578 d.finish(&mut plain).unwrap();
579 assert_eq!(plain, b"abc");
580 }
581
582 #[test]
583 fn truncated_stream_is_detected() {
584 let mut c = ZripCompressor::new(ZRIP_DEFAULT_LEVEL).unwrap();
585 let mut compressed = Vec::new();
586 c.compress(&vec![7u8; 5000], &mut compressed).unwrap();
587 c.finish(&mut compressed).unwrap();
588 compressed.truncate(compressed.len() - 1); let mut d = ZripDecompressor::new();
591 let mut plain = Vec::new();
592 d.decompress(&compressed, &mut plain).unwrap();
593 assert!(matches!(d.finish(&mut plain), Err(DecompressError::Truncated(_))));
594 }
595
596 #[test]
597 fn corrupt_stream_is_detected() {
598 let mut d = ZripDecompressor::new();
599 let mut plain = Vec::new();
600 let err = d.decompress(b"this is not a zstd frame at all", &mut plain);
601 assert!(err.is_err());
602 }
603
604 #[test]
605 fn zstd_compat_interop() {
606 let data: Vec<u8> = (0..50_000u32).map(|i| (i % 31) as u8).collect();
609 let mut c = ZripCompressor::new(ZRIP_DEFAULT_LEVEL).unwrap();
610 let mut compressed = Vec::new();
611 c.compress(&data, &mut compressed).unwrap();
612 c.finish(&mut compressed).unwrap();
613 let decoded = zstd::stream::decode_all(&compressed[..]).unwrap();
614 assert_eq!(decoded, data);
615
616 let zstd_enc = zstd::stream::encode_all(&data[..], 1).unwrap();
617 let mut d = ZripDecompressor::new();
618 let mut plain = Vec::new();
619 d.decompress(&zstd_enc, &mut plain).unwrap();
620 d.finish(&mut plain).unwrap();
621 assert_eq!(plain, data);
622 }
623}