1use crate::{Status, metadata::MetadataValue};
26use bytes::{Buf, BufMut, BytesMut};
27#[cfg(feature = "gzip")]
28use flate2::read::{GzDecoder, GzEncoder};
29#[cfg(feature = "deflate")]
30use flate2::read::{ZlibDecoder, ZlibEncoder};
31use std::{borrow::Cow, fmt};
32#[cfg(feature = "zstd")]
33use zstd::stream::read::{Decoder, Encoder};
34
35pub(crate) const ENCODING_HEADER: &str = "grpc-encoding";
36pub(crate) const ACCEPT_ENCODING_HEADER: &str = "grpc-accept-encoding";
37
38#[derive(Debug, Default, Clone, Copy)]
42pub struct EnabledCompressionEncodings {
43 inner: [Option<CompressionEncoding>; 3],
44}
45
46impl EnabledCompressionEncodings {
47 pub fn enable(&mut self, encoding: CompressionEncoding) {
51 for e in self.inner.iter_mut() {
52 match e {
53 Some(e) if *e == encoding => return,
54 None => {
55 *e = Some(encoding);
56 return;
57 }
58 _ => continue,
59 }
60 }
61 }
62
63 pub fn pop(&mut self) -> Option<CompressionEncoding> {
65 self.inner
66 .iter_mut()
67 .rev()
68 .find(|entry| entry.is_some())?
69 .take()
70 }
71
72 pub(crate) fn into_accept_encoding_header_value(self) -> Option<http::HeaderValue> {
73 let mut value = BytesMut::new();
74 for encoding in self.inner.into_iter().flatten() {
75 value.put_slice(encoding.as_str().as_bytes());
76 value.put_u8(b',');
77 }
78
79 if value.is_empty() {
80 return None;
81 }
82
83 value.put_slice(b"identity");
84 Some(http::HeaderValue::from_maybe_shared(value).unwrap())
85 }
86
87 pub fn is_enabled(&self, encoding: CompressionEncoding) -> bool {
89 self.inner.contains(&Some(encoding))
90 }
91
92 pub fn is_empty(&self) -> bool {
94 self.inner.iter().all(|e| e.is_none())
95 }
96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99pub(crate) struct CompressionSettings {
100 pub(crate) encoding: CompressionEncoding,
101 pub(crate) buffer_growth_interval: usize,
104}
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum CompressionEncoding {
110 #[allow(missing_docs)]
111 #[cfg(feature = "gzip")]
112 Gzip,
113 #[allow(missing_docs)]
114 #[cfg(feature = "deflate")]
115 Deflate,
116 #[allow(missing_docs)]
117 #[cfg(feature = "zstd")]
118 Zstd,
119}
120
121impl CompressionEncoding {
122 pub(crate) const ENCODINGS: &'static [CompressionEncoding] = &[
123 #[cfg(feature = "gzip")]
124 CompressionEncoding::Gzip,
125 #[cfg(feature = "deflate")]
126 CompressionEncoding::Deflate,
127 #[cfg(feature = "zstd")]
128 CompressionEncoding::Zstd,
129 ];
130
131 pub(crate) fn from_accept_encoding_header(
133 map: &http::HeaderMap,
134 enabled_encodings: EnabledCompressionEncodings,
135 ) -> Option<Self> {
136 if enabled_encodings.is_empty() {
137 return None;
138 }
139
140 let header_value = map.get(ACCEPT_ENCODING_HEADER)?;
141 let header_value_str = header_value.to_str().ok()?;
142
143 split_by_comma(header_value_str).find_map(|value| match value {
144 #[cfg(feature = "gzip")]
145 "gzip" if enabled_encodings.is_enabled(CompressionEncoding::Gzip) => {
146 Some(CompressionEncoding::Gzip)
147 }
148 #[cfg(feature = "deflate")]
149 "deflate" if enabled_encodings.is_enabled(CompressionEncoding::Deflate) => {
150 Some(CompressionEncoding::Deflate)
151 }
152 #[cfg(feature = "zstd")]
153 "zstd" if enabled_encodings.is_enabled(CompressionEncoding::Zstd) => {
154 Some(CompressionEncoding::Zstd)
155 }
156 _ => None,
157 })
158 }
159
160 pub(crate) fn from_encoding_header(
162 map: &http::HeaderMap,
163 enabled_encodings: EnabledCompressionEncodings,
164 ) -> Result<Option<Self>, Status> {
165 let Some(header_value) = map.get(ENCODING_HEADER) else {
166 return Ok(None);
167 };
168
169 match header_value.as_bytes() {
170 #[cfg(feature = "gzip")]
171 b"gzip" if enabled_encodings.is_enabled(CompressionEncoding::Gzip) => {
172 Ok(Some(CompressionEncoding::Gzip))
173 }
174 #[cfg(feature = "deflate")]
175 b"deflate" if enabled_encodings.is_enabled(CompressionEncoding::Deflate) => {
176 Ok(Some(CompressionEncoding::Deflate))
177 }
178 #[cfg(feature = "zstd")]
179 b"zstd" if enabled_encodings.is_enabled(CompressionEncoding::Zstd) => {
180 Ok(Some(CompressionEncoding::Zstd))
181 }
182 b"identity" => Ok(None),
183 other => {
184 let other = match std::str::from_utf8(other) {
185 Ok(s) => Cow::Borrowed(s),
186 Err(_) => Cow::Owned(format!("{other:?}")),
187 };
188
189 let mut status = Status::unimplemented(format!(
190 "Content is compressed with `{other}` which isn't supported"
191 ));
192
193 let header_value = enabled_encodings
194 .into_accept_encoding_header_value()
195 .map(MetadataValue::unchecked_from_header_value)
196 .unwrap_or_else(|| MetadataValue::from_static("identity"));
197 status
198 .metadata_mut()
199 .insert(ACCEPT_ENCODING_HEADER, header_value);
200
201 Err(status)
202 }
203 }
204 }
205
206 pub(crate) fn as_str(self) -> &'static str {
207 match self {
208 #[cfg(feature = "gzip")]
209 CompressionEncoding::Gzip => "gzip",
210 #[cfg(feature = "deflate")]
211 CompressionEncoding::Deflate => "deflate",
212 #[cfg(feature = "zstd")]
213 CompressionEncoding::Zstd => "zstd",
214 }
215 }
216
217 #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))]
218 pub(crate) fn into_header_value(self) -> http::HeaderValue {
219 http::HeaderValue::from_static(self.as_str())
220 }
221}
222
223impl fmt::Display for CompressionEncoding {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 f.write_str(self.as_str())
226 }
227}
228
229fn split_by_comma(s: &str) -> impl Iterator<Item = &str> {
230 s.split(',').map(|s| s.trim())
231}
232
233#[allow(unused_variables, unreachable_code)]
236pub(crate) fn compress(
237 settings: CompressionSettings,
238 decompressed_buf: &mut BytesMut,
239 out_buf: &mut BytesMut,
240 len: usize,
241) -> Result<(), std::io::Error> {
242 let buffer_growth_interval = settings.buffer_growth_interval;
243 let capacity = ((len / buffer_growth_interval) + 1) * buffer_growth_interval;
244 out_buf.reserve(capacity);
245
246 #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))]
247 let mut out_writer = out_buf.writer();
248
249 match settings.encoding {
250 #[cfg(feature = "gzip")]
251 CompressionEncoding::Gzip => {
252 let mut gzip_encoder = GzEncoder::new(
253 &decompressed_buf[0..len],
254 flate2::Compression::new(6),
256 );
257 std::io::copy(&mut gzip_encoder, &mut out_writer)?;
258 }
259 #[cfg(feature = "deflate")]
260 CompressionEncoding::Deflate => {
261 let mut deflate_encoder = ZlibEncoder::new(
262 &decompressed_buf[0..len],
263 flate2::Compression::new(6),
265 );
266 std::io::copy(&mut deflate_encoder, &mut out_writer)?;
267 }
268 #[cfg(feature = "zstd")]
269 CompressionEncoding::Zstd => {
270 let mut zstd_encoder = Encoder::new(
271 &decompressed_buf[0..len],
272 zstd::DEFAULT_COMPRESSION_LEVEL,
274 )?;
275 std::io::copy(&mut zstd_encoder, &mut out_writer)?;
276 }
277 }
278
279 decompressed_buf.advance(len);
280
281 Ok(())
282}
283
284#[allow(unused_variables, unreachable_code)]
286pub(crate) fn decompress(
287 settings: CompressionSettings,
288 compressed_buf: &mut BytesMut,
289 mut out_buf: bytes::buf::Limit<&mut BytesMut>,
290 len: usize,
291) -> Result<(), std::io::Error> {
292 let buffer_growth_interval = settings.buffer_growth_interval;
293 let estimate_decompressed_len = len * 2;
294 let capacity = std::cmp::min(
295 bytes::buf::Limit::limit(&out_buf),
296 ((estimate_decompressed_len / buffer_growth_interval) + 1) * buffer_growth_interval,
297 );
298
299 out_buf.get_mut().reserve(capacity);
300
301 #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))]
302 let mut out_writer = out_buf.writer();
303
304 match settings.encoding {
305 #[cfg(feature = "gzip")]
306 CompressionEncoding::Gzip => {
307 let mut gzip_decoder = GzDecoder::new(&compressed_buf[0..len]);
308 std::io::copy(&mut gzip_decoder, &mut out_writer)?;
309 }
310 #[cfg(feature = "deflate")]
311 CompressionEncoding::Deflate => {
312 let mut deflate_decoder = ZlibDecoder::new(&compressed_buf[0..len]);
313 std::io::copy(&mut deflate_decoder, &mut out_writer)?;
314 }
315 #[cfg(feature = "zstd")]
316 CompressionEncoding::Zstd => {
317 let mut zstd_decoder = Decoder::new(&compressed_buf[0..len])?;
318 std::io::copy(&mut zstd_decoder, &mut out_writer)?;
319 }
320 }
321
322 compressed_buf.advance(len);
323
324 Ok(())
325}
326
327#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
329pub enum SingleMessageCompressionOverride {
330 #[default]
335 Inherit,
336 Disable,
338}
339
340#[cfg(test)]
341mod tests {
342 #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))]
343 use http::HeaderValue;
344
345 use super::*;
346
347 #[test]
348 fn convert_none_into_header_value() {
349 let encodings = EnabledCompressionEncodings::default();
350
351 assert!(encodings.into_accept_encoding_header_value().is_none());
352 }
353
354 #[test]
355 #[cfg(feature = "gzip")]
356 fn convert_gzip_into_header_value() {
357 const GZIP: HeaderValue = HeaderValue::from_static("gzip,identity");
358
359 let encodings = EnabledCompressionEncodings {
360 inner: [Some(CompressionEncoding::Gzip), None, None],
361 };
362
363 assert_eq!(encodings.into_accept_encoding_header_value().unwrap(), GZIP);
364
365 let encodings = EnabledCompressionEncodings {
366 inner: [None, None, Some(CompressionEncoding::Gzip)],
367 };
368
369 assert_eq!(encodings.into_accept_encoding_header_value().unwrap(), GZIP);
370 }
371
372 #[test]
373 #[cfg(feature = "zstd")]
374 fn convert_zstd_into_header_value() {
375 const ZSTD: HeaderValue = HeaderValue::from_static("zstd,identity");
376
377 let encodings = EnabledCompressionEncodings {
378 inner: [Some(CompressionEncoding::Zstd), None, None],
379 };
380
381 assert_eq!(encodings.into_accept_encoding_header_value().unwrap(), ZSTD);
382
383 let encodings = EnabledCompressionEncodings {
384 inner: [None, None, Some(CompressionEncoding::Zstd)],
385 };
386
387 assert_eq!(encodings.into_accept_encoding_header_value().unwrap(), ZSTD);
388 }
389
390 #[test]
391 #[cfg(all(feature = "gzip", feature = "zstd"))]
392 fn from_accept_encoding_header_respects_server_enabled_encodings() {
393 let mut enabled = EnabledCompressionEncodings::default();
410 enabled.enable(CompressionEncoding::Zstd);
411 assert!(enabled.is_enabled(CompressionEncoding::Zstd));
412 assert!(!enabled.is_enabled(CompressionEncoding::Gzip));
413
414 let mut headers = http::HeaderMap::new();
415 headers.insert(
416 ACCEPT_ENCODING_HEADER,
417 HeaderValue::from_static("gzip,zstd,identity"),
418 );
419
420 assert_eq!(
421 CompressionEncoding::from_accept_encoding_header(&headers, enabled),
422 Some(CompressionEncoding::Zstd),
423 "server has only Zstd enabled; must not pick Gzip just because \
424 the client listed it first",
425 );
426 }
427
428 #[test]
429 #[cfg(all(feature = "gzip", feature = "zstd"))]
430 fn from_accept_encoding_header_returns_none_when_no_overlap() {
431 let mut enabled = EnabledCompressionEncodings::default();
435 enabled.enable(CompressionEncoding::Zstd);
436
437 let mut headers = http::HeaderMap::new();
438 headers.insert(
439 ACCEPT_ENCODING_HEADER,
440 HeaderValue::from_static("gzip,identity"),
441 );
442
443 assert_eq!(
444 CompressionEncoding::from_accept_encoding_header(&headers, enabled),
445 None,
446 );
447 }
448
449 #[test]
450 #[cfg(all(feature = "gzip", feature = "zstd"))]
451 fn from_accept_encoding_header_uses_client_preference_order() {
452 let mut enabled = EnabledCompressionEncodings::default();
456 enabled.enable(CompressionEncoding::Zstd);
457 enabled.enable(CompressionEncoding::Gzip);
458
459 let mut headers = http::HeaderMap::new();
460 headers.insert(
461 ACCEPT_ENCODING_HEADER,
462 HeaderValue::from_static("gzip,zstd,identity"),
463 );
464
465 assert_eq!(
466 CompressionEncoding::from_accept_encoding_header(&headers, enabled),
467 Some(CompressionEncoding::Gzip),
468 );
469 }
470
471 #[test]
472 #[cfg(all(feature = "gzip", feature = "deflate", feature = "zstd"))]
473 fn convert_compression_encodings_into_header_value() {
474 let encodings = EnabledCompressionEncodings {
475 inner: [
476 Some(CompressionEncoding::Gzip),
477 Some(CompressionEncoding::Deflate),
478 Some(CompressionEncoding::Zstd),
479 ],
480 };
481
482 assert_eq!(
483 encodings.into_accept_encoding_header_value().unwrap(),
484 HeaderValue::from_static("gzip,deflate,zstd,identity"),
485 );
486
487 let encodings = EnabledCompressionEncodings {
488 inner: [
489 Some(CompressionEncoding::Zstd),
490 Some(CompressionEncoding::Deflate),
491 Some(CompressionEncoding::Gzip),
492 ],
493 };
494
495 assert_eq!(
496 encodings.into_accept_encoding_header_value().unwrap(),
497 HeaderValue::from_static("zstd,deflate,gzip,identity"),
498 );
499 }
500}