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 assert_eq!(
166 Ok(TARGET.to_vec()),
167 BuiltInBrotliDecoder.decode(&NO_DICT_PATCH, None, TARGET.len())
168 );
169 }
170
171 #[test]
172 fn brotli_decode_with_empty_shared_dict() {
173 assert_eq!(
176 Err(DecodeError::InvalidDictionary),
177 BuiltInBrotliDecoder.decode(&NO_DICT_PATCH, Some(b""), TARGET.len())
178 );
179 }
180
181 #[test]
182 fn brotli_decode_too_little_output() {
183 assert_eq!(
184 Err(DecodeError::MaxSizeExceeded),
185 BuiltInBrotliDecoder.decode(
186 &SHARED_DICT_PATCH,
187 Some(BASE.as_bytes()),
188 TARGET.len() - 1
189 )
190 );
191 }
192
193 #[test]
194 fn brotli_decode_excess_output() {
195 assert_eq!(
196 Ok(TARGET.to_vec()),
197 BuiltInBrotliDecoder.decode(
198 &SHARED_DICT_PATCH,
199 Some(BASE.as_bytes()),
200 TARGET.len() + 1,
201 )
202 );
203 }
204
205 #[cfg(feature = "c-brotli")]
210 #[test]
211 fn brotli_decode_too_much_input() {
212 let mut patch: Vec<u8> = NO_DICT_PATCH.to_vec();
213 patch.push(0u8);
214
215 assert_eq!(
216 Err(DecodeError::ExcessInputData),
217 BuiltInBrotliDecoder.decode(&patch, None, TARGET.len())
218 );
219 }
220
221 #[test]
222 fn brotli_decode_input_missing() {
223 let patch: Vec<u8> = NO_DICT_PATCH[..NO_DICT_PATCH.len() - 1].to_vec();
225 assert!(matches!(
226 BuiltInBrotliDecoder.decode(&patch, None, TARGET.len()),
227 Err(DecodeError::InvalidStream)
228 ));
229 }
230
231 #[test]
232 fn brotli_decode_invalid() {
233 let patch = [0xFF, 0xFF, 0xFFu8];
234 assert!(matches!(
235 BuiltInBrotliDecoder.decode(&patch, None, 10),
236 Err(DecodeError::InvalidStream)
237 ));
238 }
239}