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