aboutsummaryrefslogtreecommitdiffstats
path: root/src/compress.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/compress.rs')
-rw-r--r--src/compress.rs60
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");
+ }
+}