Skip to main content

memf_core/
lzo.rs

1//! LZO1X-1 decompression for Linux kernel zram pages.
2//!
3//! Thin wrapper around the [`lzo1x`] crate, exposing a forensics-oriented API.
4//! The Linux kernel stores the expected decompressed size in the zram slot header,
5//! so callers always know the output size in advance.
6
7/// Errors that can occur during LZO1X decompression.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum LzoError {
10    /// The compressed input is malformed or truncated.
11    InvalidInput,
12    /// The destination buffer length does not match the decompressed data length.
13    OutputLength,
14}
15
16impl std::fmt::Display for LzoError {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            Self::InvalidInput => write!(f, "LZO invalid input"),
20            Self::OutputLength => write!(f, "LZO output length mismatch"),
21        }
22    }
23}
24
25impl std::error::Error for LzoError {}
26
27/// Decompress LZO1X-1 compressed data into `dst`.
28///
29/// The caller must provide a buffer of **exactly** the expected decompressed
30/// size (e.g. 4096 bytes for a Linux zram page). Returns [`LzoError::OutputLength`]
31/// if the actual decompressed size differs from `dst.len()`.
32pub fn decompress(src: &[u8], dst: &mut [u8]) -> Result<(), LzoError> {
33    lzo1x::decompress(src, dst).map_err(|e| match e {
34        lzo1x::DecompressError::InvalidInput => LzoError::InvalidInput,
35        lzo1x::DecompressError::OutputLength => LzoError::OutputLength,
36    })
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    /// Round-trip helper: compress with the lzo1x crate then decompress with ours.
44    fn compress(data: &[u8]) -> Vec<u8> {
45        lzo1x::compress(data, lzo1x::CompressLevel::default())
46    }
47
48    #[test]
49    fn decompress_empty_data() {
50        let input: &[u8] = &[];
51        let compressed = compress(input);
52        let mut dst = [];
53        decompress(&compressed, &mut dst).unwrap();
54    }
55
56    #[test]
57    fn decompress_short_literal() {
58        let data = [0xDE, 0xAD, 0xBE, 0xEF];
59        let compressed = compress(&data);
60        let mut dst = [0u8; 4];
61        decompress(&compressed, &mut dst).unwrap();
62        assert_eq!(dst, data);
63    }
64
65    #[test]
66    fn decompress_repeated_bytes_match_copy() {
67        let data = [b'A'; 32];
68        let compressed = compress(&data);
69        let mut dst = [0u8; 32];
70        decompress(&compressed, &mut dst).unwrap();
71        assert_eq!(dst, data);
72    }
73
74    #[test]
75    fn decompress_full_kernel_page() {
76        let data = vec![0x55u8; 4096];
77        let compressed = compress(&data);
78        let mut dst = vec![0u8; 4096];
79        decompress(&compressed, &mut dst).unwrap();
80        assert_eq!(dst, data);
81    }
82
83    #[test]
84    fn decompress_invalid_input_errors() {
85        let mut dst = [0u8; 4];
86        let result = decompress(&[0xFF, 0xFF, 0xFF], &mut dst);
87        assert_eq!(result, Err(LzoError::InvalidInput));
88    }
89
90    #[test]
91    fn decompress_output_length_mismatch_errors() {
92        let data = [0xDE, 0xAD, 0xBE, 0xEF];
93        let compressed = compress(&data);
94        // dst is too small — expects OutputLength error
95        let mut dst = [0u8; 2];
96        let result = decompress(&compressed, &mut dst);
97        assert_eq!(result, Err(LzoError::OutputLength));
98    }
99
100    #[test]
101    fn decompress_empty_input_errors() {
102        let mut dst = [0u8; 4];
103        let result = decompress(&[], &mut dst);
104        assert!(result.is_err());
105    }
106}