Coverage Report

Created: 2025-12-31 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rustls-0.23.35/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
}
Unexecuted instantiation: rustls::compress::default_cert_decompressors
Unexecuted instantiation: rustls::compress::default_cert_decompressors
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
}
Unexecuted instantiation: rustls::compress::default_cert_compressors
Unexecuted instantiation: rustls::compress::default_cert_compressors
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::c_api::Z_BEST_COMPRESSION;
129
    use zlib_rs::{ReturnCode, deflate, inflate};
130
131
    use super::*;
132
133
    /// A certificate decompressor for the Zlib algorithm using the `zlib-rs` crate.
134
    pub const ZLIB_DECOMPRESSOR: &dyn CertDecompressor = &ZlibRsDecompressor;
135
136
    #[derive(Debug)]
137
    struct ZlibRsDecompressor;
138
139
    impl CertDecompressor for ZlibRsDecompressor {
140
        fn decompress(&self, input: &[u8], output: &mut [u8]) -> Result<(), DecompressionFailed> {
141
            let output_len = output.len();
142
            match inflate::uncompress_slice(output, input, inflate::InflateConfig::default()) {
143
                (output_filled, ReturnCode::Ok) if output_filled.len() == output_len => Ok(()),
144
                (_, _) => Err(DecompressionFailed),
145
            }
146
        }
147
148
        fn algorithm(&self) -> CertificateCompressionAlgorithm {
149
            CertificateCompressionAlgorithm::Zlib
150
        }
151
    }
152
153
    /// A certificate compressor for the Zlib algorithm using the `zlib-rs` crate.
154
    pub const ZLIB_COMPRESSOR: &dyn CertCompressor = &ZlibRsCompressor;
155
156
    #[derive(Debug)]
157
    struct ZlibRsCompressor;
158
159
    impl CertCompressor for ZlibRsCompressor {
160
        fn compress(
161
            &self,
162
            input: Vec<u8>,
163
            level: CompressionLevel,
164
        ) -> Result<Vec<u8>, CompressionFailed> {
165
            let mut output = alloc::vec![0u8; deflate::compress_bound(input.len())];
166
            let config = match level {
167
                CompressionLevel::Interactive => deflate::DeflateConfig::default(),
168
                CompressionLevel::Amortized => deflate::DeflateConfig::new(Z_BEST_COMPRESSION),
169
            };
170
            let (output_filled, rc) = deflate::compress_slice(&mut output, &input, config);
171
            if rc != ReturnCode::Ok {
172
                return Err(CompressionFailed);
173
            }
174
175
            let used = output_filled.len();
176
            output.truncate(used);
177
            Ok(output)
178
        }
179
180
        fn algorithm(&self) -> CertificateCompressionAlgorithm {
181
            CertificateCompressionAlgorithm::Zlib
182
        }
183
    }
