1use crate::{AvroResult, Error, error::Details, types::Value};
21use strum::{EnumIter, EnumString, IntoStaticStr};
22
23#[derive(Clone, Copy, Eq, PartialEq, Debug)]
25pub struct DeflateSettings {
26 compression_level: miniz_oxide::deflate::CompressionLevel,
27}
28
29impl DeflateSettings {
30 pub fn new(compression_level: miniz_oxide::deflate::CompressionLevel) -> Self {
31 DeflateSettings { compression_level }
32 }
33
34 pub fn compression_level(&self) -> u8 {
37 self.compression_level as u8
38 }
39}
40
41impl Default for DeflateSettings {
42 fn default() -> Self {
44 Self::new(miniz_oxide::deflate::CompressionLevel::DefaultCompression)
45 }
46}
47
48#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, EnumString, IntoStaticStr)]
50#[strum(serialize_all = "kebab_case")]
51pub enum Codec {
52 Null,
54 Deflate(DeflateSettings),
58 #[cfg(feature = "snappy")]
59 Snappy,
63 #[cfg(feature = "zstandard")]
64 Zstandard(zstandard::ZstandardSettings),
66 #[cfg(feature = "bzip")]
67 Bzip2(bzip::Bzip2Settings),
70 #[cfg(feature = "xz")]
71 Xz(xz::XzSettings),
74}
75
76impl From<Codec> for Value {
77 fn from(value: Codec) -> Self {
78 Self::Bytes(<&str>::from(value).as_bytes().to_vec())
79 }
80}
81
82impl Codec {
83 pub fn compress(self, stream: &mut Vec<u8>) -> AvroResult<()> {
85 match self {
86 Codec::Null => (),
87 Codec::Deflate(settings) => {
88 let compressed =
89 miniz_oxide::deflate::compress_to_vec(stream, settings.compression_level());
90 *stream = compressed;
91 }
92 #[cfg(feature = "snappy")]
93 Codec::Snappy => {
94 let mut encoded: Vec<u8> = vec![0; snap::raw::max_compress_len(stream.len())];
95 let compressed_size = snap::raw::Encoder::new()
96 .compress(&stream[..], &mut encoded[..])
97 .map_err(Details::SnappyCompress)?;
98
99 let mut hasher = crc32fast::Hasher::new();
100 hasher.update(&stream[..]);
101 let checksum = hasher.finalize();
102 let checksum_as_bytes = checksum.to_be_bytes();
103 let checksum_len = checksum_as_bytes.len();
104 encoded.truncate(compressed_size + checksum_len);
105 encoded[compressed_size..].copy_from_slice(&checksum_as_bytes);
106
107 *stream = encoded;
108 }
109 #[cfg(feature = "zstandard")]
110 Codec::Zstandard(settings) => {
111 use std::io::Write;
112 let mut encoder = zstd::Encoder::new(Vec::new(), settings.compression_level as i32)
113 .map_err(Details::ZstdCompress)?;
114 encoder.write_all(stream).map_err(Details::ZstdCompress)?;
115 *stream = encoder.finish().map_err(Details::ZstdCompress)?;
116 }
117 #[cfg(feature = "bzip")]
118 Codec::Bzip2(settings) => {
119 use bzip2::read::BzEncoder;
120 use std::io::Read;
121
122 let mut encoder = BzEncoder::new(&stream[..], settings.compression());
123 let mut buffer = Vec::new();
124 encoder
125 .read_to_end(&mut buffer)
126 .unwrap_or_else(|_| unreachable!("No I/O errors possible with Vec<u8>"));
127 *stream = buffer;
128 }
129 #[cfg(feature = "xz")]
130 Codec::Xz(settings) => {
131 use liblzma::read::XzEncoder;
132 use std::io::Read;
133
134 let mut encoder = XzEncoder::new(&stream[..], settings.compression_level as u32);
135 let mut buffer = Vec::new();
136 encoder
137 .read_to_end(&mut buffer)
138 .unwrap_or_else(|_| unreachable!("No I/O errors possible with Vec<u8>"));
139 *stream = buffer;
140 }
141 };
142
143 Ok(())
144 }
145
146 pub fn decompress(self, stream: &mut Vec<u8>) -> AvroResult<()> {
148 let max_bytes =
152 crate::util::max_allocation_bytes(crate::util::DEFAULT_MAX_ALLOCATION_BYTES);
153 *stream = match self {
154 Codec::Null => return Ok(()),
155 Codec::Deflate(_settings) => miniz_oxide::inflate::decompress_to_vec_with_limit(stream, max_bytes).map_err(|e| {
156 use std::io::ErrorKind;
157 use miniz_oxide::inflate::TINFLStatus;
158
159 let details = match e.status {
160 TINFLStatus::FailedCannotMakeProgress | TINFLStatus::NeedsMoreInput => Details::DeflateDecompress(ErrorKind::UnexpectedEof.into()),
161 TINFLStatus::Adler32Mismatch | TINFLStatus::Failed | TINFLStatus::BadParam => Details::DeflateDecompress(ErrorKind::InvalidData.into()),
162 TINFLStatus::Done => Details::DeflateDecompress(std::io::Error::other("Unexpected error: miniz_oxide reported an error with a success status. Please report this to avro-rs developers.")),
163 TINFLStatus::HasMoreOutput => Details::MemoryAllocation {
165 desired: None,
166 maximum: max_bytes,
167 },
168 other => Details::DeflateDecompress(std::io::Error::other(format!("Unexpected error: {other:?}")))
169 };
170 Error::new(details)
171 })?,
172 #[cfg(feature = "snappy")]
173 Codec::Snappy => {
174 let data_end = stream
177 .len()
178 .checked_sub(4)
179 .ok_or(Details::BadSnappyLength(stream.len()))?;
180 let decompressed_size = snap::raw::decompress_len(&stream[..data_end])
181 .map_err(Details::GetSnappyDecompressLen)?;
182 let decompressed_size = crate::util::safe_len(decompressed_size)?;
185 let mut decoded = vec![0; decompressed_size];
186 snap::raw::Decoder::new()
187 .decompress(&stream[..data_end], &mut decoded[..])
188 .map_err(Details::SnappyDecompress)?;
189
190 let mut last_four: [u8; 4] = [0; 4];
191 last_four.copy_from_slice(&stream[data_end..]);
192 let expected: u32 = u32::from_be_bytes(last_four);
193
194 let mut hasher = crc32fast::Hasher::new();
195 hasher.update(&decoded);
196 let actual = hasher.finalize();
197
198 if expected != actual {
199 return Err(Details::SnappyCrc32{expected, actual}.into());
200 }
201 decoded
202 }
203 #[cfg(feature = "zstandard")]
204 Codec::Zstandard(_settings) => {
205 use std::io::{BufReader, Read};
206 use zstd::zstd_safe;
207
208 let mut decoded = Vec::new();
209 let buffer_size = zstd_safe::DCtx::in_size();
210 let buffer = BufReader::with_capacity(buffer_size, &stream[..]);
211 let decoder = zstd::Decoder::new(buffer).map_err(Details::ZstdDecompress)?;
212 decoder
215 .take((max_bytes as u64).saturating_add(1))
216 .read_to_end(&mut decoded)
217 .map_err(Details::ZstdDecompress)?;
218 if decoded.len() > max_bytes {
219 return Err(Details::MemoryAllocation { desired: None, maximum: max_bytes }.into());
220 }
221 decoded
222 }
223 #[cfg(feature = "bzip")]
224 Codec::Bzip2(_) => {
225 use bzip2::read::BzDecoder;
226 use std::io::Read;
227
228 let mut decoded = Vec::new();
229 BzDecoder::new(&stream[..])
230 .take((max_bytes as u64).saturating_add(1))
231 .read_to_end(&mut decoded)
232 .map_err(Details::Bzip2Decompress)?;
233 if decoded.len() > max_bytes {
234 return Err(Details::MemoryAllocation { desired: None, maximum: max_bytes }.into());
235 }
236 decoded
237 }
238 #[cfg(feature = "xz")]
239 Codec::Xz(_) => {
240 use liblzma::read::XzDecoder;
241 use std::io::Read;
242
243 let mut decoded: Vec<u8> = Vec::new();
244 XzDecoder::new(&stream[..])
245 .take((max_bytes as u64).saturating_add(1))
246 .read_to_end(&mut decoded)
247 .map_err(Details::XzDecompress)?;
248 if decoded.len() > max_bytes {
249 return Err(Details::MemoryAllocation { desired: None, maximum: max_bytes }.into());
250 }
251 decoded
252 }
253 };
254 Ok(())
255 }
256}
257
258#[cfg(feature = "bzip")]
259pub mod bzip {
260 use bzip2::Compression;
261
262 #[derive(Clone, Copy, Eq, PartialEq, Debug)]
263 pub struct Bzip2Settings {
264 pub compression_level: u8,
265 }
266
267 impl Bzip2Settings {
268 pub fn new(compression_level: u8) -> Self {
269 Self { compression_level }
270 }
271
272 pub(crate) fn compression(&self) -> Compression {
273 Compression::new(self.compression_level as u32)
274 }
275 }
276
277 impl Default for Bzip2Settings {
278 fn default() -> Self {
279 Bzip2Settings::new(Compression::best().level() as u8)
280 }
281 }
282}
283
284#[cfg(feature = "zstandard")]
285pub mod zstandard {
286 #[derive(Clone, Copy, Eq, PartialEq, Debug)]
287 pub struct ZstandardSettings {
288 pub compression_level: u8,
289 }
290
291 impl ZstandardSettings {
292 pub fn new(compression_level: u8) -> Self {
293 Self { compression_level }
294 }
295 }
296
297 impl Default for ZstandardSettings {
298 fn default() -> Self {
299 Self::new(0)
300 }
301 }
302}
303
304#[cfg(feature = "xz")]
305pub mod xz {
306 #[derive(Clone, Copy, Eq, PartialEq, Debug)]
307 pub struct XzSettings {
308 pub compression_level: u8,
309 }
310
311 impl XzSettings {
312 pub fn new(compression_level: u8) -> Self {
313 Self { compression_level }
314 }
315 }
316
317 impl Default for XzSettings {
318 fn default() -> Self {
319 XzSettings::new(9)
320 }
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use apache_avro_test_helper::TestResult;
328 use miniz_oxide::deflate::CompressionLevel;
329 use pretty_assertions::{assert_eq, assert_ne};
330
331 const INPUT: &[u8] = b"theanswertolifetheuniverseandeverythingis42theanswertolifetheuniverseandeverythingis4theanswertolifetheuniverseandeverythingis2";
332
333 #[test]
334 fn null_compress_and_decompress() -> TestResult {
335 let codec = Codec::Null;
336 let mut stream = INPUT.to_vec();
337 codec.compress(&mut stream)?;
338 assert_eq!(INPUT, stream.as_slice());
339 codec.decompress(&mut stream)?;
340 assert_eq!(INPUT, stream.as_slice());
341 Ok(())
342 }
343
344 #[test]
345 fn deflate_compress_and_decompress() -> TestResult {
346 compress_and_decompress(Codec::Deflate(DeflateSettings::new(
347 CompressionLevel::BestCompression,
348 )))
349 }
350
351 #[cfg(feature = "snappy")]
352 #[test]
353 fn snappy_compress_and_decompress() -> TestResult {
354 compress_and_decompress(Codec::Snappy)
355 }
356
357 #[cfg(feature = "snappy")]
358 #[test]
359 fn snappy_decompress_short_block_errors_without_panicking() {
360 for len in 0..4usize {
363 let mut stream = vec![0u8; len];
364 let result = Codec::Snappy.decompress(&mut stream);
365 assert!(result.is_err(), "len={len} should error, got {result:?}");
366 }
367 }
368
369 #[cfg(feature = "zstandard")]
370 #[test]
371 fn zstd_compress_and_decompress() -> TestResult {
372 compress_and_decompress(Codec::Zstandard(zstandard::ZstandardSettings::default()))
373 }
374
375 #[cfg(feature = "bzip")]
376 #[test]
377 fn bzip_compress_and_decompress() -> TestResult {
378 compress_and_decompress(Codec::Bzip2(bzip::Bzip2Settings::default()))
379 }
380
381 #[cfg(feature = "xz")]
382 #[test]
383 fn xz_compress_and_decompress() -> TestResult {
384 compress_and_decompress(Codec::Xz(xz::XzSettings::default()))
385 }
386
387 fn compress_and_decompress(codec: Codec) -> TestResult {
388 let mut stream = INPUT.to_vec();
389 codec.compress(&mut stream)?;
390 assert_ne!(INPUT, stream.as_slice());
391 assert!(INPUT.len() > stream.len());
392 codec.decompress(&mut stream)?;
393 assert_eq!(INPUT, stream.as_slice());
394 Ok(())
395 }
396
397 #[test]
398 fn codec_to_str() {
399 assert_eq!(<&str>::from(Codec::Null), "null");
400 assert_eq!(
401 <&str>::from(Codec::Deflate(DeflateSettings::default())),
402 "deflate"
403 );
404
405 #[cfg(feature = "snappy")]
406 assert_eq!(<&str>::from(Codec::Snappy), "snappy");
407
408 #[cfg(feature = "zstandard")]
409 assert_eq!(
410 <&str>::from(Codec::Zstandard(zstandard::ZstandardSettings::default())),
411 "zstandard"
412 );
413
414 #[cfg(feature = "bzip")]
415 assert_eq!(
416 <&str>::from(Codec::Bzip2(bzip::Bzip2Settings::default())),
417 "bzip2"
418 );
419
420 #[cfg(feature = "xz")]
421 assert_eq!(<&str>::from(Codec::Xz(xz::XzSettings::default())), "xz");
422 }
423
424 #[test]
425 fn codec_from_str() {
426 use std::str::FromStr;
427
428 assert_eq!(Codec::from_str("null").unwrap(), Codec::Null);
429 assert_eq!(
430 Codec::from_str("deflate").unwrap(),
431 Codec::Deflate(DeflateSettings::default())
432 );
433
434 #[cfg(feature = "snappy")]
435 assert_eq!(Codec::from_str("snappy").unwrap(), Codec::Snappy);
436
437 #[cfg(feature = "zstandard")]
438 assert_eq!(
439 Codec::from_str("zstandard").unwrap(),
440 Codec::Zstandard(zstandard::ZstandardSettings::default())
441 );
442
443 #[cfg(feature = "bzip")]
444 assert_eq!(
445 Codec::from_str("bzip2").unwrap(),
446 Codec::Bzip2(bzip::Bzip2Settings::default())
447 );
448
449 #[cfg(feature = "xz")]
450 assert_eq!(
451 Codec::from_str("xz").unwrap(),
452 Codec::Xz(xz::XzSettings::default())
453 );
454
455 assert!(Codec::from_str("not a codec").is_err());
456 }
457}