Skip to main content

oxihuman_core/
compression_snappy.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Snappy compression stub.
6
7/// Configuration for the Snappy compressor.
8#[derive(Debug, Clone, Default)]
9pub struct SnappyConfig {
10    pub verify_checksum: bool,
11}
12
13/// Snappy compressor stub.
14#[derive(Debug, Clone)]
15pub struct SnappyCompressor {
16    pub config: SnappyConfig,
17}
18
19impl SnappyCompressor {
20    pub fn new(config: SnappyConfig) -> Self {
21        Self { config }
22    }
23
24    pub fn default_compressor() -> Self {
25        Self::new(SnappyConfig {
26            verify_checksum: false,
27        })
28    }
29}
30
31/// Compress bytes using Snappy stub (4-byte LE length + raw payload).
32pub fn snappy_compress(data: &[u8]) -> Vec<u8> {
33    /* stub: store data with 4-byte length header */
34    let mut out = Vec::with_capacity(data.len() + 4);
35    out.extend_from_slice(&(data.len() as u32).to_le_bytes());
36    out.extend_from_slice(data);
37    out
38}
39
40/// Decompress bytes produced by [`snappy_compress`].
41pub fn snappy_decompress(data: &[u8]) -> Result<Vec<u8>, String> {
42    /* stub: read 4-byte header then extract payload */
43    if data.len() < 4 {
44        return Err("snappy: input too short".to_string());
45    }
46    let expected = u32::from_le_bytes(data[..4].try_into().unwrap_or_default()) as usize;
47    let payload = &data[4..];
48    if payload.len() < expected {
49        return Err("snappy: truncated payload".to_string());
50    }
51    Ok(payload[..expected].to_vec())
52}
53
54/// Return the maximum output size for a given input length (stub).
55pub fn snappy_max_compressed_length(input_len: usize) -> usize {
56    32 + input_len + input_len / 6
57}
58
59/// Return whether a byte slice could be a valid Snappy stub frame.
60pub fn snappy_validate_compressed_buffer(data: &[u8]) -> bool {
61    data.len() >= 4
62}
63
64/// Verify round-trip integrity.
65pub fn snappy_roundtrip_ok(data: &[u8]) -> bool {
66    snappy_decompress(&snappy_compress(data))
67        .map(|d| d == data)
68        .unwrap_or(false)
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_default_config() {
77        /* checksum disabled by default */
78        let c = SnappyCompressor::default_compressor();
79        assert!(!c.config.verify_checksum);
80    }
81
82    #[test]
83    fn test_compress_empty() {
84        /* empty input yields 4-byte header only */
85        assert_eq!(snappy_compress(&[]).len(), 4);
86    }
87
88    #[test]
89    fn test_roundtrip_ascii() {
90        /* ascii round-trip */
91        assert!(snappy_roundtrip_ok(b"snappy test string"));
92    }
93
94    #[test]
95    fn test_roundtrip_binary() {
96        /* binary data round-trip */
97        let data: Vec<u8> = (0u8..=255).collect();
98        assert!(snappy_roundtrip_ok(&data));
99    }
100
101    #[test]
102    fn test_decompress_too_short() {
103        /* error on < 4 bytes */
104        assert!(snappy_decompress(&[0, 1]).is_err());
105    }
106
107    #[test]
108    fn test_max_compressed_length() {
109        /* max length is larger than input */
110        assert!(snappy_max_compressed_length(100) > 100);
111    }
112
113    #[test]
114    fn test_validate_valid() {
115        /* 4-byte slice validates */
116        assert!(snappy_validate_compressed_buffer(&[0, 0, 0, 0]));
117    }
118
119    #[test]
120    fn test_validate_too_short() {
121        /* 3-byte slice fails */
122        assert!(!snappy_validate_compressed_buffer(&[0, 0, 0]));
123    }
124
125    #[test]
126    fn test_new_config() {
127        /* verify_checksum can be toggled */
128        let c = SnappyCompressor::new(SnappyConfig {
129            verify_checksum: true,
130        });
131        assert!(c.config.verify_checksum);
132    }
133}