|
| 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