Nothing to see here, move along
1use crate::error::FsError;
2use crate::lz4;
3use lancer_core::fs::{BLOCK_SIZE_MAX_LOG2, BLOCK_SIZE_MIN_LOG2, Compression, CompressionPolicy};
4
5const LZ4_HASH_SIZE: usize = 1 << 12;
6
7pub struct CompressResult {
8 pub compressed_len: usize,
9 pub physical_log2: u8,
10 pub algorithm: Compression,
11 pub logical_size: u32,
12}
13
14pub fn compress_block(
15 data: &[u8],
16 effective_policy: Compression,
17 scratch: &mut [u8],
18 hash_table: &mut [u16; LZ4_HASH_SIZE],
19) -> CompressResult {
20 let logical_size = data.len() as u32;
21 let logical_log2 = smallest_pow2_log2(data.len());
22
23 match effective_policy {
24 Compression::None => CompressResult {
25 compressed_len: 0,
26 physical_log2: logical_log2,
27 algorithm: Compression::None,
28 logical_size,
29 },
30 Compression::Lz4 => match lz4::compress(data, scratch, hash_table) {
31 Some(compressed_len) if compressed_len <= data.len() / 2 => {
32 let physical_log2 = smallest_pow2_log2(compressed_len);
33 CompressResult {
34 compressed_len,
35 physical_log2,
36 algorithm: Compression::Lz4,
37 logical_size,
38 }
39 }
40 _ => CompressResult {
41 compressed_len: 0,
42 physical_log2: logical_log2,
43 algorithm: Compression::None,
44 logical_size,
45 },
46 },
47 }
48}
49
50pub fn decompress_block(
51 src: &[u8],
52 compression: Compression,
53 logical_size: u32,
54 dst: &mut [u8],
55) -> Result<usize, FsError> {
56 match compression {
57 Compression::None => {
58 let len = (logical_size as usize).min(src.len()).min(dst.len());
59 dst[..len].copy_from_slice(&src[..len]);
60 Ok(len)
61 }
62 Compression::Lz4 => {
63 let out_len = (logical_size as usize).min(dst.len());
64 lz4::decompress(src, &mut dst[..out_len])
65 }
66 }
67}
68
69pub fn effective_compression(
70 inode_policy: CompressionPolicy,
71 parent_effective: Compression,
72) -> Compression {
73 match inode_policy {
74 CompressionPolicy::Lz4 => Compression::Lz4,
75 CompressionPolicy::Disabled => Compression::None,
76 CompressionPolicy::Inherit => parent_effective,
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn compress_none_passthrough() {
86 let data = [0x42_u8; 4096];
87 let mut scratch = [0u8; 8192];
88 let mut table = [0u16; LZ4_HASH_SIZE];
89 let result = compress_block(&data, Compression::None, &mut scratch, &mut table);
90 assert_eq!(result.compressed_len, 0);
91 assert_eq!(result.algorithm, Compression::None);
92 assert_eq!(result.logical_size, 4096);
93 }
94
95 #[test]
96 fn compress_lz4_compressible_data() {
97 let data = [0u8; 4096];
98 let mut scratch = [0u8; 8192];
99 let mut table = [0u16; LZ4_HASH_SIZE];
100 let result = compress_block(&data, Compression::Lz4, &mut scratch, &mut table);
101 assert_eq!(result.algorithm, Compression::Lz4);
102 assert!(result.compressed_len > 0);
103 assert!(result.compressed_len <= 2048);
104 assert_eq!(result.logical_size, 4096);
105 }
106
107 #[test]
108 fn compress_lz4_incompressible_falls_back() {
109 let mut data = [0u8; 4096];
110 let mut state: u64 = 0xDEAD_BEEF_CAFE_BABE;
111 data.iter_mut().for_each(|byte| {
112 state = state
113 .wrapping_mul(6364136223846793005)
114 .wrapping_add(1442695040888963407);
115 *byte = (state >> 33) as u8;
116 });
117 let mut scratch = [0u8; 8192];
118 let mut table = [0u16; LZ4_HASH_SIZE];
119 let result = compress_block(&data, Compression::Lz4, &mut scratch, &mut table);
120 assert_eq!(result.algorithm, Compression::None);
121 assert_eq!(result.compressed_len, 0);
122 }
123
124 #[test]
125 fn decompress_none_copies_data() {
126 let src = [0xAB_u8; 4096];
127 let mut dst = [0u8; 4096];
128 let len = decompress_block(&src, Compression::None, 4096, &mut dst).unwrap();
129 assert_eq!(len, 4096);
130 assert_eq!(dst, src);
131 }
132
133 #[test]
134 fn compress_decompress_lz4_roundtrip() {
135 let mut data = [0u8; 4096];
136 (0..64).for_each(|i| {
137 data[i * 64..i * 64 + 8].copy_from_slice(&(i as u64).to_le_bytes());
138 data[i * 64 + 8..i * 64 + 64].fill(0xCC);
139 });
140
141 let mut scratch = [0u8; 8192];
142 let mut table = [0u16; LZ4_HASH_SIZE];
143 let result = compress_block(&data, Compression::Lz4, &mut scratch, &mut table);
144 match result.algorithm {
145 Compression::Lz4 => {
146 let mut decompressed = [0u8; 4096];
147 let dlen = decompress_block(
148 &scratch[..result.compressed_len],
149 Compression::Lz4,
150 result.logical_size,
151 &mut decompressed,
152 )
153 .unwrap();
154 assert_eq!(&decompressed[..dlen], &data[..dlen]);
155 }
156 Compression::None => {}
157 }
158 }
159
160 #[test]
161 fn effective_compression_inherit_uses_parent() {
162 assert_eq!(
163 effective_compression(CompressionPolicy::Inherit, Compression::Lz4),
164 Compression::Lz4
165 );
166 assert_eq!(
167 effective_compression(CompressionPolicy::Inherit, Compression::None),
168 Compression::None
169 );
170 }
171
172 #[test]
173 fn effective_compression_explicit_overrides() {
174 assert_eq!(
175 effective_compression(CompressionPolicy::Lz4, Compression::None),
176 Compression::Lz4
177 );
178 assert_eq!(
179 effective_compression(CompressionPolicy::Disabled, Compression::Lz4),
180 Compression::None
181 );
182 }
183
184 #[test]
185 fn smallest_pow2_log2_boundary_values() {
186 assert_eq!(smallest_pow2_log2(1), BLOCK_SIZE_MIN_LOG2);
187 assert_eq!(smallest_pow2_log2(4096), BLOCK_SIZE_MIN_LOG2);
188 assert_eq!(smallest_pow2_log2(4097), BLOCK_SIZE_MIN_LOG2 + 1);
189 assert_eq!(smallest_pow2_log2(65536), BLOCK_SIZE_MAX_LOG2);
190 assert_eq!(smallest_pow2_log2(65537), BLOCK_SIZE_MAX_LOG2);
191 }
192}
193
194fn smallest_pow2_log2(size: usize) -> u8 {
195 (BLOCK_SIZE_MIN_LOG2..=BLOCK_SIZE_MAX_LOG2)
196 .find(|&log2| (1usize << log2) >= size)
197 .unwrap_or(BLOCK_SIZE_MAX_LOG2)
198}