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, InflateConfig, ReturnCode, compress_bound, compress_slice, decompress_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 output_len = output.len();
143 match decompress_slice(output, input, InflateConfig::default()) {
144 (output_filled, ReturnCode::Ok) if output_filled.len() == output_len => Ok(()),
145 (_, _) => Err(DecompressionFailed),
146 }
147 }
148
149 fn algorithm(&self) -> CertificateCompressionAlgorithm {
150 CertificateCompressionAlgorithm::Zlib
151 }
152 }
153
154 pub const ZLIB_COMPRESSOR: &dyn CertCompressor = &ZlibRsCompressor;
156
157 #[derive(Debug)]
158 struct ZlibRsCompressor;
159
160 impl CertCompressor for ZlibRsCompressor {
161 fn compress(
162 &self,
163 input: Vec<u8>,
164 level: CompressionLevel,
165 ) -> Result<Vec<u8>, CompressionFailed> {
166 let mut output = alloc::vec![0u8; compress_bound(input.len())];
167 let config = match level {
168 CompressionLevel::Interactive => DeflateConfig::default(),
169 CompressionLevel::Amortized => DeflateConfig::best_compression(),
170 };
171 let (output_filled, rc) = compress_slice(&mut output, &input, config);
172 if rc != ReturnCode::Ok {
173 return Err(CompressionFailed);
174 }
175
176 let used = output_filled.len();
177 output.truncate(used);
178 Ok(output)
179 }
180
181 fn algorithm(&self) -> CertificateCompressionAlgorithm {
182 CertificateCompressionAlgorithm::Zlib
183 }
184 }
185}
186
187#[cfg(feature = "zlib")]
188pub use feat_zlib_rs::{ZLIB_COMPRESSOR, ZLIB_DECOMPRESSOR};
189
190#[allow(clippy::std_instead_of_core)] #[cfg(feature = "brotli")]
192mod feat_brotli {
193 use std::io::{Cursor, Write};
194
195 use super::*;
196
197 pub const BROTLI_DECOMPRESSOR: &dyn CertDecompressor = &BrotliDecompressor;
199
200 #[derive(Debug)]
201 struct BrotliDecompressor;
202
203 impl CertDecompressor for BrotliDecompressor {
204 fn decompress(&self, input: &[u8], output: &mut [u8]) -> Result<(), DecompressionFailed> {
205 let mut in_cursor = Cursor::new(input);
206 let mut out_cursor = Cursor::new(output);
207
208 brotli::BrotliDecompress(&mut in_cursor, &mut out_cursor)
209 .map_err(|_| DecompressionFailed)?;
210
211 if out_cursor.position() as usize != out_cursor.into_inner().len() {
212 return Err(DecompressionFailed);
213 }
214
215 Ok(())
216 }
217
218 fn algorithm(&self) -> CertificateCompressionAlgorithm {
219 CertificateCompressionAlgorithm::Brotli
220 }
221 }
222
223 pub const BROTLI_COMPRESSOR: &dyn CertCompressor = &BrotliCompressor;
225
226 #[derive(Debug)]
227 struct BrotliCompressor;
228
229 impl CertCompressor for BrotliCompressor {
230 fn compress(
231 &self,
232 input: Vec<u8>,
233 level: CompressionLevel,
234 ) -> Result<Vec<u8>, CompressionFailed> {
235 let quality = match level {
236 CompressionLevel::Interactive => QUALITY_FAST,
237 CompressionLevel::Amortized => QUALITY_SLOW,
238 };
239 let output = Cursor::new(Vec::with_capacity(input.len() / 2));
240 let mut compressor = brotli::CompressorWriter::new(output, BUFFER_SIZE, quality, LGWIN);
241 compressor
242 .write_all(&input)
243 .map_err(|_| CompressionFailed)?;
244 Ok(compressor.into_inner().into_inner())
245 }
246
247 fn algorithm(&self) -> CertificateCompressionAlgorithm {
248 CertificateCompressionAlgorithm::Brotli
249 }
250 }
251
252 const BUFFER_SIZE: usize = 4096;
256
257 const LGWIN: u32 = 22;
259
260 const QUALITY_FAST: u32 = 4;
263
264 const QUALITY_SLOW: u32 = 11;
266}
267
268#[cfg(feature = "brotli")]
269pub use feat_brotli::{BROTLI_COMPRESSOR, BROTLI_DECOMPRESSOR};
270
271#[derive(Debug)]
277pub enum CompressionCache {
278 Disabled,
281
282 #[cfg(feature = "std")]
284 Enabled(CompressionCacheInner),
285}
286
287#[cfg(feature = "std")]
291#[derive(Debug)]
292pub struct CompressionCacheInner {
293 size: usize,
295
296 entries: Mutex<VecDeque<Arc<CompressionCacheEntry>>>,
300}
301
302impl CompressionCache {
303 #[cfg(feature = "std")]
306 pub fn new(size: usize) -> Self {
307 if size == 0 {
308 return Self::Disabled;
309 }
310
311 Self::Enabled(CompressionCacheInner {
312 size,
313 entries: Mutex::new(VecDeque::with_capacity(size)),
314 })
315 }
316
317 pub(crate) fn compression_for(
323 &self,
324 compressor: &dyn CertCompressor,
325 original: &CertificatePayloadTls13<'_>,
326 ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
327 match self {
328 Self::Disabled => Self::uncached_compression(compressor, original),
329
330 #[cfg(feature = "std")]
331 Self::Enabled(_) => self.compression_for_impl(compressor, original),
332 }
333 }
334
335 #[cfg(feature = "std")]
336 fn compression_for_impl(
337 &self,
338 compressor: &dyn CertCompressor,
339 original: &CertificatePayloadTls13<'_>,
340 ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
341 let (max_size, entries) = match self {
342 Self::Enabled(CompressionCacheInner { size, entries }) => (*size, entries),
343 _ => unreachable!(),
344 };
345
346 if !original.context.0.is_empty() {
349 return Self::uncached_compression(compressor, original);
350 }
351
352 let encoding = original.get_encoding();
354 let algorithm = compressor.algorithm();
355
356 let mut cache = entries
357 .lock()
358 .map_err(|_| CompressionFailed)?;
359 for (i, item) in cache.iter().enumerate() {
360 if item.algorithm == algorithm && item.original == encoding {
361 let item = cache.remove(i).unwrap();
363 cache.push_back(item.clone());
364 return Ok(item);
365 }
366 }
367 drop(cache);
368
369 let uncompressed_len = encoding.len() as u32;
371 let compressed = compressor.compress(encoding.clone(), CompressionLevel::Amortized)?;
372 let new_entry = Arc::new(CompressionCacheEntry {
373 algorithm,
374 original: encoding,
375 compressed: CompressedCertificatePayload {
376 alg: algorithm,
377 uncompressed_len,
378 compressed: PayloadU24(Payload::new(compressed)),
379 },
380 });
381
382 let mut cache = entries
384 .lock()
385 .map_err(|_| CompressionFailed)?;
386 if cache.len() == max_size {
387 cache.pop_front();
388 }
389 cache.push_back(new_entry.clone());
390 Ok(new_entry)
391 }
392
393 fn uncached_compression(
395 compressor: &dyn CertCompressor,
396 original: &CertificatePayloadTls13<'_>,
397 ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
398 let algorithm = compressor.algorithm();
399 let encoding = original.get_encoding();
400 let uncompressed_len = encoding.len() as u32;
401 let compressed = compressor.compress(encoding, CompressionLevel::Interactive)?;
402
403 Ok(Arc::new(CompressionCacheEntry {
406 algorithm,
407 original: Vec::new(),
408 compressed: CompressedCertificatePayload {
409 alg: algorithm,
410 uncompressed_len,
411 compressed: PayloadU24(Payload::new(compressed)),
412 },
413 }))
414 }
415}
416
417impl Default for CompressionCache {
418 fn default() -> Self {
419 #[cfg(feature = "std")]
420 {
421 Self::new(4)
423 }
424
425 #[cfg(not(feature = "std"))]
426 {
427 Self::Disabled
428 }
429 }
430}
431
432#[cfg_attr(not(feature = "std"), allow(dead_code))]
433#[derive(Debug)]
434pub(crate) struct CompressionCacheEntry {
435 algorithm: CertificateCompressionAlgorithm,
437 original: Vec<u8>,
438
439 compressed: CompressedCertificatePayload<'static>,
441}
442
443impl CompressionCacheEntry {
444 pub(crate) fn compressed_cert_payload(&self) -> CompressedCertificatePayload<'_> {
445 self.compressed.as_borrowed()
446 }
447}
448
449#[cfg(all(test, any(feature = "brotli", feature = "zlib")))]
450mod tests {
451 use std::{println, vec};
452
453 use super::*;
454
455 #[test]
456 #[cfg(feature = "zlib")]
457 fn test_zlib() {
458 test_compressor(ZLIB_COMPRESSOR, ZLIB_DECOMPRESSOR);
459 }
460
461 #[test]
462 #[cfg(feature = "brotli")]
463 fn test_brotli() {
464 test_compressor(BROTLI_COMPRESSOR, BROTLI_DECOMPRESSOR);
465 }
466
467 fn test_compressor(comp: &dyn CertCompressor, decomp: &dyn CertDecompressor) {
468 assert_eq!(comp.algorithm(), decomp.algorithm());
469 for sz in [16, 64, 512, 2048, 8192, 16384] {
470 test_trivial_pairwise(comp, decomp, sz);
471 }
472 test_decompress_wrong_len(comp, decomp);
473 test_decompress_garbage(decomp);
474 }
475
476 fn test_trivial_pairwise(
477 comp: &dyn CertCompressor,
478 decomp: &dyn CertDecompressor,
479 plain_len: usize,
480 ) {
481 let original = vec![0u8; plain_len];
482
483 for level in [CompressionLevel::Interactive, CompressionLevel::Amortized] {
484 let compressed = comp
485 .compress(original.clone(), level)
486 .unwrap();
487 println!(
488 "{:?} compressed trivial {} -> {} using {:?} level",
489 comp.algorithm(),
490 original.len(),
491 compressed.len(),
492 level
493 );
494 let mut recovered = vec![0xffu8; plain_len];
495 decomp
496 .decompress(&compressed, &mut recovered)
497 .unwrap();
498 assert_eq!(original, recovered);
499 }
500 }
501
502 fn test_decompress_wrong_len(comp: &dyn CertCompressor, decomp: &dyn CertDecompressor) {
503 let original = vec![0u8; 2048];
504 let compressed = comp
505 .compress(original.clone(), CompressionLevel::Interactive)
506 .unwrap();
507 println!("{compressed:?}");
508
509 let mut recovered = vec![0xffu8; original.len() + 1];
511 decomp
512 .decompress(&compressed, &mut recovered)
513 .unwrap_err();
514
515 let mut recovered = vec![0xffu8; original.len() - 1];
517 decomp
518 .decompress(&compressed, &mut recovered)
519 .unwrap_err();
520 }
521
522 fn test_decompress_garbage(decomp: &dyn CertDecompressor) {
523 let junk = [0u8; 1024];
524 let mut recovered = vec![0u8; 512];
525 decomp
526 .decompress(&junk, &mut recovered)
527 .unwrap_err();
528 }
529
530 #[test]
531 #[cfg(all(feature = "brotli", feature = "zlib"))]
532 fn test_cache_evicts_lru() {
533 use core::sync::atomic::{AtomicBool, Ordering};
534
535 use pki_types::CertificateDer;
536
537 let cache = CompressionCache::default();
538
539 let cert = CertificateDer::from(vec![1]);
540
541 let cert1 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"1"));
542 let cert2 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"2"));
543 let cert3 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"3"));
544 let cert4 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"4"));
545
546 cache
549 .compression_for(
550 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
551 &cert1,
552 )
553 .unwrap();
554 cache
555 .compression_for(
556 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
557 &cert2,
558 )
559 .unwrap();
560 cache
561 .compression_for(
562 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
563 &cert3,
564 )
565 .unwrap();
566 cache
567 .compression_for(
568 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
569 &cert4,
570 )
571 .unwrap();
572
573 cache
577 .compression_for(
578 &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
579 &cert4,
580 )
581 .unwrap();
582
583 cache
585 .compression_for(
586 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
587 &cert2,
588 )
589 .unwrap();
590 cache
591 .compression_for(
592 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
593 &cert3,
594 )
595 .unwrap();
596 cache
597 .compression_for(
598 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
599 &cert4,
600 )
601 .unwrap();
602 cache
603 .compression_for(
604 &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), false),
605 &cert4,
606 )
607 .unwrap();
608
609 cache
611 .compression_for(
612 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
613 &cert1,
614 )
615 .unwrap();
616
617 cache
620 .compression_for(
621 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
622 &cert4,
623 )
624 .unwrap();
625 cache
626 .compression_for(
627 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
628 &cert3,
629 )
630 .unwrap();
631 cache
632 .compression_for(
633 &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
634 &cert1,
635 )
636 .unwrap();
637
638 cache
641 .compression_for(
642 &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
643 &cert1,
644 )
645 .unwrap();
646
647 cache
649 .compression_for(
650 &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
651 &cert4,
652 )
653 .unwrap();
654
655 #[derive(Debug)]
656 struct RequireCompress(&'static dyn CertCompressor, AtomicBool, bool);
657
658 impl CertCompressor for RequireCompress {
659 fn compress(
660 &self,
661 input: Vec<u8>,
662 level: CompressionLevel,
663 ) -> Result<Vec<u8>, CompressionFailed> {
664 self.1.store(true, Ordering::SeqCst);
665 self.0.compress(input, level)
666 }
667
668 fn algorithm(&self) -> CertificateCompressionAlgorithm {
669 self.0.algorithm()
670 }
671 }
672
673 impl Drop for RequireCompress {
674 fn drop(&mut self) {
675 assert_eq!(self.1.load(Ordering::SeqCst), self.2);
676 }
677 }
678 }
679}