memberlist_proto/
compression.rs

1use std::borrow::Cow;
2
3use super::{Data, DataRef, DecodeError, EncodeError, WireType};
4
5#[cfg(feature = "brotli")]
6macro_rules! num_to_enum {
7  (
8    $(#[$meta:meta])*
9    $name:ident($inner:ident in [$min:expr, $max:literal]):$exp:literal:$short:literal {
10      $(
11        $(#[$value_meta:meta])*
12        $value:literal
13      ), +$(,)?
14    }
15  ) => {
16    paste::paste! {
17      $(#[$meta])*
18      #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, derive_more::IsVariant)]
19      #[cfg_attr(any(feature = "arbitrary", test), derive(arbitrary::Arbitrary))]
20      #[non_exhaustive]
21      #[repr($inner)]
22      pub enum $name {
23        $(
24          #[doc = $exp " " $value "."]
25          $(#[$value_meta])*
26          [< $short:camel $value >] = $value,
27        )*
28      }
29
30      impl $name {
31        /// Returns the maximum value.
32        #[inline]
33        pub const fn max() -> Self {
34          Self::[< $short:camel $max >]
35        }
36
37        /// Returns the minimum value.
38        #[inline]
39        pub const fn min() -> Self {
40          Self::[< $short:camel $min >]
41        }
42      }
43
44      impl $name {
45        #[allow(unused_comparisons)]
46        pub(super) const fn [< from_ $inner>](value: $inner) -> Self {
47          match value {
48            $(
49              $value => Self::[< $short:camel $value >],
50            )*
51            val if val > $max => Self::[< $short:camel $max >],
52            val if val < $min => Self::[< $short:camel $min >],
53            _ => Self::[< $short:camel $max >],
54          }
55        }
56      }
57
58      impl From<$inner> for $name {
59        fn from(value: $inner) -> Self {
60          Self::from_u8(value as $inner)
61        }
62      }
63
64      impl From<$name> for $inner {
65        fn from(value: $name) -> Self {
66          value as $inner
67        }
68      }
69
70      #[cfg(feature = "serde")]
71      const _: () = {
72        use serde::{Deserialize, Serialize};
73
74        impl Serialize for $name {
75          fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76          where
77            S: serde::Serializer,
78          {
79            serializer.[< serialize_ $inner>](*self as $inner)
80          }
81        }
82
83        impl<'de> Deserialize<'de> for $name {
84          fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
85          where
86            D: serde::Deserializer<'de> {
87            <$inner>::deserialize(deserializer).map(Self::[< from_ $inner >])
88          }
89        }
90      };
91    }
92  };
93}
94
95#[allow(unused)]
96const ZSTD_TAG: u8 = 1;
97#[allow(unused)]
98const BROTLI_TAG: u8 = 2;
99#[allow(unused)]
100const LZ4_TAG: u8 = 3;
101#[allow(unused)]
102const SNAPPY_TAG: u8 = 4;
103
104#[cfg(feature = "brotli")]
105mod brotli_impl;
106mod error;
107mod from_str;
108#[cfg(feature = "zstd")]
109mod zstd_impl;
110
111#[cfg(feature = "brotli")]
112pub use brotli_impl::*;
113pub use error::*;
114pub use from_str::*;
115
116#[cfg(feature = "zstd")]
117pub use zstd_impl::*;
118
119#[cfg(any(feature = "quickcheck", test))]
120mod quickcheck_impl;
121
122#[cfg(feature = "brotli")]
123const BROTLI_BUFFER_SIZE: usize = 4096;
124
125#[cfg(feature = "brotli")]
126const _: () = {
127  impl CompressionError {
128    #[inline]
129    const fn brotli_compress_error(err: std::io::Error) -> Self {
130      Self::Compress(CompressError::Brotli(err))
131    }
132
133    #[inline]
134    const fn brotli_decompress_error(err: std::io::Error) -> Self {
135      Self::Decompress(DecompressError::Brotli(err))
136    }
137  }
138};
139
140#[cfg(feature = "snappy")]
141const _: () = {
142  impl CompressionError {
143    #[inline]
144    const fn snappy_compress_error(err: snap::Error) -> Self {
145      Self::Compress(CompressError::Snappy(err))
146    }
147
148    #[inline]
149    const fn snappy_decompress_error(err: snap::Error) -> Self {
150      Self::Decompress(DecompressError::Snappy(err))
151    }
152  }
153};
154
155#[cfg(feature = "lz4")]
156const _: () = {
157  impl CompressionError {
158    #[inline]
159    const fn lz4_decompress_error(err: lz4_flex::block::DecompressError) -> Self {
160      Self::Decompress(DecompressError::Lz4(err))
161    }
162
163    #[inline]
164    const fn lz4_compress_error(err: lz4_flex::block::CompressError) -> Self {
165      Self::Compress(CompressError::Lz4(err))
166    }
167  }
168};
169
170#[cfg(feature = "zstd")]
171const _: () = {
172  impl CompressionError {
173    #[inline]
174    const fn zstd_compress_error(err: std::io::Error) -> Self {
175      Self::Compress(CompressError::Zstd(err))
176    }
177
178    #[inline]
179    const fn zstd_decompress_error(err: std::io::Error) -> Self {
180      Self::Decompress(DecompressError::Zstd(err))
181    }
182  }
183};
184
185/// The compressioned algorithm used to compression the message.
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::IsVariant, derive_more::Display)]
187#[non_exhaustive]
188pub enum CompressAlgorithm {
189  /// Brotli
190  #[display("brotli{_0}")]
191  #[cfg(feature = "brotli")]
192  #[cfg_attr(docsrs, doc(cfg(feature = "brotli")))]
193  Brotli(BrotliAlgorithm),
194  /// LZ4
195  #[display("lz4")]
196  #[cfg(feature = "lz4")]
197  #[cfg_attr(docsrs, doc(cfg(feature = "lz4")))]
198  Lz4,
199  /// Snappy
200  #[display("snappy")]
201  #[cfg(feature = "snappy")]
202  #[cfg_attr(docsrs, doc(cfg(feature = "snappy")))]
203  Snappy,
204  /// Zstd
205  #[display("zstd({_0})")]
206  #[cfg(feature = "zstd")]
207  #[cfg_attr(docsrs, doc(cfg(feature = "zstd")))]
208  Zstd(ZstdCompressionLevel),
209  /// Unknwon compressioned algorithm
210  #[display("unknown({_0})")]
211  Unknown(u8),
212}
213
214#[cfg(any(feature = "quickcheck", test))]
215const _: () = {
216  use quickcheck::Arbitrary;
217
218  impl CompressAlgorithm {
219    const MAX: u8 = SNAPPY_TAG;
220    const MIN: u8 = ZSTD_TAG;
221  }
222
223  impl Arbitrary for CompressAlgorithm {
224    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
225      let val = (u8::arbitrary(g) % Self::MAX) + Self::MIN;
226      match val {
227        #[cfg(feature = "zstd")]
228        ZSTD_TAG => Self::Zstd(ZstdCompressionLevel::arbitrary(g)),
229        #[cfg(feature = "brotli")]
230        BROTLI_TAG => Self::Brotli(BrotliAlgorithm::arbitrary(g)),
231        #[cfg(feature = "lz4")]
232        LZ4_TAG => Self::Lz4,
233        #[cfg(feature = "snappy")]
234        SNAPPY_TAG => Self::Snappy,
235        _ => unreachable!(),
236      }
237    }
238  }
239};
240
241#[allow(clippy::derivable_impls)]
242impl Default for CompressAlgorithm {
243  fn default() -> Self {
244    cfg_if::cfg_if! {
245      if #[cfg(feature = "snappy")] {
246        Self::Snappy
247      } else if #[cfg(feature = "lz4")] {
248        Self::Lz4
249      } else if #[cfg(feature = "brotli")] {
250        Self::Brotli(BrotliAlgorithm::default())
251      } else if #[cfg(feature = "zstd")] {
252        Self::Zstd(ZstdCompressionLevel::default())
253      } else {
254        Self::Unknown(u8::MAX)
255      }
256    }
257  }
258}
259
260impl CompressAlgorithm {
261  /// Decompresses the given buffer.
262  pub fn decompress_to(&self, src: &[u8], dst: &mut [u8]) -> Result<usize, CompressionError> {
263    match self {
264      #[cfg(feature = "brotli")]
265      Self::Brotli(_) => {
266        let mut reader = brotli::Decompressor::new(src, BROTLI_BUFFER_SIZE);
267        std::io::copy(&mut reader, &mut std::io::Cursor::new(dst))
268          .map(|bytes| bytes as usize)
269          .map_err(CompressionError::brotli_decompress_error)
270      }
271      #[cfg(feature = "lz4")]
272      Self::Lz4 => {
273        lz4_flex::decompress_into(src, dst).map_err(CompressionError::lz4_decompress_error)
274      }
275      #[cfg(feature = "snappy")]
276      Self::Snappy => snap::raw::Decoder::new()
277        .decompress(src, dst)
278        .map_err(CompressionError::snappy_decompress_error),
279      #[cfg(feature = "zstd")]
280      Self::Zstd(_) => {
281        let mut decoder = zstd::Decoder::new(std::io::Cursor::new(src))
282          .map_err(CompressionError::zstd_decompress_error)?;
283        std::io::copy(&mut decoder, &mut std::io::Cursor::new(dst))
284          .map(|bytes| bytes as usize)
285          .map_err(CompressionError::zstd_decompress_error)
286      }
287      algo => Err(CompressionError::UnknownAlgorithm(*algo)),
288    }
289  }
290
291  /// Returns the maximum compressed length of the given buffer.
292  ///
293  /// This is useful when you want to pre-allocate the buffer before compressing.
294  pub fn max_compress_len(&self, input_size: usize) -> Result<usize, CompressionError> {
295    Ok(match self {
296      #[cfg(feature = "brotli")]
297      Self::Brotli(_) => {
298        // TODO(al8n): The brotli::enc::BrotliEncoderMaxCompressedSize is not working as expected.
299        // In fuzzy tests, it is returning a value less than the actual compressed size.
300        let num_large_blocks = (input_size >> 4) + 12;
301        let overhead = 2 + (4 * num_large_blocks) + 3 + 1;
302        let result = input_size + overhead;
303        if input_size == 0 {
304          2
305        } else if result < input_size {
306          input_size
307        } else {
308          result
309        }
310      }
311      #[cfg(feature = "lz4")]
312      Self::Lz4 => lz4_flex::block::get_maximum_output_size(input_size),
313      #[cfg(feature = "snappy")]
314      Self::Snappy => snap::raw::max_compress_len(input_size),
315      #[cfg(feature = "zstd")]
316      Self::Zstd(_) => zstd::zstd_safe::compress_bound(input_size),
317      Self::Unknown(_) => return Err(CompressionError::UnknownAlgorithm(*self)),
318    })
319  }
320
321  /// Compresses the given buffer.
322  ///
323  /// The `dst` buffer should be pre-allocated with the [`max_compress_len`](Self::max_compress_len) method.
324  pub fn compress_to(&self, src: &[u8], dst: &mut [u8]) -> Result<usize, CompressionError> {
325    match self {
326      #[cfg(feature = "brotli")]
327      CompressAlgorithm::Brotli(_algo) => {
328        use std::io::Write;
329
330        let mut compressor = brotli::CompressorWriter::new(
331          std::io::Cursor::new(dst),
332          BROTLI_BUFFER_SIZE,
333          _algo.quality() as u8 as u32,
334          _algo.window() as u8 as u32,
335        );
336
337        compressor
338          .write_all(src)
339          .map(|_| compressor.into_inner().position() as usize)
340          .map_err(CompressionError::brotli_compress_error)
341      }
342      #[cfg(feature = "lz4")]
343      CompressAlgorithm::Lz4 => {
344        lz4_flex::compress_into(src, dst).map_err(CompressionError::lz4_compress_error)
345      }
346      #[cfg(feature = "snappy")]
347      CompressAlgorithm::Snappy => {
348        let mut encoder = snap::raw::Encoder::new();
349        encoder
350          .compress(src, dst)
351          .map_err(CompressionError::snappy_compress_error)
352      }
353      #[cfg(feature = "zstd")]
354      CompressAlgorithm::Zstd(_level) => {
355        let mut cursor = std::io::Cursor::new(dst);
356        zstd::stream::copy_encode(src, &mut cursor, _level.level() as i32)
357          .map(|_| cursor.position() as usize)
358          .map_err(CompressionError::zstd_compress_error)
359      }
360      algo => Err(CompressionError::UnknownAlgorithm(*algo)),
361    }
362  }
363
364  /// Returns the string representation of the algorithm.
365  #[inline]
366  pub fn as_str(&self) -> Cow<'_, str> {
367    match self {
368      #[cfg(feature = "brotli")]
369      Self::Brotli(algo) => return Cow::Owned(format!("brotli{algo}")),
370      #[cfg(feature = "lz4")]
371      Self::Lz4 => "lz4",
372      #[cfg(feature = "snappy")]
373      Self::Snappy => "snappy",
374      #[cfg(feature = "zstd")]
375      Self::Zstd(val) => return Cow::Owned(format!("zstd({})", val.level())),
376      Self::Unknown(val) => return Cow::Owned(format!("unknown({val})")),
377    }
378    .into()
379  }
380
381  /// Encodes the algorithm into a u16.
382  /// First byte is the algorithm tag.
383  /// Second byte stores additional configuration if any.
384  #[inline]
385  pub(super) const fn encode_to_u16(&self) -> u16 {
386    let (tag, extra) = match self {
387      #[cfg(feature = "brotli")]
388      Self::Brotli(algo) => (BROTLI_TAG, algo.encode()),
389      #[cfg(feature = "lz4")]
390      Self::Lz4 => (LZ4_TAG, 0),
391      #[cfg(feature = "snappy")]
392      Self::Snappy => (SNAPPY_TAG, 0),
393      #[cfg(feature = "zstd")]
394      Self::Zstd(algo) => (ZSTD_TAG, algo.level() as u8),
395      Self::Unknown(v) => (*v, 0),
396    };
397    ((tag as u16) << 8) | (extra as u16)
398  }
399
400  /// Creates a CompressAlgorithm from a u16.
401  /// First byte determines the algorithm type.
402  /// Second byte contains additional configuration if any.
403  #[inline]
404  pub(super) const fn decode_from_u16(value: u16) -> Self {
405    let tag = (value >> 8) as u8;
406    #[cfg(any(feature = "brotli", feature = "zstd"))]
407    let extra = value as u8;
408
409    match tag {
410      #[cfg(feature = "brotli")]
411      BROTLI_TAG => Self::Brotli(BrotliAlgorithm::decode(extra)),
412      #[cfg(feature = "lz4")]
413      LZ4_TAG => Self::Lz4,
414      #[cfg(feature = "snappy")]
415      SNAPPY_TAG => Self::Snappy,
416      #[cfg(feature = "zstd")]
417      ZSTD_TAG => Self::Zstd(ZstdCompressionLevel::with_level(extra as i8)),
418      v => Self::Unknown(v),
419    }
420  }
421
422  pub(crate) const fn unknown_or_disabled(&self) -> Option<CompressionError> {
423    match *self {
424      #[cfg(feature = "brotli")]
425      Self::Brotli(_) => None,
426      #[cfg(feature = "lz4")]
427      Self::Lz4 => None,
428      #[cfg(feature = "snappy")]
429      Self::Snappy => None,
430      #[cfg(feature = "zstd")]
431      Self::Zstd(_) => None,
432      Self::Unknown(value) => Some({
433        #[cfg(not(all(
434          feature = "brotli",
435          feature = "lz4",
436          feature = "snappy",
437          feature = "zstd"
438        )))]
439        {
440          match value {
441            #[cfg(feature = "brotli")]
442            BROTLI_TAG => CompressionError::disabled(*self, "brotli"),
443            #[cfg(feature = "lz4")]
444            LZ4_TAG => CompressionError::disabled(*self, "lz4"),
445            #[cfg(feature = "snappy")]
446            SNAPPY_TAG => CompressionError::disabled(*self, "snappy"),
447            #[cfg(feature = "zstd")]
448            ZSTD_TAG => CompressionError::disabled(*self, "zstd"),
449            _ => CompressionError::UnknownAlgorithm(*self),
450          }
451        }
452
453        #[cfg(all(
454          feature = "brotli",
455          feature = "lz4",
456          feature = "snappy",
457          feature = "zstd"
458        ))]
459        CompressionError::UnknownAlgorithm(Self::Unknown(value))
460      }),
461    }
462  }
463}
464
465impl From<u16> for CompressAlgorithm {
466  fn from(value: u16) -> Self {
467    Self::decode_from_u16(value)
468  }
469}
470
471impl<'a> DataRef<'a, Self> for CompressAlgorithm {
472  fn decode(buf: &'a [u8]) -> Result<(usize, Self), DecodeError>
473  where
474    Self: Sized,
475  {
476    <u16 as DataRef<u16>>::decode(buf).map(|(bytes_read, value)| (bytes_read, Self::from(value)))
477  }
478}
479
480impl Data for CompressAlgorithm {
481  const WIRE_TYPE: WireType = WireType::Varint;
482
483  type Ref<'a> = Self;
484
485  fn from_ref(val: Self::Ref<'_>) -> Result<Self, DecodeError>
486  where
487    Self: Sized,
488  {
489    Ok(val)
490  }
491
492  fn encoded_len(&self) -> usize {
493    self.encode_to_u16().encoded_len()
494  }
495
496  fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
497    self.encode_to_u16().encode(buf)
498  }
499}
500
501#[inline]
502#[allow(unused)]
503fn parse_or_default<I, O>(s: &str) -> Result<O, I::Err>
504where
505  I: core::str::FromStr,
506  O: Default + From<I>,
507{
508  match s {
509    "" | "-" | "_" => Ok(O::default()),
510    val => val.parse::<I>().map(Into::into),
511  }
512}
513
514#[cfg(feature = "serde")]
515const _: () = {
516  use serde::{Deserialize, Deserializer, Serialize, Serializer};
517
518  #[derive(serde::Serialize, serde::Deserialize)]
519  #[serde(rename_all = "snake_case")]
520  enum CompressAlgorithmHelper {
521    /// Brotli
522    #[cfg(feature = "brotli")]
523    Brotli(BrotliAlgorithm),
524    /// LZ4
525    #[cfg(feature = "lz4")]
526    Lz4,
527    /// Snappy
528    #[cfg(feature = "snappy")]
529    Snappy,
530    /// Zstd
531    #[cfg(feature = "zstd")]
532    Zstd(ZstdCompressionLevel),
533    /// Unknwon compressioned algorithm
534    Unknown(u8),
535  }
536
537  impl From<CompressAlgorithm> for CompressAlgorithmHelper {
538    fn from(algo: CompressAlgorithm) -> Self {
539      match algo {
540        #[cfg(feature = "brotli")]
541        CompressAlgorithm::Brotli(algo) => Self::Brotli(algo),
542        #[cfg(feature = "lz4")]
543        CompressAlgorithm::Lz4 => Self::Lz4,
544        #[cfg(feature = "snappy")]
545        CompressAlgorithm::Snappy => Self::Snappy,
546        #[cfg(feature = "zstd")]
547        CompressAlgorithm::Zstd(algo) => Self::Zstd(algo),
548        CompressAlgorithm::Unknown(v) => Self::Unknown(v),
549      }
550    }
551  }
552
553  impl From<CompressAlgorithmHelper> for CompressAlgorithm {
554    fn from(helper: CompressAlgorithmHelper) -> Self {
555      match helper {
556        #[cfg(feature = "brotli")]
557        CompressAlgorithmHelper::Brotli(algo) => Self::Brotli(algo),
558        #[cfg(feature = "lz4")]
559        CompressAlgorithmHelper::Lz4 => Self::Lz4,
560        #[cfg(feature = "snappy")]
561        CompressAlgorithmHelper::Snappy => Self::Snappy,
562        #[cfg(feature = "zstd")]
563        CompressAlgorithmHelper::Zstd(algo) => Self::Zstd(algo),
564        CompressAlgorithmHelper::Unknown(v) => Self::Unknown(v),
565      }
566    }
567  }
568
569  impl Serialize for CompressAlgorithm {
570    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
571    where
572      S: Serializer,
573    {
574      if serializer.is_human_readable() {
575        CompressAlgorithmHelper::from(*self).serialize(serializer)
576      } else {
577        serializer.serialize_u16(self.encode_to_u16())
578      }
579    }
580  }
581
582  impl<'de> Deserialize<'de> for CompressAlgorithm {
583    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
584    where
585      D: Deserializer<'de>,
586    {
587      if deserializer.is_human_readable() {
588        CompressAlgorithmHelper::deserialize(deserializer).map(Into::into)
589      } else {
590        u16::deserialize(deserializer).map(Self::decode_from_u16)
591      }
592    }
593  }
594};
595
596#[cfg(test)]
597mod tests {
598  use super::CompressAlgorithm;
599
600  #[quickcheck_macros::quickcheck]
601  #[cfg(feature = "serde")]
602  fn compress_algorithm_serde(algo: CompressAlgorithm) -> bool {
603    use bincode::config::standard;
604
605    let Ok(serialized) = serde_json::to_string(&algo) else {
606      return false;
607    };
608    let Ok(deserialized) = serde_json::from_str(&serialized) else {
609      return false;
610    };
611    if algo != deserialized {
612      return false;
613    }
614
615    let Ok(serialized) = bincode::serde::encode_to_vec(algo, standard()) else {
616      return false;
617    };
618
619    let Ok((deserialized, _)) = bincode::serde::decode_from_slice(&serialized, standard()) else {
620      return false;
621    };
622
623    algo == deserialized
624  }
625
626  #[quickcheck_macros::quickcheck]
627  #[cfg(feature = "lz4")]
628  fn lz4(data: Vec<u8>) -> bool {
629    let algo = CompressAlgorithm::Lz4;
630
631    let uncompressed_data_len = data.len();
632    let max_compress_len = algo.max_compress_len(uncompressed_data_len).unwrap();
633    let mut buffer = vec![0; max_compress_len];
634    let written = algo.compress_to(&data, &mut buffer).unwrap();
635    assert!(written <= max_compress_len);
636    let mut orig = vec![0; uncompressed_data_len];
637    let decompressed = algo.decompress_to(&buffer[..written], &mut orig).unwrap();
638    data == orig && uncompressed_data_len == decompressed
639  }
640
641  #[quickcheck_macros::quickcheck]
642  #[cfg(feature = "brotli")]
643  fn brotli(data: Vec<u8>) -> bool {
644    let algo = CompressAlgorithm::Brotli(Default::default());
645    let uncompressed_data_len = data.len();
646    let max_compress_len = algo.max_compress_len(uncompressed_data_len).unwrap();
647    let mut buffer = vec![0; max_compress_len];
648    let written = algo.compress_to(&data, &mut buffer).unwrap();
649    assert!(written <= max_compress_len);
650    let mut orig = vec![0; uncompressed_data_len];
651    let decompressed = algo.decompress_to(&buffer[..written], &mut orig).unwrap();
652    data == orig && uncompressed_data_len == decompressed
653  }
654
655  #[quickcheck_macros::quickcheck]
656  #[cfg(feature = "zstd")]
657  fn zstd(data: Vec<u8>) -> bool {
658    let algo = CompressAlgorithm::Zstd(Default::default());
659    let uncompressed_data_len = data.len();
660    let max_compress_len = algo.max_compress_len(uncompressed_data_len).unwrap();
661    let mut buffer = vec![0; max_compress_len];
662    let written = algo.compress_to(&data, &mut buffer).unwrap();
663    assert!(written <= max_compress_len);
664    let mut orig = vec![0; uncompressed_data_len];
665    let decompressed = algo.decompress_to(&buffer[..written], &mut orig).unwrap();
666    data == orig && uncompressed_data_len == decompressed
667  }
668
669  #[quickcheck_macros::quickcheck]
670  #[cfg(feature = "snappy")]
671  fn snappy(data: Vec<u8>) -> bool {
672    let algo = CompressAlgorithm::Snappy;
673    let uncompressed_data_len = data.len();
674    let max_compress_len = algo.max_compress_len(uncompressed_data_len).unwrap();
675    let mut buffer = vec![0; max_compress_len];
676    let written = algo.compress_to(&data, &mut buffer).unwrap();
677    assert!(written <= max_compress_len);
678    let mut orig = vec![0; uncompressed_data_len];
679    let decompressed = algo.decompress_to(&buffer[..written], &mut orig).unwrap();
680    data == orig && uncompressed_data_len == decompressed
681  }
682
683  #[cfg(feature = "snappy")]
684  #[test]
685  fn max_snappy_output_size() {
686    let algo = CompressAlgorithm::Snappy;
687    let max_compress_len = algo.max_compress_len(4096).unwrap();
688    println!("max_compress_len: {}", max_compress_len);
689    assert!(max_compress_len >= 4096);
690  }
691
692  #[cfg(feature = "lz4")]
693  #[test]
694  fn max_lz4_output_size() {
695    let algo = CompressAlgorithm::Lz4;
696    let max_compress_len = algo.max_compress_len(4096).unwrap();
697    println!("max_compress_len: {}", max_compress_len);
698    assert!(max_compress_len >= 4096);
699  }
700
701  #[cfg(feature = "brotli")]
702  #[test]
703  fn max_brotli_output_size() {
704    let algo = CompressAlgorithm::Brotli(Default::default());
705    let max_compress_len = algo.max_compress_len(4096).unwrap();
706    println!("max_compress_len: {}", max_compress_len);
707    assert!(max_compress_len >= 4096);
708  }
709
710  #[cfg(feature = "zstd")]
711  #[test]
712  fn max_zstd_output_size() {
713    let algo = CompressAlgorithm::Zstd(Default::default());
714    let max_compress_len = algo.max_compress_len(4096).unwrap();
715    println!("max_compress_len: {}", max_compress_len);
716    assert!(max_compress_len >= 4096);
717  }
718}