/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 | | } |