1use crate::config::EngineConfiguration;
4use crate::format::{BlockFlags, BlockHeader};
5use crush_core::error::{CrushError, Result};
6use libdeflater::{CompressionLvl, Compressor, Decompressor};
7
8pub struct CompressedBlock {
10 pub header: BlockHeader,
11 pub payload: Vec<u8>,
12}
13
14pub(crate) fn resolve_compression_level(level: u8) -> Result<CompressionLvl> {
23 CompressionLvl::new(i32::from(level))
24 .map_err(|_| CrushError::InvalidConfig(format!("invalid compression level {level}")))
25}
26
27pub fn compress_block(
36 compressor: &mut Compressor,
37 input: &[u8],
38 block_index: usize,
39 config: &EngineConfiguration,
40) -> Result<CompressedBlock> {
41 let checksum = if config.checksums {
42 crc32fast::hash(input)
43 } else {
44 0
45 };
46
47 let in_len = input.len();
48 let uncompressed_size = u32::try_from(in_len).map_err(|_| {
49 CrushError::InvalidConfig(format!(
50 "block {block_index} input length {in_len} exceeds u32::MAX"
51 ))
52 })?;
53
54 let buf_size = compressor.deflate_compress_bound(in_len);
56 let mut compressed = vec![0u8; buf_size];
57
58 let bytes_written = compressor
59 .deflate_compress(input, &mut compressed)
60 .map_err(|e| {
61 CrushError::InvalidFormat(format!(
62 "DEFLATE encode error at block {block_index}: {e:?}"
63 ))
64 })?;
65 debug_assert!(
66 bytes_written <= buf_size,
67 "libdeflater returned {bytes_written} > capacity {buf_size}"
68 );
69 compressed.truncate(bytes_written);
70
71 #[allow(clippy::cast_precision_loss)]
74 let use_stored =
75 in_len > 0 && (compressed.len() as f64 / in_len as f64) > config.max_expansion_ratio;
76
77 let (payload, flags, cs) = if use_stored {
78 (
79 input.to_vec(),
80 BlockFlags::default().with_stored(),
81 uncompressed_size,
82 )
83 } else {
84 let cs = u32::try_from(compressed.len()).map_err(|_| {
85 CrushError::InvalidConfig(format!("compressed block {block_index} exceeds u32::MAX"))
86 })?;
87 (compressed, BlockFlags::default(), cs)
88 };
89
90 Ok(CompressedBlock {
91 header: BlockHeader {
92 compressed_size: cs,
93 uncompressed_size,
94 checksum,
95 flags,
96 },
97 payload,
98 })
99}
100
101pub fn decompress_block_into(
115 decompressor: &mut Decompressor,
116 header: &BlockHeader,
117 payload: &[u8],
118 output: &mut [u8],
119 block_index: u64,
120 checksums_enabled: bool,
121) -> Result<()> {
122 let expected_size = header.uncompressed_size as usize;
123 if output.len() != expected_size {
124 return Err(CrushError::InvalidFormat(format!(
125 "block {block_index} output buffer size {} != header uncompressed_size {expected_size}",
126 output.len()
127 )));
128 }
129
130 if header.flags.stored() {
131 if payload.len() != expected_size {
132 return Err(CrushError::InvalidFormat(format!(
133 "stored block {block_index} payload length {} != uncompressed_size {expected_size}",
134 payload.len()
135 )));
136 }
137 output.copy_from_slice(payload);
138 } else {
139 let bytes_out = decompressor
140 .deflate_decompress(payload, output)
141 .map_err(|e| {
142 CrushError::InvalidFormat(format!(
143 "DEFLATE decode error at block {block_index}: {e:?}"
144 ))
145 })?;
146 if bytes_out != expected_size {
147 return Err(CrushError::InvalidFormat(format!(
148 "block {block_index} uncompressed size mismatch: header {expected_size} vs decoded {bytes_out}"
149 )));
150 }
151 }
152
153 if checksums_enabled && header.checksum != 0 {
154 let actual = crc32fast::hash(output);
155 if actual != header.checksum {
156 return Err(CrushError::ChecksumMismatch {
157 block_index,
158 expected: header.checksum,
159 actual,
160 });
161 }
162 }
163
164 Ok(())
165}
166
167pub fn decompress_block_payload(
177 decompressor: &mut Decompressor,
178 header: &BlockHeader,
179 payload: &[u8],
180 block_index: u64,
181 checksums_enabled: bool,
182) -> Result<Vec<u8>> {
183 let expected_size = header.uncompressed_size as usize;
184 let mut out = vec![0u8; expected_size];
185 decompress_block_into(
186 decompressor,
187 header,
188 payload,
189 &mut out,
190 block_index,
191 checksums_enabled,
192 )?;
193 Ok(out)
194}