Skip to main content

oxihuman_core/
compression_zstd.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Zstandard compression stub.
6
7/// Configuration for the Zstd compressor.
8#[derive(Debug, Clone)]
9pub struct ZstdConfig {
10    pub level: i32,
11    pub checksum: bool,
12}
13
14impl Default for ZstdConfig {
15    fn default() -> Self {
16        Self {
17            level: 3,
18            checksum: true,
19        }
20    }
21}
22
23/// Zstd compressor stub.
24#[derive(Debug, Clone)]
25pub struct ZstdCompressor {
26    pub config: ZstdConfig,
27}
28
29impl ZstdCompressor {
30    pub fn new(config: ZstdConfig) -> Self {
31        Self { config }
32    }
33
34    pub fn default_compressor() -> Self {
35        Self::new(ZstdConfig::default())
36    }
37
38    pub fn with_level(level: i32) -> Self {
39        Self::new(ZstdConfig {
40            level,
41            checksum: true,
42        })
43    }
44}
45
46/// Compress bytes using Zstd stub (stores raw with 4-byte header).
47pub fn zstd_compress(data: &[u8]) -> Vec<u8> {
48    /* stub: 4-byte LE length prefix followed by raw bytes */
49    let mut out = Vec::with_capacity(data.len() + 4);
50    out.extend_from_slice(&(data.len() as u32).to_le_bytes());
51    out.extend_from_slice(data);
52    out
53}
54
55/// Decompress bytes produced by [`zstd_compress`].
56pub fn zstd_decompress(data: &[u8]) -> Result<Vec<u8>, String> {
57    /* stub: verify and strip header */
58    if data.len() < 4 {
59        return Err("zstd: input too short".to_string());
60    }
61    let expected = u32::from_le_bytes(data[..4].try_into().unwrap_or_default()) as usize;
62    let payload = &data[4..];
63    if payload.len() < expected {
64        return Err("zstd: truncated data".to_string());
65    }
66    Ok(payload[..expected].to_vec())
67}
68
69/// Estimate the frame size for a given input length (stub).
70pub fn zstd_frame_size_estimate(input_len: usize) -> usize {
71    input_len + 12
72}
73
74/// Return true if the data length is sufficient for a valid Zstd stub frame.
75pub fn zstd_frame_valid(data: &[u8]) -> bool {
76    data.len() >= 4
77}
78
79/// Verify round-trip integrity.
80pub fn zstd_roundtrip_ok(data: &[u8]) -> bool {
81    zstd_decompress(&zstd_compress(data))
82        .map(|d| d == data)
83        .unwrap_or(false)
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_default_level() {
92        /* default compression level */
93        assert_eq!(ZstdConfig::default().level, 3);
94    }
95
96    #[test]
97    fn test_with_level() {
98        /* custom level constructor */
99        let c = ZstdCompressor::with_level(9);
100        assert_eq!(c.config.level, 9);
101    }
102
103    #[test]
104    fn test_compress_empty() {
105        /* empty input gives 4-byte header */
106        assert_eq!(zstd_compress(&[]).len(), 4);
107    }
108
109    #[test]
110    fn test_roundtrip_hello() {
111        /* hello round-trip */
112        assert!(zstd_roundtrip_ok(b"hello zstd"));
113    }
114
115    #[test]
116    fn test_roundtrip_binary() {
117        /* binary round-trip */
118        let data: Vec<u8> = (0u8..128).collect();
119        assert!(zstd_roundtrip_ok(&data));
120    }
121
122    #[test]
123    fn test_decompress_short() {
124        /* error on short input */
125        assert!(zstd_decompress(&[1, 2]).is_err());
126    }
127
128    #[test]
129    fn test_frame_size_estimate() {
130        /* estimate is larger than input */
131        assert!(zstd_frame_size_estimate(100) > 100);
132    }
133
134    #[test]
135    fn test_frame_valid() {
136        /* 4+ bytes is valid stub frame */
137        assert!(zstd_frame_valid(&[0, 0, 0, 0]));
138        assert!(!zstd_frame_valid(&[0]));
139    }
140
141    #[test]
142    fn test_checksum_default() {
143        /* checksum enabled by default */
144        assert!(ZstdConfig::default().checksum);
145    }
146}