Nothing to see here, move along
at main 198 lines 6.4 kB view raw
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}