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