Coverage Report

Created: 2026-08-05 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rustls-0.23.42/src/compress.rs
Line
Count
Source
1
//! Certificate compression and decompression support
2
//!
3
//! This crate supports compression and decompression everywhere
4
//! certificates are used, in accordance with [RFC8879][rfc8879].
5
//!
6
//! Note that this is only supported for TLS1.3 connections.
7
//!
8
//! # Getting started
9
//!
10
//! Build this crate with the `brotli` and/or `zlib` crate features.  This
11
//! adds dependencies on these crates.  They are used by default if enabled.
12
//!
13
//! We especially recommend `brotli` as it has the widest deployment so far.
14
//!
15
//! # Custom compression/decompression implementations
16
//!
17
//! 1. Implement the [`CertCompressor`] and/or [`CertDecompressor`] traits
18
//! 2. Provide those to:
19
//!   - [`ClientConfig::cert_compressors`][cc_cc] or [`ServerConfig::cert_compressors`][sc_cc].
20
//!   - [`ClientConfig::cert_decompressors`][cc_cd] or [`ServerConfig::cert_decompressors`][sc_cd].
21
//!
22
//! These are used in these circumstances:
23
//!
24
//! | Peer | Client authentication | Server authentication |
25
//! | ---- | --------------------- | --------------------- |
26
//! | *Client* | [`ClientConfig::cert_compressors`][cc_cc] | [`ClientConfig::cert_decompressors`][cc_cd] |
27
//! | *Server* | [`ServerConfig::cert_decompressors`][sc_cd] | [`ServerConfig::cert_compressors`][sc_cc] |
28
//!
29
//! [rfc8879]: https://datatracker.ietf.org/doc/html/rfc8879
30
//! [cc_cc]: crate::ClientConfig::cert_compressors
31
//! [sc_cc]: crate::ServerConfig::cert_compressors
32
//! [cc_cd]: crate::ClientConfig::cert_decompressors
33
//! [sc_cd]: crate::ServerConfig::cert_decompressors
34
35
#[cfg(feature = "std")]
36
use alloc::collections::VecDeque;
37
use alloc::vec::Vec;
38
use core::fmt::Debug;
39
#[cfg(feature = "std")]
40
use std::sync::Mutex;
41
42
use crate::enums::CertificateCompressionAlgorithm;
43
use crate::msgs::base::{Payload, PayloadU24};
44
use crate::msgs::codec::Codec;
45
use crate::msgs::handshake::{CertificatePayloadTls13, CompressedCertificatePayload};
46
use crate::sync::Arc;
47
48
/// Returns the supported `CertDecompressor` implementations enabled
49
/// by crate features.
50
0
pub fn default_cert_decompressors() -> &'static [&'static dyn CertDecompressor] {
51
0
    &[
52
0
        #[cfg(feature = "brotli")]
53
0
        BROTLI_DECOMPRESSOR,
54
0
        #[cfg(feature = "zlib")]
55
0
        ZLIB_DECOMPRESSOR,
56
0
    ]
57
0
}
58
59
/// An available certificate decompression algorithm.
60
pub trait CertDecompressor: Debug + Send + Sync {
61
    /// Decompress `input`, writing the result to `output`.
62
    ///
63
    /// `output` is sized to match the declared length of the decompressed data.
64
    ///
65
    /// `Err(DecompressionFailed)` should be returned if decompression produces more, or fewer
66
    /// bytes than fit in `output`, or if the `input` is in any way malformed.
67
    fn decompress(&self, input: &[u8], output: &mut [u8]) -> Result<(), DecompressionFailed>;
68
69
    /// Which algorithm this decompressor handles.
70
    fn algorithm(&self) -> CertificateCompressionAlgorithm;
71
}
72
73
/// Returns the supported `CertCompressor` implementations enabled
74
/// by crate features.
75
0
pub fn default_cert_compressors() -> &'static [&'static dyn CertCompressor] {
76
0
    &[
77
0
        #[cfg(feature = "brotli")]
78
0
        BROTLI_COMPRESSOR,
79
0
        #[cfg(feature = "zlib")]
80
0
        ZLIB_COMPRESSOR,
81
0
    ]
82
0
}
83
84
/// An available certificate compression algorithm.
85
pub trait CertCompressor: Debug + Send + Sync {
86
    /// Compress `input`, returning the result.
87
    ///
88
    /// `input` is consumed by this function so (if the underlying implementation
89
    /// supports it) the compression can be performed in-place.
90
    ///
91
    /// `level` is a hint as to how much effort to expend on the compression.
92
    ///
93
    /// `Err(CompressionFailed)` may be returned for any reason.
94
    fn compress(
95
        &self,
96
        input: Vec<u8>,
97
        level: CompressionLevel,
98
    ) -> Result<Vec<u8>, CompressionFailed>;
99
100
    /// Which algorithm this compressor handles.
101
    fn algorithm(&self) -> CertificateCompressionAlgorithm;
102
}
103
104
/// A hint for how many resources to dedicate to a compression.
105
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
106
pub enum CompressionLevel {
107
    /// This compression is happening interactively during a handshake.
108
    ///
109
    /// Implementations may wish to choose a conservative compression level.
110
    Interactive,
111
112
    /// The compression may be amortized over many connections.
113
    ///
114
    /// Implementations may wish to choose an aggressive compression level.
115
    Amortized,
116
}
117
118
/// A content-less error for when `CertDecompressor::decompress` fails.
119
#[derive(Debug)]
120
pub struct DecompressionFailed;
121
122
/// A content-less error for when `CertCompressor::compress` fails.
123
#[derive(Debug)]
124
pub struct CompressionFailed;
125
126
#[cfg(feature = "zlib")]
127
mod feat_zlib_rs {
128
    use zlib_rs::{
129
        DeflateConfig, InflateConfig, ReturnCode, compress_bound, compress_slice, decompress_slice,
130
    };
131
132
    use super::*;
133
134
    /// A certificate decompressor for the Zlib algorithm using the `zlib-rs` crate.
135
    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
    /// A certificate compressor for the Zlib algorithm using the `zlib-rs` crate.
155
    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")]
188
pub use feat_zlib_rs::{ZLIB_COMPRESSOR, ZLIB_DECOMPRESSOR};
189
190
#[allow(clippy::std_instead_of_core)] // awaits core::io::Cursor (1.97) in crate MSRV
191
#[cfg(feature = "brotli")]
192
mod feat_brotli {
193
    use std::io::{Cursor, Write};
194
195
    use super::*;
196
197
    /// A certificate decompressor for the brotli algorithm using the `brotli` crate.
198
    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
    /// A certificate compressor for the brotli algorithm using the `brotli` crate.
224
    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
    /// Brotli buffer size.
253
    ///
254
    /// Chosen based on brotli `examples/compress.rs`.
255
    const BUFFER_SIZE: usize = 4096;
256
257
    /// This is the default lgwin parameter, see `BrotliEncoderInitParams()`
258
    const LGWIN: u32 = 22;
259
260
    /// Compression quality we use for interactive compressions.
261
    /// See <https://blog.cloudflare.com/results-experimenting-brotli> for data.
262
    const QUALITY_FAST: u32 = 4;
263
264
    /// Compression quality we use for offline compressions (the maximum).
265
    const QUALITY_SLOW: u32 = 11;
266
}
267
268
#[cfg(feature = "brotli")]
269
pub use feat_brotli::{BROTLI_COMPRESSOR, BROTLI_DECOMPRESSOR};
270
271
/// An LRU cache for compressions.
272
///
273
/// The prospect of being able to reuse a given compression for many connections
274
/// means we can afford to spend more time on that compression (by passing
275
/// `CompressionLevel::Amortized` to the compressor).
276
#[derive(Debug)]
277
pub enum CompressionCache {
278
    /// No caching happens, and compression happens each time using
279
    /// `CompressionLevel::Interactive`.
280
    Disabled,
281
282
    /// Compressions are stored in an LRU cache.
283
    #[cfg(feature = "std")]
284
    Enabled(CompressionCacheInner),
285
}
286
287
/// Innards of an enabled CompressionCache.
288
///
289
/// You cannot make one of these directly. Use [`CompressionCache::new`].
290
#[cfg(feature = "std")]
291
#[derive(Debug)]
292
pub struct CompressionCacheInner {
293
    /// Maximum size of underlying storage.
294
    size: usize,
295
296
    /// LRU-order entries.
297
    ///
298
    /// First is least-used, last is most-used.
299
    entries: Mutex<VecDeque<Arc<CompressionCacheEntry>>>,
300
}
301
302
impl CompressionCache {
303
    /// Make a `CompressionCache` that stores up to `size` compressed
304
    /// certificate messages.
305
    #[cfg(feature = "std")]
306
0
    pub fn new(size: usize) -> Self {
307
0
        if size == 0 {
308
0
            return Self::Disabled;
309
0
        }
310
311
0
        Self::Enabled(CompressionCacheInner {
312
0
            size,
313
0
            entries: Mutex::new(VecDeque::with_capacity(size)),
314
0
        })
315
0
    }
316
317
    /// Return a `CompressionCacheEntry`, which is an owning
318
    /// wrapper for a `CompressedCertificatePayload`.
319
    ///
320
    /// `compressor` is the compression function we have negotiated.
321
    /// `original` is the uncompressed certificate message.
322
0
    pub(crate) fn compression_for(
323
0
        &self,
324
0
        compressor: &dyn CertCompressor,
325
0
        original: &CertificatePayloadTls13<'_>,
326
0
    ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
327
0
        match self {
328
0
            Self::Disabled => Self::uncached_compression(compressor, original),
329
330
            #[cfg(feature = "std")]
331
0
            Self::Enabled(_) => self.compression_for_impl(compressor, original),
332
        }
333
0
    }
334
335
    #[cfg(feature = "std")]
336
0
    fn compression_for_impl(
337
0
        &self,
338
0
        compressor: &dyn CertCompressor,
339
0
        original: &CertificatePayloadTls13<'_>,
340
0
    ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
341
0
        let (max_size, entries) = match self {
342
0
            Self::Enabled(CompressionCacheInner { size, entries }) => (*size, entries),
343
0
            _ => unreachable!(),
344
        };
345
346
        // context is a per-connection quantity, and included in the compressed data.
347
        // it is not suitable for inclusion in the cache.
348
0
        if !original.context.0.is_empty() {
349
0
            return Self::uncached_compression(compressor, original);
350
0
        }
351
352
        // cache probe:
353
0
        let encoding = original.get_encoding();
354
0
        let algorithm = compressor.algorithm();
355
356
0
        let mut cache = entries
357
0
            .lock()
358
0
            .map_err(|_| CompressionFailed)?;
359
0
        for (i, item) in cache.iter().enumerate() {
360
0
            if item.algorithm == algorithm && item.original == encoding {
361
                // this item is now MRU
362
0
                let item = cache.remove(i).unwrap();
363
0
                cache.push_back(item.clone());
364
0
                return Ok(item);
365
0
            }
366
        }
367
0
        drop(cache);
368
369
        // do compression:
370
0
        let uncompressed_len = encoding.len() as u32;
371
0
        let compressed = compressor.compress(encoding.clone(), CompressionLevel::Amortized)?;
372
0
        let new_entry = Arc::new(CompressionCacheEntry {
373
0
            algorithm,
374
0
            original: encoding,
375
0
            compressed: CompressedCertificatePayload {
376
0
                alg: algorithm,
377
0
                uncompressed_len,
378
0
                compressed: PayloadU24(Payload::new(compressed)),
379
0
            },
380
0
        });
381
382
        // insert into cache
383
0
        let mut cache = entries
384
0
            .lock()
385
0
            .map_err(|_| CompressionFailed)?;
386
0
        if cache.len() == max_size {
387
0
            cache.pop_front();
388
0
        }
389
0
        cache.push_back(new_entry.clone());
390
0
        Ok(new_entry)
391
0
    }
392
393
    /// Compress `original` using `compressor` at `Interactive` level.
394
0
    fn uncached_compression(
395
0
        compressor: &dyn CertCompressor,
396
0
        original: &CertificatePayloadTls13<'_>,
397
0
    ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
398
0
        let algorithm = compressor.algorithm();
399
0
        let encoding = original.get_encoding();
400
0
        let uncompressed_len = encoding.len() as u32;
401
0
        let compressed = compressor.compress(encoding, CompressionLevel::Interactive)?;
402
403
        // this `CompressionCacheEntry` in fact never makes it into the cache, so
404
        // `original` is left empty
405
0
        Ok(Arc::new(CompressionCacheEntry {
406
0
            algorithm,
407
0
            original: Vec::new(),
408
0
            compressed: CompressedCertificatePayload {
409
0
                alg: algorithm,
410
0
                uncompressed_len,
411
0
                compressed: PayloadU24(Payload::new(compressed)),
412
0
            },
413
0
        }))
414
0
    }
415
}
416
417
impl Default for CompressionCache {
418
0
    fn default() -> Self {
419
        #[cfg(feature = "std")]
420
        {
421
            // 4 entries allows 2 certificate chains times 2 compression algorithms
422
0
            Self::new(4)
423
        }
424
425
        #[cfg(not(feature = "std"))]
426
        {
427
            Self::Disabled
428
        }
429
0
    }
430
}
431
432
#[cfg_attr(not(feature = "std"), allow(dead_code))]
433
#[derive(Debug)]
434
pub(crate) struct CompressionCacheEntry {
435
    // cache key is algorithm + original:
436
    algorithm: CertificateCompressionAlgorithm,
437
    original: Vec<u8>,
438
439
    // cache value is compression result:
440
    compressed: CompressedCertificatePayload<'static>,
441
}
442
443
impl CompressionCacheEntry {
444
0
    pub(crate) fn compressed_cert_payload(&self) -> CompressedCertificatePayload<'_> {
445
0
        self.compressed.as_borrowed()
446
0
    }
447
}
448
449
#[cfg(all(test, any(feature = "brotli", feature = "zlib")))]
450
mod 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
        // too big
510
        let mut recovered = vec![0xffu8; original.len() + 1];
511
        decomp
512
            .decompress(&compressed, &mut recovered)
513
            .unwrap_err();
514
515
        // too small
516
        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
        // insert zlib (1), (2), (3), (4)
547
548
        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
        // -- now full
574
575
        // insert brotli (1) evicts zlib (1)
576
        cache
577
            .compression_for(
578
                &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
579
                &cert4,
580
            )
581
            .unwrap();
582
583
        // now zlib (2), (3), (4) and brotli (4) exist
584
        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
        // insert zlib (1) requires re-compression & evicts zlib (2)
610
        cache
611
            .compression_for(
612
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
613
                &cert1,
614
            )
615
            .unwrap();
616
617
        // now zlib (1), (3), (4) and brotli (4) exist
618
        // query zlib (4), (3), (1) to demonstrate LRU tracks usage rather than insertion
619
        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
        // now brotli (4), zlib (4), (3), (1)
639
        // insert brotli (1) evicting brotli (4)
640
        cache
641
            .compression_for(
642
                &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
643
                &cert1,
644
            )
645
            .unwrap();
646
647
        // verify brotli (4) disappeared
648
        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
}