lz4rip_encode/compressor.rs
1use core::fmt;
2
3use alloc::vec::Vec;
4
5use crate::compress::CompressorRef;
6use lz4rip_core::CompressError;
7
8/// A reusable block compressor that owns its dictionary.
9///
10/// This is the ergonomic API for use with `alloc`. For a no-alloc variant that
11/// borrows the dictionary, see [`CompressorRef`].
12///
13/// For one-shot compression, use [`compress`](crate::compress) or
14/// [`compress_into`](crate::compress_into) instead.
15///
16/// # Example
17/// ```
18/// use lz4rip_encode::{Compressor, get_maximum_output_size};
19///
20/// let mut comp = Compressor::new();
21/// let input = b"hello world, hello world, hello!";
22/// let mut output = vec![0u8; get_maximum_output_size(input.len())];
23/// let compressed_len = comp.compress_into(input, &mut output).unwrap();
24/// ```
25pub struct Compressor {
26 // SAFETY invariants (self-referential struct):
27 // `inner` may hold a `&[u8]` fabricated via `from_raw_parts` pointing
28 // into `dict`'s heap buffer. Sound because:
29 // 1. The `Drop` impl below drops `inner` before `dict` via ManuallyDrop.
30 // 2. `dict` is private and never reallocated after construction.
31 // 3. No Clone/Copy impl exists. Cloning would copy the Vec (new alloc)
32 // but `inner` would still point at the original buffer → UB on drop.
33 // 4. No method exposes `inner` by value or mutates `dict`.
34 inner: core::mem::ManuallyDrop<CompressorRef<'static>>,
35 #[allow(dead_code)]
36 dict: Vec<u8>,
37}
38
39impl fmt::Debug for Compressor {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 f.debug_struct("Compressor")
42 .field("dict_len", &self.dict.len())
43 .finish()
44 }
45}
46
47impl Compressor {
48 /// Create a new compressor without a dictionary.
49 pub fn new() -> Self {
50 Compressor {
51 inner: core::mem::ManuallyDrop::new(CompressorRef::new()),
52 dict: Vec::new(),
53 }
54 }
55
56 /// Create a new compressor seeded with an external dictionary.
57 ///
58 /// The dictionary is cloned into owned storage.
59 /// If `dict` is shorter than 4 bytes, it is ignored.
60 pub fn with_dict(dict: &[u8]) -> Self {
61 let dict = dict.to_vec();
62 // SAFETY: We create a &'static [u8] pointing into `dict`'s heap buffer.
63 // Sound because `dict` is stored in self, never reallocated, and `inner`
64 // (which holds the reference) is dropped before `dict` per field order.
65 let dict_ref: &'static [u8] =
66 unsafe { core::slice::from_raw_parts(dict.as_ptr(), dict.len()) };
67 Compressor {
68 inner: core::mem::ManuallyDrop::new(CompressorRef::with_dict(dict_ref)),
69 dict,
70 }
71 }
72
73 /// Compress `input` into `output`, returning the number of compressed bytes.
74 ///
75 /// `output` must be at least [`get_maximum_output_size`]`(input.len())` bytes.
76 pub fn compress_into(
77 &mut self,
78 input: &[u8],
79 output: &mut [u8],
80 ) -> Result<usize, CompressError> {
81 self.inner.compress_into(input, output)
82 }
83
84 /// Compress `input` into a new `Vec<u8>`.
85 pub fn compress(&mut self, input: &[u8]) -> Vec<u8> {
86 self.inner.compress(input)
87 }
88}
89
90impl Drop for Compressor {
91 fn drop(&mut self) {
92 // SAFETY: Drop `inner` before `dict` so the fabricated &'static [u8]
93 // is released while `dict`'s heap buffer is still alive.
94 // After this, `dict` is dropped by the compiler's field-drop glue.
95 unsafe { core::mem::ManuallyDrop::drop(&mut self.inner) };
96 }
97}
98
99impl Default for Compressor {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105// SAFETY: Compressor is self-referential (`inner` borrows `dict`'s heap buffer).
106// Deriving Clone would copy the Vec (new allocation) while `inner` still points
107// at the original buffer, causing use-after-free on drop.
108const _: () = {
109 trait NotClone {
110 const OK: () = ();
111 }
112 impl<T: Clone> NotClone for T {}
113 impl NotClone for Compressor {}
114 #[allow(clippy::let_unit_value)]
115 let _ = <Compressor as NotClone>::OK;
116};