1mod block;
3mod cost;
4mod dictionary;
5mod entropy;
6mod literals;
7mod matchers;
8mod params;
9mod seqstore;
10mod sequences;
11
12pub use dictionary::EncoderDictionary;
13pub use params::{MAX_LEVEL, MIN_LEVEL};
14
15use crate::error::{Error, Result};
16use crate::frame::{write_block_header, BlockType, FrameFormat, FrameHeader, BLOCK_MAX};
17use crate::xxh64::xxh64;
18use alloc::vec::Vec;
19use entropy::{EntropyState, PredefinedC};
20use matchers::{opt::OptState, BlockInput, Tables};
21use params::CParams;
22use seqstore::{Reps, SeqStore};
23use sequences::Codes;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct CompressionConfig {
29 pub level: i32,
31 pub window_log: u32,
33 pub checksum: bool,
35 pub content_size: bool,
37 pub dict_id: bool,
39 pub format: FrameFormat,
41}
42
43impl CompressionConfig {
44 pub const FAST: CompressionConfig =
46 CompressionConfig { level: 1, window_log: 23, checksum: false, content_size: true, dict_id: true, format: FrameFormat::Standard };
47 pub const DEFAULT: CompressionConfig = CompressionConfig { level: 3, ..Self::FAST };
49 pub const HIGH: CompressionConfig = CompressionConfig { level: 19, ..Self::FAST };
51 pub const MESSAGE: CompressionConfig = CompressionConfig { dict_id: false, ..Self::DEFAULT };
53
54 pub fn validate(&self) -> Result<()> {
56 if self.level == 0 || !(MIN_LEVEL..=MAX_LEVEL).contains(&self.level) {
57 return Err(Error::Parameter("level must be in MIN_LEVEL..=MAX_LEVEL and not 0"));
58 }
59 if !(10..=30).contains(&self.window_log) {
60 return Err(Error::Parameter("window_log must be in 10..=30"));
61 }
62 Ok(())
63 }
64}
65
66pub(crate) struct Workspace {
68 pub params: CParams,
69 pub tables: Tables,
70 pub store: SeqStore,
71 pub opt: OptState,
72 pub entropy: EntropyState,
73 pub pre: PredefinedC,
74 pub codes: Codes,
75 pub reps: Reps,
76}
77
78pub struct Compressor {
80 cfg: CompressionConfig,
81 w: Workspace,
82 prev_len: usize,
83}
84
85impl Compressor {
86 pub fn new(cfg: CompressionConfig) -> Result<Compressor> {
88 cfg.validate()?;
89 let w = Workspace {
90 params: params::lookup(cfg.level, 0),
91 tables: Tables::new(),
92 store: SeqStore::new(),
93 opt: OptState::new(),
94 entropy: EntropyState::new(),
95 pre: PredefinedC::new()?,
96 codes: Codes::default(),
97 reps: [1, 4, 8],
98 };
99 Ok(Compressor { cfg, w, prev_len: 0 })
100 }
101
102 pub fn config(&self) -> &CompressionConfig {
104 &self.cfg
105 }
106
107 pub fn compress(&mut self, src: &[u8], dict: Option<&EncoderDictionary>, out: &mut Vec<u8>) -> Result<()> {
110 let p = self.frame_params(src.len(), dict)?;
111 write_header(&self.cfg, &p, src.len(), dict, out);
112 let whole = Job { start: 0, end: src.len(), prefix_start: 0, first: true, last: true };
113 self.blocks(src, &whole, dict, &p, out)?;
114 write_checksum(&self.cfg, src, out);
115 Ok(())
116 }
117
118 pub fn compress_job(&mut self, src: &[u8], job: &Job, dict: Option<&EncoderDictionary>, out: &mut Vec<u8>) -> Result<()> {
122 if job.start > job.end || job.end > src.len() || job.prefix_start > job.start {
123 return Err(Error::Parameter("job range outside the input"));
124 }
125 let p = self.frame_params(src.len(), dict)?;
126 let window_start = job.start.saturating_sub(1 << p.window_log);
127 let job = Job { prefix_start: job.prefix_start.max(window_start), ..*job };
128 self.blocks(src, &job, if job.first { dict } else { None }, &p, out)
129 }
130
131 fn frame_params(&self, len: usize, dict: Option<&EncoderDictionary>) -> Result<CParams> {
132 checked_params(&self.cfg, len, dict)
133 }
134
135 fn blocks(&mut self, src: &[u8], job: &Job, dict: Option<&EncoderDictionary>, p: &CParams, out: &mut Vec<u8>) -> Result<()> {
138 let w = &mut self.w;
139 w.params = *p;
140 let hash2_log = if p.strategy == params::Strategy::DFast { p.chain_log } else { 0 };
141 let chain_log = if p.strategy >= params::Strategy::Greedy { p.chain_log } else { 0 };
142 w.tables.start_frame(src.len(), p.hash_log, hash2_log, chain_log, self.prev_len);
143 self.prev_len = src.len();
144 matchers::load_prefix(&mut w.tables, src, job.prefix_start, job.start, p);
145 let dict_tables = dict.and_then(|d| d.tables.as_ref());
146 w.entropy.reset(dict_tables);
147 w.reps = match (job.first, dict) {
148 (true, Some(d)) => d.reps,
149 (true, None) => [1, 4, 8],
150 (false, _) => [0, 0, 0],
151 };
152 w.opt.first_block = true;
153 let window = 1usize << p.window_log;
154 if job.start == job.end {
155 if job.last {
156 write_block_header(true, BlockType::Raw, 0, out);
157 }
158 return Ok(());
159 }
160 let block = BLOCK_MAX.min(window);
161 let mut start = job.start;
162 while start < job.end {
163 let end = (start + block).min(job.end);
164 let attached = dict.filter(|_| end <= window && job.prefix_start == 0).map(|d| &d.matcher);
165 let b = BlockInput { src, start, end, low: job.prefix_start, max_dist: window, dict: attached };
166 block::compress(w, &b, dict_tables, job.last && end == job.end, start > 0, out)?;
167 start = end;
168 }
169 Ok(())
170 }
171}
172
173fn checked_params(cfg: &CompressionConfig, len: usize, dict: Option<&EncoderDictionary>) -> Result<CParams> {
175 cfg.validate()?;
176 if len > (u32::MAX >> 2) as usize {
177 return Err(Error::Parameter("input larger than 1 GiB; split it into frames"));
178 }
179 if dict.is_some_and(|d| d.level != cfg.level) {
180 return Err(Error::Parameter("dictionary prepared for a different level"));
181 }
182 Ok(frame_params(cfg, len, dict))
183}
184
185pub(crate) fn begin_frame(cfg: &CompressionConfig, len: usize, dict: Option<&EncoderDictionary>, out: &mut Vec<u8>) -> Result<()> {
187 let p = checked_params(cfg, len, dict)?;
188 write_header(cfg, &p, len, dict, out);
189 Ok(())
190}
191
192pub(crate) fn write_header(cfg: &CompressionConfig, p: &CParams, len: usize, dict: Option<&EncoderDictionary>, out: &mut Vec<u8>) {
194 let window = 1u64 << p.window_log;
195 let header = FrameHeader {
196 window_size: window,
197 content_size: cfg.content_size.then_some(len as u64),
198 dict_id: if cfg.dict_id { dict.map_or(0, |d| d.id) } else { 0 },
199 checksum: cfg.checksum,
200 single_segment: cfg.content_size && len as u64 <= window,
201 header_len: 0,
202 };
203 header.write_format(cfg.format, p.window_log, out);
204}
205
206pub(crate) fn write_checksum(cfg: &CompressionConfig, src: &[u8], out: &mut Vec<u8>) {
208 if cfg.checksum {
209 out.extend_from_slice(&(xxh64(src, 0) as u32).to_le_bytes());
210 }
211}
212
213#[derive(Clone, Copy, Debug, PartialEq, Eq)]
216pub struct Job {
217 pub start: usize,
219 pub end: usize,
221 pub prefix_start: usize,
223 pub first: bool,
225 pub last: bool,
227}
228
229fn frame_params(cfg: &CompressionConfig, len: usize, dict: Option<&EncoderDictionary>) -> CParams {
232 let mut p = params::adjust(params::lookup(cfg.level, len as u64), len as u64, 0);
233 if let Some(d) = dict {
234 let dp = d.matcher.params;
235 p.strategy = dp.strategy;
236 p.min_match = dp.min_match;
237 p.search_log = dp.search_log;
238 p.target_length = dp.target_length;
239 p.hash_log = p.hash_log.max(6);
240 p.chain_log = p.chain_log.max(6);
241 }
242 p.window_log = p.window_log.min(cfg.window_log);
243 p
244}