diff options
| author | tv <tv@krebsco.de> | 2026-11-05 03:58:12 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-05 03:58:12 +0100 |
| commit | f7ce22ed0ba4a758bdc7de0f21b6d0cb45a866b1 (patch) | |
| tree | 7c35b83e5ae21bb6253c1f82bbed347667af31b9 /src/compress.rs | |
| parent | a02ed87abf6a0dc9d3f36bafcf333272586093aa (diff) | |
deflate blob chunks
Diffstat (limited to 'src/compress.rs')
| -rw-r--r-- | src/compress.rs | 60 |
1 files changed, 60 insertions, 0 deletions
diff --git a/src/compress.rs b/src/compress.rs new file mode 100644 index 0000000..77e634d --- /dev/null +++ b/src/compress.rs @@ -0,0 +1,60 @@ +//! Deflate for what goes over the wire: large sync messages (`swarm::sync`). Automerge +//! sends changes as they are stored, uncompressed; text and JSON in them shrink to about +//! half. + +use std::io::{Read, Write}; + +use anyhow::{Result, bail}; +use flate2::Compression; +use flate2::read::DeflateDecoder; +use flate2::write::DeflateEncoder; + +/// Below this, deflating saves too little to be worth it. +pub(crate) const WORTH_FROM: usize = 1024; + +/// `bytes` deflated, if that makes them at least a tenth smaller. +pub(crate) fn deflate(bytes: &[u8]) -> Option<Vec<u8>> { + if bytes.len() < WORTH_FROM { + return None; + } + let mut encoder = DeflateEncoder::new(Vec::with_capacity(bytes.len() / 2), Compression::fast()); + encoder.write_all(bytes).ok()?; + let deflated = encoder.finish().ok()?; + (deflated.len() < bytes.len() - bytes.len() / 10).then_some(deflated) +} + +/// `bytes` inflated, if they come to at most `limit` bytes: a small message can't make +/// us hold a large one. +pub(crate) fn inflate(bytes: &[u8], limit: usize) -> Result<Vec<u8>> { + let mut inflated = Vec::with_capacity((bytes.len() * 3).min(limit)); + DeflateDecoder::new(bytes) + .take(limit as u64 + 1) + .read_to_end(&mut inflated)?; + if inflated.len() > limit { + bail!("inflates to more than {limit} bytes"); + } + Ok(inflated) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// What deflates inflates back; little, or what doesn't shrink, isn't deflated; more + /// than the limit is refused. + #[test] + fn deflates_and_inflates_within_limits() { + let text = "a key, a value; ".repeat(1000); + let deflated = deflate(text.as_bytes()).expect("text shrinks"); + assert!(deflated.len() < text.len() / 4); + assert_eq!(inflate(&deflated, text.len()).unwrap(), text.as_bytes()); + assert!( + inflate(&deflated, text.len() - 1).is_err(), + "over the limit" + ); + assert!(deflate(b"short").is_none()); + let mut rng = crate::testrand::Rng::new(1); + let noise: Vec<u8> = (0..4096).map(|_| rng.next_u64() as u8).collect(); + assert!(deflate(&noise).is_none(), "noise doesn't shrink"); + } +} |
