1#[cfg(feature = "std")]
36use alloc::collections::VecDeque;
37use alloc::vec::Vec;
38use core::fmt::Debug;
39#[cfg(feature = "std")]
40use std::sync::Mutex;
41
42use crate::enums::CertificateCompressionAlgorithm;
43use crate::msgs::base::{Payload, PayloadU24};
44use crate::msgs::codec::Codec;
45use crate::msgs::handshake::{CertificatePayloadTls13, CompressedCertificatePayload};
46use crate::sync::Arc;
47
48pub fn default_cert_decompressors() -> &'static [&'static dyn CertDecompressor] {
51 &[
52 #[cfg(feature = "brotli")]
53 BROTLI_DECOMPRESSOR,
54 #[cfg(feature = "zlib")]
55 ZLIB_DECOMPRESSOR,
56 ]
57}
58
59pub trait CertDecompressor: Debug + Send + Sync {
61 fn decompress(&self, input: &[u8], output: &mut [u8]) -> Result<(), DecompressionFailed>;
68
69 fn algorithm(&self) -> CertificateCompressionAlgorithm;
71}
72
73pub fn default_cert_compressors() -> &'static [&'static dyn CertCompressor] {
76 &[
77 #[cfg(feature = "brotli")]
78 BROTLI_COMPRESSOR,
79 #[cfg(feature = "zlib")]
80 ZLIB_COMPRESSOR,
81 ]
82}
83
84pub trait CertCompressor: Debug + Send + Sync {
86 fn compress(
95 &self,
96 input: Vec<u8>,
97 level: CompressionLevel,
98 ) -> Result<Vec<u8>, CompressionFailed>;
99
100 fn algorithm(&self) -> CertificateCompressionAlgorithm;
102}
103
104#[derive(Debug, Copy, Clone, Eq, PartialEq)]
106pub enum CompressionLevel {
107 Interactive,
111
112 Amortized,
116}
117
118#[derive(Debug)]
120pub struct DecompressionFailed;
121
122#[derive(Debug)]
124pub struct CompressionFailed;
125
126#[cfg(feature = "zlib")]
127mod feat_zlib_rs {
128 use zlib_rs::{
129 DeflateConfig, Inflate, InflateFlush, ReturnCode, Status, compress_bound, compress_slice,
130 };
131
132 use super::*;
133
134 pub const ZLIB_DECOMPRESSOR: &dyn CertDecompressor = &ZlibRsDecompressor;
136
137 #[derive(Debug)]
138 struct ZlibRsDecompressor;
139
140 impl CertDecompressor for ZlibRsDecompressor {
141 fn decompress(&self, input: &[u8], output: &mut [u8]) -> Result<(), DecompressionFailed> {
142 let mut state = Inflate::new(true, ZLIB_WINDOW_BITS);
145 match state.decompress(input, output, InflateFlush::Finish) {
146 Ok(Status::StreamEnd)
147 if state.total_in() == input.len() as u64
148 && state.total_out() == output.len() as u64 =>
149 {
150 Ok(())
151 }
152 _ => Err(DecompressionFailed),
153 }
154 }
155
156 fn algorithm(&self) -> CertificateCompressionAlgorithm {
157 CertificateCompressionAlgorithm::Zlib
158 }
159 }
160
161 pub const ZLIB_COMPRESSOR: &dyn CertCompressor = &ZlibRsCompressor;
163
164 #[derive(Debug)]
165 struct ZlibRsCompressor;
166
167 impl CertCompressor for ZlibRsCompressor {
168 fn compress(
169 &self,
170 input: Vec<u8>,
171 level: CompressionLevel,
172 ) -> Result<Vec<u8>, CompressionFailed> {
173 let mut output = alloc::vec![0u8; compress_bound(input.len())];
174 let config = match level {
175 CompressionLevel::Interactive => DeflateConfig::default(),
176 CompressionLevel::Amortized => DeflateConfig::best_compression(),
177 };
178 let (output_filled, rc) = compress_slice(&mut output, &input, config);
179 if rc != ReturnCode::Ok {
180 return Err(CompressionFailed);
181 }
182
183 let used = output_filled.len();
184 output.truncate(used);
185 Ok(output)
186 }
187
188 fn algorithm(&self) -> CertificateCompressionAlgorithm {
189 CertificateCompressionAlgorithm::Zlib
190 }
191 }
192
193 const ZLIB_WINDOW_BITS: u8 = 15;
195}
196
197#[cfg(feature = "zlib")]
198pub use feat_zlib_rs::{ZLIB_COMPRESSOR, ZLIB_DECOMPRESSOR};
199
200#[allow(clippy::std_instead_of_core)] #[cfg(feature = "brotli")]
202mod feat_brotli {
203 use std::io::{Cursor, Write};
204
205 use brotli_decompressor::{BrotliDecompressStream, BrotliResult, BrotliState, StandardAlloc};
206
207 use super::*;
208
209 pub const BROTLI_DECOMPRESSOR: &dyn CertDecompressor = &BrotliDecompressor;
211
212 #[derive(Debug)]
213 struct BrotliDecompressor;
214
215 impl CertDecompressor for BrotliDecompressor {
216 fn decompress(&self, input: &[u8], output: &mut [u8]) -> Result<(), DecompressionFailed> {
217 let mut state = BrotliState::new_strict(
218 StandardAlloc::default(),
219 StandardAlloc::default(),
220 StandardAlloc::default(),
221 );
222 let (mut available_in, mut input_offset) = (input.len(), 0);
223 let (mut available_out, mut output_offset, mut total_out) = (output.len(), 0, 0);
224
225 match BrotliDecompressStream(
226 &mut available_in,
227 &mut input_offset,
228 input,
229 &mut available_out,
230 &mut output_offset,
231 output,
232 &mut total_out,
233 &mut state,
234 ) {
235 BrotliResult::ResultSuccess if available_out == 0 && available_in == 0 => Ok(()),
236 _ => Err(DecompressionFailed),
237 }
238 }
239
240 fn algorithm(&self) -> CertificateCompressionAlgorithm {
241 CertificateCompressionAlgorithm::Brotli
242 }
243 }
244
245 pub const BROTLI_COMPRESSOR: &dyn CertCompressor = &BrotliCompressor;
247
248 #[derive(Debug)]
249 struct BrotliCompressor;
250
251 impl CertCompressor for BrotliCompressor {
252 fn compress(
253 &self,
254 input: Vec<u8>,
255 level: CompressionLevel,
256 ) -> Result<Vec<u8>, CompressionFailed> {
257 let quality = match level {
258 CompressionLevel::Interactive => QUALITY_FAST,
259 CompressionLevel::Amortized => QUALITY_SLOW,
260 };
261 let output = Cursor::new(Vec::with_capacity(input.len() / 2));
262 let mut compressor = brotli::CompressorWriter::new(output, BUFFER_SIZE, quality, LGWIN);
263 compressor
264 .write_all(&input)
265 .map_err(|_| CompressionFailed)?;
266 Ok(compressor.into_inner().into_inner())
267 }
268
269 fn algorithm(&self) -> CertificateCompressionAlgorithm {
270 CertificateCompressionAlgorithm::Brotli
271 }
272 }
273
274 const BUFFER_SIZE: usize = 4096;
278
279 const LGWIN: u32 = 22;
281
282 const QUALITY_FAST: u32 = 4;
285
286 const QUALITY_SLOW: u32 = 11;
288}
289
290#[cfg(feature = "brotli")]
291pub use feat_brotli::{BROTLI_COMPRESSOR, BROTLI_DECOMPRESSOR};
292
293#[derive(Debug)]
299pub enum CompressionCache {
300 Disabled,
303
304 #[cfg(feature = "std")]
306 Enabled(CompressionCacheInner),
307}
308
309#[cfg(feature = "std")]
313#[derive(Debug)]
314pub struct CompressionCacheInner {
315 size: usize,
317
318 entries: Mutex<VecDeque<Arc<CompressionCacheEntry>>>,
322}
323
324impl CompressionCache {
325 #[cfg(feature = "std")]
328 pub fn new(size: usize) -> Self {
329 if size == 0 {
330 return Self::Disabled;
331 }
332
333 Self::Enabled(CompressionCacheInner {
334 size,
335 entries: Mutex::new(VecDeque::with_capacity(size)),
336 })
337 }
338
339 pub(crate) fn compression_for(
345 &self,
346 compressor: &dyn CertCompressor,
347 original: &CertificatePayloadTls13<'_>,
348 ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
349 match self {
350 Self::Disabled => Self::uncached_compression(compressor, original),
351
352 #[cfg(feature = "std")]
353 Self::Enabled(_) => self.compression_for_impl(compressor, original),
354 }
355 }
356
357 #[cfg(feature = "std")]
358 fn compression_for_impl(
359 &self,
360 compressor: &dyn CertCompressor,
361 original: &CertificatePayloadTls13<'_>,
362 ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
363 let (max_size, entries) = match self {
364 Self::Enabled(CompressionCacheInner { size, entries }) => (*size, entries),
365 _ => unreachable!(),
366 };
367
368 if !original.context.0.is_empty() {
371 return Self::uncached_compression(compressor, original);
372 }
373
374 let encoding = original.get_encoding();
376 let algorithm = compressor.algorithm();
377
378 let mut cache = entries
379 .lock()
380 .map_err(|_| CompressionFailed)?;
381 for (i, item) in cache.iter().enumerate() {
382 if item.algorithm == algorithm && item.original == encoding {
383 let item = cache.remove(i).unwrap();
385 cache.push_back(item.clone());
386 return Ok(item);
387 }
388 }
389 drop(cache);
390
391 let uncompressed_len = encoding.len() as u32;
393 let compressed = compressor.compress(encoding.clone(), CompressionLevel::Amortized)?;
394 let new_entry = Arc::new(CompressionCacheEntry {
395 algorithm,
396 original: encoding,
397 compressed: CompressedCertificatePayload {
398 alg: algorithm,
399 uncompressed_len,
400 compressed: PayloadU24::from(Payload::new(compressed)),
401 },
402 });
403
404 let mut cache = entries
406 .lock()
407 .map_err(|_| CompressionFailed)?;
408 if cache.len() == max_size {
409 cache.pop_front();
410 }
411 cache.push_back(new_entry.clone());
412 Ok(new_entry)
413 }
414
415 fn uncached_compression(
417 compressor: &dyn CertCompressor,
418 original: &CertificatePayloadTls13<'_>,
419 ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
420 let algorithm = compressor.algorithm();
421 let encoding = original.get_encoding();
422 let uncompressed_len = encoding.len() as u32;
423 let compressed = compressor.compress(encoding, CompressionLevel::Interactive)?;
424
425 Ok(Arc::new(CompressionCacheEntry {
428 algorithm,
429 original: Vec::new(),
430 compressed: CompressedCertificatePayload {
431 alg: algorithm,
432 uncompressed_len,
433 compressed: PayloadU24::from(Payload::new(compressed)),
434 },
435 }))
436 }
437}
438
439impl Default for CompressionCache {
440 fn default() -> Self {
441 #[cfg(feature = "std")]
442 {
443 Self::new(4)
445 }
446
447 #[cfg(not(feature = "std"))]
448 {
449 Self::Disabled
450 }
451 }
452}
453
454#[cfg_attr(not(feature = "std"), allow(dead_code))]
455#[derive(Debug)]
456pub(crate) struct CompressionCacheEntry {
457 algorithm: CertificateCompressionAlgorithm,
459 original: Vec<u8>,
460
461 compressed: CompressedCertificatePayload<'static>,
463}
464
465impl CompressionCacheEntry {
466 pub(crate) fn compressed_cert_payload(&self) -> CompressedCertificatePayload<'_> {
467 self.compressed.as_borrowed()
468 }
469}
470
471#[cfg(all(test, any(feature = "brotli", feature = "zlib")))]
472mod tests {
473 use std::{println, vec};
474
475 use super::*;
476
477 #[test]
478 #[cfg(feature = "zlib")]
479 fn test_zlib() {
480 test_compressor(ZLIB_COMPRESSOR, ZLIB_DECOMPRESSOR);
481 }
482
483 #[test]
484 #[cfg(feature = "brotli")]
485 fn test_brotli() {
486 test_compressor(BROTLI_COMPRESSOR, BROTLI_DECOMPRESSOR);
487 }
488
489 #[test]
490 #[cfg(feature = "brotli")]
491 fn test_brotli_rejects_large_window() {
492 let large_window_empty = [0x11, 0xd0];
493 brotli::BrotliDecompress(&mut &large_window_empty[..], &mut Vec::new()).unwrap();
494 BROTLI_DECOMPRESSOR
495 .decompress(&large_window_empty, &mut [])
496 .unwrap_err();
497 }
498
499 fn test_compressor(comp: &dyn CertCompressor, decomp: &dyn CertDecompressor) {
500 assert_eq!(comp.algorithm(), decomp.algorithm());
501 for sz in [16, 64, 512, 2048, 8192, 16384] {
502 test_trivial_pairwise(comp, decomp, sz);
503 }
504 test_decompress_wrong_len(comp, decomp);
505 test_decompress_trailing_data(comp, decomp);
506 test_decompress_garbage(decomp);
507 }
508
509 fn test_trivial_pairwise(
510 comp: &dyn CertCompressor,
511 decomp: &dyn CertDecompressor,
512 plain_len: usize,
513 ) {
514 let original = vec![0u8; plain_len];
515
516 for level in [CompressionLevel::Interactive, CompressionLevel::Amortized] {
517 let compressed = comp
518 .compress(original.clone(), level)
519 .unwrap();
520 println!(
521 "{:?} compressed trivial {} -> {} using {:?} level",
522 comp.algorithm(),
523 original.len(),
524 compressed.len(),
525 level
526 );
527 let mut recovered = vec![0xffu8; plain_len];
528 decomp
529 .decompress(&compressed, &mut recovered)
530 .unwrap();
531 assert_eq!(original, recovered);
532 }
533 }
534
535 fn test_decompress_wrong_len(comp: &dyn CertCompressor, decomp: &dyn CertDecompressor) {
536 let original = vec![0u8; 2048];
537 let compressed = comp
538 .compress(original.clone(), CompressionLevel::Interactive)
539 .unwrap();
540 println!("{compressed:?}");
541
542 let mut recovered = vec![0xffu8; original.len() + 1];
544 decomp
545 .decompress(&compressed, &mut recovered)
546 .unwrap_err();
547
548 let mut recovered = vec![0xffu8; original.len() - 1];
550 decomp
551 .decompress(&compressed, &mut recovered)
552 .unwrap_err();
553 }
554
555 fn test_decompress_trailing_data(comp: &dyn CertCompressor, decomp: &dyn CertDecompressor) {
556 let original = vec![0u8; 2048];
557 let mut compressed = comp
558 .compress(original.clone(), CompressionLevel::Interactive)
559 .unwrap();
560 compressed.extend_from_slice(b"trailing data");
561
562 let mut recovered = vec![0xffu8; original.len()];
563 decomp
564 .decompress(&compressed, &mut recovered)
565 .unwrap_err();
566 }
567
568 fn test_decompress_garbage(decomp: &dyn CertDecompressor) {
569 let junk = [0u8; 1024];
570 let mut recovered = vec![0u8; 512];
571 decomp
572 .decompress(&junk, &mut recovered)
573 .unwrap_err();
574 }
575
576 #[test]
577 #[cfg(all(feature = "brotli", feature = "zlib"))]
578 fn test_cache_evicts_lru() {
579 use core::sync::atomic::{AtomicBool, Ordering};
580
581 use pki_types::CertificateDer;
582
583 let cache = CompressionCache::default();
584
585 let cert = CertificateDer::from(vec![1]);
586
587 let cert1 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"1"));
588 let cert2 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"2"));
589 let cert3 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"3"));
590 let cert4 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"4"));
591
592 cache
595 .compression_for(
596 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
597 &cert1,
598 )
599 .unwrap();
600 cache
601 .compression_for(
602 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
603 &cert2,
604 )
605 .unwrap();
606 cache
607 .compression_for(
608 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
609 &cert3,
610 )
611 .unwrap();
612 cache
613 .compression_for(
614 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
615 &cert4,
616 )
617 .unwrap();
618
619 cache
623 .compression_for(
624 &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
625 &cert4,
626 )
627 .unwrap();
628
629 cache
631 .compression_for(
632 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
633 &cert2,
634 )
635 .unwrap();
636 cache
637 .compression_for(
638 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
639 &cert3,
640 )
641 .unwrap();
642 cache
643 .compression_for(
644 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
645 &cert4,
646 )
647 .unwrap();
648 cache
649 .compression_for(
650 &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), false),
651 &cert4,
652 )
653 .unwrap();
654
655 cache
657 .compression_for(
658 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
659 &cert1,
660 )
661 .unwrap();
662
663 cache
666 .compression_for(
667 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
668 &cert4,
669 )
670 .unwrap();
671 cache
672 .compression_for(
673 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
674 &cert3,
675 )
676 .unwrap();
677 cache
678 .compression_for(
679 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
680 &cert1,
681 )
682 .unwrap();
683
684 cache
687 .compression_for(
688 &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
689 &cert1,
690 )
691 .unwrap();
692
693 cache
695 .compression_for(
696 &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
697 &cert4,
698 )
699 .unwrap();
700
701 #[derive(Debug)]
702 struct RequireCompress(&'static dyn CertCompressor, AtomicBool, bool);
703
704 impl CertCompressor for RequireCompress {
705 fn compress(
706 &self,
707 input: Vec<u8>,
708 level: CompressionLevel,
709 ) -> Result<Vec<u8>, CompressionFailed> {
710 self.1.store(true, Ordering::SeqCst);
711 self.0.compress(input, level)
712 }
713
714 fn algorithm(&self) -> CertificateCompressionAlgorithm {
715 self.0.algorithm()
716 }
717 }
718
719 impl Drop for RequireCompress {
720 fn drop(&mut self) {
721 assert_eq!(self.1.load(Ordering::SeqCst), self.2);
722 }
723 }
724 }
725}