1use std::io::{Read, Write};
6
7use flate2::{Compression, bufread::MultiGzDecoder, write::GzEncoder};
8use ruzstd::{
9 decoding::StreamingDecoder,
10 encoding::{CompressionLevel, compress_to_vec},
11};
12
13use crate::{
14 Error, Limits,
15 message::{self, Request},
16};
17
18pub(crate) const MARKER: u8 = 0x04;
19const MIN_BODY_LEN: usize = 64;
20const MIN_ZSTD_WINDOW: usize = 1024 * 1024;
24const MAX_ZSTD_WINDOW: usize = 8 * 1024 * 1024;
25const MAX_ZSTD_FRAMES: usize = 16;
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum CompressionCoding {
30 Gzip,
32 Zstd,
34}
35
36impl CompressionCoding {
37 pub(crate) const fn wire_id(self) -> u8 {
38 match self {
39 Self::Gzip => 1,
40 Self::Zstd => 2,
41 }
42 }
43
44 pub(crate) const fn from_wire_id(id: u8) -> Result<Option<Self>, Error> {
45 match id {
46 0 => Ok(None),
47 1 => Ok(Some(Self::Gzip)),
48 2 => Ok(Some(Self::Zstd)),
49 _ => Err(Error::MalformedEnvelope),
50 }
51 }
52}
53
54pub(crate) fn encode_request(
55 request: &Request,
56 limits: Limits,
57 preferred: Option<CompressionCoding>,
58) -> Result<Vec<u8>, Error> {
59 let Some(preferred) = preferred else {
60 return message::encode_request(request, limits);
61 };
62 let coded = maybe_compress(&request.body, preferred)?;
63 let body_coding = coded.as_ref().map(|_| preferred);
64 let mut output = vec![
65 MARKER,
66 body_coding.map_or(0, CompressionCoding::wire_id),
67 preferred.wire_id(),
68 ];
69 let bhttp = message::encode_request_with_body(
70 request,
71 coded.as_deref().unwrap_or(&request.body),
72 limits,
73 )?;
74 output.extend_from_slice(&bhttp);
75 Ok(output)
76}
77
78pub(crate) fn decode_request(
79 input: &[u8],
80 limits: Limits,
81 enabled: bool,
82) -> Result<
83 (
84 Request,
85 Option<CompressionCoding>,
86 Option<CompressionCoding>,
87 ),
88 Error,
89> {
90 if !input.starts_with(&[MARKER]) {
91 return Ok((message::decode_request(input, limits)?, None, None));
92 }
93 if !enabled || input.len() < 4 {
94 return Err(Error::MalformedEnvelope);
95 }
96 let body_coding = CompressionCoding::from_wire_id(input[1])?;
97 let response_coding = CompressionCoding::from_wire_id(input[2])?;
98 if body_coding.is_none() && response_coding.is_none() {
99 return Err(Error::MalformedEnvelope);
100 }
101 let request = if body_coding.is_some() {
102 message::decode_coded_request(&input[3..], limits)?
103 } else {
104 message::decode_request(&input[3..], limits)?
105 };
106 Ok((request, body_coding, response_coding))
107}
108
109pub(crate) fn finish_request(
110 mut request: Request,
111 coding: Option<CompressionCoding>,
112 limits: Limits,
113) -> Result<Request, Error> {
114 if let Some(coding) = coding {
115 request.body = decompress(&request.body, coding, limits.max_body_len)?;
116 message::validate_decoded_request_body(&request)?;
117 }
118 Ok(request)
119}
120
121pub(crate) fn maybe_compress(
122 body: &[u8],
123 coding: CompressionCoding,
124) -> Result<Option<Vec<u8>>, Error> {
125 if body.len() < MIN_BODY_LEN {
126 return Ok(None);
127 }
128 let coded = compress(body, coding)?;
129 Ok((coded.len() < body.len()).then_some(coded))
130}
131
132pub(crate) fn compress(body: &[u8], coding: CompressionCoding) -> Result<Vec<u8>, Error> {
133 let coded = match coding {
134 CompressionCoding::Gzip => {
135 let mut encoder = GzEncoder::new(Vec::new(), Compression::fast());
136 encoder
137 .write_all(body)
138 .map_err(|_| Error::CompressionFailure)?;
139 encoder.finish().map_err(|_| Error::CompressionFailure)?
140 }
141 CompressionCoding::Zstd => compress_to_vec(body, CompressionLevel::Fastest),
142 };
143 Ok(coded)
144}
145
146pub(crate) fn decompress(
147 coded: &[u8],
148 coding: CompressionCoding,
149 maximum_body_len: usize,
150) -> Result<Vec<u8>, Error> {
151 if coded.is_empty() {
152 return Err(Error::MalformedEnvelope);
153 }
154 match coding {
155 CompressionCoding::Gzip => {
156 let mut decoder = MultiGzDecoder::new(coded);
157 let body = read_bounded(&mut decoder, maximum_body_len)?;
158 if !decoder.into_inner().is_empty() {
159 return Err(Error::MalformedEnvelope);
160 }
161 Ok(body)
162 }
163 CompressionCoding::Zstd => {
164 let mut remaining = coded;
165 let mut body = Vec::new();
166 let mut decoded_frames = 0_usize;
167 let mut seen_frames = 0_usize;
168 while !remaining.is_empty() {
169 seen_frames += 1;
170 if seen_frames > MAX_ZSTD_FRAMES {
171 return Err(Error::LimitExceeded);
172 }
173 if let Some(skip_len) = skippable_frame_len(remaining)? {
175 remaining = &remaining[skip_len..];
176 continue;
177 }
178 let mut decoder = StreamingDecoder::new_with_max_window_size(
179 remaining,
180 maximum_body_len.clamp(MIN_ZSTD_WINDOW, MAX_ZSTD_WINDOW) as u64,
181 )
182 .map_err(|_| Error::MalformedEnvelope)?;
183 let declared_size = decoder.decoder.content_size();
184 if declared_size > maximum_body_len.saturating_sub(body.len()) as u64 {
185 return Err(Error::LimitExceeded);
186 }
187 let decoded_before = body.len();
188 read_bounded_into(&mut decoder, &mut body, maximum_body_len)?;
189 if declared_size != 0 && declared_size != (body.len() - decoded_before) as u64 {
190 return Err(Error::MalformedEnvelope);
191 }
192 if decoder.decoder.get_checksum_from_data().is_some()
193 && decoder.decoder.get_checksum_from_data()
194 != decoder.decoder.get_calculated_checksum()
195 {
196 return Err(Error::MalformedEnvelope);
197 }
198 remaining = decoder.into_inner();
199 decoded_frames += 1;
200 }
201 if decoded_frames == 0 {
202 return Err(Error::MalformedEnvelope);
203 }
204 Ok(body)
205 }
206 }
207}
208
209fn skippable_frame_len(input: &[u8]) -> Result<Option<usize>, Error> {
210 if input.len() < 4 {
211 return Err(Error::MalformedEnvelope);
212 }
213 let magic = u32::from_le_bytes([input[0], input[1], input[2], input[3]]);
214 if !(0x184d_2a50..=0x184d_2a5f).contains(&magic) {
215 return Ok(None);
216 }
217 if input.len() < 8 {
218 return Err(Error::MalformedEnvelope);
219 }
220 let length = u32::from_le_bytes([input[4], input[5], input[6], input[7]]) as usize;
221 let total = length.checked_add(8).ok_or(Error::MalformedEnvelope)?;
222 if total > input.len() {
223 return Err(Error::MalformedEnvelope);
224 }
225 Ok(Some(total))
226}
227
228fn read_bounded(reader: &mut impl Read, maximum_body_len: usize) -> Result<Vec<u8>, Error> {
229 let mut output = Vec::new();
230 read_bounded_into(reader, &mut output, maximum_body_len)?;
231 Ok(output)
232}
233
234fn read_bounded_into(
235 reader: &mut impl Read,
236 output: &mut Vec<u8>,
237 maximum_body_len: usize,
238) -> Result<(), Error> {
239 let mut buffer = [0_u8; 8192];
240 loop {
241 let count = reader
242 .read(&mut buffer)
243 .map_err(|_| Error::MalformedEnvelope)?;
244 if count == 0 {
245 return Ok(());
246 }
247 if count > maximum_body_len.saturating_sub(output.len()) {
248 return Err(Error::LimitExceeded);
249 }
250 output.extend_from_slice(&buffer[..count]);
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::{CompressionCoding, decompress, encode_request, maybe_compress};
257 use crate::{Error, Limits, Method, Request};
258
259 const GZIP: &[u8] = &[
262 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcb, 0x48, 0xcd, 0xc9, 0xc9,
263 0x57, 0x48, 0x2b, 0xca, 0xcf, 0x55, 0x48, 0xcc, 0x53, 0xc8, 0xcc, 0x4b, 0x49, 0x2d, 0x48,
264 0x05, 0x12, 0x79, 0x25, 0x0a, 0xe9, 0x55, 0x99, 0x05, 0x0a, 0xa9, 0x79, 0xc9, 0xf9, 0x29,
265 0xa9, 0x45, 0x0a, 0x19, 0x44, 0x29, 0x03, 0x00, 0x61, 0xc4, 0x34, 0x58, 0x4d, 0x00, 0x00,
266 0x00,
267 ];
268 const ZSTD: &[u8] = &[
269 0x28, 0xb5, 0x2f, 0xfd, 0x04, 0x48, 0x7d, 0x01, 0x00, 0x74, 0x02, 0x68, 0x65, 0x6c, 0x6c,
270 0x6f, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, 0x6e, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x70,
271 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x20, 0x7a, 0x73, 0x74, 0x64, 0x20, 0x65, 0x6e, 0x63,
272 0x6f, 0x64, 0x65, 0x72, 0x20, 0x01, 0x00, 0xaf, 0x1e, 0xf5, 0x04, 0x2a, 0x5c, 0xa5, 0xa6,
273 ];
274
275 #[test]
276 fn independent_gzip_members_and_strict_trailer() {
277 let expected =
278 b"hello from an independent gzip encoder hello from an independent gzip encoder";
279 assert_eq!(
280 decompress(GZIP, CompressionCoding::Gzip, 1024),
281 Ok(expected.to_vec())
282 );
283 let mut joined = GZIP.to_vec();
284 joined.extend_from_slice(GZIP);
285 assert_eq!(
286 decompress(&joined, CompressionCoding::Gzip, 1024),
287 Ok(expected.repeat(2))
288 );
289 assert_eq!(
290 decompress(&GZIP[..GZIP.len() - 1], CompressionCoding::Gzip, 1024),
291 Err(Error::MalformedEnvelope)
292 );
293 let mut bad_checksum = GZIP.to_vec();
294 bad_checksum[GZIP.len() - 8] ^= 1;
295 assert_eq!(
296 decompress(&bad_checksum, CompressionCoding::Gzip, 1024),
297 Err(Error::MalformedEnvelope)
298 );
299 let mut garbage = GZIP.to_vec();
300 garbage.push(0xff);
301 assert_eq!(
302 decompress(&garbage, CompressionCoding::Gzip, 1024),
303 Err(Error::MalformedEnvelope)
304 );
305 }
306
307 #[test]
308 fn independent_zstd_frames_and_skippable_frame() {
309 let expected =
310 b"hello from an independent zstd encoder hello from an independent zstd encoder";
311 assert_eq!(
312 decompress(ZSTD, CompressionCoding::Zstd, 1024),
313 Ok(expected.to_vec())
314 );
315 let mut joined = ZSTD.to_vec();
316 joined.extend_from_slice(ZSTD);
317 assert_eq!(
318 decompress(&joined, CompressionCoding::Zstd, 1024),
319 Ok(expected.repeat(2))
320 );
321 let mut skipped = vec![0x50, 0x2a, 0x4d, 0x18, 3, 0, 0, 0, b'x', b'y', b'z'];
322 skipped.extend_from_slice(ZSTD);
323 assert_eq!(
324 decompress(&skipped, CompressionCoding::Zstd, 1024),
325 Ok(expected.to_vec())
326 );
327 assert_eq!(
328 decompress(&ZSTD[..ZSTD.len() - 1], CompressionCoding::Zstd, 1024),
329 Err(Error::MalformedEnvelope)
330 );
331 let mut bad_checksum = ZSTD.to_vec();
332 bad_checksum[ZSTD.len() - 1] ^= 1;
333 assert_eq!(
334 decompress(&bad_checksum, CompressionCoding::Zstd, 1024),
335 Err(Error::MalformedEnvelope)
336 );
337 let mut garbage = ZSTD.to_vec();
338 garbage.push(0xff);
339 assert_eq!(
340 decompress(&garbage, CompressionCoding::Zstd, 1024),
341 Err(Error::MalformedEnvelope)
342 );
343 assert_eq!(
344 decompress(&ZSTD.repeat(17), CompressionCoding::Zstd, 4096),
345 Err(Error::LimitExceeded)
346 );
347 let mut oversized_window = ZSTD.to_vec();
348 oversized_window[5] = 0x90;
349 assert_eq!(
350 decompress(&oversized_window, CompressionCoding::Zstd, 64 * 1024 * 1024),
351 Err(Error::MalformedEnvelope)
352 );
353 let mut sized = ZSTD.to_vec();
356 sized[4] = 0x24;
357 sized[5] = 77;
358 assert_eq!(
359 decompress(&sized, CompressionCoding::Zstd, 1024),
360 Ok(expected.to_vec())
361 );
362 sized[5] = 76;
363 assert_eq!(
364 decompress(&sized, CompressionCoding::Zstd, 1024),
365 Err(Error::MalformedEnvelope)
366 );
367 sized[5] = 200;
368 assert_eq!(
369 decompress(&sized, CompressionCoding::Zstd, 100),
370 Err(Error::LimitExceeded)
371 );
372 }
373
374 #[test]
375 fn decoded_length_is_bounded_during_expansion() -> Result<(), Error> {
376 for coding in [CompressionCoding::Gzip, CompressionCoding::Zstd] {
377 let encoded =
378 maybe_compress(&vec![b'a'; 4096], coding)?.ok_or(Error::CompressionFailure)?;
379 assert_eq!(decompress(&encoded, coding, 128), Err(Error::LimitExceeded));
380 }
381 Ok(())
382 }
383
384 #[test]
385 fn body_coding_extension_has_literal_wire_prefixes() -> Result<(), Error> {
386 let limits = Limits::default();
387 let request = Request {
388 method: Method::Post,
389 authority: b"api.example.test".to_vec(),
390 path: b"/compressed".to_vec(),
391 headers: Vec::new(),
392 body: vec![b'a'; 4096],
393 };
394 for (coding, request_prefix) in [
395 (CompressionCoding::Gzip, [0x04, 0x01, 0x01]),
396 (CompressionCoding::Zstd, [0x04, 0x02, 0x02]),
397 ] {
398 assert!(encode_request(&request, limits, Some(coding))?.starts_with(&request_prefix));
399
400 let mut small_request = request.clone();
401 small_request.body.clear();
402 assert!(
403 encode_request(&small_request, limits, Some(coding))?.starts_with(&[
404 0x04,
405 0x00,
406 request_prefix[2]
407 ])
408 );
409 }
410 Ok(())
411 }
412}