titan_api_codec/transform/
brotli.rs

1//! Defines transforms that utilize [brotli] to compress and decompress data.
2//!
3//! [brotli]: https://github.com/google/brotli
4
5use super::common::BinaryTransform;
6
7use brotli::enc::{BrotliCompress, BrotliCompressCustomAlloc, BrotliEncoderParams, StandardAlloc};
8use brotli::{BrotliDecompress, BrotliDecompressCustomAlloc};
9use bytes::{Buf, BufMut, Bytes, BytesMut};
10
11const INPUT_BUFFER_SIZE: usize = 4096;
12const OUTPUT_BUFFER_SIZE: usize = 4096;
13
14/// Transform that applies brotli compression to the input.
15pub struct BrotliCompressor {
16    input_buffer: Vec<u8>,
17    output_buffer: Vec<u8>,
18    params: BrotliEncoderParams,
19    alloc: StandardAlloc,
20}
21
22impl Default for BrotliCompressor {
23    fn default() -> Self {
24        Self {
25            input_buffer: vec![0u8; INPUT_BUFFER_SIZE],
26            output_buffer: vec![0u8; OUTPUT_BUFFER_SIZE],
27            params: BrotliEncoderParams::default(),
28            alloc: StandardAlloc::default(),
29        }
30    }
31}
32
33impl BinaryTransform for BrotliCompressor {
34    fn transform_mut(&mut self, data: Bytes) -> Result<Bytes, std::io::Error> {
35        let mut buffer = BytesMut::new().writer();
36        BrotliCompressCustomAlloc(
37            &mut data.reader(),
38            &mut buffer,
39            &mut self.input_buffer,
40            &mut self.output_buffer,
41            &self.params,
42            self.alloc,
43        )?;
44        Ok(buffer.into_inner().into())
45    }
46
47    fn transform(&self, data: Bytes) -> Result<Bytes, std::io::Error> {
48        let mut buffer = BytesMut::new().writer();
49        BrotliCompress(&mut data.reader(), &mut buffer, &self.params)?;
50        Ok(buffer.into_inner().into())
51    }
52}
53
54/// Transform that decompress brotli-compressed data back to its original content.
55pub struct BrotliDecompressor {
56    input_buffer: Vec<u8>,
57    output_buffer: Vec<u8>,
58    alloc: StandardAlloc,
59}
60
61impl Default for BrotliDecompressor {
62    fn default() -> Self {
63        Self {
64            input_buffer: vec![0u8; INPUT_BUFFER_SIZE],
65            output_buffer: vec![0u8; OUTPUT_BUFFER_SIZE],
66            alloc: StandardAlloc::default(),
67        }
68    }
69}
70
71impl BinaryTransform for BrotliDecompressor {
72    fn transform_mut(&mut self, data: Bytes) -> Result<Bytes, std::io::Error> {
73        let mut buffer = BytesMut::new().writer();
74        BrotliDecompressCustomAlloc(
75            &mut data.reader(),
76            &mut buffer,
77            &mut self.input_buffer,
78            &mut self.output_buffer,
79            self.alloc,
80            self.alloc,
81            self.alloc,
82        )?;
83        Ok(buffer.into_inner().into())
84    }
85
86    fn transform(&self, data: Bytes) -> Result<Bytes, std::io::Error> {
87        let mut buffer = BytesMut::new().writer();
88        BrotliDecompress(&mut data.reader(), &mut buffer)?;
89        Ok(buffer.into_inner().into())
90    }
91}
92
93#[cfg(test)]
94mod test {
95    use super::{BrotliCompressor, BrotliDecompressor};
96    use crate::transform::BinaryTransform;
97    use bytes::Bytes;
98    use lipsum::lipsum;
99
100    #[test]
101    fn test_roundtrip_default() {
102        let compressor = BrotliCompressor::default();
103        let decompressor = BrotliDecompressor::default();
104
105        let data = Bytes::from(lipsum(1000));
106
107        let compressed = compressor
108            .transform(data.clone())
109            .expect("should compress via brotli");
110        let uncompressed = decompressor
111            .transform(compressed)
112            .expect("should decompress from brotli");
113
114        assert_eq!(data, uncompressed);
115    }
116
117    #[test]
118    fn test_roundtrip_mut_default() {
119        let mut compressor = BrotliCompressor::default();
120        let mut decompressor = BrotliDecompressor::default();
121
122        let data = Bytes::from(lipsum(1000));
123
124        let compressed = compressor
125            .transform_mut(data.clone())
126            .expect("should compress via brotli");
127        let uncompressed = decompressor
128            .transform_mut(compressed)
129            .expect("should decompress from brotli");
130
131        assert_eq!(data, uncompressed);
132    }
133}