nydus_utils/compress/
mod.rs

1// Copyright 2020 Ant Group. All rights reserved.
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use std::borrow::Cow;
6use std::convert::TryFrom;
7use std::fmt;
8use std::io::{BufReader, Error, Read, Result, Write};
9use std::str::FromStr;
10
11mod lz4_standard;
12use self::lz4_standard::*;
13
14#[cfg(feature = "zran")]
15pub mod zlib_random;
16
17const COMPRESSION_MINIMUM_RATIO: usize = 100;
18
19/// Supported compression algorithms.
20#[repr(u32)]
21#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
22pub enum Algorithm {
23    #[default]
24    None = 0,
25    Lz4Block = 1,
26    GZip = 2,
27    Zstd = 3,
28}
29
30impl fmt::Display for Algorithm {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        let output = match self {
33            Algorithm::None => "none",
34            Algorithm::Lz4Block => "lz4_block",
35            Algorithm::GZip => "gzip",
36            Algorithm::Zstd => "zstd",
37        };
38        write!(f, "{}", output)
39    }
40}
41
42impl FromStr for Algorithm {
43    type Err = Error;
44
45    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
46        match s {
47            "none" => Ok(Self::None),
48            "lz4_block" => Ok(Self::Lz4Block),
49            "gzip" => Ok(Self::GZip),
50            "zstd" => Ok(Self::Zstd),
51            _ => Err(einval!("compression algorithm should be none or lz4_block")),
52        }
53    }
54}
55
56impl TryFrom<u32> for Algorithm {
57    type Error = ();
58
59    fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
60        if value == Algorithm::None as u32 {
61            Ok(Algorithm::None)
62        } else if value == Algorithm::Lz4Block as u32 {
63            Ok(Algorithm::Lz4Block)
64        } else if value == Algorithm::GZip as u32 {
65            Ok(Algorithm::GZip)
66        } else if value == Algorithm::Zstd as u32 {
67            Ok(Algorithm::Zstd)
68        } else {
69            Err(())
70        }
71    }
72}
73
74impl TryFrom<u64> for Algorithm {
75    type Error = ();
76
77    fn try_from(value: u64) -> std::result::Result<Self, Self::Error> {
78        if value == Algorithm::None as u64 {
79            Ok(Algorithm::None)
80        } else if value == Algorithm::Lz4Block as u64 {
81            Ok(Algorithm::Lz4Block)
82        } else if value == Algorithm::GZip as u64 {
83            Ok(Algorithm::GZip)
84        } else if value == Algorithm::Zstd as u64 {
85            Ok(Algorithm::Zstd)
86        } else {
87            Err(())
88        }
89    }
90}
91
92impl Algorithm {
93    /// Check whether the compression algorithm is none.
94    pub fn is_none(self) -> bool {
95        self == Self::None
96    }
97}
98
99/// Compress data with the specified compression algorithm.
100pub fn compress(src: &[u8], algorithm: Algorithm) -> Result<(Cow<[u8]>, bool)> {
101    let src_size = src.len();
102    if src_size == 0 {
103        return Ok((Cow::Borrowed(src), false));
104    }
105
106    let compressed = match algorithm {
107        Algorithm::None => return Ok((Cow::Borrowed(src), false)),
108        Algorithm::Lz4Block => lz4_compress(src)?,
109        Algorithm::GZip => {
110            let dst: Vec<u8> = Vec::new();
111            let mut gz = flate2::write::GzEncoder::new(dst, flate2::Compression::default());
112            gz.write_all(src)?;
113            gz.finish()?
114        }
115        Algorithm::Zstd => zstd_compress(src)?,
116    };
117
118    // Abandon compressed data when compression ratio greater than COMPRESSION_MINIMUM_RATIO
119    if (COMPRESSION_MINIMUM_RATIO == 100 && compressed.len() >= src_size)
120        || ((100 * compressed.len() / src_size) >= COMPRESSION_MINIMUM_RATIO)
121    {
122        Ok((Cow::Borrowed(src), false))
123    } else {
124        Ok((Cow::Owned(compressed), true))
125    }
126}
127
128/// Decompress a source slice or file stream into destination slice, with provided compression algorithm.
129/// Use the file as decompress source if provided.
130pub fn decompress(src: &[u8], dst: &mut [u8], algorithm: Algorithm) -> Result<usize> {
131    match algorithm {
132        Algorithm::None => {
133            assert_eq!(src.len(), dst.len());
134            dst.copy_from_slice(src);
135            Ok(dst.len())
136        }
137        Algorithm::Lz4Block => lz4_decompress(src, dst),
138        Algorithm::GZip => {
139            let mut gz = flate2::bufread::GzDecoder::new(src);
140            gz.read_exact(dst)?;
141            Ok(dst.len())
142        }
143        Algorithm::Zstd => zstd::bulk::decompress_to_buffer(src, dst),
144    }
145}
146
147#[allow(clippy::large_enum_variant)]
148/// Stream decoder for gzip/lz4/zstd.
149pub enum Decoder<'a, R: Read> {
150    None(R),
151    Gzip(flate2::bufread::MultiGzDecoder<BufReader<R>>),
152    Zstd(zstd::stream::Decoder<'a, BufReader<R>>),
153}
154
155impl<R: Read> Decoder<'_, R> {
156    /// Create a new instance of `Decoder`.
157    pub fn new(reader: R, algorithm: Algorithm) -> Result<Self> {
158        let decoder = match algorithm {
159            Algorithm::None => Decoder::None(reader),
160            Algorithm::GZip => {
161                Decoder::Gzip(flate2::bufread::MultiGzDecoder::new(BufReader::new(reader)))
162            }
163            Algorithm::Lz4Block => panic!("Decoder doesn't support lz4_block"),
164            Algorithm::Zstd => Decoder::Zstd(zstd::stream::Decoder::new(reader)?),
165        };
166        Ok(decoder)
167    }
168}
169
170impl<R: Read> Read for Decoder<'_, R> {
171    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
172        match self {
173            Decoder::None(r) => r.read(buf),
174            Decoder::Gzip(r) => r.read(buf),
175            Decoder::Zstd(r) => r.read(buf),
176        }
177    }
178}
179
180/// Stream decoder for zlib/gzip.
181pub struct ZlibDecoder<R> {
182    stream: flate2::bufread::MultiGzDecoder<BufReader<R>>,
183}
184
185impl<R: Read> ZlibDecoder<R> {
186    /// Create a new instance of `ZlibDecoder`.
187    pub fn new(reader: R) -> Self {
188        ZlibDecoder {
189            stream: flate2::bufread::MultiGzDecoder::new(BufReader::new(reader)),
190        }
191    }
192}
193
194impl<R: Read> Read for ZlibDecoder<R> {
195    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
196        self.stream.read(buf)
197    }
198}
199
200/// Estimate the maximum compressed data size from uncompressed data size.
201///
202/// Gzip is special that it doesn't carry compress_size. We need to read the maximum possible size
203/// of compressed data for `chunk_decompress_size`, and try to decompress `chunk_decompress_size`
204/// bytes of data out of it.
205//
206// Per man(1) gzip
207// The worst case expansion is a few bytes for the gzip file header, plus 5 bytes every 32K block,
208// or an expansion ratio of 0.015% for large files.
209//
210// Per http://www.zlib.org/rfc-gzip.html#header-trailer, each member has the following structure:
211// +---+---+---+---+---+---+---+---+---+---+
212// |ID1|ID2|CM |FLG|     MTIME     |XFL|OS | (more-->)
213// +---+---+---+---+---+---+---+---+---+---+
214// (if FLG.FEXTRA set)
215// +---+---+=================================+
216// | XLEN  |...XLEN bytes of "extra field"...| (more-->)
217// +---+---+=================================+
218// (if FLG.FNAME set)
219// +=========================================+
220// |...original file name, zero-terminated...| (more-->)
221// +=========================================+
222// (if FLG.FCOMMENT set)
223// +===================================+
224// |...file comment, zero-terminated...| (more-->)
225// +===================================+
226// (if FLG.FHCRC set)
227// +---+---+
228// | CRC16 |
229// +---+---+
230// +=======================+
231// |...compressed blocks...| (more-->)
232// +=======================+
233//   0   1   2   3   4   5   6   7
234// +---+---+---+---+---+---+---+---+
235// |     CRC32     |     ISIZE     |
236// +---+---+---+---+---+---+---+---+
237// gzip head+footer is at least 10+8 bytes, stargz header doesn't include any flags
238// so it's 18 bytes. Let's read at least 128 bytes more, to allow the decompressor to
239// find out end of the gzip stream.
240//
241// Ideally we should introduce a streaming cache for stargz that maintains internal
242// chunks and expose stream APIs.
243pub fn compute_compressed_gzip_size(size: usize, max_size: usize) -> usize {
244    let size = size + 10 + 8 + 5 + (size / (16 << 10)) * 5 + 128;
245
246    std::cmp::min(size, max_size)
247}
248
249fn zstd_compress(src: &[u8]) -> Result<Vec<u8>> {
250    zstd::bulk::compress(src, zstd::DEFAULT_COMPRESSION_LEVEL)
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use std::fs::OpenOptions;
257    use std::io::{Seek, SeekFrom};
258    use std::path::Path;
259    use std::str::FromStr;
260    use vmm_sys_util::tempfile::TempFile;
261
262    #[test]
263    fn test_compress_algorithm_gzip() {
264        let buf = vec![0x2u8; 4095];
265        let compressed = compress(&buf, Algorithm::GZip).unwrap();
266        assert!(compressed.1);
267        let (compressed, _) = compressed;
268        assert_ne!(compressed.len(), 0);
269
270        let mut decompressed = vec![0; buf.len()];
271        let sz = decompress(&compressed, decompressed.as_mut_slice(), Algorithm::GZip).unwrap();
272        assert_eq!(sz, 4095);
273        assert_eq!(buf, decompressed);
274
275        let mut tmp_file = TempFile::new().unwrap().into_file();
276        tmp_file.write_all(&compressed).unwrap();
277        tmp_file.seek(SeekFrom::Start(0)).unwrap();
278
279        let mut decompressed = vec![0; buf.len()];
280        let mut decoder = Decoder::new(tmp_file, Algorithm::GZip).unwrap();
281        decoder.read_exact(decompressed.as_mut_slice()).unwrap();
282        assert_eq!(sz, 4095);
283        assert_eq!(buf, decompressed);
284    }
285
286    #[test]
287    fn test_compress_algorithm_none() {
288        let buf = [
289            0x1u8, 0x2u8, 0x3u8, 0x4u8, 0x1u8, 0x2u8, 0x3u8, 0x4u8, 0x1u8, 0x2u8, 0x3u8, 0x4u8,
290            0x1u8, 0x2u8, 0x3u8, 0x4u8,
291        ];
292        let mut dst = [0x0u8; 16];
293        let (compressed, _) = compress(&buf, Algorithm::None).unwrap();
294        assert_eq!(buf.to_vec(), compressed.to_vec());
295        let _len = decompress(&buf, &mut dst, Algorithm::None).unwrap();
296        assert_eq!(dst.to_vec(), compressed.to_vec());
297    }
298
299    #[test]
300    fn test_compress_algorithm_ztsd() {
301        let buf = vec![0x2u8; 4097];
302        let mut decompressed = vec![0; buf.len()];
303        let (compressed, _) = compress(&buf, Algorithm::Zstd).unwrap();
304        let sz = decompress(&compressed, decompressed.as_mut_slice(), Algorithm::Zstd).unwrap();
305        assert_eq!(sz, 4097);
306        assert_eq!(buf, decompressed);
307    }
308
309    #[test]
310    fn test_compress_algorithm_lz4() {
311        let buf = [
312            0x1u8, 0x2u8, 0x3u8, 0x4u8, 0x1u8, 0x2u8, 0x3u8, 0x4u8, 0x1u8, 0x2u8, 0x3u8, 0x4u8,
313            0x1u8, 0x2u8, 0x3u8, 0x4u8,
314        ];
315        let mut decompressed = vec![0; buf.len()];
316        let (compressed, _) = compress(&buf, Algorithm::Lz4Block).unwrap();
317        let _len = decompress(
318            &compressed,
319            decompressed.as_mut_slice(),
320            Algorithm::Lz4Block,
321        )
322        .unwrap();
323        assert_eq!(decompressed.to_vec(), buf.to_vec());
324    }
325
326    #[test]
327    fn test_lz4_compress_decompress_1_byte() {
328        let buf = vec![0x1u8];
329        let compressed = lz4_compress(&buf).unwrap();
330        let mut decompressed = vec![0; buf.len()];
331        let sz = decompress(
332            &compressed,
333            decompressed.as_mut_slice(),
334            Algorithm::Lz4Block,
335        )
336        .unwrap();
337
338        assert_eq!(sz, 1);
339        assert_eq!(buf, decompressed);
340    }
341
342    #[test]
343    fn test_lz4_compress_decompress_2_bytes() {
344        let buf = vec![0x2u8, 0x3u8];
345        let compressed = lz4_compress(&buf).unwrap();
346        let mut decompressed = vec![0; buf.len()];
347        let sz = decompress(
348            &compressed,
349            decompressed.as_mut_slice(),
350            Algorithm::Lz4Block,
351        )
352        .unwrap();
353
354        assert_eq!(sz, 2);
355        assert_eq!(buf, decompressed);
356    }
357
358    #[test]
359    fn test_lz4_compress_decompress_16_bytes() {
360        let buf = [
361            0x1u8, 0x2u8, 0x3u8, 0x4u8, 0x1u8, 0x2u8, 0x3u8, 0x4u8, 0x1u8, 0x2u8, 0x3u8, 0x4u8,
362            0x1u8, 0x2u8, 0x3u8, 0x4u8,
363        ];
364        let compressed = lz4_compress(&buf).unwrap();
365        let mut decompressed = vec![0; buf.len()];
366        let sz = decompress(
367            &compressed,
368            decompressed.as_mut_slice(),
369            Algorithm::Lz4Block,
370        )
371        .unwrap();
372
373        assert_eq!(sz, 16);
374        assert_eq!(&buf, decompressed.as_slice());
375    }
376
377    #[test]
378    fn test_lz4_compress_decompress_4095_bytes() {
379        let buf = vec![0x2u8; 4095];
380        let compressed = lz4_compress(&buf).unwrap();
381        let mut decompressed = vec![0; buf.len()];
382        let sz = decompress(
383            &compressed,
384            decompressed.as_mut_slice(),
385            Algorithm::Lz4Block,
386        )
387        .unwrap();
388
389        assert_eq!(sz, 4095);
390        assert_eq!(buf, decompressed);
391    }
392
393    #[test]
394    fn test_lz4_compress_decompress_4096_bytes() {
395        let buf = vec![0x2u8; 4096];
396        let compressed = lz4_compress(&buf).unwrap();
397        let mut decompressed = vec![0; buf.len()];
398        let sz = decompress(
399            &compressed,
400            decompressed.as_mut_slice(),
401            Algorithm::Lz4Block,
402        )
403        .unwrap();
404
405        assert_eq!(sz, 4096);
406        assert_eq!(buf, decompressed);
407    }
408
409    #[test]
410    fn test_lz4_compress_decompress_4097_bytes() {
411        let buf = vec![0x2u8; 4097];
412        let compressed = lz4_compress(&buf).unwrap();
413        let mut decompressed = vec![0; buf.len()];
414        let sz = decompress(
415            &compressed,
416            decompressed.as_mut_slice(),
417            Algorithm::Lz4Block,
418        )
419        .unwrap();
420
421        assert_eq!(sz, 4097);
422        assert_eq!(buf, decompressed);
423    }
424
425    #[test]
426    fn test_zstd_compress_decompress_1_byte() {
427        let buf = vec![0x1u8];
428        let compressed = zstd_compress(&buf).unwrap();
429        let mut decompressed = vec![0; buf.len()];
430        let sz = decompress(&compressed, decompressed.as_mut_slice(), Algorithm::Zstd).unwrap();
431
432        assert_eq!(sz, 1);
433        assert_eq!(buf, decompressed);
434    }
435
436    #[test]
437    fn test_zstd_compress_decompress_2_bytes() {
438        let buf = vec![0x2u8, 0x3u8];
439        let compressed = zstd_compress(&buf).unwrap();
440        let mut decompressed = vec![0; buf.len()];
441        let sz = decompress(&compressed, decompressed.as_mut_slice(), Algorithm::Zstd).unwrap();
442
443        assert_eq!(sz, 2);
444        assert_eq!(buf, decompressed);
445    }
446
447    #[test]
448    fn test_zstd_compress_decompress_16_bytes() {
449        let buf = [
450            0x1u8, 0x2u8, 0x3u8, 0x4u8, 0x1u8, 0x2u8, 0x3u8, 0x4u8, 0x1u8, 0x2u8, 0x3u8, 0x4u8,
451            0x1u8, 0x2u8, 0x3u8, 0x4u8,
452        ];
453        let compressed = zstd_compress(&buf).unwrap();
454        let mut decompressed = vec![0; buf.len()];
455        let sz = decompress(&compressed, decompressed.as_mut_slice(), Algorithm::Zstd).unwrap();
456
457        assert_eq!(sz, 16);
458        assert_eq!(&buf, decompressed.as_slice());
459    }
460
461    #[test]
462    fn test_zstd_compress_decompress_4095_bytes() {
463        let buf = vec![0x2u8; 4095];
464        let compressed = zstd_compress(&buf).unwrap();
465        let mut decompressed = vec![0; buf.len()];
466        let sz = decompress(&compressed, decompressed.as_mut_slice(), Algorithm::Zstd).unwrap();
467
468        assert_eq!(sz, 4095);
469        assert_eq!(buf, decompressed);
470    }
471
472    #[test]
473    fn test_zstd_compress_decompress_4096_bytes() {
474        let buf = vec![0x2u8; 4096];
475        let compressed = zstd_compress(&buf).unwrap();
476        let mut decompressed = vec![0; buf.len()];
477        let sz = decompress(&compressed, decompressed.as_mut_slice(), Algorithm::Zstd).unwrap();
478
479        assert_eq!(sz, 4096);
480        assert_eq!(buf, decompressed);
481    }
482
483    #[test]
484    fn test_zstd_compress_decompress_4097_bytes() {
485        let buf = vec![0x2u8; 4097];
486        let compressed = zstd_compress(&buf).unwrap();
487        let mut decompressed = vec![0; buf.len()];
488        let sz = decompress(&compressed, decompressed.as_mut_slice(), Algorithm::Zstd).unwrap();
489
490        assert_eq!(sz, 4097);
491        assert_eq!(buf, decompressed);
492    }
493
494    #[test]
495    fn test_new_decoder_none() {
496        let buf = b"This is a test";
497        let mut decoder = Decoder::new(buf.as_slice(), Algorithm::None).unwrap();
498        let mut buf2 = vec![0u8; 1024];
499        let res = decoder.read(&mut buf2).unwrap();
500        assert_eq!(res, 14);
501        assert_eq!(&buf2[0..14], buf.as_slice());
502    }
503
504    #[test]
505    fn test_gzip_decoder() {
506        let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
507        let path = Path::new(root_dir).join("../tests/texture/zran/zlib_sample.txt.gz");
508        let file = OpenOptions::new().read(true).open(path).unwrap();
509        let mut decoder = Decoder::new(file, Algorithm::GZip).unwrap();
510        let mut buf = [0u8; 8];
511
512        decoder.read_exact(&mut buf).unwrap();
513        assert_eq!(&String::from_utf8_lossy(&buf), "This is ");
514        decoder.read_exact(&mut buf).unwrap();
515        assert_eq!(&String::from_utf8_lossy(&buf), "a test f");
516        decoder.read_exact(&mut buf).unwrap();
517        assert_eq!(&String::from_utf8_lossy(&buf), "ile for ");
518        let ret = decoder.read(&mut buf).unwrap();
519        assert_eq!(ret, 6);
520        assert_eq!(&String::from_utf8_lossy(&buf[0..6]), "zlib.\n");
521        let ret = decoder.read(&mut buf).unwrap();
522        assert_eq!(ret, 0);
523    }
524
525    #[test]
526    fn test_zlib_decoder() {
527        let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
528        let path = Path::new(root_dir).join("../tests/texture/zran/zlib_sample.txt.gz");
529        let file = OpenOptions::new().read(true).open(path).unwrap();
530        let mut decoder = ZlibDecoder::new(file);
531        let mut buf = [0u8; 8];
532
533        decoder.read_exact(&mut buf).unwrap();
534        assert_eq!(&String::from_utf8_lossy(&buf), "This is ");
535        decoder.read_exact(&mut buf).unwrap();
536        assert_eq!(&String::from_utf8_lossy(&buf), "a test f");
537        decoder.read_exact(&mut buf).unwrap();
538        assert_eq!(&String::from_utf8_lossy(&buf), "ile for ");
539        let ret = decoder.read(&mut buf).unwrap();
540        assert_eq!(ret, 6);
541        assert_eq!(&String::from_utf8_lossy(&buf[0..6]), "zlib.\n");
542        let ret = decoder.read(&mut buf).unwrap();
543        assert_eq!(ret, 0);
544        print!(
545            "{:?}, {:?}, {:?}, {:?},",
546            Algorithm::GZip,
547            Algorithm::Lz4Block,
548            Algorithm::Zstd,
549            Algorithm::None
550        )
551    }
552
553    #[test]
554    fn test_algorithm_from() {
555        assert_eq!(Algorithm::from_str("none").unwrap(), Algorithm::None);
556        assert_eq!(
557            Algorithm::from_str("lz4_block").unwrap(),
558            Algorithm::Lz4Block
559        );
560        assert_eq!(Algorithm::from_str("gzip").unwrap(), Algorithm::GZip);
561        assert_eq!(Algorithm::from_str("zstd").unwrap(), Algorithm::Zstd);
562        assert!(Algorithm::from_str("foo").is_err());
563        assert_eq!(
564            Algorithm::try_from(Algorithm::None as u32).unwrap(),
565            Algorithm::None
566        );
567        assert_eq!(
568            Algorithm::try_from(Algorithm::Lz4Block as u32).unwrap(),
569            Algorithm::Lz4Block
570        );
571        assert_eq!(
572            Algorithm::try_from(Algorithm::GZip as u32).unwrap(),
573            Algorithm::GZip
574        );
575        assert_eq!(
576            Algorithm::try_from(Algorithm::Zstd as u32).unwrap(),
577            Algorithm::Zstd
578        );
579        assert!(Algorithm::try_from(u32::MAX).is_err());
580
581        assert_eq!(
582            Algorithm::try_from(Algorithm::None as u64).unwrap(),
583            Algorithm::None
584        );
585        assert_eq!(
586            Algorithm::try_from(Algorithm::Lz4Block as u64).unwrap(),
587            Algorithm::Lz4Block
588        );
589        assert_eq!(
590            Algorithm::try_from(Algorithm::GZip as u64).unwrap(),
591            Algorithm::GZip
592        );
593        assert_eq!(
594            Algorithm::try_from(Algorithm::Zstd as u64).unwrap(),
595            Algorithm::Zstd
596        );
597        assert!(Algorithm::try_from(u64::MAX).is_err());
598        assert!(Algorithm::None.is_none());
599        assert!(!Algorithm::Lz4Block.is_none());
600        assert!(!Algorithm::GZip.is_none());
601        assert!(!Algorithm::Zstd.is_none());
602    }
603
604    #[test]
605    fn test_algorithm_to_string() {
606        assert_eq!(Algorithm::None.to_string(), "none");
607        assert_eq!(Algorithm::Lz4Block.to_string(), "lz4_block");
608        assert_eq!(Algorithm::GZip.to_string(), "gzip");
609        assert_eq!(Algorithm::Zstd.to_string(), "zstd");
610    }
611
612    #[test]
613    fn test_algorithm_from_str() {
614        assert_eq!(Algorithm::from_str("none").unwrap(), Algorithm::None);
615        assert_eq!(
616            Algorithm::from_str("lz4_block").unwrap(),
617            Algorithm::Lz4Block
618        );
619        assert_eq!(Algorithm::from_str("gzip").unwrap(), Algorithm::GZip);
620        assert_eq!(Algorithm::from_str("zstd").unwrap(), Algorithm::Zstd);
621    }
622
623    #[test]
624    fn test_algorithm_to_string_and_from_str_consistency() {
625        let algorithms = vec![
626            Algorithm::None,
627            Algorithm::Lz4Block,
628            Algorithm::GZip,
629            Algorithm::Zstd,
630        ];
631
632        for algo in algorithms {
633            let stringified = algo.to_string();
634            let parsed = Algorithm::from_str(&stringified).unwrap();
635            assert_eq!(algo, parsed, "Mismatch for algorithm: {:?}", algo);
636        }
637    }
638
639    #[test]
640    fn test_algorithm_from_str_invalid_input() {
641        assert!(Algorithm::from_str("invalid_algorithm").is_err());
642        assert!(Algorithm::from_str("GZIP").is_err());
643        assert!(Algorithm::from_str("LZ4_BLOCK").is_err());
644    }
645}