1//! Compressing one console snapshot (`console::Console::snapshot_fixed`, 2//! 1.3 MB) against a reference, usually the previous keyframe of its tier 3//! (`crate::keyframes`): XOR the two, then zstd the result. 4//! 5//! XOR turns every byte the snapshot did not change into zero, so the input 6//! to zstd is mostly zero runs with scattered pockets of change, which is 7//! what zstd compresses best. A keyframe with no reference is XORed against 8//! nothing (the bytes themselves), and zstd alone then finds the state's own 9//! redundancy: long zero regions, repeated tiles. 10//! 11//! The XOR is only as good as the alignment. It pairs byte `i` with byte 12//! `i`, so a snapshot whose fields moved relative to its reference deltas 13//! badly. That is why keyframes are `snapshot_fixed`: varint snapshots came 14//! in 70 lengths across 83 keyframes, and 2 s deltas averaged 866 KB of 1.31 15//! MB (apps/replay-probe, 2026-09-22). zstd limits that damage too, where the 16//! hand-rolled run-length coder this replaced could not. 17//! 18//! The zstd is `ruzstd` (pure Rust), whose encoder implements only its 19//! fastest level. `third-party/rust/Cargo.toml` says why that crate. 20 21use std::io::{self, Read}; 22 23use ruzstd::decoding::StreamingDecoder; 24use ruzstd::encoding::{CompressionLevel, compress_to_vec}; 25 26/// `data` XORed against `reference` byte for byte, where `reference` is 27/// shorter than `data` (or absent) its missing bytes count as zero. 28fn xor(reference: Option<&[u8]>, data: &[u8]) -> Vec<u8> { 29 let reference = reference.unwrap_or(&[]); 30 data.iter().enumerate().map(|(i, &b)| b ^ reference.get(i).copied().unwrap_or(0)).collect() 31} 32 33/// `data` compressed against `reference`. [`decompress`] needs the exact 34/// same `reference` and `data.len()` back. 35pub fn compress(reference: Option<&[u8]>, data: &[u8]) -> Vec<u8> { 36 compress_to_vec(xor(reference, data).as_slice(), CompressionLevel::Fastest) 37} 38 39/// The inverse of [`compress`]. `len` is the original snapshot's length; 40/// a stream that decodes to anything else is corrupt and is refused rather 41/// than handed on as a machine. 42pub fn decompress(reference: Option<&[u8]>, compressed: &[u8], len: usize) -> io::Result<Vec<u8>> { 43 let mut source = compressed; 44 let mut decoder = StreamingDecoder::new(&mut source).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; 45 let mut xored = Vec::with_capacity(len); 46 decoder.read_to_end(&mut xored)?; 47 if xored.len() != len { 48 return Err(io::Error::new( 49 io::ErrorKind::InvalidData, 50 format!("keyframe decodes to {} bytes, expected {len}", xored.len()), 51 )); 52 } 53 Ok(xor(reference, &xored)) 54} 55 56#[cfg(test)] 57mod tests { 58 use super::*; 59 60 fn roundtrip(reference: Option<&[u8]>, data: &[u8]) { 61 let compressed = compress(reference, data); 62 let back = decompress(reference, &compressed, data.len()).unwrap(); 63 assert_eq!(back, data, "roundtrip with reference of len {:?}", reference.map(<[u8]>::len)); 64 } 65 66 #[test] 67 fn empty_data_round_trips() { 68 roundtrip(None, &[]); 69 roundtrip(Some(&[1, 2, 3]), &[]); 70 } 71 72 #[test] 73 fn identical_to_reference_compresses_to_almost_nothing() { 74 let data: Vec<u8> = (0..100_000u32).map(|i| (i * 31 % 251) as u8).collect(); 75 let compressed = compress(Some(&data), &data); 76 assert!(compressed.len() < 200, "all-zero XOR should be tiny, got {}", compressed.len()); 77 assert_eq!(decompress(Some(&data), &compressed, data.len()).unwrap(), data); 78 } 79 80 #[test] 81 fn no_reference_still_round_trips() { 82 let data: Vec<u8> = (0..2000).map(|i| (i * 37 % 251) as u8).collect(); 83 roundtrip(None, &data); 84 } 85 86 #[test] 87 fn a_reference_of_a_different_length_still_round_trips() { 88 let reference = vec![5u8; 40]; 89 let data: Vec<u8> = (0..300).map(|i| (i % 17) as u8).collect(); 90 roundtrip(Some(&reference), &data); 91 roundtrip(Some(&data), &reference); 92 } 93 94 #[test] 95 fn scattered_changes_compress_well() { 96 // The shape of a live console against its keyframe 2 s earlier: 97 // mostly unchanged, with isolated changed bytes. 98 let n = 100_000; 99 let reference: Vec<u8> = (0..n).map(|i| (i * 7 % 251) as u8).collect(); 100 let mut data = reference.clone(); 101 for i in (0..data.len()).step_by(40) { 102 data[i] ^= 1; 103 } 104 roundtrip(Some(&reference), &data); 105 let compressed = compress(Some(&reference), &data); 106 assert!(compressed.len() < n / 10, "compressed {} vs raw {n}", compressed.len()); 107 } 108 109 #[test] 110 fn a_wrong_length_is_refused() { 111 let data = vec![3u8; 500]; 112 let compressed = compress(None, &data); 113 assert!(decompress(None, &compressed, 499).is_err()); 114 } 115 116 #[test] 117 fn garbage_is_refused_not_decoded() { 118 assert!(decompress(None, &[1, 2, 3, 4, 5, 6, 7, 8], 8).is_err()); 119 } 120}