184
}
185
186
#[cfg(feature = "zlib")]
187
pub use feat_zlib_rs::{ZLIB_COMPRESSOR, ZLIB_DECOMPRESSOR};
188
189
#[cfg(feature = "brotli")]
190
mod feat_brotli {
191
    use std::io::{Cursor, Write};
192
193
    use super::*;
194
195
    /// A certificate decompressor for the brotli algorithm using the `brotli` crate.
196
    pub const BROTLI_DECOMPRESSOR: &dyn CertDecompressor = &BrotliDecompressor;
197
198
    #[derive(Debug)]
199
    struct BrotliDecompressor;
200
201
    impl CertDecompressor for BrotliDecompressor {
202
        fn decompress(&self, input: &[u8], output: &mut [u8]) -> Result<(), DecompressionFailed> {
203
            let mut in_cursor = Cursor::new(input);
204
            let mut out_cursor = Cursor::new(output);
205
206
            brotli::BrotliDecompress(&mut in_cursor, &mut out_cursor)
207
                .map_err(|_| DecompressionFailed)?;
208
209
            if out_cursor.position() as usize != out_cursor.into_inner().len() {
210
                return Err(DecompressionFailed);
211
            }
212
213
            Ok(())
214
        }
215
216
        fn algorithm(&self) -> CertificateCompressionAlgorithm {
217
            CertificateCompressionAlgorithm::Brotli
218
        }
219
    }
220
221
    /// A certificate compressor for the brotli algorithm using the `brotli` crate.
222
    pub const BROTLI_COMPRESSOR: &dyn CertCompressor = &BrotliCompressor;
223
224
    #[derive(Debug)]
225
    struct BrotliCompressor;
226
227
    impl CertCompressor for BrotliCompressor {
228
        fn compress(
229
            &self,
230
            input: Vec<u8>,
231
            level: CompressionLevel,
232
        ) -> Result<Vec<u8>, CompressionFailed> {
233
            let quality = match level {
234
                CompressionLevel::Interactive => QUALITY_FAST,
235
                CompressionLevel::Amortized => QUALITY_SLOW,
236
            };
237
            let output = Cursor::new(Vec::with_capacity(input.len() / 2));
238
            let mut compressor = brotli::CompressorWriter::new(output, BUFFER_SIZE, quality, LGWIN);
239
            compressor
240
                .write_all(&input)
241
                .map_err(|_| CompressionFailed)?;
242
            Ok(compressor.into_inner().into_inner())
243
        }
244
245
        fn algorithm(&self) -> CertificateCompressionAlgorithm {
246
            CertificateCompressionAlgorithm::Brotli
247
        }
248
    }
249
250
    /// Brotli buffer size.
251
    ///
252
    /// Chosen based on brotli `examples/compress.rs`.
253
    const BUFFER_SIZE: usize = 4096;
254
255
    /// This is the default lgwin parameter, see `BrotliEncoderInitParams()`
256
    const LGWIN: u32 = 22;
257
258
    /// Compression quality we use for interactive compressions.
259
    /// See <https://blog.cloudflare.com/results-experimenting-brotli> for data.
260
    const QUALITY_FAST: u32 = 4;
261
262
    /// Compression quality we use for offline compressions (the maximum).
263
    const QUALITY_SLOW: u32 = 11;
264
}
265
266
#[cfg(feature = "brotli")]
267
pub use feat_brotli::{BROTLI_COMPRESSOR, BROTLI_DECOMPRESSOR};
268
269
/// An LRU cache for compressions.
270
///
271
/// The prospect of being able to reuse a given compression for many connections
272
/// means we can afford to spend more time on that compression (by passing
273
/// `CompressionLevel::Amortized` to the compressor).
274
#[derive(Debug)]
275
pub enum CompressionCache {
276
    /// No caching happens, and compression happens each time using
277
    /// `CompressionLevel::Interactive`.
278
    Disabled,
279
280
    /// Compressions are stored in an LRU cache.
281
    #[cfg(feature = "std")]
282
    Enabled(CompressionCacheInner),
283
}
284
285
/// Innards of an enabled CompressionCache.
286
///
287
/// You cannot make one of these directly. Use [`CompressionCache::new`].
288
#[cfg(feature = "std")]
289
#[derive(Debug)]
290
pub struct CompressionCacheInner {
291
    /// Maximum size of underlying storage.
292
    size: usize,
293
294
    /// LRU-order entries.
295
    ///
296
    /// First is least-used, last is most-used.
297
    entries: Mutex<VecDeque<Arc<CompressionCacheEntry>>>,
298
}
299
300
impl CompressionCache {
301
    /// Make a `CompressionCache` that stores up to `size` compressed
302
    /// certificate messages.
303
    #[cfg(feature = "std")]
304
    pub fn new(size: usize) -> Self {
305
        if size == 0 {
306
            return Self::Disabled;
307
        }
308
309
        Self::Enabled(CompressionCacheInner {
310
            size,
311
            entries: Mutex::new(VecDeque::with_capacity(size)),
312
        })
313
    }
314
315
    /// Return a `CompressionCacheEntry`, which is an owning
316
    /// wrapper for a `CompressedCertificatePayload`.
317
    ///
318
    /// `compressor` is the compression function we have negotiated.
319
    /// `original` is the uncompressed certificate message.
320
0
    pub(crate) fn compression_for(
321
0
        &self,
322
0
        compressor: &dyn CertCompressor,
323
0
        original: &CertificatePayloadTls13<'_>,
324
0
    ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
325
0
        match self {
326
0
            Self::Disabled => Self::uncached_compression(compressor, original),
327
328
            #[cfg(feature = "std")]
329
            Self::Enabled(_) => self.compression_for_impl(compressor, original),
330
        }
331
0
    }
Unexecuted instantiation: <rustls::compress::CompressionCache>::compression_for
Unexecuted instantiation: <rustls::compress::CompressionCache>::compression_for
332
333
    #[cfg(feature = "std")]
334
    fn compression_for_impl(
335
        &self,
336
        compressor: &dyn CertCompressor,
337
        original: &CertificatePayloadTls13<'_>,
338
    ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
339
        let (max_size, entries) = match self {
340
            Self::Enabled(CompressionCacheInner { size, entries }) => (*size, entries),
341
            _ => unreachable!(),
342
        };
343
344
        // context is a per-connection quantity, and included in the compressed data.
345
        // it is not suitable for inclusion in the cache.
346
        if !original.context.0.is_empty() {
347
            return Self::uncached_compression(compressor, original);
348
        }
349
350
        // cache probe:
351
        let encoding = original.get_encoding();
352
        let algorithm = compressor.algorithm();
353
354
        let mut cache = entries
355
            .lock()
356
            .map_err(|_| CompressionFailed)?;
357
        for (i, item) in cache.iter().enumerate() {
358
            if item.algorithm == algorithm && item.original == encoding {
359
                // this item is now MRU
360
                let item = cache.remove(i).unwrap();
361
                cache.push_back(item.clone());
362
                return Ok(item);
363
            }
364
        }
365
        drop(cache);
366
367
        // do compression:
368
        let uncompressed_len = encoding.len() as u32;
369
        let compressed = compressor.compress(encoding.clone(), CompressionLevel::Amortized)?;
370
        let new_entry = Arc::new(CompressionCacheEntry {
371
            algorithm,
372
            original: encoding,
373
            compressed: CompressedCertificatePayload {
374
                alg: algorithm,
375
                uncompressed_len,
376
                compressed: PayloadU24(Payload::new(compressed)),
377
            },
378
        });
379
380
        // insert into cache
381
        let mut cache = entries
382
            .lock()
383
            .map_err(|_| CompressionFailed)?;
384
        if cache.len() == max_size {
385
            cache.pop_front();
386
        }
387
        cache.push_back(new_entry.clone());
388
        Ok(new_entry)
389
    }
390
391
    /// Compress `original` using `compressor` at `Interactive` level.
392
0
    fn uncached_compression(
393
0
        compressor: &dyn CertCompressor,
394
0
        original: &CertificatePayloadTls13<'_>,
395
0
    ) -> Result<Arc<CompressionCacheEntry>, CompressionFailed> {
396
0
        let algorithm = compressor.algorithm();
397
0
        let encoding = original.get_encoding();
398
0
        let uncompressed_len = encoding.len() as u32;
399
0
        let compressed = compressor.compress(encoding, CompressionLevel::Interactive)?;
400
401
        // this `CompressionCacheEntry` in fact never makes it into the cache, so
402
        // `original` is left empty
403
0
        Ok(Arc::new(CompressionCacheEntry {
404
0
            algorithm,
405
0
            original: Vec::new(),
406
0
            compressed: CompressedCertificatePayload {
407
0
                alg: algorithm,
408
0
                uncompressed_len,
409
0
                compressed: PayloadU24(Payload::new(compressed)),
410
0
            },
411
0
        }))
412
0
    }
Unexecuted instantiation: <rustls::compress::CompressionCache>::uncached_compression
Unexecuted instantiation: <rustls::compress::CompressionCache>::uncached_compression
413
}
414
415
impl Default for CompressionCache {
416
0
    fn default() -> Self {
417
        #[cfg(feature = "std")]
418
        {
419
            // 4 entries allows 2 certificate chains times 2 compression algorithms
420
            Self::new(4)
421
        }
422
423
        #[cfg(not(feature = "std"))]
424
        {
425
0
            Self::Disabled
426
        }
427
0
    }
Unexecuted instantiation: <rustls::compress::CompressionCache as core::default::Default>::default
Unexecuted instantiation: <rustls::compress::CompressionCache as core::default::Default>::default
428
}
429
430
#[cfg_attr(not(feature = "std"), allow(dead_code))]
431
#[derive(Debug)]
432
pub(crate) struct CompressionCacheEntry {
433
    // cache key is algorithm + original:
434
    algorithm: CertificateCompressionAlgorithm,
435
    original: Vec<u8>,
436
437
    // cache value is compression result:
438
    compressed: CompressedCertificatePayload<'static>,
439
}
440
441
impl CompressionCacheEntry {
442
0
    pub(crate) fn compressed_cert_payload(&self) -> CompressedCertificatePayload<'_> {
443
0
        self.compressed.as_borrowed()
444
0
    }
