1use crate::error::{Error, Result};
5use std::io::{Read, Write};
6use zstd::stream::raw::{CParameter, DParameter};
7
8pub const STORE: u8 = 0;
9pub const ZSTD: u8 = 1;
10pub const ZSTD_DICT: u8 = 2;
11pub const XZ: u8 = 3;
12pub const PPMD: u8 = 4;
13pub const ZSTD_DELTA: u8 = 5;
15
16pub fn name(codec: u8) -> &'static str {
17 match codec {
18 STORE => "store",
19 ZSTD => "zstd",
20 ZSTD_DICT => "zstd+dict",
21 XZ => "xz",
22 PPMD => "ppmd",
23 ZSTD_DELTA => "zstd+delta",
24 _ => "unknown",
25 }
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum CodecChoice {
31 Zstd,
33 Xz,
35 Ppmd,
37 Best,
39}
40
41impl CodecChoice {
42 pub fn by_name(s: &str) -> Option<Self> {
43 match s.to_ascii_lowercase().as_str() {
44 "zstd" => Some(Self::Zstd),
45 "xz" | "lzma" | "lzma2" => Some(Self::Xz),
46 "ppmd" => Some(Self::Ppmd),
47 "best" | "auto" => Some(Self::Best),
48 _ => None,
49 }
50 }
51}
52
53#[derive(Clone, Debug)]
55pub struct Encoded {
56 pub codec: u8,
57 pub bytes: Vec<u8>,
58 pub params: Vec<u32>,
59 pub filter: u8,
61}
62
63pub const FILTER_NONE: u8 = 0;
64pub const FILTER_X86: u8 = 1;
65
66pub fn x86_convert(data: &mut [u8], ip: u32, state: &mut u32, encoding: bool) -> usize {
71 #[inline]
72 fn test_ms_byte(b: u8) -> bool {
73 (b.wrapping_add(1) & 0xFE) == 0
74 }
75 if data.len() < 5 {
76 return 0;
77 }
78 let size = data.len() - 4;
79 let ip = ip.wrapping_add(5);
80 let mut pos = 0usize;
81 let mut mask = *state & 7;
82 loop {
83 let mut p = pos;
84 while p < size && (data[p] & 0xFE) != 0xE8 {
85 p += 1;
86 }
87 let d = p - pos;
88 pos = p;
89 if p >= size {
90 *state = if d > 2 { 0 } else { mask >> d };
91 return pos;
92 }
93 if d > 2 {
94 mask = 0;
95 } else {
96 mask >>= d;
97 if mask != 0 && (mask > 4 || mask == 3 || test_ms_byte(data[p + (mask as usize >> 1) + 1])) {
98 mask = (mask >> 1) | 4;
99 pos += 1;
100 continue;
101 }
102 }
103 if test_ms_byte(data[p + 4]) {
104 let mut v = u32::from_le_bytes([data[p + 1], data[p + 2], data[p + 3], data[p + 4]]);
105 let cur = ip.wrapping_add(pos as u32);
106 pos += 5;
107 if encoding {
108 v = v.wrapping_add(cur);
109 } else {
110 v = v.wrapping_sub(cur);
111 }
112 if mask != 0 {
113 let sh = (mask & 6) << 2;
114 if test_ms_byte((v >> sh) as u8) {
115 v ^= (0x100u32 << sh).wrapping_sub(1);
116 if encoding {
117 v = v.wrapping_add(cur);
118 } else {
119 v = v.wrapping_sub(cur);
120 }
121 }
122 mask = 0;
123 }
124 data[p + 1] = v as u8;
125 data[p + 2] = (v >> 8) as u8;
126 data[p + 3] = (v >> 16) as u8;
127 data[p + 4] = 0u8.wrapping_sub(((v >> 24) & 1) as u8);
128 } else {
129 mask = (mask >> 1) | 4;
130 pos += 1;
131 }
132 }
133}
134
135pub fn looks_like_x86(data: &[u8]) -> bool {
139 if data.len() < 4096 || looks_like_text(data) {
140 return false;
141 }
142 let sample = &data[..data.len().min(256 << 10)];
143 let hits = sample.windows(5).filter(|w| (w[0] & 0xFE) == 0xE8 && (w[4] == 0 || w[4] == 0xFF)).count();
144 hits * 1000 / sample.len() >= 1
145}
146
147pub fn arm64_convert(data: &mut [u8], now_pos: u32, encoding: bool) -> usize {
150 let mut i = 0usize;
151 while i + 4 <= data.len() {
152 let pc = now_pos.wrapping_add(i as u32);
153 let mut instr = u32::from_le_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]);
154 if (instr >> 26) == 0x25 {
155 let src = instr;
156 instr = 0x9400_0000;
157 let mut p = pc >> 2;
158 if !encoding {
159 p = 0u32.wrapping_sub(p);
160 }
161 instr |= src.wrapping_add(p) & 0x03FF_FFFF;
162 data[i..i + 4].copy_from_slice(&instr.to_le_bytes());
163 } else if (instr & 0x9F00_0000) == 0x9000_0000 {
164 let src = ((instr >> 29) & 3) | ((instr >> 3) & 0x001F_FFFC);
165 if (src.wrapping_add(0x0002_0000) & 0x001C_0000) == 0 {
166 instr &= 0x9000_001F;
167 let mut p = pc >> 12;
168 if !encoding {
169 p = 0u32.wrapping_sub(p);
170 }
171 let dest = src.wrapping_add(p);
172 instr |= (dest & 3) << 29;
173 instr |= (dest & 0x0003_FFFC) << 3;
174 instr |= 0u32.wrapping_sub(dest & 0x0002_0000) & 0x00E0_0000;
175 data[i..i + 4].copy_from_slice(&instr.to_le_bytes());
176 }
177 }
178 i += 4;
179 }
180 i
181}
182
183pub fn arm64_alignment(data: &[u8]) -> Option<u8> {
188 if data.len() < 4096 || looks_like_text(data) {
189 return None;
190 }
191 let sample = &data[..data.len().min(256 << 10)];
192 let words = sample.len() / 4;
193 let mut best = (0usize, 0u8);
194 for off in 0..4usize {
195 let hits = sample[off..]
196 .chunks_exact(4)
197 .filter(|w| {
198 let v = u32::from_le_bytes([w[0], w[1], w[2], w[3]]);
199 (v >> 26) == 0x25 && matches!((v >> 20) & 0x3F, 0 | 0x3F)
200 })
201 .count();
202 if hits > best.0 {
203 best = (hits, off as u8);
204 }
205 }
206 (best.0 * 1000 / words.max(1) >= 5).then_some(best.1)
207}
208
209pub fn filter_id(f: u8) -> u8 {
212 f & 0x0F
213}
214
215pub fn filter_param(f: u8) -> u8 {
216 f >> 4
217}
218
219pub const FILTER_ARM64: u8 = 2;
220
221fn apply_filter(f: u8, data: &[u8], encoding: bool) -> Result<Vec<u8>> {
222 let mut v = data.to_vec();
223 match filter_id(f) {
224 FILTER_NONE => {}
225 FILTER_X86 => {
226 let mut state = 0u32;
227 x86_convert(&mut v, 0, &mut state, encoding);
228 }
229 FILTER_ARM64 => {
230 let off = (filter_param(f) as usize).min(3).min(v.len());
231 arm64_convert(&mut v[off..], 0, encoding);
232 }
233 other => return Err(Error::Corrupt(format!("unknown filter id {other}"))),
234 }
235 Ok(v)
236}
237
238pub fn ppmd_params(len: usize) -> (u32, u32) {
241 let mem = ((len as u64) * 16).clamp(16 << 20, 256 << 20) as u32;
242 (16, mem)
243}
244
245fn ppmd_compress(data: &[u8], order: u32, mem: u32) -> Result<Vec<u8>> {
246 let mut enc = ppmd_rust::Ppmd7Encoder::new(Vec::with_capacity(data.len() / 3 + 64), order, mem).map_err(|e| Error::Encoding(format!("ppmd: {e:?}")))?;
247 enc.write_all(data)?;
248 Ok(enc.finish(true)?)
249}
250
251fn ppmd_decompress(data: &[u8], ulen: usize, order: u32, mem: u32) -> Result<Vec<u8>> {
252 let mut dec = ppmd_rust::Ppmd7Decoder::new(data, order, mem).map_err(|e| Error::Corrupt(format!("ppmd: {e:?}")))?;
253 let mut out = vec![0u8; ulen];
254 dec.read_exact(&mut out)?;
255 Ok(out)
256}
257
258pub fn looks_like_text(data: &[u8]) -> bool {
260 let sample = &data[..data.len().min(64 << 10)];
261 if sample.is_empty() {
262 return false;
263 }
264 let textish = sample.iter().filter(|&&b| b == b'\n' || b == b'\r' || b == b'\t' || (0x20..0x7f).contains(&b) || b >= 0x80).count();
265 let zeros = sample.iter().filter(|&&b| b == 0).count();
266 zeros == 0 && textish * 100 / sample.len() >= 97
267}
268
269#[derive(Clone, Debug)]
270pub struct CodecOptions {
271 pub level: i32,
272 pub long_window_log: u32,
274 pub dict: Option<Vec<u8>>,
275 pub choice: CodecChoice,
276 pub effort: u8,
283}
284
285impl Default for CodecOptions {
286 fn default() -> Self {
287 Self { level: 19, long_window_log: 27, dict: None, choice: CodecChoice::Zstd, effort: EFFORT_DEFAULT }
288 }
289}
290
291pub const EFFORT_DEFAULT: u8 = 5;
293
294pub const SAMPLE_SLICE: usize = 8 << 10;
296
297pub fn sample_slices(len: usize) -> usize {
302 (len / (128 << 10)).clamp(8, 32)
303}
304
305pub fn sample_full_trial_max(len: usize) -> usize {
309 2 * sample_slices(len) * SAMPLE_SLICE
310}
311
312pub fn sample_offsets(len: usize) -> Vec<usize> {
316 if len <= sample_full_trial_max(len) {
317 return Vec::new();
318 }
319 let n = sample_slices(len);
320 let span = (len - SAMPLE_SLICE) as u64;
321 (0..n).map(|i| ((i as u64 * span / (n as u64 - 1)) as usize) & !3).collect()
322}
323
324pub fn sample(data: &[u8]) -> std::borrow::Cow<'_, [u8]> {
327 let offsets = sample_offsets(data.len());
328 if offsets.is_empty() {
329 return std::borrow::Cow::Borrowed(data);
330 }
331 let mut s = Vec::with_capacity(offsets.len() * SAMPLE_SLICE);
332 for off in offsets {
333 s.extend_from_slice(&data[off..off + SAMPLE_SLICE]);
334 }
335 std::borrow::Cow::Owned(s)
336}
337
338pub fn samples_block(len: usize, o: &CodecOptions) -> bool {
341 o.choice == CodecChoice::Best && (2..=4).contains(&o.effort) && len > sample_full_trial_max(len)
342}
343
344fn zstd_compress(data: &[u8], level: i32, max_window_log: u32) -> Result<Vec<u8>> {
345 let mut c = zstd::bulk::Compressor::new(level)?;
346 let needed = usize::BITS - data.len().max(1024).leading_zeros(); let window_log = needed.clamp(10, if max_window_log > 0 { max_window_log } else { 23 });
352 c.set_parameter(CParameter::WindowLog(window_log))?;
353 Ok(c.compress(data)?)
354}
355
356fn xz_compress(data: &[u8], level: i32) -> Result<Vec<u8>> {
360 use liblzma::stream::{Check, Filters, LzmaOptions, Stream};
361 let preset: u32 = if level >= 15 { 9 | (1u32 << 31) } else { (level.clamp(1, 9)) as u32 };
362 let mut opts = LzmaOptions::new_preset(preset).map_err(|e| Error::Encoding(format!("xz preset: {e}")))?;
363 let dict = (data.len().max(4096) as u32).next_power_of_two().min(64 << 20);
364 opts.dict_size(dict);
365 let mut filters = Filters::new();
366 filters.lzma2(&opts);
367 let stream = Stream::new_stream_encoder(&filters, Check::None).map_err(|e| Error::Encoding(format!("xz encoder: {e}")))?;
368 let mut enc = liblzma::write::XzEncoder::new_stream(Vec::with_capacity(data.len() / 2 + 64), stream);
369 enc.write_all(data)?;
370 Ok(enc.finish()?)
371}
372
373pub fn zstd_compress_with_prefix(data: &[u8], level: i32, prefix: &[u8]) -> Result<Vec<u8>> {
376 let need = (prefix.len() + data.len()).max(1024);
377 let window_log = (usize::BITS - (need - 1).leading_zeros()).clamp(10, 27);
378 let mut c = zstd::bulk::Compressor::new(level)?;
379 c.set_parameter(CParameter::WindowLog(window_log))?;
380 c.set_dictionary(level, prefix)?;
381 Ok(c.compress(data)?)
382}
383
384pub fn zstd_decompress_with_prefix(data: &[u8], ulen: usize, prefix: &[u8]) -> Result<Vec<u8>> {
385 let mut d = zstd::bulk::Decompressor::with_dictionary(prefix)?;
386 d.set_parameter(DParameter::WindowLogMax(31))?;
387 let out = d.decompress(data, ulen)?;
388 if out.len() != ulen {
389 return Err(Error::Corrupt(format!("decoded {} bytes, expected {ulen}", out.len())));
390 }
391 Ok(out)
392}
393
394fn filter_candidates(data: &[u8]) -> Vec<u8> {
397 let mut filters = vec![FILTER_NONE];
398 if looks_like_x86(data) {
399 filters.push(FILTER_X86);
400 } else if let Some(off) = arm64_alignment(data) {
401 filters.push(FILTER_ARM64 | (off << 4));
402 }
403 filters
404}
405
406#[derive(Clone, Copy, Debug)]
408enum CodecSet<'a> {
409 ByChoice,
413 Only(&'a [u8]),
415}
416
417fn compress_trial(data: &[u8], o: &CodecOptions, variants: &[(u8, CodecSet)]) -> Result<Encoded> {
421 let mut best = Encoded { codec: STORE, bytes: data.to_vec(), params: Vec::new(), filter: FILTER_NONE };
422 for &(filter, set) in variants {
423 let filtered;
424 let input: &[u8] = if filter == FILTER_NONE {
425 data
426 } else {
427 filtered = apply_filter(filter, data, true)?;
428 &filtered
429 };
430 let mut consider = |codec: u8, out: Vec<u8>, params: Vec<u32>| {
431 if out.len() < best.bytes.len() {
432 best = Encoded { codec, bytes: out, params, filter };
433 }
434 };
435 let (try_zstd, try_dict, try_xz, try_ppmd) = match set {
436 CodecSet::ByChoice => {
437 let zstd = matches!(o.choice, CodecChoice::Zstd | CodecChoice::Best);
438 (zstd, zstd, matches!(o.choice, CodecChoice::Xz | CodecChoice::Best), o.choice == CodecChoice::Ppmd || (o.choice == CodecChoice::Best && looks_like_text(input)))
439 }
440 CodecSet::Only(codecs) => (codecs.contains(&ZSTD), codecs.contains(&ZSTD_DICT), codecs.contains(&XZ), codecs.contains(&PPMD)),
441 };
442 let mut incompressible = false;
443 if try_zstd {
444 let z = zstd_compress(input, o.level, o.long_window_log)?;
445 incompressible = z.len() * 100 >= input.len() * 97;
446 consider(ZSTD, z, Vec::new());
447 }
448 if try_dict {
449 if let Some(d) = &o.dict {
450 let mut c = zstd::bulk::Compressor::with_dictionary(o.level, d)?;
451 consider(ZSTD_DICT, c.compress(input)?, Vec::new());
452 }
453 }
454 if incompressible && o.choice == CodecChoice::Best && matches!(set, CodecSet::ByChoice) {
455 continue;
456 }
457 if try_xz && input.len() >= 256 {
458 consider(XZ, xz_compress(input, o.level)?, Vec::new());
459 }
460 if try_ppmd && input.len() >= 1024 {
461 let (order, mem) = ppmd_params(input.len());
462 consider(PPMD, ppmd_compress(input, order, mem)?, vec![order, mem]);
463 }
464 }
465 Ok(best)
466}
467
468#[derive(Clone, Debug, PartialEq, Eq)]
470pub struct Decision {
471 pub filter: u8,
473 pub codec: u8,
475 pub also: Option<u8>,
478 pub incompressible: bool,
481}
482
483type Candidate = (u8, u8, usize);
485
486fn pick(candidates: &[Candidate]) -> Option<Candidate> {
489 let mut best: Option<Candidate> = None;
490 for &c in candidates {
491 if best.is_none_or(|b| c.2 < b.2) {
492 best = Some(c);
493 }
494 }
495 best
496}
497
498pub fn decide_on_sample(sample: &[u8], o: &CodecOptions) -> Result<Decision> {
504 let effort = o.effort.clamp(1, 5);
505 let mut candidates: Vec<Candidate> = Vec::new();
506 let mut incompressible_unfiltered = false;
507 for filter in filter_candidates(sample) {
508 let filtered;
509 let input: &[u8] = if filter == FILTER_NONE {
510 sample
511 } else {
512 filtered = apply_filter(filter, sample, true)?;
513 &filtered
514 };
515 let z = zstd_compress(input, o.level, o.long_window_log)?;
516 let incompressible = z.len() * 100 >= input.len() * 97;
517 if filter == FILTER_NONE {
518 incompressible_unfiltered = incompressible;
519 }
520 candidates.push((filter, ZSTD, z.len()));
521 if let Some(d) = &o.dict {
522 let mut c = zstd::bulk::Compressor::with_dictionary(o.level, d)?;
523 candidates.push((filter, ZSTD_DICT, c.compress(input)?.len()));
524 }
525 if incompressible {
526 continue;
527 }
528 if effort >= 2 && input.len() >= 256 {
529 candidates.push((filter, XZ, xz_compress(input, o.level)?.len()));
530 }
531 if effort >= 3 && input.len() >= 1024 && looks_like_text(input) {
532 let (order, mem) = ppmd_params(input.len());
533 candidates.push((filter, PPMD, ppmd_compress(input, order, mem)?.len()));
534 }
535 }
536 let (filter, codec, _) = pick(&candidates).expect("zstd is always a candidate");
537 let also = (effort == 4 && codec == PPMD).then_some(XZ);
538 Ok(Decision { filter, codec, also, incompressible: incompressible_unfiltered })
539}
540
541pub fn compress_best(data: &[u8], o: &CodecOptions) -> Result<Encoded> {
551 if o.choice == CodecChoice::Best && o.effort.clamp(1, 5) == 1 {
552 let variants: Vec<(u8, CodecSet)> = filter_candidates(data).into_iter().map(|f| (f, CodecSet::Only(&[ZSTD, ZSTD_DICT]))).collect();
554 return compress_trial(data, o, &variants);
555 }
556 if !samples_block(data.len(), o) {
557 let variants: Vec<(u8, CodecSet)> = filter_candidates(data).into_iter().map(|f| (f, CodecSet::ByChoice)).collect();
559 return compress_trial(data, o, &variants);
560 }
561 let d = decide_on_sample(&sample(data), o)?;
562 let run: Vec<u8> = std::iter::once(d.codec).chain(d.also).collect();
563 compress_trial(data, o, &[(d.filter, CodecSet::Only(&run))])
564}
565
566pub fn decompress(codec: u8, data: &[u8], ulen: usize, dict: Option<&[u8]>) -> Result<Vec<u8>> {
567 decompress_with(codec, data, ulen, dict, &[], FILTER_NONE)
568}
569
570pub fn decompress_with(codec: u8, data: &[u8], ulen: usize, dict: Option<&[u8]>, params: &[u32], filter: u8) -> Result<Vec<u8>> {
571 let out = decompress_inner(codec, data, ulen, dict, params)?;
572 if filter == FILTER_NONE {
573 return Ok(out);
574 }
575 apply_filter(filter, &out, false)
576}
577
578fn decompress_inner(codec: u8, data: &[u8], ulen: usize, dict: Option<&[u8]>, params: &[u32]) -> Result<Vec<u8>> {
579 let out = match codec {
580 PPMD => {
581 if params.len() != 2 {
582 return Err(Error::Corrupt("ppmd blob without parameters".into()));
583 }
584 let (order, mem) = (params[0], params[1]);
585 if mem > (1 << 30) {
586 return Err(Error::Limit(format!("ppmd memory {mem} exceeds the reader limit")));
587 }
588 ppmd_decompress(data, ulen, order, mem)?
589 }
590 STORE => data.to_vec(),
591 ZSTD => {
592 let mut d = zstd::bulk::Decompressor::new()?;
593 d.set_parameter(DParameter::WindowLogMax(31))?;
594 d.decompress(data, ulen)?
595 }
596 ZSTD_DICT => {
597 let dict = dict.ok_or_else(|| Error::Missing("dictionary required by blob".into()))?;
598 let mut d = zstd::bulk::Decompressor::with_dictionary(dict)?;
599 d.set_parameter(DParameter::WindowLogMax(31))?;
600 d.decompress(data, ulen)?
601 }
602 ZSTD_DELTA => return Err(Error::Corrupt("delta blob needs its dictionary chunks (reader path)".into())),
603 XZ => {
604 let stream = liblzma::stream::Stream::new_stream_decoder(1 << 30, 0).map_err(|e| Error::Encoding(format!("xz decoder: {e}")))?;
605 let mut dec = liblzma::read::XzDecoder::new_stream(data, stream);
606 let mut out = Vec::with_capacity(ulen);
607 let mut limited = (&mut dec).take(ulen as u64 + 1);
609 limited.read_to_end(&mut out)?;
610 out
611 }
612 other => return Err(Error::Corrupt(format!("unknown codec id {other}"))),
613 };
614 if out.len() != ulen {
615 return Err(Error::Corrupt(format!("blob length {} != declared {}", out.len(), ulen)));
616 }
617 Ok(out)
618}
619
620pub fn train_dict(samples: &[&[u8]], max_size: usize) -> Option<Vec<u8>> {
622 if samples.len() < 8 {
623 return None;
624 }
625 zstd::dict::from_samples(samples, max_size).ok()
626}
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631
632 #[test]
633 fn xz_roundtrip_and_best_choice() {
634 let text: Vec<u8> = "the quick brown fox jumps over the lazy dog; ".repeat(2000).into_bytes();
635 let e = compress_best(&text, &CodecOptions { choice: CodecChoice::Xz, ..Default::default() }).unwrap();
636 assert_eq!(e.codec, XZ);
637 assert_eq!(decompress(XZ, &e.bytes, text.len(), None).unwrap(), text);
638 let b = compress_best(&text, &CodecOptions { choice: CodecChoice::Best, ..Default::default() }).unwrap();
639 assert!(b.bytes.len() <= e.bytes.len());
640 assert!(decompress(XZ, &e.bytes[..e.bytes.len() - 4], text.len(), None).is_err(), "truncated xz must fail");
641 }
642
643 #[test]
644 fn x86_filter_roundtrips_and_helps_on_code() {
645 let mut code = Vec::new();
647 let mut x = 0x1234_5678u32;
648 for i in 0..20_000u32 {
649 x ^= x << 13;
650 x ^= x >> 17;
651 x ^= x << 5;
652 if i % 7 == 0 {
653 code.push(0xE8);
654 let rel = x % 4096;
655 code.extend_from_slice(&rel.to_le_bytes());
656 } else {
657 code.push((x >> 24) as u8);
658 }
659 }
660 assert!(looks_like_x86(&code));
661 let mut enc = code.clone();
662 let mut st = 0;
663 x86_convert(&mut enc, 0, &mut st, true);
664 assert_ne!(enc, code);
665 let mut dec = enc.clone();
666 let mut st2 = 0;
667 x86_convert(&mut dec, 0, &mut st2, false);
668 assert_eq!(dec, code, "x86 filter must be exactly invertible");
669 let e = compress_best(&code, &CodecOptions { choice: CodecChoice::Zstd, ..Default::default() }).unwrap();
670 assert_eq!(decompress_with(e.codec, &e.bytes, code.len(), None, &e.params, e.filter).unwrap(), code);
671 let mut y = 0x2545_F491u32;
673 let rnd: Vec<u8> = (0..50_000)
674 .map(|_| {
675 y ^= y << 13;
676 y ^= y >> 17;
677 y ^= y << 5;
678 (y >> 24) as u8
679 })
680 .collect();
681 assert!(!looks_like_x86(&rnd) && arm64_alignment(&rnd).is_none());
682 let r = compress_best(&rnd, &CodecOptions::default()).unwrap();
683 assert_eq!(r.codec, STORE);
684 assert_eq!(decompress_with(r.codec, &r.bytes, rnd.len(), None, &r.params, r.filter).unwrap(), rnd);
685 }
686
687 #[test]
688 fn branch_filters_roundtrip_on_adversarial_inputs() {
689 let mut x = 0x9E37_79B9u32;
692 let mut next = || {
693 x ^= x << 13;
694 x ^= x >> 17;
695 x ^= x << 5;
696 x
697 };
698 for round in 0..300 {
699 let len = 5 + (next() % 3000) as usize + if round % 3 == 0 { 4096 } else { 0 };
700 let mut buf = vec![0u8; len];
701 for b in buf.iter_mut() {
702 let r = next();
703 *b = match r % 6 {
704 0 => 0xE8,
705 1 => 0xE9,
706 2 => 0x00,
707 3 => 0xFF,
708 _ => (r >> 8) as u8,
709 };
710 }
711 for &f in &[FILTER_X86, FILTER_ARM64, FILTER_ARM64 | 0x10, FILTER_ARM64 | 0x20, FILTER_ARM64 | 0x30] {
712 let enc = apply_filter(f, &buf, true).unwrap();
713 let dec = apply_filter(f, &enc, false).unwrap();
714 assert_eq!(dec, buf, "filter {f} round {round} len {len}");
715 }
716 assert!(apply_filter(0x0F, &buf, true).is_err());
717 let mut words = Vec::with_capacity(len);
718 for i in 0..len / 4 {
719 let r = next();
720 let w = match i % 4 {
721 0 => 0x9400_0000 | (r & 0x0000_FFFF), 1 => 0x97FF_0000 | (r & 0x0000_FFFF), 2 => 0x9000_0000 | (r & 0x60FF_FFFF), _ => r,
725 };
726 words.extend_from_slice(&w.to_le_bytes());
727 }
728 let enc = apply_filter(FILTER_ARM64, &words, true).unwrap();
729 assert_ne!(enc, words);
730 assert_eq!(apply_filter(FILTER_ARM64, &enc, false).unwrap(), words);
731 let mut shifted = vec![0xAAu8; 3];
733 shifted.extend_from_slice(&words);
734 if words.len() >= 4096 {
735 assert_eq!(arm64_alignment(&shifted), Some(3), "round {round}");
736 }
737 }
738 }
739
740 fn xorshift(x: &mut u32) -> u32 {
742 *x ^= *x << 13;
743 *x ^= *x >> 17;
744 *x ^= *x << 5;
745 *x
746 }
747
748 fn prose(len: usize, seed: u32) -> Vec<u8> {
750 let words = [
751 "archive",
752 "agent",
753 "chunk",
754 "merkle",
755 "verify",
756 "content",
757 "reference",
758 "dictionary",
759 "solid",
760 "the",
761 "of",
762 "a",
763 "block",
764 "sample",
765 "codec",
766 "deterministic",
767 "every",
768 "byte",
769 "restored",
770 "and",
771 ];
772 let mut x = seed;
773 let mut out = Vec::with_capacity(len + 16);
774 let mut i = 0usize;
775 while out.len() < len {
776 out.extend_from_slice(words[(xorshift(&mut x) % words.len() as u32) as usize].as_bytes());
777 out.push(if i % 11 == 10 { b'\n' } else { b' ' });
778 i += 1;
779 }
780 out.truncate(len);
781 out
782 }
783
784 fn random(len: usize, seed: u32) -> Vec<u8> {
786 let mut x = seed;
787 (0..len).map(|_| (xorshift(&mut x) >> 24) as u8).collect()
788 }
789
790 fn best(effort: u8) -> CodecOptions {
791 CodecOptions { choice: CodecChoice::Best, effort, ..Default::default() }
792 }
793
794 fn assert_decodes(e: &Encoded, data: &[u8]) {
795 assert_eq!(decompress_with(e.codec, &e.bytes, data.len(), None, &e.params, e.filter).unwrap(), data);
796 }
797
798 fn same(a: &Encoded, b: &Encoded) -> bool {
799 a.codec == b.codec && a.filter == b.filter && a.params == b.params && a.bytes == b.bytes
800 }
801
802 #[test]
803 fn sample_is_deterministic_aligned_and_covers_the_block() {
804 for len in [100usize << 10, 129 << 10, 1 << 20, 4 << 20, 10 << 20] {
805 let n = (len / (128 << 10)).clamp(8, 32);
806 assert_eq!(sample_slices(len), n, "len {len}");
807 let d = random(len, 7);
808 let s = sample(&d);
809 assert_eq!(s, sample(&d), "the sample is a pure function of the block");
810 let offsets = sample_offsets(len);
811 if len <= 2 * n * SAMPLE_SLICE {
812 assert!(offsets.is_empty() && matches!(s, std::borrow::Cow::Borrowed(_)), "len {len}: small blocks are not sampled");
813 assert_eq!(&*s, &d[..]);
814 continue;
815 }
816 assert_eq!(offsets.len(), n);
817 assert_eq!(offsets[0], 0, "the first slice starts at 0");
818 assert!(offsets.iter().all(|o| o % 4 == 0), "{offsets:?}");
819 assert!(offsets.windows(2).all(|w| w[0] + SAMPLE_SLICE <= w[1]), "slices do not overlap: {offsets:?}");
820 let last = *offsets.last().unwrap();
821 assert!(last + SAMPLE_SLICE <= len && last + SAMPLE_SLICE + 3 >= len, "the last slice ends at the block (up to alignment): {last} of {len}");
822 assert_eq!(s.len(), n * SAMPLE_SLICE);
823 for (i, o) in offsets.iter().enumerate() {
824 assert_eq!(&s[i * SAMPLE_SLICE..(i + 1) * SAMPLE_SLICE], &d[*o..*o + SAMPLE_SLICE]);
825 }
826 }
827 assert_eq!(sample_full_trial_max(1 << 20), 128 << 10);
828 assert!(sample(&[]).is_empty() && sample_offsets(0).is_empty() && sample_offsets(SAMPLE_SLICE).is_empty());
829 }
830
831 #[test]
832 fn effort_5_and_small_blocks_take_the_unchanged_path() {
833 assert_eq!(CodecOptions::default().effort, 5);
834 assert_eq!(EFFORT_DEFAULT, 5);
835 let mut block = prose(60 << 10, 11);
837 block.extend_from_slice(&random(40 << 10, 12));
838 for effort in 1..=5 {
839 assert!(!samples_block(block.len(), &best(effort)), "effort {effort}");
840 }
841 let full = compress_best(&block, &best(5)).unwrap();
842 assert_ne!(full.codec, STORE);
843 for effort in 2..=5 {
844 let e = compress_best(&block, &best(effort)).unwrap();
845 assert!(same(&e, &full), "effort {effort} must be the full trial on a block of {} bytes", block.len());
846 assert_decodes(&e, &block);
847 }
848 let e1 = compress_best(&block, &best(1)).unwrap();
850 let z = compress_best(&block, &CodecOptions { choice: CodecChoice::Zstd, ..Default::default() }).unwrap();
851 assert!(same(&e1, &z) && e1.codec == ZSTD);
852 assert!(same(&compress_best(&block, &best(0)).unwrap(), &e1));
854 assert!(same(&compress_best(&block, &best(9)).unwrap(), &full));
855 for choice in [CodecChoice::Zstd, CodecChoice::Xz, CodecChoice::Ppmd] {
857 let big = prose(300 << 10, 13);
858 let a = compress_best(&big, &CodecOptions { choice, effort: 3, ..Default::default() }).unwrap();
859 let b = compress_best(&big, &CodecOptions { choice, effort: 5, ..Default::default() }).unwrap();
860 assert!(same(&a, &b), "{choice:?}");
861 assert!(!samples_block(big.len(), &CodecOptions { choice, effort: 3, ..Default::default() }));
862 }
863 }
864
865 #[test]
866 fn decision_tie_break_matches_consider() {
867 let order: Vec<Candidate> =
870 [(FILTER_NONE, ZSTD), (FILTER_NONE, ZSTD_DICT), (FILTER_NONE, XZ), (FILTER_NONE, PPMD), (FILTER_X86, ZSTD), (FILTER_X86, ZSTD_DICT), (FILTER_X86, XZ), (FILTER_X86, PPMD)]
871 .iter()
872 .map(|&(f, c)| (f, c, 100))
873 .collect();
874 assert_eq!(pick(&order), Some((FILTER_NONE, ZSTD, 100)));
875 for start in 1..order.len() {
876 assert_eq!(pick(&order[start..]), Some(order[start]), "the first candidate in push order wins a tie");
877 }
878 let mut reversed = order.clone();
879 reversed.reverse();
880 assert_eq!(pick(&reversed), Some((FILTER_X86, PPMD, 100)), "pick follows the order it is given, not the codec id");
881 let mut c = order.clone();
883 c[6].2 = 99;
884 assert_eq!(pick(&c), Some((FILTER_X86, XZ, 99)));
885 c[7].2 = 99;
886 assert_eq!(pick(&c), Some((FILTER_X86, XZ, 99)));
887 c[3].2 = 99;
888 assert_eq!(pick(&c), Some((FILTER_NONE, PPMD, 99)));
889 assert_eq!(pick(&[]), None);
890 }
891
892 #[test]
893 fn sampled_choice_agrees_with_the_full_trial_on_fixtures() {
894 let len = 1 << 20;
895 let prose_block = prose(len, 21);
897 let template = prose(40 << 10, 22);
899 let mut versions = Vec::with_capacity(len + template.len());
900 let mut x = 23u32;
901 while versions.len() < len {
902 let mut v = template.clone();
903 for _ in 0..40 {
904 let at = (xorshift(&mut x) as usize) % (v.len() - 8);
905 v[at..at + 8].copy_from_slice(b"EDITED! ");
906 }
907 versions.extend_from_slice(&v);
908 }
909 versions.truncate(len);
910 let noisy = random(len, 24);
913 for (name, block) in [("prose", &prose_block), ("versions", &versions), ("random", &noisy)] {
914 assert!(samples_block(block.len(), &best(3)), "{name}");
915 let full = compress_best(block, &best(5)).unwrap();
916 let e3 = compress_best(block, &best(3)).unwrap();
917 let e4 = compress_best(block, &best(4)).unwrap();
918 for e in [&full, &e3, &e4] {
919 assert_decodes(e, block);
920 }
921 let d = decide_on_sample(&sample(block), &best(3)).unwrap();
922 println!(
923 "{name}: full {} {} B, effort 3 {} {} B, effort 4 {} {} B, decision {d:?}",
924 super::name(full.codec),
925 full.bytes.len(),
926 super::name(e3.codec),
927 e3.bytes.len(),
928 super::name(e4.codec),
929 e4.bytes.len()
930 );
931 match name {
932 "prose" => {
933 assert_eq!(e3.codec, full.codec, "the sample must pick the full trial's codec on prose");
934 assert!(same(&e3, &full), "the winner run on the whole block gives the full trial's bytes");
935 assert!(same(&e4, &full));
936 assert!(!d.incompressible && d.filter == FILTER_NONE);
937 }
938 "versions" => {
939 assert_eq!(e4.bytes.len(), full.bytes.len(), "effort 4's xz safety net");
942 assert!(matches!(e3.codec, XZ | PPMD) && matches!(full.codec, XZ | PPMD));
943 }
944 _ => {
945 assert_eq!(full.codec, STORE, "the full trial stores a block zstd cannot shrink at all");
946 assert!(same(&e3, &full) && same(&e4, &full));
947 assert!(d.incompressible && d.codec == ZSTD && d.also.is_none(), "the sample takes the incompressible shortcut: {d:?}");
948 }
949 }
950 }
951 }
952
953 #[test]
954 fn effort_2_never_picks_ppmd_and_effort_1_is_zstd_only() {
955 let block = prose(1 << 20, 31);
956 assert!(looks_like_text(&block));
957 let e2 = compress_best(&block, &best(2)).unwrap();
958 assert!(matches!(e2.codec, ZSTD | XZ), "effort 2 is LZ-only, got {}", name(e2.codec));
959 let d2 = decide_on_sample(&sample(&block), &best(2)).unwrap();
960 assert_ne!(d2.codec, PPMD);
961 assert!(d2.also.is_none());
962 let d3 = decide_on_sample(&sample(&block), &best(3)).unwrap();
963 assert_eq!(d3.codec, PPMD, "PPMd wins prose");
964 assert!(d3.also.is_none(), "only effort 4 adds the xz check");
965 assert_eq!(decide_on_sample(&sample(&block), &best(4)).unwrap().also, Some(XZ));
966 let e1 = compress_best(&block, &best(1)).unwrap();
967 let z = compress_best(&block, &CodecOptions { choice: CodecChoice::Zstd, ..Default::default() }).unwrap();
968 assert_eq!(e1.codec, ZSTD);
969 assert!(same(&e1, &z), "effort 1 is `--codec zstd`");
970 assert!(!samples_block(block.len(), &best(1)) && !samples_block(block.len(), &best(5)));
971 assert!(samples_block(block.len(), &best(2)) && samples_block(block.len(), &best(4)));
972 for e in [&e1, &e2] {
973 assert_decodes(e, &block);
974 }
975 assert!(compress_best(&block, &best(3)).unwrap().bytes.len() <= e1.bytes.len());
976 }
977
978 #[test]
979 fn arm64_alignment_survives_sampling() {
980 let words = 48 << 10; let mut x = 41u32;
986 let set: Vec<u32> = (0..64).map(|_| (xorshift(&mut x) & !0xFC00_0000) | 0x8800_0000).collect();
987 let targets: Vec<u32> = (0..8).map(|i| 0x1000 + i * 0x340).collect();
988 let mut code = vec![0xAAu8; 3];
989 for i in 0..words {
990 let pc = 4 * i as u32;
991 let w = if i % 4 == 3 { 0x9400_0000 | (targets[(xorshift(&mut x) % 8) as usize].wrapping_sub(pc) >> 2) & 0x03FF_FFFF } else { set[(xorshift(&mut x) % 64) as usize] };
992 code.extend_from_slice(&w.to_le_bytes());
993 }
994 assert_eq!(arm64_alignment(&code), Some(3));
995 let s = sample(&code);
996 assert!(matches!(s, std::borrow::Cow::Owned(_)));
997 assert_eq!(arm64_alignment(&s), Some(3), "4-byte aligned slices keep the block's word alignment");
998 let d = decide_on_sample(&s, &best(3)).unwrap();
999 assert_eq!((filter_id(d.filter), filter_param(d.filter)), (FILTER_ARM64, 3), "{d:?}");
1000 let e3 = compress_best(&code, &best(3)).unwrap();
1001 assert_eq!((filter_id(e3.filter), filter_param(e3.filter)), (FILTER_ARM64, 3));
1002 assert_decodes(&e3, &code);
1003 let full = compress_best(&code, &best(5)).unwrap();
1004 assert!(same(&e3, &full), "the sampled decision agrees with the full trial on this block");
1005 let e1 = compress_best(&code, &best(1)).unwrap();
1007 assert_eq!((e1.codec, filter_id(e1.filter)), (ZSTD, FILTER_ARM64));
1008 assert_decodes(&e1, &code);
1009 }
1010
1011 #[test]
1012 fn ppmd_roundtrip() {
1013 let mut text = String::new();
1014 for i in 0..4000 {
1015 text.push_str(&format!("Paragraph {i}: content-defined chunking keeps identical data identical; PPMd models text well.\n"));
1016 }
1017 let data = text.into_bytes();
1018 assert!(looks_like_text(&data));
1019 let e = compress_best(&data, &CodecOptions { choice: CodecChoice::Ppmd, ..Default::default() }).unwrap();
1020 assert_eq!(e.codec, PPMD);
1021 assert_eq!(e.params.len(), 2);
1022 assert_eq!(decompress_with(PPMD, &e.bytes, data.len(), None, &e.params, FILTER_NONE).unwrap(), data);
1023 assert!(decompress_with(PPMD, &e.bytes, data.len(), None, &[], FILTER_NONE).is_err());
1024 let z = compress_best(&data, &CodecOptions { choice: CodecChoice::Zstd, ..Default::default() }).unwrap();
1025 println!("ppmd {} vs zstd {}", e.bytes.len(), z.bytes.len());
1026 }
1027}