Skip to main content

grpc_core/
compression.rs

1//! Per-message compression and `grpc-accept-encoding` negotiation.
2//!
3//! Zstd uses its frame format, LZ4 uses a single standard frame (without legacy
4//! or skippable frames), Gzip uses RFC 1952, Deflate uses the RFC 1950 zlib
5//! wrapper, and Snappy uses its raw block format. Compression contexts are
6//! independent for each gRPC message.
7
8use std::{
9    io::{Read, Write},
10    str::FromStr,
11};
12
13use crate::status::Status;
14
15/// Maximum decoded message size accepted by the default configuration.
16pub const DEFAULT_MAX_DECODING_MESSAGE_SIZE: usize = 16 * 1024 * 1024;
17/// Messages smaller than this are sent without compression by default.
18pub const DEFAULT_MIN_COMPRESS_SIZE_BYTES: usize = 256;
19
20/// A supported value of the `grpc-encoding` header.
21#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
22pub enum CompressionAlgorithm {
23    /// Zstandard frames, compressed at level 1.
24    #[default]
25    Zstd,
26    /// LZ4 frames.
27    Lz4,
28    /// RFC 1952 gzip streams.
29    Gzip,
30    /// RFC 1950 zlib-wrapped deflate streams.
31    Deflate,
32    /// Snappy raw blocks.
33    Snappy,
34    /// Uncompressed bytes.
35    Identity,
36}
37
38impl CompressionAlgorithm {
39    /// The lowercase wire token used in gRPC encoding headers.
40    pub const fn as_str(self) -> &'static str {
41        match self {
42            Self::Zstd => "zstd",
43            Self::Lz4 => "lz4",
44            Self::Gzip => "gzip",
45            Self::Deflate => "deflate",
46            Self::Snappy => "snappy",
47            Self::Identity => "identity",
48        }
49    }
50
51    /// Parses an exact, case-sensitive gRPC encoding token.
52    ///
53    /// # Errors
54    /// Returns `Unimplemented` for an unsupported encoding.
55    pub fn from_encoding(encoding: &str) -> Result<Self, Status> {
56        match encoding {
57            "zstd" => Ok(Self::Zstd),
58            "lz4" => Ok(Self::Lz4),
59            "gzip" => Ok(Self::Gzip),
60            "deflate" => Ok(Self::Deflate),
61            "snappy" => Ok(Self::Snappy),
62            "identity" => Ok(Self::Identity),
63            _ => Err(Status::unimplemented("unsupported grpc-encoding")),
64        }
65    }
66
67    /// Compresses a complete message without applying the adaptive threshold.
68    ///
69    /// # Errors
70    /// Returns `Internal` if a compressor fails.
71    pub fn compress(self, input: &[u8]) -> Result<Vec<u8>, Status> {
72        let failed = |_| Status::internal("message compression failed");
73        match self {
74            Self::Identity => Ok(input.to_vec()),
75            Self::Zstd => zstd::stream::encode_all(input, 1).map_err(failed),
76            Self::Lz4 => {
77                let mut encoder = lz4_flex::frame::FrameEncoder::new(Vec::new());
78                encoder.write_all(input).map_err(failed)?;
79                encoder
80                    .finish()
81                    .map_err(|_| Status::internal("LZ4 compression failed"))
82            }
83            Self::Gzip => {
84                let mut encoder =
85                    flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
86                encoder.write_all(input).map_err(failed)?;
87                encoder.finish().map_err(failed)
88            }
89            Self::Deflate => {
90                let mut encoder =
91                    flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::fast());
92                encoder.write_all(input).map_err(failed)?;
93                encoder.finish().map_err(failed)
94            }
95            Self::Snappy => snap::raw::Encoder::new()
96                .compress_vec(input)
97                .map_err(|_| Status::internal("Snappy compression failed")),
98        }
99    }
100
101    /// Decodes a message with a strict limit on the returned byte count.
102    ///
103    /// Streaming decoders read at most one byte beyond the limit and never
104    /// reserve output memory from an untrusted advertised decompressed size.
105    /// Zstd's decoding window is capped at 16 MiB independently of this limit;
106    /// LZ4 frame blocks are bounded by the format's 4 MiB maximum.
107    ///
108    /// # Errors
109    /// Returns `ResourceExhausted` if output exceeds `max_size`, or
110    /// `Internal` if the compressed stream is malformed.
111    pub fn decompress(self, input: &[u8], max_size: usize) -> Result<Vec<u8>, Status> {
112        match self {
113            Self::Identity => copy_bounded(input, max_size),
114            Self::Zstd => {
115                let mut decoder =
116                    zstd::stream::read::Decoder::new(input).map_err(|_| malformed())?;
117                decoder.window_log_max(24).map_err(|_| malformed())?;
118                read_bounded(decoder, max_size)
119            }
120            Self::Lz4 => {
121                validate_lz4_extent(input)?;
122                read_bounded(
123                    Lz4MessageReader(lz4_flex::frame::FrameDecoder::new(StrictFrameReader(input))),
124                    max_size,
125                )
126            }
127            Self::Gzip => read_bounded(flate2::read::MultiGzDecoder::new(input), max_size),
128            Self::Deflate => decode_deflate(input, max_size),
129            Self::Snappy => {
130                let size = snap::raw::decompress_len(input).map_err(|_| malformed())?;
131                if size > max_size {
132                    return Err(too_large());
133                }
134                let mut output = vec![0; size];
135                let written = snap::raw::Decoder::new()
136                    .decompress(input, &mut output)
137                    .map_err(|_| malformed())?;
138                output.truncate(written);
139                Ok(output)
140            }
141        }
142    }
143}
144
145impl FromStr for CompressionAlgorithm {
146    type Err = Status;
147
148    fn from_str(value: &str) -> Result<Self, Self::Err> {
149        Self::from_encoding(value)
150    }
151}
152
153/// Payload bytes and the value of the gRPC frame's compressed flag.
154#[derive(Clone, Debug, Eq, PartialEq)]
155pub struct CompressedMessage {
156    pub compressed: bool,
157    pub data: Vec<u8>,
158}
159
160/// Compression policy for outgoing and incoming messages.
161#[derive(Clone, Copy, Debug, Eq, PartialEq)]
162pub struct CompressionConfig {
163    pub algorithm: CompressionAlgorithm,
164    pub min_compress_size_bytes: usize,
165    pub max_decoding_message_size: usize,
166}
167
168impl Default for CompressionConfig {
169    fn default() -> Self {
170        Self {
171            algorithm: CompressionAlgorithm::default(),
172            min_compress_size_bytes: DEFAULT_MIN_COMPRESS_SIZE_BYTES,
173            max_decoding_message_size: DEFAULT_MAX_DECODING_MESSAGE_SIZE,
174        }
175    }
176}
177
178impl CompressionConfig {
179    /// Compresses only when the threshold is met and the result is smaller.
180    ///
181    /// # Errors
182    /// Returns `Internal` if the selected compressor fails.
183    pub fn compress(self, input: &[u8]) -> Result<CompressedMessage, Status> {
184        if self.algorithm != CompressionAlgorithm::Identity
185            && input.len() >= self.min_compress_size_bytes
186        {
187            let data = self.algorithm.compress(input)?;
188            if data.len() < input.len() {
189                return Ok(CompressedMessage {
190                    compressed: true,
191                    data,
192                });
193            }
194        }
195        Ok(CompressedMessage {
196            compressed: false,
197            data: input.to_vec(),
198        })
199    }
200
201    /// Decodes according to a frame's compressed flag, enforcing the size limit
202    /// for both compressed and uncompressed messages.
203    ///
204    /// # Errors
205    /// Returns `Internal` if compressed bytes have no selected encoding
206    /// or are malformed, and `ResourceExhausted` if the decoded limit is exceeded.
207    pub fn decompress(self, input: &[u8], compressed: bool) -> Result<Vec<u8>, Status> {
208        if !compressed {
209            return copy_bounded(input, self.max_decoding_message_size);
210        }
211        if self.algorithm == CompressionAlgorithm::Identity {
212            return Err(Status::internal(
213                "compressed flag requires a non-identity encoding",
214            ));
215        }
216        self.algorithm
217            .decompress(input, self.max_decoding_message_size)
218    }
219}
220
221/// Selects the first locally preferred encoding explicitly accepted by a peer.
222/// Unknown tokens are ignored; identity is always available as the fallback.
223/// An absent header can be represented by an empty string. Parameters and
224/// wildcards are not part of the gRPC accept-encoding grammar.
225pub fn negotiate_compression(
226    preferred: &[CompressionAlgorithm],
227    peer_accept_encoding: &str,
228) -> CompressionAlgorithm {
229    preferred
230        .iter()
231        .copied()
232        .find(|algorithm| {
233            *algorithm == CompressionAlgorithm::Identity
234                || peer_accept_encoding
235                    .split(',')
236                    .any(|token| token.trim() == algorithm.as_str())
237        })
238        .unwrap_or(CompressionAlgorithm::Identity)
239}
240
241/// Formats a comma-separated header, removing duplicate algorithms.
242pub fn accept_encoding_header(algorithms: &[CompressionAlgorithm]) -> String {
243    let mut header = String::new();
244    for (index, algorithm) in algorithms.iter().enumerate() {
245        if algorithms[..index].contains(algorithm) {
246            continue;
247        }
248        if !header.is_empty() {
249            header.push(',');
250        }
251        header.push_str(algorithm.as_str());
252    }
253    header
254}
255
256// LZ4's reader treats EOF before an end marker as a clean end of input.
257// An exhausted source must instead fail until the decoder consumes that marker.
258struct StrictFrameReader<'a>(&'a [u8]);
259
260// The upstream decoder also returns zero for a data block that produces no
261// bytes. Only the validated frame boundary establishes message completion.
262struct Lz4MessageReader<'a>(lz4_flex::frame::FrameDecoder<StrictFrameReader<'a>>);
263
264impl Read for Lz4MessageReader<'_> {
265    fn read(&mut self, output: &mut [u8]) -> std::io::Result<usize> {
266        loop {
267            let count = self.0.read(output)?;
268            if count != 0 || output.is_empty() || self.0.get_ref().0.is_empty() {
269                return Ok(count);
270            }
271        }
272    }
273}
274
275// Check block extents independently of decoded byte counts. Header validity,
276// block contents, declared content size and checksums remain upstream's job.
277fn validate_lz4_extent(input: &[u8]) -> Result<(), Status> {
278    if input.get(..4) != Some(&[0x04, 0x22, 0x4d, 0x18]) {
279        return Err(malformed());
280    }
281    let flags = *input.get(4).ok_or_else(malformed)?;
282    let header_size = 7 + usize::from(flags & 8 != 0) * 8 + usize::from(flags & 1 != 0) * 4;
283    let mut remaining = input.get(header_size..).ok_or_else(malformed)?;
284    loop {
285        let header = remaining.get(..4).ok_or_else(malformed)?;
286        let size = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
287        remaining = &remaining[4..];
288        if size == 0 {
289            let checksum_size = usize::from(flags & 4 != 0) * 4;
290            return if remaining.len() == checksum_size {
291                Ok(())
292            } else {
293                Err(malformed())
294            };
295        }
296        let data_size = usize::try_from(size & 0x7fff_ffff).map_err(|_| malformed())?;
297        let block_size = data_size
298            .checked_add(usize::from(flags & 16 != 0) * 4)
299            .ok_or_else(malformed)?;
300        remaining = remaining.get(block_size..).ok_or_else(malformed)?;
301    }
302}
303
304impl Read for StrictFrameReader<'_> {
305    fn read(&mut self, output: &mut [u8]) -> std::io::Result<usize> {
306        if self.0.is_empty() && !output.is_empty() {
307            return Err(std::io::Error::new(
308                std::io::ErrorKind::InvalidData,
309                "missing LZ4 end marker",
310            ));
311        }
312        self.0.read(output)
313    }
314}
315
316fn decode_deflate(input: &[u8], max_size: usize) -> Result<Vec<u8>, Status> {
317    let mut decoder = flate2::Decompress::new(true);
318    let mut output = Vec::new();
319    let mut chunk = [0; 8192];
320    loop {
321        let before_in = decoder.total_in();
322        let before_out = decoder.total_out();
323        let remaining = max_size - output.len();
324        let wanted = chunk.len().min(remaining.saturating_add(1));
325        let state = decoder
326            .decompress(
327                &input[before_in as usize..],
328                &mut chunk[..wanted],
329                flate2::FlushDecompress::None,
330            )
331            .map_err(|_| malformed())?;
332        let count = (decoder.total_out() - before_out) as usize;
333        if count > remaining {
334            return Err(too_large());
335        }
336        output.extend_from_slice(&chunk[..count]);
337        if state == flate2::Status::StreamEnd {
338            if decoder.total_in() != input.len() as u64 {
339                return Err(malformed());
340            }
341            return Ok(output);
342        }
343        if before_in == decoder.total_in() && count == 0 {
344            return Err(malformed());
345        }
346    }
347}
348
349fn copy_bounded(input: &[u8], max_size: usize) -> Result<Vec<u8>, Status> {
350    if input.len() > max_size {
351        return Err(too_large());
352    }
353    Ok(input.to_vec())
354}
355
356fn read_bounded(mut reader: impl Read, max_size: usize) -> Result<Vec<u8>, Status> {
357    let mut output = Vec::new();
358    let mut chunk = [0; 8192];
359    loop {
360        let remaining = max_size - output.len();
361        let wanted = chunk.len().min(remaining.saturating_add(1));
362        let count = reader.read(&mut chunk[..wanted]).map_err(|error| {
363            // libzstd exposes its window admission error through io::Error's
364            // message rather than a typed source or a distinct ErrorKind.
365            if error.to_string() == "Frame requires too much memory for decoding" {
366                Status::resource_exhausted("Zstd decoding window exceeds 16 MiB")
367            } else {
368                malformed()
369            }
370        })?;
371        if count == 0 {
372            return Ok(output);
373        }
374        if count > remaining {
375            return Err(too_large());
376        }
377        output.extend_from_slice(&chunk[..count]);
378    }
379}
380
381fn too_large() -> Status {
382    Status::resource_exhausted("decoded message exceeds configured size limit")
383}
384
385fn malformed() -> Status {
386    Status::internal("malformed compressed message")
387}
388
389#[cfg(test)]
390mod unit_tests {
391    use super::*;
392    use crate::status::Code;
393
394    const ALGORITHMS: [CompressionAlgorithm; 6] = [
395        CompressionAlgorithm::Zstd,
396        CompressionAlgorithm::Lz4,
397        CompressionAlgorithm::Gzip,
398        CompressionAlgorithm::Deflate,
399        CompressionAlgorithm::Snappy,
400        CompressionAlgorithm::Identity,
401    ];
402
403    #[test]
404    fn all_algorithms_round_trip_and_enforce_exact_limit() {
405        let payload = vec![b'x'; 32768];
406        for algorithm in ALGORITHMS {
407            let encoded = algorithm.compress(&payload).unwrap();
408            assert_eq!(
409                algorithm.decompress(&encoded, payload.len()).unwrap(),
410                payload
411            );
412            assert_eq!(
413                algorithm
414                    .decompress(&encoded, payload.len() - 1)
415                    .unwrap_err()
416                    .code(),
417                Code::ResourceExhausted
418            );
419            let empty = algorithm.compress(&[]).unwrap();
420            assert_eq!(algorithm.decompress(&empty, 0).unwrap(), b"");
421        }
422    }
423
424    #[test]
425    fn adaptive_threshold_and_negative_compression_keep_flag_clear() {
426        for algorithm in ALGORITHMS {
427            let config = CompressionConfig {
428                algorithm,
429                ..CompressionConfig::default()
430            };
431            let small = vec![b'x'; 255];
432            let output = config.compress(&small).unwrap();
433            assert!(!output.compressed);
434            assert_eq!(output.data, small);
435            let output = config.compress(&vec![b'x'; 256]).unwrap();
436            assert_eq!(
437                output.compressed,
438                algorithm != CompressionAlgorithm::Identity
439            );
440            assert_eq!(
441                config.decompress(&output.data, output.compressed).unwrap(),
442                vec![b'x'; 256]
443            );
444            let config = CompressionConfig {
445                min_compress_size_bytes: 0,
446                ..config
447            };
448            let output = config.compress(b"a").unwrap();
449            assert!(!output.compressed);
450            assert_eq!(output.data, b"a");
451        }
452    }
453
454    #[test]
455    fn scenario_high_expansion_streams_stop_at_small_limit() {
456        let payload = vec![0; 1024 * 1024];
457        for algorithm in ALGORITHMS {
458            let encoded = algorithm.compress(&payload).unwrap();
459            assert_eq!(
460                algorithm.decompress(&encoded, 1024).unwrap_err().code(),
461                Code::ResourceExhausted
462            );
463        }
464    }
465
466    #[test]
467    fn scenario_default_limit_rejects_large_output_and_zstd_window() {
468        let config = CompressionConfig::default();
469        let payload = vec![0; DEFAULT_MAX_DECODING_MESSAGE_SIZE + 1];
470        let encoded = config.algorithm.compress(&payload).unwrap();
471        assert_eq!(
472            config.decompress(&encoded, true).unwrap_err().code(),
473            Code::ResourceExhausted
474        );
475        // A standard frame with a 32 MiB window and an empty final block.
476        let large_window = [0x28, 0xb5, 0x2f, 0xfd, 0, 0x78, 1, 0, 0];
477        assert_eq!(
478            config.decompress(&large_window, true).unwrap_err().code(),
479            Code::ResourceExhausted
480        );
481    }
482
483    #[test]
484    fn scenario_corrupt_compressed_input_returns_status() {
485        for algorithm in ALGORITHMS
486            .into_iter()
487            .filter(|a| *a != CompressionAlgorithm::Identity)
488        {
489            let config = CompressionConfig {
490                algorithm,
491                max_decoding_message_size: 1024,
492                ..CompressionConfig::default()
493            };
494            let corrupt = b"not a compressed stream";
495            assert_eq!(
496                algorithm.decompress(corrupt, 1024).unwrap_err().code(),
497                Code::Internal
498            );
499            assert_eq!(
500                config.decompress(corrupt, true).unwrap_err().code(),
501                Code::Internal
502            );
503        }
504    }
505
506    #[test]
507    fn scenario_truncated_streams_are_rejected() {
508        for algorithm in ALGORITHMS
509            .into_iter()
510            .filter(|a| *a != CompressionAlgorithm::Identity)
511        {
512            let encoded = algorithm.compress(&vec![b'x'; 1024]).unwrap();
513            for length in 0..encoded.len() {
514                assert_eq!(
515                    algorithm
516                        .decompress(&encoded[..length], 2048)
517                        .unwrap_err()
518                        .code(),
519                    Code::Internal,
520                    "{algorithm:?} truncated stream of {length}/{} bytes",
521                    encoded.len()
522                );
523            }
524        }
525    }
526
527    #[test]
528    fn scenario_trailing_garbage_is_not_silently_discarded() {
529        for algorithm in ALGORITHMS
530            .into_iter()
531            .filter(|a| *a != CompressionAlgorithm::Identity)
532        {
533            let encoded = algorithm.compress(b"message").unwrap();
534            for suffix in [&b"\0"[..], &b"garbage"[..], &[255; 16][..]] {
535                let mut trailing = encoded.clone();
536                trailing.extend_from_slice(suffix);
537                assert_eq!(
538                    algorithm.decompress(&trailing, 1024).unwrap_err().code(),
539                    Code::Internal,
540                    "{algorithm:?} accepted trailing {suffix:?}"
541                );
542            }
543        }
544    }
545
546    #[test]
547    fn scenario_lz4_empty_blocks_require_end_marker_and_preserve_following_data() {
548        let algorithm = CompressionAlgorithm::Lz4;
549        let empty = algorithm.compress(b"").unwrap();
550        let following = algorithm.compress(b"following block").unwrap();
551        for block in [&[0, 0, 0, 0x80][..], &[1, 0, 0, 0, 0][..]] {
552            let mut encoded = empty[..empty.len() - 4].to_vec();
553            encoded.extend_from_slice(block);
554            assert_eq!(
555                algorithm.decompress(&encoded, 64).unwrap_err().code(),
556                Code::Internal
557            );
558            encoded.extend_from_slice(&following[7..]);
559            assert_eq!(
560                algorithm.decompress(&encoded, 64).unwrap(),
561                b"following block"
562            );
563            assert_eq!(
564                algorithm.decompress(&encoded, 14).unwrap_err().code(),
565                Code::ResourceExhausted
566            );
567        }
568    }
569
570    #[test]
571    fn scenario_lz4_optional_fields_and_linked_blocks_round_trip() {
572        let payload = vec![b'x'; 200_000];
573        let info = lz4_flex::frame::FrameInfo::new()
574            .block_size(lz4_flex::frame::BlockSize::Max64KB)
575            .block_mode(lz4_flex::frame::BlockMode::Linked)
576            .content_size(Some(payload.len() as u64))
577            .block_checksums(true)
578            .content_checksum(true);
579        let mut encoder = lz4_flex::frame::FrameEncoder::with_frame_info(info, Vec::new());
580        encoder.write_all(&payload).unwrap();
581        let encoded = encoder.finish().unwrap();
582        assert_eq!(
583            CompressionAlgorithm::Lz4
584                .decompress(&encoded, payload.len())
585                .unwrap(),
586            payload
587        );
588        for length in [encoded.len() - 1, encoded.len() - 4] {
589            assert_eq!(
590                CompressionAlgorithm::Lz4
591                    .decompress(&encoded[..length], payload.len())
592                    .unwrap_err()
593                    .code(),
594                Code::Internal
595            );
596        }
597        let first_block_size = u32::from_le_bytes(encoded[15..19].try_into().unwrap());
598        let block_checksum_offset = 19 + (first_block_size & 0x7fff_ffff) as usize;
599        for offset in [block_checksum_offset, encoded.len() - 1] {
600            let mut corrupt = encoded.clone();
601            corrupt[offset] ^= 1;
602            assert_eq!(
603                CompressionAlgorithm::Lz4
604                    .decompress(&corrupt, payload.len())
605                    .unwrap_err()
606                    .code(),
607                Code::Internal
608            );
609        }
610    }
611
612    #[test]
613    fn scenario_concatenated_streams_preserve_bytes_and_enforce_combined_limit() {
614        for algorithm in [CompressionAlgorithm::Zstd, CompressionAlgorithm::Gzip] {
615            let mut encoded = algorithm.compress(b"first").unwrap();
616            encoded.extend(algorithm.compress(b"second").unwrap());
617            assert_eq!(algorithm.decompress(&encoded, 11).unwrap(), b"firstsecond");
618            assert_eq!(
619                algorithm.decompress(&encoded, 10).unwrap_err().code(),
620                Code::ResourceExhausted
621            );
622            encoded.pop();
623            assert_eq!(
624                algorithm.decompress(&encoded, 11).unwrap_err().code(),
625                Code::Internal
626            );
627        }
628        for algorithm in [
629            CompressionAlgorithm::Lz4,
630            CompressionAlgorithm::Deflate,
631            CompressionAlgorithm::Snappy,
632        ] {
633            let mut encoded = algorithm.compress(b"first").unwrap();
634            encoded.extend(algorithm.compress(b"second").unwrap());
635            assert_eq!(
636                algorithm.decompress(&encoded, 11).unwrap_err().code(),
637                Code::Internal
638            );
639        }
640    }
641
642    #[test]
643    fn uncompressed_frames_obey_limits_and_identity_rejects_compressed_flag() {
644        let config = CompressionConfig {
645            algorithm: CompressionAlgorithm::Identity,
646            max_decoding_message_size: 3,
647            ..CompressionConfig::default()
648        };
649        assert_eq!(
650            config.decompress(b"four", false).unwrap_err().code(),
651            Code::ResourceExhausted
652        );
653        assert_eq!(
654            config.decompress(b"one", true).unwrap_err().code(),
655            Code::Internal
656        );
657    }
658
659    #[test]
660    fn negotiation_uses_exact_tokens_and_local_preference() {
661        use CompressionAlgorithm::{Gzip, Identity, Lz4, Zstd};
662        assert_eq!(negotiate_compression(&[Zstd, Gzip], "gzip, zstd"), Zstd);
663        assert_eq!(negotiate_compression(&[Zstd, Gzip], "other, gzip"), Gzip);
664        for value in ["", "ZSTD", "x-zstd", "*", "zstd;q=1"] {
665            assert_eq!(negotiate_compression(&[Zstd, Gzip], value), Identity);
666        }
667        assert_eq!(
668            accept_encoding_header(&[Zstd, Lz4, Zstd, Identity]),
669            "zstd,lz4,identity"
670        );
671        for algorithm in ALGORITHMS {
672            assert_eq!(
673                algorithm.as_str().parse::<CompressionAlgorithm>().unwrap(),
674                algorithm
675            );
676        }
677        assert_eq!(
678            CompressionAlgorithm::from_encoding("unknown")
679                .unwrap_err()
680                .code(),
681            Code::Unimplemented
682        );
683    }
684}