Unexecuted instantiation: <rustls::compress::CompressionCacheEntry>::compressed_cert_payload
Unexecuted instantiation: <rustls::compress::CompressionCacheEntry>::compressed_cert_payload
445
}
446
447
#[cfg(all(test, any(feature = "brotli", feature = "zlib")))]
448
mod tests {
449
    use std::{println, vec};
450
451
    use super::*;
452
453
    #[test]
454
    #[cfg(feature = "zlib")]
455
    fn test_zlib() {
456
        test_compressor(ZLIB_COMPRESSOR, ZLIB_DECOMPRESSOR);
457
    }
458
459
    #[test]
460
    #[cfg(feature = "brotli")]
461
    fn test_brotli() {
462
        test_compressor(BROTLI_COMPRESSOR, BROTLI_DECOMPRESSOR);
463
    }
464
465
    fn test_compressor(comp: &dyn CertCompressor, decomp: &dyn CertDecompressor) {
466
        assert_eq!(comp.algorithm(), decomp.algorithm());
467
        for sz in [16, 64, 512, 2048, 8192, 16384] {
468
            test_trivial_pairwise(comp, decomp, sz);
469
        }
470
        test_decompress_wrong_len(comp, decomp);
471
        test_decompress_garbage(decomp);
472
    }
473
474
    fn test_trivial_pairwise(
475
        comp: &dyn CertCompressor,
476
        decomp: &dyn CertDecompressor,
477
        plain_len: usize,
478
    ) {
479
        let original = vec![0u8; plain_len];
480
481
        for level in [CompressionLevel::Interactive, CompressionLevel::Amortized] {
482
            let compressed = comp
483
                .compress(original.clone(), level)
484
                .unwrap();
485
            println!(
486
                "{:?} compressed trivial {} -> {} using {:?} level",
487
                comp.algorithm(),
488
                original.len(),
489
                compressed.len(),
490
                level
491
            );
492
            let mut recovered = vec![0xffu8; plain_len];
493
            decomp
494
                .decompress(&compressed, &mut recovered)
495
                .unwrap();
496
            assert_eq!(original, recovered);
497
        }
498
    }
499
500
    fn test_decompress_wrong_len(comp: &dyn CertCompressor, decomp: &dyn CertDecompressor) {
501
        let original = vec![0u8; 2048];
502
        let compressed = comp
503
            .compress(original.clone(), CompressionLevel::Interactive)
504
            .unwrap();
505
        println!("{compressed:?}");
506
507
        // too big
508
        let mut recovered = vec![0xffu8; original.len() + 1];
509
        decomp
510
            .decompress(&compressed, &mut recovered)
511
            .unwrap_err();
512
513
        // too small
514
        let mut recovered = vec![0xffu8; original.len() - 1];
515
        decomp
516
            .decompress(&compressed, &mut recovered)
517
            .unwrap_err();
518
    }
519
520
    fn test_decompress_garbage(decomp: &dyn CertDecompressor) {
521
        let junk = [0u8; 1024];
522
        let mut recovered = vec![0u8; 512];
523
        decomp
524
            .decompress(&junk, &mut recovered)
525
            .unwrap_err();
526
    }
527
528
    #[test]
529
    #[cfg(all(feature = "brotli", feature = "zlib"))]
530
    fn test_cache_evicts_lru() {
531
        use core::sync::atomic::{AtomicBool, Ordering};
532
533
        use pki_types::CertificateDer;
534
535
        let cache = CompressionCache::default();
536
537
        let cert = CertificateDer::from(vec![1]);
538
539
        let cert1 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"1"));
