hermes_core/compression/
zstd.rs1use std::io::{self, Write};
9
10#[derive(Debug, Clone, Copy)]
12pub struct CompressionLevel(pub i32);
13
14impl CompressionLevel {
15 pub const FAST: Self = Self(1);
17 pub const DEFAULT: Self = Self(3);
19 pub const BETTER: Self = Self(9);
21 pub const BEST: Self = Self(19);
23 pub const MAX: Self = Self(22);
25}
26
27impl Default for CompressionLevel {
28 fn default() -> Self {
29 Self::MAX }
31}
32
33#[derive(Clone)]
35pub struct CompressionDict {
36 raw_dict: crate::directories::OwnedBytes,
37}
38
39impl CompressionDict {
40 pub fn train(samples: &[&[u8]], dict_size: usize) -> io::Result<Self> {
45 let raw_dict = zstd::dict::from_samples(samples, dict_size).map_err(io::Error::other)?;
46 Ok(Self {
47 raw_dict: crate::directories::OwnedBytes::new(raw_dict),
48 })
49 }
50
51 pub fn from_bytes(bytes: Vec<u8>) -> Self {
53 Self {
54 raw_dict: crate::directories::OwnedBytes::new(bytes),
55 }
56 }
57
58 pub fn from_owned_bytes(bytes: crate::directories::OwnedBytes) -> Self {
60 Self { raw_dict: bytes }
61 }
62
63 pub fn as_bytes(&self) -> &[u8] {
65 self.raw_dict.as_slice()
66 }
67
68 pub fn len(&self) -> usize {
70 self.raw_dict.len()
71 }
72
73 pub fn is_empty(&self) -> bool {
75 self.raw_dict.is_empty()
76 }
77}
78
79pub fn compress(data: &[u8], level: CompressionLevel) -> io::Result<Vec<u8>> {
81 zstd::encode_all(data, level.0).map_err(io::Error::other)
82}
83
84pub fn compress_with_dict(
86 data: &[u8],
87 level: CompressionLevel,
88 dict: &CompressionDict,
89) -> io::Result<Vec<u8>> {
90 let mut encoder = zstd::Encoder::with_dictionary(Vec::new(), level.0, dict.raw_dict.as_slice())
91 .map_err(io::Error::other)?;
92 encoder.write_all(data)?;
93 encoder.finish().map_err(io::Error::other)
94}
95
96const DECOMPRESS_CAPACITY: usize = 512 * 1024;
98
99pub fn decompress(data: &[u8]) -> io::Result<Vec<u8>> {
105 thread_local! {
106 static DECOMPRESSOR: std::cell::RefCell<zstd::bulk::Decompressor<'static>> =
107 std::cell::RefCell::new(zstd::bulk::Decompressor::new().unwrap());
108 }
109 DECOMPRESSOR.with(|dc| {
110 dc.borrow_mut()
111 .decompress(data, DECOMPRESS_CAPACITY)
112 .map_err(io::Error::other)
113 })
114}
115
116pub fn decompress_with_dict(data: &[u8], dict: &CompressionDict) -> io::Result<Vec<u8>> {
122 let mut decompressor = zstd::bulk::Decompressor::with_dictionary(dict.raw_dict.as_slice())
123 .map_err(io::Error::other)?;
124 decompressor
125 .decompress(data, DECOMPRESS_CAPACITY)
126 .map_err(io::Error::other)
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_roundtrip() {
135 let data = b"Hello, World! This is a test of compression.".repeat(100);
136 let compressed = compress(&data, CompressionLevel::default()).unwrap();
137 let decompressed = decompress(&compressed).unwrap();
138 assert_eq!(data, decompressed.as_slice());
139 assert!(compressed.len() < data.len());
140 }
141
142 #[test]
143 fn test_empty_data() {
144 let data: &[u8] = &[];
145 let compressed = compress(data, CompressionLevel::default()).unwrap();
146 let decompressed = decompress(&compressed).unwrap();
147 assert!(decompressed.is_empty());
148 }
149
150 #[test]
151 fn test_compression_levels() {
152 let data = b"Test data for compression levels".repeat(100);
153 for level in [1, 3, 9, 19] {
154 let compressed = compress(&data, CompressionLevel(level)).unwrap();
155 let decompressed = decompress(&compressed).unwrap();
156 assert_eq!(data.as_slice(), decompressed.as_slice());
157 }
158 }
159}