1use alloc::vec::Vec;
4use core::convert::TryInto;
5#[cfg(feature = "hash")]
6use twox_hash::XxHash64;
7
8#[cfg(feature = "hash")]
9use core::hash::Hasher;
10
11mod adaptive;
12mod offset_history;
13
14use adaptive::best_block_segment_lengths;
15pub(crate) use offset_history::OffsetHistory;
16
17use super::{
18 block_header::BlockHeader, frame_header::FrameHeader, levels::*,
19 match_generator::MatchGeneratorDriver, CompressionFileProfile, CompressionFileType,
20 CompressionLevel, Matcher,
21};
22use crate::fse::fse_encoder::{default_ll_table, default_ml_table, default_of_table, FSETable};
23use alloc::rc::Rc;
24
25use crate::io::{Read, Write};
26
27pub struct FrameCompressor<R: Read, W: Write, M: Matcher> {
47 uncompressed_data: Option<R>,
48 compressed_data: Option<W>,
49 compression_level: CompressionLevel,
50 file_type_hint: CompressionFileType,
51 file_profile_hint: CompressionFileProfile,
52 state: CompressState<M>,
53 #[cfg(feature = "hash")]
54 hasher: XxHash64,
55}
56
57#[derive(Clone)]
58pub(crate) struct FseTables {
59 pub(crate) ll_default: FSETable,
60 pub(crate) ll_previous: Option<Rc<FSETable>>,
61 pub(crate) ll_repeat_valid: bool,
62 pub(crate) ml_default: FSETable,
63 pub(crate) ml_previous: Option<Rc<FSETable>>,
64 pub(crate) ml_repeat_valid: bool,
65 pub(crate) of_default: FSETable,
66 pub(crate) of_previous: Option<Rc<FSETable>>,
67 pub(crate) of_repeat_valid: bool,
68}
69
70#[derive(Clone)]
71pub(crate) struct FseTableSnapshot {
72 ll_previous: Option<Rc<FSETable>>,
73 ll_repeat_valid: bool,
74 ml_previous: Option<Rc<FSETable>>,
75 ml_repeat_valid: bool,
76 of_previous: Option<Rc<FSETable>>,
77 of_repeat_valid: bool,
78}
79
80impl FseTables {
81 pub fn new() -> Self {
82 Self {
83 ll_default: default_ll_table(),
84 ll_previous: None,
85 ll_repeat_valid: false,
86 ml_default: default_ml_table(),
87 ml_previous: None,
88 ml_repeat_valid: false,
89 of_default: default_of_table(),
90 of_previous: None,
91 of_repeat_valid: false,
92 }
93 }
94
95 pub fn reset(&mut self) {
96 self.ll_previous = None;
97 self.ll_repeat_valid = false;
98 self.ml_previous = None;
99 self.ml_repeat_valid = false;
100 self.of_previous = None;
101 self.of_repeat_valid = false;
102 }
103
104 pub(crate) fn snapshot_previous(&self) -> FseTableSnapshot {
105 FseTableSnapshot {
106 ll_previous: self.ll_previous.clone(),
107 ll_repeat_valid: self.ll_repeat_valid,
108 ml_previous: self.ml_previous.clone(),
109 ml_repeat_valid: self.ml_repeat_valid,
110 of_previous: self.of_previous.clone(),
111 of_repeat_valid: self.of_repeat_valid,
112 }
113 }
114
115 pub(crate) fn restore_previous(&mut self, snapshot: FseTableSnapshot) {
116 self.ll_previous = snapshot.ll_previous;
117 self.ll_repeat_valid = snapshot.ll_repeat_valid;
118 self.ml_previous = snapshot.ml_previous;
119 self.ml_repeat_valid = snapshot.ml_repeat_valid;
120 self.of_previous = snapshot.of_previous;
121 self.of_repeat_valid = snapshot.of_repeat_valid;
122 }
123
124 pub(crate) fn downgrade_offset_repeat_validity(&mut self) {
127 self.of_repeat_valid = false;
128 }
129}
130
131pub(crate) struct CompressState<M: Matcher> {
132 pub(crate) matcher: M,
133 pub(crate) last_huff_table: Option<crate::huff0::huff0_encoder::HuffmanTable>,
134 pub(crate) fse_tables: FseTables,
135 pub(crate) offset_history: OffsetHistory,
136 pub(crate) file_type_hint: CompressionFileType,
137 pub(crate) file_profile_hint: CompressionFileProfile,
138}
139
140impl<R: Read, W: Write> FrameCompressor<R, W, MatchGeneratorDriver> {
141 pub fn new(compression_level: CompressionLevel) -> Self {
143 Self {
144 uncompressed_data: None,
145 compressed_data: None,
146 compression_level,
147 file_type_hint: CompressionFileType::Unknown,
148 file_profile_hint: CompressionFileProfile::None,
149 state: CompressState {
150 matcher: MatchGeneratorDriver::new(1024 * 128, 4),
151 last_huff_table: None,
152 fse_tables: FseTables::new(),
153 offset_history: OffsetHistory::new(),
154 file_type_hint: CompressionFileType::Unknown,
155 file_profile_hint: CompressionFileProfile::None,
156 },
157 #[cfg(feature = "hash")]
158 hasher: XxHash64::with_seed(0),
159 }
160 }
161
162 pub fn new_with_file_type(
164 compression_level: CompressionLevel,
165 file_type_hint: CompressionFileType,
166 ) -> Self {
167 Self::new_with_hints(
168 compression_level,
169 file_type_hint,
170 CompressionFileProfile::None,
171 )
172 }
173
174 pub(crate) fn new_with_hints(
175 compression_level: CompressionLevel,
176 file_type_hint: CompressionFileType,
177 file_profile_hint: CompressionFileProfile,
178 ) -> Self {
179 let mut compressor = Self::new(compression_level);
180 compressor.file_type_hint = file_type_hint;
181 compressor.file_profile_hint = file_profile_hint;
182 compressor
183 }
184}
185
186impl<R: Read, W: Write, M: Matcher> FrameCompressor<R, W, M> {
187 pub fn new_with_matcher(matcher: M, compression_level: CompressionLevel) -> Self {
189 Self {
190 uncompressed_data: None,
191 compressed_data: None,
192 file_type_hint: CompressionFileType::Unknown,
193 file_profile_hint: CompressionFileProfile::None,
194 state: CompressState {
195 matcher,
196 last_huff_table: None,
197 fse_tables: FseTables::new(),
198 offset_history: OffsetHistory::new(),
199 file_type_hint: CompressionFileType::Unknown,
200 file_profile_hint: CompressionFileProfile::None,
201 },
202 compression_level,
203 #[cfg(feature = "hash")]
204 hasher: XxHash64::with_seed(0),
205 }
206 }
207
208 pub fn set_source(&mut self, uncompressed_data: R) -> Option<R> {
212 self.uncompressed_data.replace(uncompressed_data)
213 }
214
215 pub fn set_drain(&mut self, compressed_data: W) -> Option<W> {
219 self.compressed_data.replace(compressed_data)
220 }
221
222 pub fn compress(&mut self) {
230 self.state.file_type_hint = self.file_type_hint;
232 self.state.file_profile_hint = self.file_profile_hint;
233 self.state.matcher.set_file_type_hint(self.file_type_hint);
234 self.state
235 .matcher
236 .set_internal_file_profile_hint(self.file_profile_hint.internal_hint_code());
237 self.state.matcher.reset(self.compression_level);
238 self.state.last_huff_table = None;
239 self.state.fse_tables.reset();
240 self.state.offset_history = OffsetHistory::new();
241 let (newest, second, third) = self.state.offset_history.as_offsets();
242 self.state.matcher.set_repeat_offsets(newest, second, third);
243 #[cfg(feature = "hash")]
244 {
245 self.hasher = XxHash64::with_seed(0);
246 }
247 let source = self.uncompressed_data.as_mut().unwrap();
248 let drain = self.compressed_data.as_mut().unwrap();
249 let output: &mut Vec<u8> = &mut Vec::with_capacity(1024 * 130);
251 let header = FrameHeader {
253 frame_content_size: None,
254 single_segment: false,
255 content_checksum: cfg!(feature = "hash"),
256 dictionary_id: None,
257 window_size: Some(self.state.matcher.window_size()),
258 };
259 header.serialize(output);
260 let mut pending_byte = None;
262 loop {
263 let mut uncompressed_data = self.state.matcher.get_next_space();
265 let mut read_bytes = if let Some(byte) = pending_byte.take() {
266 uncompressed_data[0] = byte;
267 1
268 } else {
269 0
270 };
271 let mut last_block;
272 'read_loop: loop {
273 let new_bytes = source.read(&mut uncompressed_data[read_bytes..]).unwrap();
274 if new_bytes == 0 {
275 last_block = true;
276 break 'read_loop;
277 }
278 read_bytes += new_bytes;
279 if read_bytes == uncompressed_data.len() {
280 last_block = false;
281 break 'read_loop;
282 }
283 }
284 if !last_block {
285 let mut lookahead = [0u8; 1];
286 match source.read(&mut lookahead).unwrap() {
287 0 => last_block = true,
288 1 => pending_byte = Some(lookahead[0]),
289 _ => unreachable!("single-byte read cannot return more than one byte"),
290 }
291 }
292 uncompressed_data.resize(read_bytes, 0);
293 #[cfg(feature = "hash")]
295 self.hasher.write(&uncompressed_data);
296 if uncompressed_data.is_empty() {
298 let header = BlockHeader {
299 last_block: true,
300 block_type: crate::blocks::block::BlockType::Raw,
301 block_size: 0,
302 };
303 header.serialize(output);
305 drain.write_all(output).unwrap();
306 output.clear();
307 break;
308 }
309
310 compress_with_level_policy(
311 &mut self.state,
312 self.compression_level,
313 last_block,
314 uncompressed_data,
315 output,
316 );
317 drain.write_all(output).unwrap();
318 output.clear();
319 if last_block {
320 break;
321 }
322 }
323
324 #[cfg(feature = "hash")]
327 {
328 let content_checksum = self.hasher.finish();
331 drain
332 .write_all(&(content_checksum as u32).to_le_bytes())
333 .unwrap();
334 }
335 }
336
337 pub fn source_mut(&mut self) -> Option<&mut R> {
339 self.uncompressed_data.as_mut()
340 }
341
342 pub fn drain_mut(&mut self) -> Option<&mut W> {
344 self.compressed_data.as_mut()
345 }
346
347 pub fn source(&self) -> Option<&R> {
349 self.uncompressed_data.as_ref()
350 }
351
352 pub fn drain(&self) -> Option<&W> {
354 self.compressed_data.as_ref()
355 }
356
357 pub fn take_source(&mut self) -> Option<R> {
359 self.uncompressed_data.take()
360 }
361
362 pub fn take_drain(&mut self) -> Option<W> {
364 self.compressed_data.take()
365 }
366
367 pub fn replace_matcher(&mut self, mut match_generator: M) -> M {
369 core::mem::swap(&mut match_generator, &mut self.state.matcher);
370 match_generator
371 }
372
373 pub fn set_compression_level(
375 &mut self,
376 compression_level: CompressionLevel,
377 ) -> CompressionLevel {
378 let old = self.compression_level;
379 self.compression_level = compression_level;
380 old
381 }
382
383 pub fn compression_level(&self) -> CompressionLevel {
385 self.compression_level
386 }
387
388 pub fn set_file_type_hint(
390 &mut self,
391 file_type_hint: CompressionFileType,
392 ) -> CompressionFileType {
393 let old = self.file_type_hint;
394 self.file_type_hint = file_type_hint;
395 old
396 }
397}
398
399fn compress_with_level_policy<M: Matcher>(
400 state: &mut CompressState<M>,
401 level: CompressionLevel,
402 last_block: bool,
403 uncompressed_data: Vec<u8>,
404 output: &mut Vec<u8>,
405) {
406 if level.is_uncompressed() {
407 let header = BlockHeader {
408 last_block,
409 block_type: crate::blocks::block::BlockType::Raw,
410 block_size: uncompressed_data.len().try_into().unwrap(),
411 };
412 header.serialize(output);
413 output.extend_from_slice(&uncompressed_data);
414 } else if level.uses_fastest_legacy_profile() {
415 compress_fastest(state, last_block, uncompressed_data, output);
416 } else if level.uses_best_legacy_profile() {
417 compress_best_adaptive(state, level, last_block, uncompressed_data, output);
418 } else {
419 compress_at_level(state, level, last_block, uncompressed_data, output);
420 }
421}
422
423fn compress_best_adaptive<M: Matcher>(
424 state: &mut CompressState<M>,
425 level: CompressionLevel,
426 last_block: bool,
427 uncompressed_data: Vec<u8>,
428 output: &mut Vec<u8>,
429) {
430 let segment_lengths = best_block_segment_lengths(uncompressed_data.as_slice());
431 if segment_lengths.len() == 1 {
432 compress_at_level(state, level, last_block, uncompressed_data, output);
433 return;
434 }
435
436 let mut remaining = uncompressed_data;
437 let segment_count = segment_lengths.len();
438 for (segment_idx, segment_len) in segment_lengths.into_iter().enumerate() {
439 let segment_last = last_block && segment_idx + 1 == segment_count;
440 let segment = if segment_idx + 1 == segment_count {
441 core::mem::take(&mut remaining)
442 } else {
443 let tail = remaining.split_off(segment_len);
444 core::mem::replace(&mut remaining, tail)
445 };
446 compress_at_level_without_incompressible_probe(state, level, segment_last, segment, output);
447 }
448}
449
450#[cfg(test)]
451mod tests;