540
        let cert2 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"2"));
541
        let cert3 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"3"));
542
        let cert4 = CertificatePayloadTls13::new([&cert].into_iter(), Some(b"4"));
543
544
        // insert zlib (1), (2), (3), (4)
545
546
        cache
547
            .compression_for(
548
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
549
                &cert1,
550
            )
551
            .unwrap();
552
        cache
553
            .compression_for(
554
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
555
                &cert2,
556
            )
557
            .unwrap();
558
        cache
559
            .compression_for(
560
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
561
                &cert3,
562
            )
563
            .unwrap();
564
        cache
565
            .compression_for(
566
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
567
                &cert4,
568
            )
569
            .unwrap();
570
571
        // -- now full
572
573
        // insert brotli (1) evicts zlib (1)
574
        cache
575
            .compression_for(
576
                &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
577
                &cert4,
578
            )
579
            .unwrap();
580
581
        // now zlib (2), (3), (4) and brotli (4) exist
582
        cache
583
            .compression_for(
584
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
585
                &cert2,
586
            )
587
            .unwrap();
588
        cache
589
            .compression_for(
590
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
591
                &cert3,
592
            )
593
            .unwrap();
594
        cache
595
            .compression_for(
596
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
597
                &cert4,
598
            )
599
            .unwrap();
