Skip to content

Commit 4871548

Browse files
authored
feat(zgfx): add LZ77 compression support (#1097)
Adds ZGFX (RDP8) LZ77 compression to complement the existing decompressor, plus a high-level API for EGFX PDU preparation with auto/always/never mode selection. The compressor uses a hash table mapping 3-byte prefixes to history positions for O(1) match candidate lookup against the 2.5 MB sliding window.
1 parent 7853e3c commit 4871548

File tree

4 files changed

+662
-3
lines changed

4 files changed

+662
-3
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! High-level ZGFX compression API for EGFX PDU preparation.
2+
3+
use super::compressor::Compressor;
4+
use super::wrapper::{wrap_compressed, wrap_uncompressed, ZGFX_SEGMENTED_MAXSIZE};
5+
use super::ZgfxError;
6+
7+
/// Controls whether ZGFX compression is applied.
8+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9+
pub enum CompressionMode {
10+
/// Send uncompressed (no CPU overhead).
11+
Never,
12+
/// Compress and use the smaller result (bandwidth vs CPU trade-off).
13+
Auto,
14+
/// Always compress (best bandwidth).
15+
Always,
16+
}
17+
18+
/// Compress and wrap EGFX PDU bytes into ZGFX segment format for DVC transmission.
19+
///
20+
/// In `Auto` mode, compression is only used when it actually reduces size.
21+
/// The `compressor` maintains history state across calls for back-reference
22+
/// efficiency.
23+
pub fn compress_and_wrap_egfx(
24+
data: &[u8],
25+
compressor: &mut Compressor,
26+
mode: CompressionMode,
27+
) -> Result<Vec<u8>, ZgfxError> {
28+
match mode {
29+
CompressionMode::Never => Ok(wrap_uncompressed(data)),
30+
CompressionMode::Auto => {
31+
let compressed = compressor.compress(data)?;
32+
33+
// Only use compressed wrapping if it fits a single segment.
34+
// Incompressible data can expand beyond the limit; fall back
35+
// to uncompressed which handles multipart natively.
36+
if compressed.len() <= ZGFX_SEGMENTED_MAXSIZE {
37+
let wrapped_compressed = wrap_compressed(&compressed);
38+
let wrapped_uncompressed = wrap_uncompressed(data);
39+
40+
if wrapped_compressed.len() < wrapped_uncompressed.len() {
41+
Ok(wrapped_compressed)
42+
} else {
43+
Ok(wrapped_uncompressed)
44+
}
45+
} else {
46+
Ok(wrap_uncompressed(data))
47+
}
48+
}
49+
CompressionMode::Always => {
50+
let compressed = compressor.compress(data)?;
51+
52+
if compressed.len() <= ZGFX_SEGMENTED_MAXSIZE {
53+
Ok(wrap_compressed(&compressed))
54+
} else {
55+
// Compressed output too large for single segment;
56+
// send uncompressed to avoid invalid segmentation
57+
Ok(wrap_uncompressed(data))
58+
}
59+
}
60+
}
61+
}
62+
63+
#[cfg(test)]
64+
mod tests {
65+
use super::*;
66+
67+
#[test]
68+
fn mode_never_produces_uncompressed() {
69+
let mut compressor = Compressor::new();
70+
let data = b"Test data";
71+
72+
let wrapped = compress_and_wrap_egfx(data, &mut compressor, CompressionMode::Never).unwrap();
73+
74+
assert_eq!(wrapped[0], 0xE0);
75+
assert_eq!(wrapped[1], 0x04); // RDP8, not compressed
76+
}
77+
78+
#[test]
79+
fn mode_always_produces_compressed() {
80+
let mut compressor = Compressor::new();
81+
let data = b"Test data";
82+
83+
let wrapped = compress_and_wrap_egfx(data, &mut compressor, CompressionMode::Always).unwrap();
84+
85+
assert_eq!(wrapped[0], 0xE0);
86+
assert_eq!(wrapped[1], 0x24); // RDP8 + COMPRESSED
87+
}
88+
89+
#[test]
90+
fn mode_auto_compresses_repetitive_data() {
91+
let mut compressor = Compressor::new();
92+
let data = b"AAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCC";
93+
94+
let wrapped = compress_and_wrap_egfx(data, &mut compressor, CompressionMode::Auto).unwrap();
95+
96+
assert_eq!(wrapped[0], 0xE0);
97+
assert_eq!(wrapped[1], 0x24);
98+
}
99+
100+
#[test]
101+
fn round_trip_all_modes() {
102+
use super::super::Decompressor;
103+
104+
let data = b"Test data with some repetition: AAAA BBBB CCCC";
105+
let mut decompressor = Decompressor::new();
106+
107+
for mode in [CompressionMode::Never, CompressionMode::Auto, CompressionMode::Always] {
108+
let mut compressor = Compressor::new();
109+
let wrapped = compress_and_wrap_egfx(data, &mut compressor, mode).unwrap();
110+
111+
let mut output = Vec::new();
112+
decompressor.decompress(&wrapped, &mut output).unwrap();
113+
114+
assert_eq!(&output, data, "Round-trip failed for mode {mode:?}");
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)