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