600
        cache
601
            .compression_for(
602
                &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), false),
603
                &cert4,
604
            )
605
            .unwrap();
606
607
        // insert zlib (1) requires re-compression & evicts zlib (2)
608
        cache
609
            .compression_for(
610
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), true),
611
                &cert1,
612
            )
613
            .unwrap();
614
615
        // now zlib (1), (3), (4) and brotli (4) exist
616
        // query zlib (4), (3), (1) to demonstrate LRU tracks usage rather than insertion
617
        cache
618
            .compression_for(
619
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
620
                &cert4,
621
            )
622
            .unwrap();
623
        cache
624
            .compression_for(
625
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
626
                &cert3,
627
            )
628
            .unwrap();
629
        cache
630
            .compression_for(
631
                &RequireCompress(ZLIB_COMPRESSOR, AtomicBool::default(), false),
632
                &cert1,
633
            )
634
            .unwrap();
635
636
        // now brotli (4), zlib (4), (3), (1)
637
        // insert brotli (1) evicting brotli (4)
638
        cache
639
            .compression_for(
640
                &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
641
                &cert1,
642
            )
643
            .unwrap();
644
645
        // verify brotli (4) disappeared
646
        cache
647
            .compression_for(
648
                &RequireCompress(BROTLI_COMPRESSOR, AtomicBool::default(), true),
649
                &cert4,
650
            )
651
            .unwrap();
652
653
        #[derive(Debug)]
654
        struct RequireCompress(&'static dyn CertCompressor, AtomicBool, bool);
655
656
        impl CertCompressor for RequireCompress {
657
            fn compress(
658
                &self,
659
                input: Vec<u8>,
660
                level: CompressionLevel,
661
            ) -> Result<Vec<u8>, CompressionFailed> {
662
                self.1.store(true, Ordering::SeqCst);
663
                self.0.compress(input, level)
664
            }
665
666
            fn algorithm(&self) -> CertificateCompressionAlgorithm {
667
                self.0.algorithm()
668
            }
669
        }
670
671
        impl Drop for RequireCompress {
672
            fn drop(&mut self) {
673
                assert_eq!(self.1.load(Ordering::SeqCst), self.2);
674
            }
675
        }
676
    }
677
}