1use std::collections::HashMap;
56#[cfg(feature = "streaming")]
57use std::pin::Pin;
58use std::sync::Arc;
59
60use bytes::Bytes;
61#[cfg(feature = "streaming")]
62use tokio::io::AsyncBufRead;
63#[cfg(feature = "streaming")]
64use tokio::io::AsyncRead;
65
66use crate::error::ConnectError;
67
68#[cfg(any(feature = "gzip", feature = "zstd"))]
69fn malformed_compressed_payload(message: impl Into<String>) -> ConnectError {
70 ConnectError::invalid_argument(message)
71}
72
73#[cfg(feature = "streaming")]
79#[cfg_attr(docsrs, doc(cfg(feature = "streaming")))]
80pub type BoxedAsyncRead = Pin<Box<dyn AsyncRead + Send>>;
81
82#[cfg(feature = "streaming")]
84#[cfg_attr(docsrs, doc(cfg(feature = "streaming")))]
85pub type BoxedAsyncBufRead = Pin<Box<dyn AsyncBufRead + Send>>;
86
87pub trait CompressionProvider: Send + Sync + 'static {
100 fn name(&self) -> &'static str;
105
106 fn compress(&self, data: &[u8]) -> Result<Bytes, ConnectError>;
108
109 fn decompressor<'a>(&self, data: &'a [u8])
121 -> Result<Box<dyn std::io::Read + 'a>, ConnectError>;
122
123 fn decompress_with_limit(&self, data: &[u8], max_size: usize) -> Result<Bytes, ConnectError> {
141 use std::io::Read;
142 let reader = self.decompressor(data)?;
143 let capacity = initial_decompress_capacity(data.len(), 2, Some(max_size));
144 let mut buf = Vec::with_capacity(capacity);
145 reader
146 .take((max_size as u64).saturating_add(1))
147 .read_to_end(&mut buf)
148 .map_err(|e| ConnectError::invalid_argument(format!("decompression failed: {e}")))?;
149 if buf.len() > max_size {
150 return Err(ConnectError::resource_exhausted(format!(
151 "decompressed size exceeds limit {max_size}"
152 )));
153 }
154 Ok(Bytes::from(buf))
155 }
156}
157
158#[cfg(feature = "streaming")]
165#[cfg_attr(docsrs, doc(cfg(feature = "streaming")))]
166pub trait StreamingCompressionProvider: CompressionProvider {
167 fn decompress_stream(&self, reader: BoxedAsyncBufRead) -> BoxedAsyncRead;
171
172 fn compress_stream(&self, reader: BoxedAsyncBufRead) -> BoxedAsyncRead;
176}
177
178#[derive(Clone)]
184pub struct CompressionRegistry {
185 providers: Arc<HashMap<&'static str, Arc<dyn CompressionProvider>>>,
186 #[cfg(feature = "streaming")]
187 streaming_providers: Arc<HashMap<&'static str, Arc<dyn StreamingCompressionProvider>>>,
188 accept_encoding: Arc<str>,
192}
193
194impl std::fmt::Debug for CompressionRegistry {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 f.debug_struct("CompressionRegistry")
197 .field("providers", &self.providers.keys().collect::<Vec<_>>())
198 .finish()
199 }
200}
201
202impl CompressionRegistry {
203 pub fn new() -> Self {
208 Self {
209 providers: Arc::new(HashMap::new()),
210 #[cfg(feature = "streaming")]
211 streaming_providers: Arc::new(HashMap::new()),
212 accept_encoding: Arc::from(""),
213 }
214 }
215
216 fn rebuild_accept_encoding(&mut self) {
218 let mut encodings: Vec<_> = self.providers.keys().copied().collect();
219 encodings.sort_unstable();
220 self.accept_encoding = Arc::from(encodings.join(", "));
221 }
222
223 #[must_use]
240 pub fn register<P: CompressionProvider>(mut self, provider: P) -> Self {
241 let providers = Arc::make_mut(&mut self.providers);
242 providers.insert(provider.name(), Arc::new(provider));
243 self.rebuild_accept_encoding();
244 self
245 }
246
247 #[must_use]
251 pub fn get(&self, name: &str) -> Option<Arc<dyn CompressionProvider>> {
252 self.providers.get(name).cloned()
253 }
254
255 pub fn supports(&self, name: &str) -> bool {
257 self.providers.contains_key(name)
258 }
259
260 pub fn supported_encodings(&self) -> Vec<&'static str> {
262 self.providers.keys().copied().collect()
263 }
264
265 pub fn accept_encoding_header(&self) -> &str {
270 &self.accept_encoding
271 }
272
273 pub fn negotiate_encoding(
284 &self,
285 accept_encoding: Option<&str>,
286 request_encoding: Option<&str>,
287 ) -> Option<&'static str> {
288 if let Some(accept) = accept_encoding {
290 for encoding in accept.split(',').map(|s| s.trim()) {
291 if encoding == "identity" {
292 continue; }
294 if let Some((key, _)) = self.providers.get_key_value(encoding) {
295 return Some(*key);
296 }
297 }
298 return None; }
300
301 if let Some(req_enc) = request_encoding
303 && req_enc != "identity"
304 && let Some((key, _)) = self.providers.get_key_value(req_enc)
305 {
306 return Some(*key);
307 }
308
309 None }
311
312 pub fn decompress_with_limit(
319 &self,
320 encoding: &str,
321 data: Bytes,
322 max_size: usize,
323 ) -> Result<Bytes, ConnectError> {
324 if encoding == "identity" {
326 if data.len() > max_size {
327 return Err(ConnectError::resource_exhausted(format!(
328 "message size {} exceeds limit {}",
329 data.len(),
330 max_size
331 )));
332 }
333 return Ok(data);
334 }
335
336 let provider = self.get(encoding).ok_or_else(|| {
337 ConnectError::unimplemented(format!("unsupported compression encoding: {encoding}"))
338 })?;
339
340 if data.is_empty() {
349 return Ok(data);
350 }
351
352 provider.decompress_with_limit(&data, max_size)
353 }
354
355 pub fn compress(&self, encoding: &str, data: &[u8]) -> Result<Bytes, ConnectError> {
359 if encoding == "identity" {
361 return Ok(Bytes::copy_from_slice(data));
362 }
363
364 let provider = self.get(encoding).ok_or_else(|| {
365 ConnectError::unimplemented(format!("unsupported compression encoding: {encoding}"))
366 })?;
367
368 provider.compress(data)
369 }
370
371 #[cfg(feature = "streaming")]
378 #[cfg_attr(docsrs, doc(cfg(feature = "streaming")))]
379 #[must_use]
380 pub fn register_streaming<P: StreamingCompressionProvider>(mut self, provider: P) -> Self {
381 let name = provider.name();
382 let provider = Arc::new(provider);
383
384 let providers = Arc::make_mut(&mut self.providers);
386 providers.insert(name, provider.clone());
387
388 let streaming_providers = Arc::make_mut(&mut self.streaming_providers);
389 streaming_providers.insert(name, provider);
390
391 self.rebuild_accept_encoding();
392 self
393 }
394
395 #[cfg(feature = "streaming")]
399 #[cfg_attr(docsrs, doc(cfg(feature = "streaming")))]
400 pub fn get_streaming(&self, name: &str) -> Option<Arc<dyn StreamingCompressionProvider>> {
401 self.streaming_providers.get(name).cloned()
402 }
403
404 #[cfg(feature = "streaming")]
406 #[cfg_attr(docsrs, doc(cfg(feature = "streaming")))]
407 pub fn supports_streaming(&self, name: &str) -> bool {
408 self.streaming_providers.contains_key(name)
409 }
410
411 #[cfg(feature = "streaming")]
416 #[cfg_attr(docsrs, doc(cfg(feature = "streaming")))]
417 pub fn decompress_stream(
418 &self,
419 encoding: &str,
420 reader: BoxedAsyncBufRead,
421 ) -> Result<BoxedAsyncRead, ConnectError> {
422 if encoding == "identity" {
424 return Ok(reader);
425 }
426
427 let provider = self.get_streaming(encoding).ok_or_else(|| {
428 ConnectError::unimplemented(format!(
429 "streaming decompression not supported for encoding: {encoding}"
430 ))
431 })?;
432
433 Ok(provider.decompress_stream(reader))
434 }
435
436 #[cfg(feature = "streaming")]
441 #[cfg_attr(docsrs, doc(cfg(feature = "streaming")))]
442 pub fn compress_stream(
443 &self,
444 encoding: &str,
445 reader: BoxedAsyncBufRead,
446 ) -> Result<BoxedAsyncRead, ConnectError> {
447 if encoding == "identity" {
449 return Ok(reader);
450 }
451
452 let provider = self.get_streaming(encoding).ok_or_else(|| {
453 ConnectError::unimplemented(format!(
454 "streaming compression not supported for encoding: {encoding}"
455 ))
456 })?;
457
458 Ok(provider.compress_stream(reader))
459 }
460}
461
462#[derive(Debug, Clone, Copy)]
484pub struct CompressionPolicy {
485 enabled: bool,
487 min_size: usize,
490}
491
492pub const DEFAULT_COMPRESSION_MIN_SIZE: usize = 1024;
498
499impl Default for CompressionPolicy {
500 fn default() -> Self {
501 Self {
502 enabled: true,
503 min_size: DEFAULT_COMPRESSION_MIN_SIZE,
504 }
505 }
506}
507
508impl CompressionPolicy {
509 pub fn disabled() -> Self {
511 Self {
512 enabled: false,
513 min_size: 0,
514 }
515 }
516
517 #[must_use]
522 pub fn min_size(mut self, size: usize) -> Self {
523 self.min_size = size;
524 self
525 }
526
527 #[inline]
534 pub fn should_compress(&self, message_size: usize) -> bool {
535 self.enabled && message_size >= self.min_size
536 }
537
538 pub(crate) fn with_override(&self, override_compress: Option<bool>) -> Self {
544 match override_compress {
545 None => *self,
546 Some(true) => Self {
547 enabled: true,
548 min_size: 0,
549 },
550 Some(false) => Self::disabled(),
551 }
552 }
553}
554
555impl Default for CompressionRegistry {
556 #[allow(unused_mut)]
558 fn default() -> Self {
559 let mut registry = Self::new();
560
561 #[cfg(all(feature = "gzip", feature = "streaming"))]
563 {
564 registry = registry.register_streaming(GzipProvider::default());
565 }
566
567 #[cfg(all(feature = "gzip", not(feature = "streaming")))]
568 {
569 registry = registry.register(GzipProvider::default());
570 }
571
572 #[cfg(all(feature = "zstd", feature = "streaming"))]
573 {
574 registry = registry.register_streaming(ZstdProvider::default());
575 }
576
577 #[cfg(all(feature = "zstd", not(feature = "streaming")))]
578 {
579 registry = registry.register(ZstdProvider::default());
580 }
581
582 registry
583 }
584}
585
586#[cfg(feature = "gzip")]
613#[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
614pub struct GzipProvider {
615 level: u32,
617 compressors: std::sync::Mutex<Vec<flate2::Compress>>,
618 decompressors: std::sync::Mutex<Vec<flate2::Decompress>>,
619}
620
621#[cfg(feature = "gzip")]
622#[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
623impl std::fmt::Debug for GzipProvider {
624 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625 f.debug_struct("GzipProvider")
626 .field("level", &self.level)
627 .field(
628 "pool_compressors",
629 &self.compressors.lock().map(|v| v.len()).unwrap_or(0),
630 )
631 .field(
632 "pool_decompressors",
633 &self.decompressors.lock().map(|v| v.len()).unwrap_or(0),
634 )
635 .finish()
636 }
637}
638
639#[cfg(feature = "gzip")]
640#[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
641impl Default for GzipProvider {
642 fn default() -> Self {
643 Self::with_level(Self::DEFAULT_LEVEL)
644 }
645}
646
647#[cfg(feature = "gzip")]
648#[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
649impl GzipProvider {
650 pub const DEFAULT_LEVEL: u32 = 1;
654
655 pub fn new() -> Self {
657 Self::default()
658 }
659
660 pub fn with_level(level: u32) -> Self {
670 debug_assert!(level <= 9, "gzip level must be 0-9, got {level}");
671 Self {
672 level,
673 compressors: std::sync::Mutex::new(Vec::new()),
674 decompressors: std::sync::Mutex::new(Vec::new()),
675 }
676 }
677
678 const MAX_POOL_SIZE: usize = 64;
681
682 fn take_compressor(&self) -> flate2::Compress {
683 self.compressors
684 .lock()
685 .unwrap_or_else(|e| e.into_inner())
686 .pop()
687 .unwrap_or_else(|| flate2::Compress::new(flate2::Compression::new(self.level), false))
688 }
689
690 fn return_compressor(&self, mut c: flate2::Compress) {
691 c.reset();
692 let mut pool = self.compressors.lock().unwrap_or_else(|e| e.into_inner());
693 if pool.len() < Self::MAX_POOL_SIZE {
694 pool.push(c);
695 }
696 }
697
698 fn take_decompressor(&self) -> flate2::Decompress {
699 self.decompressors
700 .lock()
701 .unwrap_or_else(|e| e.into_inner())
702 .pop()
703 .unwrap_or_else(|| flate2::Decompress::new(false))
704 }
705
706 fn return_decompressor(&self, mut d: flate2::Decompress) {
707 d.reset(false);
708 let mut pool = self.decompressors.lock().unwrap_or_else(|e| e.into_inner());
709 if pool.len() < Self::MAX_POOL_SIZE {
710 pool.push(d);
711 }
712 }
713
714 fn compress_inner(
715 compressor: &mut flate2::Compress,
716 data: &[u8],
717 ) -> Result<Bytes, ConnectError> {
718 let mut output = Vec::with_capacity(data.len() + 32);
719
720 output.extend_from_slice(&[
722 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, ]);
729
730 let start_in = compressor.total_in();
732 loop {
733 let consumed = (compressor.total_in() - start_in) as usize;
734 output.reserve(output.capacity().max(4096));
735 let status = compressor
736 .compress_vec(
737 &data[consumed..],
738 &mut output,
739 flate2::FlushCompress::Finish,
740 )
741 .map_err(|e| ConnectError::internal(format!("gzip compression failed: {e}")))?;
742 if status == flate2::Status::StreamEnd {
743 break;
744 }
745 }
746
747 let mut crc = flate2::Crc::new();
749 crc.update(data);
750 output.extend_from_slice(&crc.sum().to_le_bytes());
751 output.extend_from_slice(&(data.len() as u32).to_le_bytes());
752
753 Ok(Bytes::from(output))
754 }
755
756 fn decompress_inner(
757 decompressor: &mut flate2::Decompress,
758 data: &[u8],
759 max_size: Option<usize>,
760 ) -> Result<Bytes, ConnectError> {
761 let deflate_start = gzip_header_len(data)?;
762 let stream_data = &data[deflate_start..];
763
764 let mut output = Vec::with_capacity(initial_decompress_capacity(data.len(), 2, max_size));
765
766 let start_in = decompressor.total_in();
769 loop {
770 let consumed = (decompressor.total_in() - start_in) as usize;
771 if output.capacity() == output.len() {
772 if let Some(limit) = max_size
773 && output.len() > limit
774 {
775 return Err(ConnectError::resource_exhausted(format!(
776 "decompressed size exceeds limit {limit}"
777 )));
778 }
779 let mut additional = output.len().max(4096);
784 if let Some(limit) = max_size {
785 additional =
786 additional.min(limit.saturating_add(1).saturating_sub(output.capacity()));
787 }
788 output.reserve_exact(additional);
789 }
790 let status = decompressor
791 .decompress_vec(
792 &stream_data[consumed..],
793 &mut output,
794 flate2::FlushDecompress::None,
795 )
796 .map_err(|e| {
797 malformed_compressed_payload(format!("gzip decompression failed: {e}"))
798 })?;
799 match status {
800 flate2::Status::StreamEnd => break,
801 flate2::Status::Ok => {}
802 flate2::Status::BufError => {
808 return Err(malformed_compressed_payload(
809 "gzip decompression stalled: truncated or invalid deflate stream",
810 ));
811 }
812 }
813 }
814
815 if let Some(limit) = max_size
816 && output.len() > limit
817 {
818 return Err(ConnectError::resource_exhausted(format!(
819 "decompressed size exceeds limit {limit}"
820 )));
821 }
822
823 let deflate_consumed = (decompressor.total_in() - start_in) as usize;
825 let trailer_start = deflate_consumed;
826 if stream_data.len() < trailer_start + 8 {
827 return Err(malformed_compressed_payload(
828 "gzip data too short for trailer",
829 ));
830 }
831 let trailer = &stream_data[trailer_start..trailer_start + 8];
832
833 let expected_crc = u32::from_le_bytes([trailer[0], trailer[1], trailer[2], trailer[3]]);
834 let expected_size = u32::from_le_bytes([trailer[4], trailer[5], trailer[6], trailer[7]]);
835
836 let mut crc = flate2::Crc::new();
837 crc.update(&output);
838 if crc.sum() != expected_crc {
839 return Err(malformed_compressed_payload("gzip CRC32 mismatch"));
840 }
841 if expected_size != (output.len() as u32) {
842 return Err(malformed_compressed_payload("gzip size mismatch"));
843 }
844
845 Ok(Bytes::from(output))
846 }
847}
848
849#[cfg(feature = "gzip")]
852fn gzip_header_len(data: &[u8]) -> Result<usize, ConnectError> {
853 if data.len() < 10 {
854 return Err(malformed_compressed_payload(
855 "gzip data too short for header",
856 ));
857 }
858 if data[0] != 0x1f || data[1] != 0x8b {
859 return Err(malformed_compressed_payload("invalid gzip magic"));
860 }
861 if data[2] != 0x08 {
862 return Err(malformed_compressed_payload(
863 "unsupported gzip compression method",
864 ));
865 }
866 let flags = data[3];
867 let mut pos = 10;
868
869 if flags & 0x04 != 0 {
871 if pos + 2 > data.len() {
872 return Err(malformed_compressed_payload("truncated gzip header"));
873 }
874 let xlen = u16::from_le_bytes([data[pos], data[pos + 1]]) as usize;
875 pos += 2 + xlen;
876 }
877
878 if flags & 0x08 != 0 {
880 while pos < data.len() && data[pos] != 0 {
881 pos += 1;
882 }
883 if pos >= data.len() {
884 return Err(malformed_compressed_payload("truncated gzip header"));
885 }
886 pos += 1; }
888
889 if flags & 0x10 != 0 {
891 while pos < data.len() && data[pos] != 0 {
892 pos += 1;
893 }
894 if pos >= data.len() {
895 return Err(malformed_compressed_payload("truncated gzip header"));
896 }
897 pos += 1; }
899
900 if flags & 0x02 != 0 {
902 pos += 2;
903 }
904
905 if pos > data.len() {
906 return Err(malformed_compressed_payload("truncated gzip header"));
907 }
908 Ok(pos)
909}
910
911#[cfg(feature = "gzip")]
912#[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
913impl CompressionProvider for GzipProvider {
914 fn name(&self) -> &'static str {
915 "gzip"
916 }
917
918 fn compress(&self, data: &[u8]) -> Result<Bytes, ConnectError> {
919 let mut compressor = self.take_compressor();
920 let result = Self::compress_inner(&mut compressor, data);
921 self.return_compressor(compressor);
922 result
923 }
924
925 fn decompressor<'a>(
926 &self,
927 data: &'a [u8],
928 ) -> Result<Box<dyn std::io::Read + 'a>, ConnectError> {
929 Ok(Box::new(flate2::read::GzDecoder::new(data)))
930 }
931
932 fn decompress_with_limit(&self, data: &[u8], max_size: usize) -> Result<Bytes, ConnectError> {
933 let mut decompressor = self.take_decompressor();
934 let result = Self::decompress_inner(&mut decompressor, data, Some(max_size));
935 self.return_decompressor(decompressor);
936 result
937 }
938}
939
940#[cfg(all(feature = "gzip", feature = "streaming"))]
941#[cfg_attr(docsrs, doc(cfg(all(feature = "gzip", feature = "streaming"))))]
942impl StreamingCompressionProvider for GzipProvider {
943 fn decompress_stream(&self, reader: BoxedAsyncBufRead) -> BoxedAsyncRead {
944 Box::pin(async_compression::tokio::bufread::GzipDecoder::new(reader))
945 }
946
947 fn compress_stream(&self, reader: BoxedAsyncBufRead) -> BoxedAsyncRead {
948 Box::pin(
949 async_compression::tokio::bufread::GzipEncoder::with_quality(
950 reader,
951 async_compression::Level::Precise(self.level as i32),
952 ),
953 )
954 }
955}
956
957#[cfg(feature = "zstd")]
966#[cfg_attr(docsrs, doc(cfg(feature = "zstd")))]
967pub struct ZstdProvider {
968 level: i32,
970 compressors: std::sync::Mutex<Vec<zstd::bulk::Compressor<'static>>>,
971}
972
973#[cfg(feature = "zstd")]
974#[cfg_attr(docsrs, doc(cfg(feature = "zstd")))]
975impl std::fmt::Debug for ZstdProvider {
976 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
977 f.debug_struct("ZstdProvider")
978 .field("level", &self.level)
979 .field(
980 "pool_compressors",
981 &self.compressors.lock().map(|v| v.len()).unwrap_or(0),
982 )
983 .finish()
984 }
985}
986
987#[cfg(feature = "zstd")]
988#[cfg_attr(docsrs, doc(cfg(feature = "zstd")))]
989impl ZstdProvider {
990 pub const DEFAULT_LEVEL: i32 = 3;
992
993 pub fn new() -> Self {
995 Self::default()
996 }
997
998 pub fn with_level(level: i32) -> Self {
1003 Self {
1004 level,
1005 compressors: std::sync::Mutex::new(Vec::new()),
1006 }
1007 }
1008}
1009
1010#[cfg(feature = "zstd")]
1011#[cfg_attr(docsrs, doc(cfg(feature = "zstd")))]
1012impl Default for ZstdProvider {
1013 fn default() -> Self {
1014 Self {
1015 level: Self::DEFAULT_LEVEL,
1016 compressors: std::sync::Mutex::new(Vec::new()),
1017 }
1018 }
1019}
1020
1021#[cfg(feature = "zstd")]
1022impl ZstdProvider {
1023 const MAX_POOL_SIZE: usize = 64;
1025
1026 fn take_compressor(&self) -> Result<zstd::bulk::Compressor<'static>, ConnectError> {
1027 if let Some(c) = self
1028 .compressors
1029 .lock()
1030 .unwrap_or_else(|e| e.into_inner())
1031 .pop()
1032 {
1033 return Ok(c);
1034 }
1035 zstd::bulk::Compressor::new(self.level)
1036 .map_err(|e| ConnectError::internal(format!("failed to create zstd compressor: {e}")))
1037 }
1038
1039 fn return_compressor(&self, c: zstd::bulk::Compressor<'static>) {
1040 let mut pool = self.compressors.lock().unwrap_or_else(|e| e.into_inner());
1041 if pool.len() < Self::MAX_POOL_SIZE {
1042 pool.push(c);
1043 }
1044 }
1045
1046 fn decompress_impl(data: &[u8], max_size: Option<usize>) -> Result<Bytes, ConnectError> {
1053 use std::io::Read;
1054
1055 let mut decoder = zstd::Decoder::new(data)
1056 .map_err(|e| malformed_compressed_payload(format!("zstd decompression failed: {e}")))?;
1057
1058 let mut decompressed =
1059 Vec::with_capacity(initial_decompress_capacity(data.len(), 4, max_size));
1060
1061 match max_size {
1062 Some(limit) => {
1063 decoder
1066 .take((limit as u64).saturating_add(1))
1067 .read_to_end(&mut decompressed)
1068 .map_err(|e| {
1069 malformed_compressed_payload(format!("zstd decompression failed: {e}"))
1070 })?;
1071 if decompressed.len() > limit {
1072 return Err(ConnectError::resource_exhausted(format!(
1073 "decompressed size exceeds limit {limit}"
1074 )));
1075 }
1076 }
1077 None => {
1078 decoder.read_to_end(&mut decompressed).map_err(|e| {
1079 malformed_compressed_payload(format!("zstd decompression failed: {e}"))
1080 })?;
1081 }
1082 }
1083 Ok(Bytes::from(decompressed))
1084 }
1085}
1086
1087#[cfg(feature = "zstd")]
1088#[cfg_attr(docsrs, doc(cfg(feature = "zstd")))]
1089impl CompressionProvider for ZstdProvider {
1090 fn name(&self) -> &'static str {
1091 "zstd"
1092 }
1093
1094 fn compress(&self, data: &[u8]) -> Result<Bytes, ConnectError> {
1095 let mut compressor = self.take_compressor()?;
1096 let result = compressor
1097 .compress(data)
1098 .map(Bytes::from)
1099 .map_err(|e| ConnectError::internal(format!("zstd compression failed: {e}")));
1100 self.return_compressor(compressor);
1101 result
1102 }
1103
1104 fn decompressor<'a>(
1105 &self,
1106 data: &'a [u8],
1107 ) -> Result<Box<dyn std::io::Read + 'a>, ConnectError> {
1108 let decoder = zstd::Decoder::new(data)
1109 .map_err(|e| malformed_compressed_payload(format!("zstd decompression failed: {e}")))?;
1110 Ok(Box::new(decoder))
1111 }
1112
1113 fn decompress_with_limit(&self, data: &[u8], max_size: usize) -> Result<Bytes, ConnectError> {
1114 Self::decompress_impl(data, Some(max_size))
1115 }
1116}
1117
1118#[cfg(all(feature = "zstd", feature = "streaming"))]
1119#[cfg_attr(docsrs, doc(cfg(all(feature = "zstd", feature = "streaming"))))]
1120impl StreamingCompressionProvider for ZstdProvider {
1121 fn decompress_stream(&self, reader: BoxedAsyncBufRead) -> BoxedAsyncRead {
1122 Box::pin(async_compression::tokio::bufread::ZstdDecoder::new(reader))
1123 }
1124
1125 fn compress_stream(&self, reader: BoxedAsyncBufRead) -> BoxedAsyncRead {
1126 Box::pin(
1127 async_compression::tokio::bufread::ZstdEncoder::with_quality(
1128 reader,
1129 async_compression::Level::Precise(self.level),
1130 ),
1131 )
1132 }
1133}
1134
1135fn initial_decompress_capacity(
1154 input_len: usize,
1155 multiplier: usize,
1156 max_size: Option<usize>,
1157) -> usize {
1158 let mut capacity = input_len.saturating_mul(multiplier).max(256);
1159 if let Some(limit) = max_size {
1160 capacity = capacity.min(limit.saturating_add(1));
1161 }
1162 capacity
1163}
1164
1165#[cfg(test)]
1166mod tests {
1167 use super::*;
1168
1169 #[cfg(any(feature = "gzip", feature = "zstd"))]
1170 fn assert_invalid_argument(err: &ConnectError) {
1171 assert_eq!(
1172 err.code,
1173 crate::error::ErrorCode::InvalidArgument,
1174 "{err:?}"
1175 );
1176 }
1177
1178 #[test]
1179 fn test_empty_registry() {
1180 let registry = CompressionRegistry::new();
1181 assert!(!registry.supports("gzip"));
1182 assert!(!registry.supports("zstd"));
1183 assert!(registry.supported_encodings().is_empty());
1184 }
1185
1186 #[test]
1187 fn test_identity_always_works() {
1188 let registry = CompressionRegistry::new();
1189 let data = b"hello world";
1190 let result = registry
1191 .decompress_with_limit("identity", Bytes::from_static(data), usize::MAX)
1192 .unwrap();
1193 assert_eq!(&result[..], data);
1194 }
1195
1196 #[cfg(feature = "gzip")]
1197 #[test]
1198 fn test_gzip_large_roundtrip() {
1199 let provider = GzipProvider::default();
1200 let data: Vec<u8> = (0..1_000_000).map(|i| (i % 256) as u8).collect();
1201 let compressed = provider.compress(&data).unwrap();
1202 let decompressed = provider
1203 .decompress_with_limit(&compressed, usize::MAX)
1204 .unwrap();
1205 assert_eq!(&decompressed[..], &data[..]);
1206 }
1207
1208 #[cfg(feature = "gzip")]
1209 #[test]
1210 fn test_gzip_pooled_cross_compat_with_gz_decoder() {
1211 use std::io::Read;
1212 let provider = GzipProvider::default();
1213 let data: Vec<u8> = (0..100_000).map(|i| (i % 256) as u8).collect();
1214 let compressed = provider.compress(&data).unwrap();
1215 let mut decoder = flate2::read::GzDecoder::new(&compressed[..]);
1217 let mut decompressed = Vec::new();
1218 decoder.read_to_end(&mut decompressed).unwrap();
1219 assert_eq!(&decompressed[..], &data[..]);
1220 }
1221
1222 #[cfg(feature = "gzip")]
1223 #[test]
1224 fn test_gzip_pooled_cross_compat_with_gz_encoder() {
1225 use std::io::Write;
1226 let provider = GzipProvider::default();
1227 let data: Vec<u8> = (0..100_000).map(|i| (i % 256) as u8).collect();
1228 let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::new(6));
1230 encoder.write_all(&data).unwrap();
1231 let compressed = encoder.finish().unwrap();
1232 let decompressed = provider
1234 .decompress_with_limit(&compressed, usize::MAX)
1235 .unwrap();
1236 assert_eq!(&decompressed[..], &data[..]);
1237 }
1238
1239 #[cfg(feature = "gzip")]
1240 #[test]
1241 fn test_gzip_default_level_is_fast() {
1242 assert_eq!(GzipProvider::DEFAULT_LEVEL, 1);
1243 let provider = GzipProvider::default();
1245 let data = vec![b'x'; 50_000];
1246 let compressed = provider.compress(&data).unwrap();
1247 assert!(compressed.len() < data.len());
1248 let decompressed = provider
1249 .decompress_with_limit(&compressed, usize::MAX)
1250 .unwrap();
1251 assert_eq!(&decompressed[..], &data[..]);
1252 }
1253
1254 #[cfg(feature = "gzip")]
1255 #[test]
1256 fn test_gzip_cross_level_decode() {
1257 let fast = GzipProvider::default();
1261 let slow = GzipProvider::with_level(6);
1262 let data: Vec<u8> = (0..32_768).map(|i| (i % 251) as u8).collect();
1263 for src in [&fast, &slow] {
1264 let _ = src.compress(&data).unwrap();
1265 let compressed = src.compress(&data).unwrap();
1266 for dst in [&fast, &slow] {
1267 let out = dst.decompress_with_limit(&compressed, usize::MAX).unwrap();
1268 assert_eq!(&out[..], &data[..]);
1269 }
1270 }
1271 }
1272
1273 #[cfg(all(feature = "gzip", feature = "streaming"))]
1274 #[tokio::test]
1275 async fn test_gzip_streaming_honors_level() {
1276 use tokio::io::AsyncReadExt;
1277 async fn stream_compress(p: &GzipProvider, data: &[u8]) -> Vec<u8> {
1278 let reader: BoxedAsyncBufRead = Box::pin(std::io::Cursor::new(data.to_vec()));
1279 let mut enc = p.compress_stream(reader);
1280 let mut out = Vec::new();
1281 enc.read_to_end(&mut out).await.unwrap();
1282 out
1283 }
1284 let data = b"hello world, streaming gzip at the configured level".repeat(200);
1285 let fast = stream_compress(&GzipProvider::with_level(1), &data).await;
1286 let best = stream_compress(&GzipProvider::with_level(9), &data).await;
1287 for c in [&fast, &best] {
1289 let out = GzipProvider::default()
1290 .decompress_with_limit(c, usize::MAX)
1291 .unwrap();
1292 assert_eq!(&out[..], &data[..]);
1293 }
1294 assert!(
1297 best.len() < fast.len(),
1298 "level 9 ({}) should be smaller than level 1 ({})",
1299 best.len(),
1300 fast.len()
1301 );
1302 }
1303
1304 #[cfg(feature = "gzip")]
1307 fn gz_hdr(flags: u8, extra: &[u8]) -> Vec<u8> {
1310 let mut h = vec![
1311 0x1f, 0x8b, 0x08, flags, 0, 0, 0, 0, 0, 0xff, ];
1317 h.extend_from_slice(extra);
1318 h
1319 }
1320
1321 #[cfg(feature = "gzip")]
1322 #[test]
1323 fn test_gzip_header_len_basic() {
1324 assert_eq!(gzip_header_len(&gz_hdr(0x00, &[])).unwrap(), 10);
1326 }
1327
1328 #[cfg(feature = "gzip")]
1329 #[test]
1330 fn test_gzip_header_len_fextra() {
1331 let extra = [3u8, 0, 0xAA, 0xBB, 0xCC]; assert_eq!(gzip_header_len(&gz_hdr(0x04, &extra)).unwrap(), 10 + 2 + 3);
1334 }
1335
1336 #[cfg(feature = "gzip")]
1337 #[test]
1338 fn test_gzip_header_len_fextra_truncated() {
1339 let extra = [100u8, 0, 0xAA, 0xBB];
1341 assert!(gzip_header_len(&gz_hdr(0x04, &extra)).is_err());
1342 }
1343
1344 #[cfg(feature = "gzip")]
1345 #[test]
1346 fn test_gzip_header_len_fname() {
1347 let extra = b"test.txt\0";
1349 assert_eq!(gzip_header_len(&gz_hdr(0x08, extra)).unwrap(), 10 + 9);
1350 }
1351
1352 #[cfg(feature = "gzip")]
1353 #[test]
1354 fn test_gzip_header_len_fname_truncated() {
1355 assert!(gzip_header_len(&gz_hdr(0x08, b"nonul")).is_err());
1357 }
1358
1359 #[cfg(feature = "gzip")]
1360 #[test]
1361 fn test_gzip_header_len_fcomment() {
1362 let extra = b"a comment\0";
1364 assert_eq!(gzip_header_len(&gz_hdr(0x10, extra)).unwrap(), 10 + 10);
1365 }
1366
1367 #[cfg(feature = "gzip")]
1368 #[test]
1369 fn test_gzip_header_len_fcomment_truncated() {
1370 assert!(gzip_header_len(&gz_hdr(0x10, b"nonul")).is_err());
1371 }
1372
1373 #[cfg(feature = "gzip")]
1374 #[test]
1375 fn test_gzip_header_len_fhcrc() {
1376 assert_eq!(gzip_header_len(&gz_hdr(0x02, &[0xAB, 0xCD])).unwrap(), 12);
1378 }
1379
1380 #[cfg(feature = "gzip")]
1381 #[test]
1382 fn test_gzip_header_len_fhcrc_truncated() {
1383 assert!(gzip_header_len(&gz_hdr(0x02, &[0xAB])).is_err());
1385 }
1386
1387 #[cfg(feature = "gzip")]
1388 #[test]
1389 fn test_gzip_header_len_all_flags() {
1390 let mut extra = Vec::new();
1392 extra.extend_from_slice(&[2u8, 0, 0xAA, 0xBB]); extra.extend_from_slice(b"name\0"); extra.extend_from_slice(b"cmt\0"); extra.extend_from_slice(&[0x12, 0x34]); let flags = 0x04 | 0x08 | 0x10 | 0x02;
1397 assert_eq!(
1398 gzip_header_len(&gz_hdr(flags, &extra)).unwrap(),
1399 10 + 4 + 5 + 4 + 2
1400 );
1401 }
1402
1403 #[cfg(feature = "gzip")]
1404 #[test]
1405 fn test_gzip_header_len_bad_magic() {
1406 let mut hdr = gz_hdr(0x00, &[]);
1407 hdr[0] = 0x00;
1408 assert!(gzip_header_len(&hdr).is_err());
1409 }
1410
1411 #[cfg(feature = "gzip")]
1412 #[test]
1413 fn test_gzip_header_len_bad_method() {
1414 let mut hdr = gz_hdr(0x00, &[]);
1415 hdr[2] = 0x07; assert!(gzip_header_len(&hdr).is_err());
1417 }
1418
1419 #[cfg(feature = "gzip")]
1420 #[test]
1421 fn test_gzip_header_len_too_short() {
1422 assert!(gzip_header_len(&[0x1f, 0x8b, 0x08]).is_err());
1423 }
1424
1425 #[cfg(feature = "gzip")]
1426 #[test]
1427 fn test_gzip_provider() {
1428 let provider = GzipProvider::default();
1429 let data = b"hello world, this is a test of gzip compression";
1430
1431 let compressed = provider.compress(data).unwrap();
1432 assert_ne!(&compressed[..], data);
1433
1434 let decompressed = provider
1435 .decompress_with_limit(&compressed, usize::MAX)
1436 .unwrap();
1437 assert_eq!(&decompressed[..], data);
1438 }
1439
1440 const ALLOCATION_TEST_LIMIT: usize = 4 * 1024 * 1024;
1443
1444 fn backing_capacity(bytes: Bytes) -> usize {
1450 bytes
1451 .try_into_mut()
1452 .expect("freshly decompressed Bytes has no other references")
1453 .capacity()
1454 }
1455
1456 const SMALL_MESSAGE_RETENTION_BOUND: usize = 4096;
1461
1462 #[test]
1466 fn test_backing_capacity_observes_overallocation() {
1467 let mut vec = Vec::with_capacity(1024 * 1024);
1468 vec.extend_from_slice(b"tiny payload");
1469 let capacity = backing_capacity(Bytes::from(vec));
1470 assert!(
1471 capacity >= 1024 * 1024,
1472 "expected the over-allocated backing buffer to be visible, got {capacity}"
1473 );
1474 }
1475
1476 #[cfg(feature = "gzip")]
1480 #[test]
1481 fn test_gzip_decompress_small_message_allocation() {
1482 let provider = GzipProvider::default();
1483 let compressed = provider.compress(b"tiny payload").unwrap();
1484 let out = provider
1485 .decompress_with_limit(&compressed, ALLOCATION_TEST_LIMIT)
1486 .unwrap();
1487 assert_eq!(&out[..], b"tiny payload");
1488 let capacity = backing_capacity(out);
1489 assert!(
1490 capacity < SMALL_MESSAGE_RETENTION_BOUND,
1491 "small gzip message retained a {capacity}-byte backing buffer"
1492 );
1493 }
1494
1495 #[cfg(feature = "zstd")]
1497 #[test]
1498 fn test_zstd_decompress_small_message_allocation() {
1499 let provider = ZstdProvider::default();
1500 let compressed = provider.compress(b"tiny payload").unwrap();
1501 let out = provider
1502 .decompress_with_limit(&compressed, ALLOCATION_TEST_LIMIT)
1503 .unwrap();
1504 assert_eq!(&out[..], b"tiny payload");
1505 let capacity = backing_capacity(out);
1506 assert!(
1507 capacity < SMALL_MESSAGE_RETENTION_BOUND,
1508 "small zstd message retained a {capacity}-byte backing buffer"
1509 );
1510 }
1511
1512 #[test]
1515 fn test_default_trait_decompress_small_message_allocation() {
1516 let provider = MockProvider;
1517 let compressed = provider.compress(b"tiny payload").unwrap();
1518 let out = provider
1519 .decompress_with_limit(&compressed, ALLOCATION_TEST_LIMIT)
1520 .unwrap();
1521 assert_eq!(&out[..], b"tiny payload");
1522 let capacity = backing_capacity(out);
1523 assert!(
1524 capacity < SMALL_MESSAGE_RETENTION_BOUND,
1525 "small message retained a {capacity}-byte backing buffer via the default impl"
1526 );
1527 }
1528
1529 #[cfg(feature = "gzip")]
1538 fn run_with_timeout<T, F>(timeout: std::time::Duration, f: F) -> T
1539 where
1540 T: Send + 'static,
1541 F: FnOnce() -> T + Send + 'static,
1542 {
1543 let (tx, rx) = std::sync::mpsc::channel();
1544 std::thread::spawn(move || {
1545 let _ = tx.send(f());
1546 });
1547 match rx.recv_timeout(timeout) {
1548 Ok(value) => value,
1549 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
1550 panic!("operation did not complete within {timeout:?}; decompression appears stuck")
1551 }
1552 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
1553 panic!("worker thread panicked before producing a result")
1554 }
1555 }
1556 }
1557
1558 #[cfg(feature = "gzip")]
1562 const TRUNCATION_TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
1563
1564 #[cfg(feature = "gzip")]
1567 const MINIMAL_GZIP_HEADER: [u8; 10] =
1568 [0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff];
1569
1570 #[cfg(feature = "gzip")]
1574 #[test]
1575 fn test_gzip_decompress_header_only() {
1576 let err = run_with_timeout(TRUNCATION_TEST_TIMEOUT, move || {
1577 GzipProvider::default().decompress_with_limit(&MINIMAL_GZIP_HEADER, 1024)
1578 })
1579 .expect_err("header-only gzip member must be rejected");
1580 assert_invalid_argument(&err);
1581 assert!(
1582 err.to_string()
1583 .contains("truncated or invalid deflate stream"),
1584 "unexpected error message: {err}"
1585 );
1586 }
1587
1588 #[cfg(feature = "gzip")]
1591 #[test]
1592 fn test_gzip_decompress_truncated_deflate_stream() {
1593 let provider = GzipProvider::default();
1594 let data = b"hello world, this is a test of gzip compression";
1595 let compressed = provider.compress(data).unwrap();
1596
1597 let err = run_with_timeout(TRUNCATION_TEST_TIMEOUT, move || {
1598 provider.decompress_with_limit(&compressed[..14], 1024)
1601 })
1602 .expect_err("truncated deflate stream must be rejected");
1603 assert_invalid_argument(&err);
1604 let msg = err.to_string();
1610 assert!(
1611 msg.contains("truncated or invalid deflate stream")
1612 || msg.contains("too short for trailer"),
1613 "unexpected error message: {msg}"
1614 );
1615 }
1616
1617 #[cfg(feature = "gzip")]
1620 #[test]
1621 fn test_gzip_registry_decompress_truncated() {
1622 let registry = CompressionRegistry::new().register(GzipProvider::default());
1623 let err = run_with_timeout(TRUNCATION_TEST_TIMEOUT, move || {
1624 registry.decompress_with_limit(
1625 "gzip",
1626 Bytes::copy_from_slice(&MINIMAL_GZIP_HEADER),
1627 1024,
1628 )
1629 })
1630 .expect_err("truncated gzip payload must be rejected via the registry");
1631 assert_invalid_argument(&err);
1632 assert!(
1633 err.to_string()
1634 .contains("truncated or invalid deflate stream"),
1635 "unexpected error message: {err}"
1636 );
1637 }
1638
1639 #[cfg(feature = "gzip")]
1644 #[test]
1645 fn test_gzip_decompress_missing_trailer() {
1646 let provider = GzipProvider::default();
1647 let data = b"hello world, this is a test of gzip compression";
1648 let compressed = provider.compress(data).unwrap();
1649
1650 let missing_trailer = &compressed[..compressed.len() - 8];
1651 let err = provider
1652 .decompress_with_limit(missing_trailer, 1024)
1653 .expect_err("gzip member without its trailer must be rejected");
1654 assert_invalid_argument(&err);
1655 assert!(
1656 err.to_string().contains("too short for trailer"),
1657 "unexpected error message: {err}"
1658 );
1659 }
1660
1661 #[cfg(feature = "gzip")]
1662 #[test]
1663 fn test_gzip_malformed_payloads_are_invalid_argument() {
1664 let provider = GzipProvider::default();
1665
1666 let err = provider
1667 .decompress_with_limit(b"not gzip", 1024)
1668 .expect_err("bad gzip header must be rejected");
1669 assert_invalid_argument(&err);
1670
1671 let data = b"hello world, this is a test of gzip compression";
1672 let compressed = provider.compress(data).unwrap();
1673 let trailer_start = compressed.len() - 8;
1674
1675 let mut bad_crc = compressed.to_vec();
1676 bad_crc[trailer_start] ^= 0xff;
1677 let err = provider
1678 .decompress_with_limit(&bad_crc, 1024)
1679 .expect_err("gzip CRC mismatch must be rejected");
1680 assert_invalid_argument(&err);
1681
1682 let mut bad_size = compressed.to_vec();
1683 let last = bad_size.len() - 1;
1684 bad_size[last] ^= 0xff;
1685 let err = provider
1686 .decompress_with_limit(&bad_size, 1024)
1687 .expect_err("gzip size mismatch must be rejected");
1688 assert_invalid_argument(&err);
1689 }
1690
1691 #[cfg(feature = "gzip")]
1692 #[test]
1693 fn test_gzip_registry() {
1694 let registry = CompressionRegistry::new().register(GzipProvider::default());
1695
1696 assert!(registry.supports("gzip"));
1697 assert!(!registry.supports("zstd"));
1698
1699 let data = b"test data";
1700 let compressed = registry.compress("gzip", data).unwrap();
1701 let decompressed = registry
1702 .decompress_with_limit("gzip", compressed, usize::MAX)
1703 .unwrap();
1704 assert_eq!(&decompressed[..], data);
1705 }
1706
1707 #[cfg(feature = "zstd")]
1708 #[test]
1709 fn test_zstd_provider() {
1710 let provider = ZstdProvider::default();
1711 let data = b"hello world, this is a test of zstd compression";
1712
1713 let compressed = provider.compress(data).unwrap();
1714 assert_ne!(&compressed[..], data);
1715
1716 let decompressed = provider
1717 .decompress_with_limit(&compressed, usize::MAX)
1718 .unwrap();
1719 assert_eq!(&decompressed[..], data);
1720 }
1721
1722 #[cfg(feature = "zstd")]
1723 #[test]
1724 fn test_zstd_malformed_payload_is_invalid_argument() {
1725 let err = ZstdProvider::default()
1726 .decompress_with_limit(b"not zstd", 1024)
1727 .expect_err("malformed zstd payload must be rejected");
1728 assert_invalid_argument(&err);
1729 }
1730
1731 #[cfg(feature = "zstd")]
1732 #[test]
1733 fn test_zstd_high_compression_ratio() {
1734 let provider = ZstdProvider::default();
1739 let data = vec![0u8; 100_000];
1740 let compressed = provider.compress(&data).unwrap();
1741 assert!(
1743 compressed.len() * 4 < data.len(),
1744 "expected high compression ratio; got {} bytes -> {} bytes",
1745 data.len(),
1746 compressed.len()
1747 );
1748 let decompressed = provider
1749 .decompress_with_limit(&compressed, usize::MAX)
1750 .unwrap();
1751 assert_eq!(decompressed.len(), data.len());
1752 assert!(decompressed.iter().all(|&b| b == 0));
1753 }
1754
1755 #[cfg(feature = "zstd")]
1756 #[test]
1757 fn test_zstd_registry() {
1758 let registry = CompressionRegistry::new().register(ZstdProvider::default());
1759
1760 assert!(registry.supports("zstd"));
1761 assert!(!registry.supports("gzip"));
1762
1763 let data = b"test data";
1764 let compressed = registry.compress("zstd", data).unwrap();
1765 let decompressed = registry
1766 .decompress_with_limit("zstd", compressed, usize::MAX)
1767 .unwrap();
1768 assert_eq!(&decompressed[..], data);
1769 }
1770
1771 #[test]
1772 fn test_unsupported_encoding() {
1773 let registry = CompressionRegistry::new();
1774 let result =
1775 registry.decompress_with_limit("unknown", Bytes::from_static(b"data"), usize::MAX);
1776 assert!(result.is_err());
1777 }
1778
1779 #[test]
1780 #[cfg(feature = "zstd")]
1781 fn test_decompress_empty_body_with_encoding_header() {
1782 let registry = CompressionRegistry::default();
1787 let result = registry.decompress_with_limit("zstd", Bytes::new(), usize::MAX);
1788 assert_eq!(result.unwrap().len(), 0);
1789
1790 let result = registry.decompress_with_limit("gzip", Bytes::new(), usize::MAX);
1791 assert_eq!(result.unwrap().len(), 0);
1792
1793 let result = registry.decompress_with_limit("zstd", Bytes::new(), 1024);
1795 assert_eq!(result.unwrap().len(), 0);
1796 }
1797
1798 #[test]
1799 fn test_decompress_empty_body_unknown_encoding_still_errors() {
1800 let registry = CompressionRegistry::default();
1804 let result = registry.decompress_with_limit("foo", Bytes::new(), usize::MAX);
1805 let err = result.unwrap_err();
1806 assert_eq!(err.code, crate::error::ErrorCode::Unimplemented);
1807 }
1808
1809 #[cfg(all(feature = "gzip", feature = "zstd"))]
1810 #[test]
1811 fn test_default_registry() {
1812 let registry = CompressionRegistry::default();
1813 assert!(registry.supports("gzip"));
1814 assert!(registry.supports("zstd"));
1815 }
1816
1817 #[test]
1818 fn test_accept_encoding_header() {
1819 let registry = CompressionRegistry::new();
1820 assert_eq!(registry.accept_encoding_header(), "");
1821
1822 #[cfg(feature = "gzip")]
1823 {
1824 let registry = CompressionRegistry::new().register(GzipProvider::default());
1825 assert_eq!(registry.accept_encoding_header(), "gzip");
1826 }
1827 }
1828
1829 #[cfg(all(feature = "gzip", feature = "zstd"))]
1830 #[test]
1831 fn test_accept_encoding_header_sorted_deterministic() {
1832 let r1 = CompressionRegistry::new()
1834 .register(GzipProvider::default())
1835 .register(ZstdProvider::default());
1836 let r2 = CompressionRegistry::new()
1837 .register(ZstdProvider::default())
1838 .register(GzipProvider::default());
1839 assert_eq!(r1.accept_encoding_header(), "gzip, zstd");
1840 assert_eq!(r2.accept_encoding_header(), "gzip, zstd");
1841 }
1842
1843 struct MockProvider;
1845
1846 impl CompressionProvider for MockProvider {
1847 fn name(&self) -> &'static str {
1848 "mock"
1849 }
1850
1851 fn compress(&self, data: &[u8]) -> Result<Bytes, ConnectError> {
1852 Ok(Bytes::from(data.iter().rev().copied().collect::<Vec<_>>()))
1854 }
1855
1856 fn decompressor<'a>(
1857 &self,
1858 data: &'a [u8],
1859 ) -> Result<Box<dyn std::io::Read + 'a>, ConnectError> {
1860 let reversed: Vec<u8> = data.iter().rev().copied().collect();
1862 Ok(Box::new(std::io::Cursor::new(reversed)))
1863 }
1864 }
1865
1866 struct FailingReadProvider;
1869
1870 impl CompressionProvider for FailingReadProvider {
1871 fn name(&self) -> &'static str {
1872 "failing"
1873 }
1874
1875 fn compress(&self, data: &[u8]) -> Result<Bytes, ConnectError> {
1876 Ok(Bytes::copy_from_slice(data))
1877 }
1878
1879 fn decompressor<'a>(
1880 &self,
1881 _data: &'a [u8],
1882 ) -> Result<Box<dyn std::io::Read + 'a>, ConnectError> {
1883 struct FailingReader;
1884 impl std::io::Read for FailingReader {
1885 fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
1886 Err(std::io::Error::new(
1887 std::io::ErrorKind::InvalidData,
1888 "corrupt stream",
1889 ))
1890 }
1891 }
1892 Ok(Box::new(FailingReader))
1893 }
1894 }
1895
1896 #[test]
1899 fn test_default_trait_decompress_malformed_is_invalid_argument() {
1900 let err = FailingReadProvider
1901 .decompress_with_limit(b"whatever", usize::MAX)
1902 .unwrap_err();
1903 assert_eq!(err.code, crate::error::ErrorCode::InvalidArgument);
1904 }
1905
1906 #[test]
1907 fn test_custom_provider() {
1908 let registry = CompressionRegistry::new().register(MockProvider);
1909
1910 assert!(registry.supports("mock"));
1911
1912 let data = b"hello";
1913 let compressed = registry.compress("mock", data).unwrap();
1914 assert_eq!(&compressed[..], b"olleh");
1915
1916 let decompressed = registry
1917 .decompress_with_limit("mock", compressed, usize::MAX)
1918 .unwrap();
1919 assert_eq!(&decompressed[..], data);
1920 }
1921
1922 #[cfg(all(feature = "gzip", feature = "streaming"))]
1923 #[tokio::test]
1924 async fn test_gzip_streaming() {
1925 use tokio::io::AsyncReadExt;
1926
1927 let registry = CompressionRegistry::default();
1928 assert!(registry.supports_streaming("gzip"));
1929
1930 let data = b"hello world, this is a test of streaming gzip compression";
1932
1933 let compressed = registry.compress("gzip", data).unwrap();
1935
1936 let reader: BoxedAsyncBufRead = Box::pin(std::io::Cursor::new(compressed.to_vec()));
1938 let mut decompressor = registry.decompress_stream("gzip", reader).unwrap();
1939
1940 let mut decompressed = Vec::new();
1941 decompressor.read_to_end(&mut decompressed).await.unwrap();
1942
1943 assert_eq!(&decompressed[..], data);
1944 }
1945
1946 #[cfg(all(feature = "zstd", feature = "streaming"))]
1947 #[tokio::test]
1948 async fn test_zstd_streaming() {
1949 use tokio::io::AsyncReadExt;
1950
1951 let registry = CompressionRegistry::default();
1952 assert!(registry.supports_streaming("zstd"));
1953
1954 let data = b"hello world, this is a test of streaming zstd compression";
1956
1957 let compressed = registry.compress("zstd", data).unwrap();
1959
1960 let reader: BoxedAsyncBufRead = Box::pin(std::io::Cursor::new(compressed.to_vec()));
1962 let mut decompressor = registry.decompress_stream("zstd", reader).unwrap();
1963
1964 let mut decompressed = Vec::new();
1965 decompressor.read_to_end(&mut decompressed).await.unwrap();
1966
1967 assert_eq!(&decompressed[..], data);
1968 }
1969
1970 #[cfg(all(feature = "gzip", feature = "streaming"))]
1971 #[tokio::test]
1972 async fn test_streaming_compress_decompress_roundtrip() {
1973 use tokio::io::AsyncReadExt;
1974
1975 let registry = CompressionRegistry::default();
1976
1977 let data = b"hello world, this is a roundtrip test of streaming compression";
1979
1980 let input: BoxedAsyncBufRead = Box::pin(std::io::Cursor::new(data.to_vec()));
1982 let mut compressor = registry.compress_stream("gzip", input).unwrap();
1983
1984 let mut compressed = Vec::new();
1985 compressor.read_to_end(&mut compressed).await.unwrap();
1986
1987 let reader: BoxedAsyncBufRead = Box::pin(std::io::Cursor::new(compressed));
1989 let mut decompressor = registry.decompress_stream("gzip", reader).unwrap();
1990
1991 let mut decompressed = Vec::new();
1992 decompressor.read_to_end(&mut decompressed).await.unwrap();
1993
1994 assert_eq!(&decompressed[..], data);
1995 }
1996
1997 #[cfg(feature = "gzip")]
1998 #[test]
1999 fn test_gzip_decompress_with_limit_under() {
2000 let provider = GzipProvider::default();
2001 let data = b"hello world";
2002 let compressed = provider.compress(data).unwrap();
2003
2004 let result = provider.decompress_with_limit(&compressed, 1024);
2006 assert!(result.is_ok());
2007 assert_eq!(&result.unwrap()[..], data);
2008 }
2009
2010 #[cfg(feature = "gzip")]
2011 #[test]
2012 fn test_gzip_decompress_with_limit_exact() {
2013 let provider = GzipProvider::default();
2014 let data = b"hello world";
2015 let compressed = provider.compress(data).unwrap();
2016
2017 let result = provider.decompress_with_limit(&compressed, data.len());
2019 assert!(result.is_ok());
2020 assert_eq!(&result.unwrap()[..], data);
2021 }
2022
2023 #[cfg(feature = "gzip")]
2024 #[test]
2025 fn test_gzip_decompress_with_limit_exceeded() {
2026 let provider = GzipProvider::default();
2027 let data = vec![0u8; 1024];
2029 let compressed = provider.compress(&data).unwrap();
2030
2031 let result = provider.decompress_with_limit(&compressed, 512);
2033 assert!(result.is_err());
2034 let err = result.unwrap_err();
2035 assert_eq!(err.code, crate::ErrorCode::ResourceExhausted);
2036 }
2037
2038 #[cfg(feature = "zstd")]
2039 #[test]
2040 fn test_zstd_decompress_with_limit_under() {
2041 let provider = ZstdProvider::default();
2042 let data = b"hello world";
2043 let compressed = provider.compress(data).unwrap();
2044
2045 let result = provider.decompress_with_limit(&compressed, 1024);
2046 assert!(result.is_ok());
2047 assert_eq!(&result.unwrap()[..], data);
2048 }
2049
2050 #[cfg(feature = "zstd")]
2051 #[test]
2052 fn test_zstd_decompress_with_limit_exact() {
2053 let provider = ZstdProvider::default();
2054 let data = b"hello world";
2055 let compressed = provider.compress(data).unwrap();
2056
2057 let result = provider.decompress_with_limit(&compressed, data.len());
2058 assert!(result.is_ok());
2059 assert_eq!(&result.unwrap()[..], data);
2060 }
2061
2062 #[cfg(feature = "zstd")]
2063 #[test]
2064 fn test_zstd_decompress_with_limit_exceeded() {
2065 let provider = ZstdProvider::default();
2066 let data = vec![0u8; 1024];
2067 let compressed = provider.compress(&data).unwrap();
2068
2069 let result = provider.decompress_with_limit(&compressed, 512);
2070 assert!(result.is_err());
2071 let err = result.unwrap_err();
2072 assert_eq!(err.code, crate::ErrorCode::ResourceExhausted);
2073 }
2074
2075 #[test]
2076 fn test_compression_policy_default() {
2077 let policy = CompressionPolicy::default();
2078 assert!(!policy.should_compress(512));
2080 assert!(!policy.should_compress(1023));
2081 assert!(policy.should_compress(1024));
2083 assert!(policy.should_compress(4096));
2084 }
2085
2086 #[test]
2087 fn test_compression_policy_disabled() {
2088 let policy = CompressionPolicy::disabled();
2089 assert!(!policy.should_compress(0));
2090 assert!(!policy.should_compress(1024));
2091 assert!(!policy.should_compress(1_000_000));
2092 }
2093
2094 #[test]
2095 fn test_compression_policy_custom_min_size() {
2096 let policy = CompressionPolicy::default().min_size(4096);
2097 assert!(!policy.should_compress(1024));
2098 assert!(!policy.should_compress(4095));
2099 assert!(policy.should_compress(4096));
2100 assert!(policy.should_compress(8192));
2101 }
2102
2103 #[test]
2104 fn test_compression_policy_empty_message() {
2105 let default_policy = CompressionPolicy::default();
2107 assert!(!default_policy.should_compress(0));
2108
2109 let zero_min = CompressionPolicy::default().min_size(0);
2113 assert!(zero_min.should_compress(0));
2114
2115 let disabled = CompressionPolicy::disabled();
2116 assert!(!disabled.should_compress(0));
2117 }
2118
2119 #[test]
2120 fn test_compression_policy_with_override() {
2121 let policy = CompressionPolicy::default();
2122
2123 let effective = policy.with_override(None);
2125 assert!(!effective.should_compress(512));
2126 assert!(effective.should_compress(2048));
2127
2128 let forced = policy.with_override(Some(true));
2131 assert!(forced.should_compress(0));
2132 assert!(forced.should_compress(1));
2133
2134 let disabled = policy.with_override(Some(false));
2136 assert!(!disabled.should_compress(0));
2137 assert!(!disabled.should_compress(1_000_000));
2138 }
2139
2140 #[test]
2141 fn test_identity_decompress_with_limit() {
2142 let registry = CompressionRegistry::new();
2143 let data = Bytes::from_static(b"hello world");
2144
2145 let result = registry.decompress_with_limit("identity", data.clone(), 1024);
2147 assert!(result.is_ok());
2148
2149 let result = registry.decompress_with_limit("identity", data.clone(), data.len());
2151 assert!(result.is_ok());
2152
2153 let result = registry.decompress_with_limit("identity", data, 5);
2155 assert!(result.is_err());
2156 }
2157}