1#[cfg(feature = "c-brotli")]
2mod c_brotli;
3
4#[cfg(feature = "c-brotli")]
5mod sys;
6
7#[cfg(feature = "rust-brotli")]
8mod rust_brotli;
9
10pub mod decode_error;
11
12#[cfg(fuzzing)]
13use decode_error::DecodeError;
14
15pub trait SharedBrotliDecoder {
20 fn decode(
28 &self,
29 encoded: &[u8],
30 shared_dictionary: Option<&[u8]>,
31 max_uncompressed_length: usize,
32 ) -> Result<Vec<u8>, decode_error::DecodeError>;
33}
34
35pub struct BuiltInBrotliDecoder;
39
40pub struct NoopBrotliDecoder;
44
45impl SharedBrotliDecoder for Box<dyn SharedBrotliDecoder> {
46 fn decode(
47 &self,
48 encoded: &[u8],
49 shared_dictionary: Option<&[u8]>,
50 max_uncompressed_length: usize,
51 ) -> Result<Vec<u8>, decode_error::DecodeError> {
52 self.as_ref()
53 .decode(encoded, shared_dictionary, max_uncompressed_length)
54 }
55}
56
57impl SharedBrotliDecoder for BuiltInBrotliDecoder {
58 fn decode(
59 &self,
60 encoded: &[u8],
61 shared_dictionary: Option<&[u8]>,
62 max_uncompressed_length: usize,
63 ) -> Result<Vec<u8>, decode_error::DecodeError> {
64 cfg_if::cfg_if! {
65 if #[cfg(feature = "c-brotli")] {
66 #[allow(clippy::needless_return)]
67 return c_brotli::shared_brotli_decode_c(
68 encoded,
69 shared_dictionary,
70 max_uncompressed_length,
71 );
72 } else if #[cfg(feature = "rust-brotli")] {
73 return rust_brotli::shared_brotli_decode_rust(encoded, shared_dictionary, max_uncompressed_length);
74 } else {
75 compile_error!("At least one of 'c-brotli' or 'rust-brotli' must be enabled.");
76 }
77 }
78 }
79}
80
81impl SharedBrotliDecoder for NoopBrotliDecoder {
82 fn decode(
83 &self,
84 encoded: &[u8],
85 _shared_dictionary: Option<&[u8]>,
86 max_uncompressed_length: usize,
87 ) -> Result<Vec<u8>, decode_error::DecodeError> {
88 if encoded.len() <= max_uncompressed_length {
89 Ok(encoded.to_vec())
90 } else {
91 Err(decode_error::DecodeError::MaxSizeExceeded)
92 }
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use decode_error::DecodeError;
100
101 const TARGET: &[u8] = "hijkabcdeflmnohijkabcdeflmno\n".as_bytes();
102 const BASE: &str = "abcdef\n";
103
104 const SHARED_DICT_PATCH: [u8; 23] = [
108 0xa1, 0xe0, 0x00, 0xc0, 0x2f, 0x3a, 0x38, 0xf4, 0x01, 0xd1, 0xaf, 0x54, 0x84, 0x14, 0x71,
109 0x2a, 0x80, 0x04, 0xa2, 0x1c, 0xd3, 0xdd, 0x07,
110 ];
111
112 const NO_DICT_PATCH: [u8; 26] = [
115 0xa1, 0xe0, 0x00, 0xc0, 0x2f, 0x96, 0x1c, 0xf3, 0x03, 0xb1, 0xcf, 0x45, 0x95, 0x22, 0x4a,
116 0xc5, 0x03, 0x21, 0xb2, 0x9a, 0x58, 0xd4, 0x7c, 0xf6, 0x1e, 0x00u8,
117 ];
118
119 #[test]
120 fn brotli_decode_with_shared_dict() {
121 assert_eq!(
122 Ok(TARGET.to_vec()),
123 BuiltInBrotliDecoder.decode(&SHARED_DICT_PATCH, Some(BASE.as_bytes()), TARGET.len(),)
124 );
125 }
126
127 #[test]
128 fn brotli_decode_rust_brotli_regression_case() {
129 let patch: &[u8] = &[
132 27, 103, 0, 96, 47, 14, 120, 211, 142, 228, 22, 15, 167, 193, 55, 28, 228, 226, 254,
133 54, 10, 36, 226, 192, 19, 76, 50, 8, 169, 92, 9, 197, 47, 12, 211, 114, 34, 175, 18,
134 241, 122, 134, 170, 32, 189, 4, 112, 153, 119, 12, 237, 23, 120, 130, 2,
135 ];
136
137 let dict: Vec<u8> = vec![
138 2, 0, 0, 0, 0, 213, 195, 31, 121, 231, 225, 250, 238, 34, 174, 158, 246, 208, 145, 187,
139 92, 2, 0, 0, 4, 0, 0, 0, 46, 0, 0, 0, 0, 0, 11, 123, 105, 100, 125, 46, 105, 102, 116,
140 95, 116, 107, 20, 0, 0, 52, 40, 103, 221, 215, 223, 255, 95, 54, 15, 13, 85, 53, 206,
141 115, 249, 165, 159, 159, 16, 29, 37, 17, 114, 1, 163, 2, 16, 33, 51, 4, 32, 0, 226, 29,
142 19, 88, 254, 195, 129, 23, 25, 22, 8, 19, 21, 41, 130, 136, 51, 8, 67, 209, 52, 204,
143 204, 70, 199, 130, 252, 47, 16, 40, 186, 251, 62, 63, 19, 236, 147, 240, 211, 215, 59,
144 ];
145
146 let decompressed = BuiltInBrotliDecoder.decode(patch, Some(&dict), 500);
147
148 assert_eq!(
149 decompressed,
150 Ok(vec![
151 0x02, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x16, 0xa6, 0x25, 0x18, 0xf8, 0x68, 0x63, 0x4e,
152 0xe4, 0x09, 0x2b, 0xa1, 0xe2, 0x4b, 0xba, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
153 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x7b, 0x69, 0x64, 0x7d, 0x2e, 0x69, 0x66,
154 0x74, 0x5f, 0x74, 0x6b, 0x14, 0x00, 0x00, 0x38, 0x1d, 0x25, 0x11, 0x72, 0x01, 0xa3,
155 0x02, 0x10, 0x21, 0x33, 0x04, 0x20, 0x00, 0xe2, 0x1d, 0x13, 0x58, 0xfe, 0xc3, 0x81,
156 0x17, 0x19, 0x16, 0x08, 0x13, 0x15, 0x29, 0x82, 0x88, 0x33, 0x08, 0x43, 0xd1, 0x34,
157 0xcc, 0xcc, 0x46, 0xc7, 0x82, 0xfc, 0x2f, 0x10, 0x28, 0xba, 0xfb, 0x3e, 0x3f, 0x13,
158 0xec, 0x93, 0xf0, 0xd3, 0xd7, 0x3b,
159 ])
160 );
161 }
162
163 #[test]
164 fn brotli_decode_without_shared_dict() {
165 let base = "".as_bytes();
166
167 assert_eq!(
168 Ok(TARGET.to_vec()),
169 BuiltInBrotliDecoder.decode(&NO_DICT_PATCH, None, TARGET.len())
170 );
171
172 assert_eq!(
174 Ok(TARGET.to_vec()),
175 BuiltInBrotliDecoder.decode(&NO_DICT_PATCH, Some(base), TARGET.len())
176 );
177 }
178
179 #[test]
180 fn brotli_decode_too_little_output() {
181 assert_eq!(
182 Err(DecodeError::MaxSizeExceeded),
183 BuiltInBrotliDecoder.decode(
184 &SHARED_DICT_PATCH,
185 Some(BASE.as_bytes()),
186 TARGET.len() - 1
187 )
188 );
189 }
190
191 #[test]
192 fn brotli_decode_excess_output() {
193 assert_eq!(
194 Ok(TARGET.to_vec()),
195 BuiltInBrotliDecoder.decode(
196 &SHARED_DICT_PATCH,
197 Some(BASE.as_bytes()),
198 TARGET.len() + 1,
199 )
200 );
201 }
202
203 #[cfg(feature = "c-brotli")]
208 #[test]
209 fn brotli_decode_too_much_input() {
210 let mut patch: Vec<u8> = NO_DICT_PATCH.to_vec();
211 patch.push(0u8);
212
213 assert_eq!(
214 Err(DecodeError::ExcessInputData),
215 BuiltInBrotliDecoder.decode(&patch, None, TARGET.len())
216 );
217 }
218
219 #[test]
220 fn brotli_decode_input_missing() {
221 let patch: Vec<u8> = NO_DICT_PATCH[..NO_DICT_PATCH.len() - 1].to_vec();
223 assert!(matches!(
224 BuiltInBrotliDecoder.decode(&patch, None, TARGET.len()),
225 Err(DecodeError::InvalidStream)
226 ));
227 }
228
229 #[test]
230 fn brotli_decode_invalid() {
231 let patch = [0xFF, 0xFF, 0xFFu8];
232 assert!(matches!(
233 BuiltInBrotliDecoder.decode(&patch, None, 10),
234 Err(DecodeError::InvalidStream)
235 ));
236 }
237}