1#![cfg_attr(not(feature = "std"), no_std)]
2#![cfg_attr(feature = "nightly", feature(optimize_attribute))]
3#![cfg_attr(feature = "paranoid", forbid(unsafe_code))]
4
5#[cfg(feature = "alloc")]
6extern crate alloc;
7
8pub(crate) mod block_encoder;
9#[cfg(feature = "std")]
10pub mod context;
11pub(crate) mod dfast;
12pub(crate) mod fast;
13#[cfg(feature = "ldm")]
14pub(crate) mod ldm;
15pub(crate) mod primitives;
16pub(crate) mod sequences;
17pub mod strategy;
18#[cfg(feature = "std")]
19pub mod streaming;
20
21#[cfg(feature = "alloc")]
22use alloc::vec;
23#[cfg(feature = "alloc")]
24use alloc::vec::Vec;
25
26use crate::strategy::Strategy;
27use zrip_core::error::CompressError;
28use zrip_core::frame::{MAX_BLOCK_SIZE, ZSTD_MAGIC};
29use zrip_core::xxhash::xxh64;
30
31pub(crate) fn write_frame_header(output: &mut Vec<u8>, content_size: usize, dict_id: Option<u32>) {
32 output.extend_from_slice(&ZSTD_MAGIC.to_le_bytes());
33
34 let fcs_size = if content_size <= 255 {
35 1
36 } else if content_size <= 0xFFFF + 256 {
37 2
38 } else if content_size <= 0xFFFF_FFFF {
39 4
40 } else {
41 8
42 };
43 let fcs_flag: u8 = match fcs_size {
44 1 => 0,
45 2 => 1,
46 4 => 2,
47 _ => 3,
48 };
49
50 let dict_id_flag: u8 = match dict_id {
51 None => 0,
52 Some(id) if id <= 0xFF => 1,
53 Some(id) if id <= 0xFFFF => 2,
54 Some(_) => 3,
55 };
56
57 let descriptor = 0x20 | 0x04 | (fcs_flag << 6) | dict_id_flag;
58 output.push(descriptor);
59
60 match dict_id {
61 Some(id) if id <= 0xFF => output.push(id as u8),
62 Some(id) if id <= 0xFFFF => output.extend_from_slice(&(id as u16).to_le_bytes()),
63 Some(id) => output.extend_from_slice(&id.to_le_bytes()),
64 None => {}
65 }
66
67 match fcs_size {
68 1 => output.push(content_size as u8),
69 2 => {
70 let v = (content_size - 256) as u16;
71 output.extend_from_slice(&v.to_le_bytes());
72 }
73 4 => output.extend_from_slice(&(content_size as u32).to_le_bytes()),
74 _ => output.extend_from_slice(&(content_size as u64).to_le_bytes()),
75 }
76}
77
78pub(crate) fn block_looks_incompressible(data: &[u8]) -> bool {
79 const SAMPLE: usize = 1024;
80 const DISTINCT_THRESHOLD: u32 = 200;
81 const MAX_FREQ_DENOM: u32 = 24;
82 if data.len() < SAMPLE {
83 return false;
84 }
85 let mut counts = [0u16; 256];
86 for &b in &data[..SAMPLE] {
87 counts[b as usize] += 1;
88 }
89 let mut distinct: u32 = 0;
90 let mut max_freq: u16 = 0;
91 for &c in &counts {
92 distinct += (c > 0) as u32;
93 max_freq = max_freq.max(c);
94 }
95 distinct >= DISTINCT_THRESHOLD && (max_freq as u32) <= SAMPLE as u32 / MAX_FREQ_DENOM
96}
97
98pub(crate) fn clamp_params_to_src_size(params: &mut strategy::LevelParams, src_len: usize) {
99 params.hash_log = params
100 .hash_log
101 .clamp(strategy::HASH_LOG_MIN, strategy::HASH_LOG_MAX);
102 params.chain_log = params
103 .chain_log
104 .clamp(strategy::HASH_LOG_MIN, strategy::HASH_LOG_MAX);
105 if src_len >= 2 {
106 let src_log = 32 - ((src_len as u32) - 1).leading_zeros();
107 params.hash_log = params.hash_log.min(src_log).max(strategy::HASH_LOG_MIN);
108 params.chain_log = params.chain_log.min(src_log).max(strategy::HASH_LOG_MIN);
109 params.window_log = params.window_log.min(src_log);
110 }
111}
112
113pub fn compress_with_params(
114 input: &[u8],
115 params: &strategy::LevelParams,
116) -> Result<Vec<u8>, CompressError> {
117 let mut params = *params;
118 clamp_params_to_src_size(&mut params, input.len());
119 compress_inner(input, ¶ms)
120}
121
122pub fn compress(input: &[u8], level: i32) -> Result<Vec<u8>, CompressError> {
123 let params = strategy::level_params_for_size(level, input.len())
124 .ok_or(CompressError::InvalidLevel(level))?;
125 compress_inner(input, ¶ms)
126}
127
128pub fn compress_opts(
129 input: &[u8],
130 level: i32,
131 opts: &strategy::Options,
132) -> Result<Vec<u8>, CompressError> {
133 let mut params = strategy::level_params_for_size(level, input.len())
134 .ok_or(CompressError::InvalidLevel(level))?;
135 strategy::apply_options(&mut params, opts);
136 compress_inner(input, ¶ms)
137}
138
139#[allow(clippy::unnecessary_wraps)]
140fn compress_inner(input: &[u8], params: &strategy::LevelParams) -> Result<Vec<u8>, CompressError> {
141 let mut params = *params;
142 strategy::apply_raw_literals_size_override(&mut params, input.len());
143 let mut output = Vec::with_capacity(input.len() + 32);
144 compress_frame(input, ¶ms, &mut output);
145 Ok(output)
146}
147
148fn compress_frame(input: &[u8], params: &strategy::LevelParams, output: &mut Vec<u8>) {
149 write_frame_header(output, input.len(), None);
150
151 if input.is_empty() {
152 block_encoder::encode_raw_block(&[], true, output);
153 } else {
154 let mut rep_offsets = [1u32, 4, 8];
155 let mut offset = 0;
156 let mut sequences = Vec::with_capacity(MAX_BLOCK_SIZE / 8);
157 let mut workspace = block_encoder::BlockEncodeWorkspace::new();
158
159 #[cfg(feature = "ldm")]
160 let mut ldm_state = params.ldm_params.as_ref().map(ldm::LdmState::new);
161
162 match params.strategy {
163 Strategy::Fast => {
164 let hash_size = 1usize << params.hash_log;
165 let mut hash_table = vec![0u32; hash_size];
166 while offset < input.len() {
167 let chunk_size = (input.len() - offset).min(MAX_BLOCK_SIZE);
168 let block_end = offset + chunk_size;
169 let is_last = block_end >= input.len();
170
171 if block_looks_incompressible(&input[offset..block_end]) {
172 block_encoder::encode_raw_block(&input[offset..block_end], is_last, output);
173 } else {
174 #[cfg(feature = "ldm")]
175 let used_ldm = if let Some(ref mut ldm) = ldm_state {
176 let mut empty = Vec::new();
177 ldm.compress_block(
178 input,
179 offset,
180 block_end,
181 params,
182 &rep_offsets,
183 &mut hash_table,
184 &mut empty,
185 &mut sequences,
186 );
187 true
188 } else {
189 false
190 };
191 #[cfg(not(feature = "ldm"))]
192 let used_ldm = false;
193
194 if !used_ldm {
195 fast::compress_fast_block(
196 input,
197 offset,
198 block_end,
199 params,
200 &rep_offsets,
201 &mut hash_table,
202 &mut sequences,
203 );
204 }
205 if params.force_raw_literals {
206 block_encoder::encode_compressed_block_raw(
207 &input[offset..block_end],
208 &sequences,
209 &mut rep_offsets,
210 is_last,
211 output,
212 &mut workspace,
213 );
214 } else {
215 block_encoder::encode_compressed_block(
216 &input[offset..block_end],
217 &sequences,
218 &mut rep_offsets,
219 is_last,
220 output,
221 &mut workspace,
222 strategy::use_custom_sequence_tables(params, input.len()),
223 );
224 }
225 }
226 offset = block_end;
227 }
228 }
229 Strategy::DFast => {
230 let short_size = 1usize << params.chain_log;
231 let long_size = 1usize << params.hash_log;
232 let mut hash_short = vec![0u32; short_size];
233 let mut hash_long = vec![0u32; long_size];
234 while offset < input.len() {
235 let chunk_size = (input.len() - offset).min(MAX_BLOCK_SIZE);
236 let block_end = offset + chunk_size;
237 let is_last = block_end >= input.len();
238
239 if block_looks_incompressible(&input[offset..block_end]) {
240 block_encoder::encode_raw_block(&input[offset..block_end], is_last, output);
241 } else {
242 #[cfg(feature = "ldm")]
243 let used_ldm = if let Some(ref mut ldm) = ldm_state {
244 ldm.compress_block(
245 input,
246 offset,
247 block_end,
248 params,
249 &rep_offsets,
250 &mut hash_short,
251 &mut hash_long,
252 &mut sequences,
253 );
254 true
255 } else {
256 false
257 };
258 #[cfg(not(feature = "ldm"))]
259 let used_ldm = false;
260
261 if !used_ldm {
262 dfast::compress_dfast_block(
263 input,
264 offset,
265 block_end,
266 params,
267 &rep_offsets,
268 &mut hash_short,
269 &mut hash_long,
270 &mut sequences,
271 );
272 }
273 block_encoder::encode_compressed_block(
274 &input[offset..block_end],
275 &sequences,
276 &mut rep_offsets,
277 is_last,
278 output,
279 &mut workspace,
280 strategy::use_custom_sequence_tables(params, input.len()),
281 );
282 }
283 offset = block_end;
284 }
285 }
286 }
287 }
288
289 let hash = xxh64(input, 0);
290 let checksum = (hash & 0xFFFF_FFFF) as u32;
291 output.extend_from_slice(&checksum.to_le_bytes());
292}
293
294pub fn compress_with_dict(
295 input: &[u8],
296 level: i32,
297 dict: &zrip_core::dict::Dictionary,
298) -> Result<Vec<u8>, CompressError> {
299 let total_window = dict.content().len() + input.len();
300 let mut params = strategy::level_params_for_size(level, total_window)
301 .ok_or(CompressError::InvalidLevel(level))?;
302 strategy::apply_raw_literals_size_override(&mut params, input.len());
303
304 let mut output = Vec::with_capacity(input.len() + 32);
305 write_frame_header(&mut output, input.len(), Some(dict.id()));
306
307 if input.is_empty() {
308 block_encoder::encode_raw_block(&[], true, &mut output);
309 } else {
310 let prefix = dict.content();
311 let mut rep_offsets = *dict.rep_offsets();
312 let mut workspace = block_encoder::BlockEncodeWorkspace::new();
313
314 workspace.prev_ll = dict
315 .ll_table()
316 .map(|(dt, al)| block_encoder::FseEncodeTable::from_decode_table(dt, al, 35));
317 workspace.prev_of = dict
318 .of_table()
319 .map(|(dt, al)| block_encoder::FseEncodeTable::from_decode_table(dt, al, 31));
320 workspace.prev_ml = dict
321 .ml_table()
322 .map(|(dt, al)| block_encoder::FseEncodeTable::from_decode_table(dt, al, 52));
323 workspace.prev_huffman = dict.huf_table().and_then(|(dt, tl)| {
324 zrip_core::huffman::encode::HuffmanEncodeTable::from_decode_table(dt, tl)
325 });
326
327 if input.len() <= MAX_BLOCK_SIZE {
328 let sequences = match params.strategy {
329 Strategy::Fast => {
330 fast::compress_fast_with_prefix(input, ¶ms, &rep_offsets, prefix)
331 }
332 Strategy::DFast => {
333 dfast::compress_dfast_with_prefix(input, ¶ms, &rep_offsets, prefix)
334 }
335 };
336 if params.force_raw_literals {
337 block_encoder::encode_compressed_block_raw(
338 input,
339 &sequences,
340 &mut rep_offsets,
341 true,
342 &mut output,
343 &mut workspace,
344 );
345 } else {
346 block_encoder::encode_compressed_block(
347 input,
348 &sequences,
349 &mut rep_offsets,
350 true,
351 &mut output,
352 &mut workspace,
353 strategy::use_custom_sequence_tables(¶ms, input.len()),
354 );
355 }
356 } else {
357 let mut combined = Vec::with_capacity(prefix.len() + input.len());
358 combined.extend_from_slice(prefix);
359 combined.extend_from_slice(input);
360 let plen = prefix.len();
361 let hash_size = 1usize << params.hash_log;
362 let mut sequences = Vec::new();
363
364 match params.strategy {
365 Strategy::Fast => {
366 let mut hash_table = vec![0u32; hash_size];
367 fast::prefill_hash_table(&combined, plen, params.hash_log, &mut hash_table);
368 let mut offset = 0;
369 while offset < input.len() {
370 let chunk_size = (input.len() - offset).min(MAX_BLOCK_SIZE);
371 let is_last = offset + chunk_size >= input.len();
372 fast::compress_fast_block(
373 &combined,
374 plen + offset,
375 plen + offset + chunk_size,
376 ¶ms,
377 &rep_offsets,
378 &mut hash_table,
379 &mut sequences,
380 );
381 if params.force_raw_literals {
382 block_encoder::encode_compressed_block_raw(
383 &input[offset..offset + chunk_size],
384 &sequences,
385 &mut rep_offsets,
386 is_last,
387 &mut output,
388 &mut workspace,
389 );
390 } else {
391 block_encoder::encode_compressed_block(
392 &input[offset..offset + chunk_size],
393 &sequences,
394 &mut rep_offsets,
395 is_last,
396 &mut output,
397 &mut workspace,
398 strategy::use_custom_sequence_tables(¶ms, input.len()),
399 );
400 }
401 offset += chunk_size;
402 }
403 }
404 Strategy::DFast => {
405 let short_size = 1usize << params.chain_log;
406 let long_size = 1usize << params.hash_log;
407 let mut hash_short = vec![0u32; short_size];
408 let mut hash_long = vec![0u32; long_size];
409 dfast::prefill_hash_tables(
410 &combined,
411 plen,
412 params.hash_log,
413 params.chain_log,
414 params.min_match,
415 &mut hash_short,
416 &mut hash_long,
417 );
418 let mut offset = 0;
419 while offset < input.len() {
420 let chunk_size = (input.len() - offset).min(MAX_BLOCK_SIZE);
421 let is_last = offset + chunk_size >= input.len();
422 dfast::compress_dfast_block(
423 &combined,
424 plen + offset,
425 plen + offset + chunk_size,
426 ¶ms,
427 &rep_offsets,
428 &mut hash_short,
429 &mut hash_long,
430 &mut sequences,
431 );
432 block_encoder::encode_compressed_block(
433 &input[offset..offset + chunk_size],
434 &sequences,
435 &mut rep_offsets,
436 is_last,
437 &mut output,
438 &mut workspace,
439 strategy::use_custom_sequence_tables(¶ms, input.len()),
440 );
441 offset += chunk_size;
442 }
443 }
444 }
445 }
446 }
447
448 let hash = xxh64(input, 0);
449 let checksum = (hash & 0xFFFF_FFFF) as u32;
450 output.extend_from_slice(&checksum.to_le_bytes());
451
452 Ok(output)
453}
454
455pub fn compress_into(input: &[u8], output: &mut [u8], level: i32) -> Result<usize, CompressError> {
456 let mut params = strategy::level_params_for_size(level, input.len())
457 .ok_or(CompressError::InvalidLevel(level))?;
458 strategy::apply_raw_literals_size_override(&mut params, input.len());
459 let mut buf = Vec::with_capacity(output.len());
460 compress_frame(input, ¶ms, &mut buf);
461 if buf.len() > output.len() {
462 return Err(CompressError::OutputTooSmall);
463 }
464 output[..buf.len()].copy_from_slice(&buf);
465 Ok(buf.len())
466}
467
468#[cfg(all(test, miri, not(feature = "paranoid")))]
469mod ub_tests {
470 use super::*;
471
472 #[test]
473 fn public_compress_with_params_accepts_zero_hash_log() {
474 let mut params = strategy::level_params(1).unwrap();
480 params.hash_log = 0;
481 params.chain_log = 0;
482 let _ = compress_with_params(b"abcdefghijklmnop", ¶ms);
483 }
484}