Skip to main content

applesauce_core/compressor/
mod.rs

1#[cfg(feature = "lzvn")]
2use self::lzvn::Lzvn;
3// Enable if feature lzfse or system-lzfse is enabled:
4#[cfg(any(feature = "lzfse", feature = "system-lzfse"))]
5use self::lzfse::Lzfse;
6#[cfg(feature = "zlib")]
7use self::zlib::Zlib;
8use crate::decmpfs::BlockInfo;
9use std::{fmt, io};
10
11#[cfg(any(feature = "lzfse", feature = "lzvn"))]
12mod lz;
13#[cfg(feature = "lzfse")]
14mod lzfse;
15#[cfg(feature = "lzvn")]
16mod lzvn;
17#[cfg(feature = "zlib")]
18mod zlib;
19
20pub(crate) trait CompressorImpl {
21    /// The offset to start data at, for the specified number of blocks
22    #[must_use]
23    fn header_size(block_count: u64) -> u64;
24
25    #[must_use]
26    fn trailer_size() -> u64 {
27        0
28    }
29
30    fn compress(&mut self, dst: &mut [u8], src: &[u8], level: u32) -> io::Result<usize>;
31    fn decompress(&mut self, dst: &mut [u8], src: &[u8]) -> io::Result<usize>;
32
33    fn read_block_info<R: io::Read + io::Seek>(
34        reader: R,
35        orig_file_size: u64,
36    ) -> io::Result<Vec<BlockInfo>>;
37
38    fn finish<W: io::Write + io::Seek>(writer: W, block_sizes: &[u32]) -> io::Result<()>;
39}
40
41pub struct Compressor(Data);
42
43impl Compressor {
44    #[cfg(feature = "zlib")]
45    #[must_use]
46    pub fn zlib() -> Self {
47        Self(Data::Zlib(Zlib::new()))
48    }
49
50    #[cfg(feature = "lzfse")]
51    #[must_use]
52    pub fn lzfse() -> Self {
53        Self(Data::Lzfse(Lzfse::new()))
54    }
55
56    #[cfg(feature = "lzvn")]
57    #[must_use]
58    pub fn lzvn() -> Self {
59        Self(Data::Lzvn(Lzvn::new()))
60    }
61
62    #[must_use]
63    pub fn kind(&self) -> Kind {
64        match self.0 {
65            #[cfg(feature = "zlib")]
66            Data::Zlib(_) => Kind::Zlib,
67            #[cfg(feature = "lzfse")]
68            Data::Lzfse(_) => Kind::Lzfse,
69            #[cfg(feature = "lzvn")]
70            Data::Lzvn(_) => Kind::Lzvn,
71        }
72    }
73}
74
75enum Data {
76    #[cfg(feature = "zlib")]
77    Zlib(Zlib),
78    #[cfg(feature = "lzfse")]
79    Lzfse(Lzfse),
80    #[cfg(feature = "lzvn")]
81    Lzvn(Lzvn),
82}
83
84impl Compressor {
85    #[must_use]
86    pub fn blocks_start(&self, block_count: u64) -> u64 {
87        self.kind().header_size(block_count)
88    }
89
90    pub fn compress(&mut self, dst: &mut [u8], src: &[u8], level: u32) -> io::Result<usize> {
91        match self.0 {
92            #[cfg(feature = "zlib")]
93            Data::Zlib(ref mut i) => i.compress(dst, src, level),
94            #[cfg(feature = "lzfse")]
95            Data::Lzfse(ref mut i) => i.compress(dst, src, level),
96            #[cfg(feature = "lzvn")]
97            Data::Lzvn(ref mut i) => i.compress(dst, src, level),
98        }
99    }
100
101    pub fn decompress(&mut self, dst: &mut [u8], src: &[u8]) -> io::Result<usize> {
102        match self.0 {
103            #[cfg(feature = "zlib")]
104            Data::Zlib(ref mut i) => i.decompress(dst, src),
105            #[cfg(feature = "lzfse")]
106            Data::Lzfse(ref mut i) => i.decompress(dst, src),
107            #[cfg(feature = "lzvn")]
108            Data::Lzvn(ref mut i) => i.decompress(dst, src),
109        }
110    }
111
112    pub fn finish<W: io::Write + io::Seek>(
113        &mut self,
114        writer: W,
115        block_sizes: &[u32],
116    ) -> io::Result<()> {
117        self.kind().finish(writer, block_sizes)
118    }
119}
120
121#[derive(Debug, Copy, Clone, PartialEq, Eq)]
122#[repr(u8)]
123pub enum Kind {
124    Zlib = 0,
125    Lzvn,
126    Lzfse,
127}
128
129impl fmt::Display for Kind {
130    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131        f.write_str(self.name())
132    }
133}
134
135impl Kind {
136    #[must_use]
137    pub const fn name(self) -> &'static str {
138        match self {
139            Kind::Zlib => "ZLIB",
140            Kind::Lzvn => "LZVN",
141            Kind::Lzfse => "LZFSE",
142        }
143    }
144
145    #[must_use]
146    #[inline]
147    pub const fn supported(self) -> bool {
148        // Clippy falsely sees these arms as identical:
149        //   https://github.com/rust-lang/rust-clippy/issues/9775
150        #[allow(clippy::match_same_arms)]
151        match self {
152            Kind::Zlib => cfg!(feature = "zlib"),
153            Kind::Lzvn => cfg!(feature = "lzvn"),
154            Kind::Lzfse => cfg!(feature = "lzfse"),
155        }
156    }
157
158    #[must_use]
159    pub fn compressor(self) -> Option<Compressor> {
160        let data = match self {
161            #[cfg(feature = "zlib")]
162            Kind::Zlib => Data::Zlib(Zlib::new()),
163            #[cfg(feature = "lzfse")]
164            Kind::Lzfse => Data::Lzfse(Lzfse::new()),
165            #[cfg(feature = "lzvn")]
166            Kind::Lzvn => Data::Lzvn(Lzvn::new()),
167            #[allow(unreachable_patterns)]
168            _ => return None,
169        };
170        Some(Compressor(data))
171    }
172
173    #[must_use]
174    pub fn header_size(self, block_count: u64) -> u64 {
175        match self {
176            #[cfg(feature = "zlib")]
177            Kind::Zlib => Zlib::header_size(block_count),
178            #[cfg(feature = "lzvn")]
179            Kind::Lzvn => Lzvn::header_size(block_count),
180            #[cfg(feature = "lzfse")]
181            Kind::Lzfse => Lzfse::header_size(block_count),
182            #[allow(unreachable_patterns)]
183            _ => panic!("Unsupported compression kind {self}"),
184        }
185    }
186
187    pub fn read_block_info<R: io::Read + io::Seek>(
188        self,
189        reader: R,
190        orig_file_size: u64,
191    ) -> io::Result<Vec<BlockInfo>> {
192        match self {
193            #[cfg(feature = "zlib")]
194            Kind::Zlib => Zlib::read_block_info(reader, orig_file_size),
195            #[cfg(feature = "lzvn")]
196            Kind::Lzvn => Lzvn::read_block_info(reader, orig_file_size),
197            #[cfg(feature = "lzfse")]
198            Kind::Lzfse => Lzfse::read_block_info(reader, orig_file_size),
199            #[allow(unreachable_patterns)]
200            _ => panic!("Unsupported compression kind {self}"),
201        }
202    }
203
204    pub fn finish<W: io::Write + io::Seek>(self, writer: W, block_sizes: &[u32]) -> io::Result<()> {
205        match self {
206            #[cfg(feature = "zlib")]
207            Kind::Zlib => Zlib::finish(writer, block_sizes),
208            #[cfg(feature = "lzvn")]
209            Kind::Lzvn => Lzvn::finish(writer, block_sizes),
210            #[cfg(feature = "lzfse")]
211            Kind::Lzfse => Lzfse::finish(writer, block_sizes),
212            #[allow(unreachable_patterns)]
213            _ => panic!("Unsupported compression kind {self}"),
214        }
215    }
216}
217
218impl Default for Kind {
219    #[inline]
220    fn default() -> Self {
221        if Self::Lzfse.supported() {
222            Self::Lzfse
223        } else if Self::Zlib.supported() {
224            Self::Zlib
225        } else {
226            Self::Lzvn
227        }
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    const PLAINTEXT: &[u8] = include_bytes!("mod.rs");
236
237    pub(super) fn compressor_round_trip<C: CompressorImpl>(c: &mut C) {
238        let mut buf = vec![0u8; PLAINTEXT.len() * 2];
239        let len = c.compress(&mut buf, PLAINTEXT, 6).unwrap();
240        assert!(len > 0);
241        assert!(len < buf.len());
242        let ciphertext = &buf[..len];
243        let mut buf = vec![0u8; PLAINTEXT.len() + 1];
244        let len = c.decompress(&mut buf, ciphertext).unwrap();
245        assert_eq!(&buf[..len], PLAINTEXT);
246    }
247}