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