1use alloc::vec::Vec;
4use core::{fmt, marker::PhantomData, mem::MaybeUninit};
5
6use super::{levels::c_port, CompressionLevel};
7use crate::workspace::{Arena, ArenaError, ArenaSize, ReusableVec};
8
9use c_port::{
10 WorkspaceCctxParameters as CctxParameters, WorkspaceDFastMatchState as DFastMatchState,
11 WorkspaceFastMatchState as FastMatchState, WorkspaceFrameBlockState as FrameBlockState,
12 WorkspaceGreedyMatchState as GreedyMatchState, WorkspaceLazyBlockStrategy as LazyBlockStrategy,
13 WorkspaceLdmWorkspace as LdmWorkspace, WorkspaceOptBlockState as OptBlockState,
14 WorkspaceOptFrameStrategy as OptFrameStrategy, WorkspaceParamSwitch as ParamSwitch,
15 WorkspaceStrategy as Strategy,
16};
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub enum EncoderWorkspaceError {
21 Capacity(crate::workspace::CapacityError),
22 ContextPoisoned,
24 UnsupportedWorkspaceConfiguration,
26 InsufficientWorkspace { required: usize, provided: usize },
27 InputTooLarge { maximum: usize, provided: usize },
28 OutputTooSmall { required: usize, provided: usize },
29 SizeOverflow,
30}
31
32impl fmt::Display for EncoderWorkspaceError {
33 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 Self::Capacity(error) => write!(formatter, "encoder storage admission failed: {error:?}"),
36 Self::UnsupportedWorkspaceConfiguration => formatter.write_str("encoder configuration is not supported by caller-backed storage"),
37 Self::ContextPoisoned => formatter.write_str("encoder context must be recreated after an incomplete encode"),
38 Self::InsufficientWorkspace { required, provided } => write!(
39 formatter,
40 "encoder workspace needs {required} bytes but only {provided} were provided"
41 ),
42 Self::InputTooLarge { maximum, provided } => write!(
43 formatter,
44 "encoder workspace accepts at most {maximum} input bytes but received {provided}"
45 ),
46 Self::OutputTooSmall { required, provided } => write!(
47 formatter,
48 "encoded output needs {required} bytes but only {provided} were provided"
49 ),
50 Self::SizeOverflow => formatter.write_str("encoder workspace size overflowed"),
51 }
52 }
53}
54
55#[allow(clippy::large_enum_variant)]
58enum MatchWorkspace {
59 Uncompressed,
60 Fast(FastMatchState),
61 DFast(DFastMatchState),
62 Greedy {
63 state: GreedyMatchState,
64 depth: LazyBlockStrategy,
65 },
66 Opt {
67 state: OptBlockState,
68 strategy: OptFrameStrategy,
69 },
70}
71
72impl From<crate::workspace::CapacityError> for EncoderWorkspaceError {
73 fn from(error: crate::workspace::CapacityError) -> Self { Self::Capacity(error) }
74}
75
76struct EncoderCore {
77 poisoned: bool,
81 level: i32,
82 maximum_input: usize,
83 base_cctx: CctxParameters,
84 frame: FrameBlockState,
85 matcher: MatchWorkspace,
86 ldm: Option<LdmWorkspace>,
87}
88
89impl EncoderCore {
90 fn add_size(
91 size: &mut ArenaSize,
92 level: i32,
93 maximum_input: usize,
94 ) -> Result<CctxParameters, EncoderWorkspaceError> {
95 let cctx = CctxParameters::for_level(level, maximum_input as u64, 0);
96 FrameBlockState::add_workspace_size(size).map_err(map_arena)?;
97 let block_size = crate::common::MAX_BLOCK_SIZE as usize;
98 if level == 0 {
99 return Ok(cctx);
100 }
101 match cctx.compression.strategy {
102 Strategy::Fast => {
103 FastMatchState::add_workspace_size(size, cctx.compression, block_size)
104 .map_err(map_arena)?;
105 }
106 Strategy::DFast => {
107 DFastMatchState::add_workspace_size(size, cctx.compression, block_size)
108 .map_err(map_arena)?;
109 }
110 Strategy::Greedy | Strategy::Lazy | Strategy::Lazy2 | Strategy::BtLazy2 => {
111 GreedyMatchState::add_workspace_size(size, cctx.compression, block_size)
112 .map_err(map_arena)?;
113 }
114 Strategy::BtOpt | Strategy::BtUltra | Strategy::BtUltra2 => {
115 OptBlockState::add_workspace_size(size, cctx.compression, block_size)
116 .map_err(map_arena)?;
117 }
118 }
119 if cctx.ldm.enable_ldm == ParamSwitch::Enable {
120 LdmWorkspace::add_workspace_size(size, cctx.ldm, maximum_input).map_err(map_arena)?;
121 }
122 Ok(cctx)
123 }
124
125 fn new_in(
126 arena: &mut Arena<'_>,
127 level: i32,
128 maximum_input: usize,
129 cctx: CctxParameters,
130 ) -> Result<Self, EncoderWorkspaceError> {
131 let block_size = crate::common::MAX_BLOCK_SIZE as usize;
132 let frame = FrameBlockState::new_in(arena, cctx.compression, cctx.max_block_size)
133 .map_err(map_arena)?;
134 let matcher = if level == 0 {
135 MatchWorkspace::Uncompressed
136 } else {
137 match cctx.compression.strategy {
138 Strategy::Fast => MatchWorkspace::Fast(
139 FastMatchState::new_in(arena, cctx.compression, block_size)
140 .map_err(map_arena)?,
141 ),
142 Strategy::DFast => MatchWorkspace::DFast(
143 DFastMatchState::new_in(arena, cctx.compression, block_size)
144 .map_err(map_arena)?,
145 ),
146 strategy @ (Strategy::Greedy
147 | Strategy::Lazy
148 | Strategy::Lazy2
149 | Strategy::BtLazy2) => MatchWorkspace::Greedy {
150 state: GreedyMatchState::new_in(arena, cctx.compression, block_size)
151 .map_err(map_arena)?,
152 depth: match strategy {
153 Strategy::Greedy => LazyBlockStrategy::Greedy,
154 Strategy::Lazy => LazyBlockStrategy::Lazy,
155 Strategy::Lazy2 => LazyBlockStrategy::Lazy2,
156 Strategy::BtLazy2 => LazyBlockStrategy::BtLazy2,
157 _ => unreachable!(),
158 },
159 },
160 strategy @ (Strategy::BtOpt | Strategy::BtUltra | Strategy::BtUltra2) => {
161 MatchWorkspace::Opt {
162 state: OptBlockState::new_in(arena, cctx.compression, block_size)
163 .map_err(map_arena)?,
164 strategy: match strategy {
165 Strategy::BtOpt => OptFrameStrategy::BtOpt,
166 Strategy::BtUltra => OptFrameStrategy::BtUltra,
167 Strategy::BtUltra2 => OptFrameStrategy::BtUltra2,
168 _ => unreachable!(),
169 },
170 }
171 }
172 }
173 };
174 let ldm = if cctx.ldm.enable_ldm == ParamSwitch::Enable {
175 Some(LdmWorkspace::new_in(arena, cctx.ldm, maximum_input).map_err(map_arena)?)
176 } else {
177 None
178 };
179 Ok(Self {
180 poisoned: false,
181 level,
182 maximum_input,
183 base_cctx: cctx,
184 frame,
185 matcher,
186 ldm,
187 })
188 }
189
190 fn encode_into<'output>(
191 &mut self,
192 input: &[u8],
193 output: &'output mut [u8],
194 ) -> Result<&'output [u8], EncoderWorkspaceError> {
195 if self.poisoned { return Err(EncoderWorkspaceError::ContextPoisoned); }
196 if input.len() > self.maximum_input {
197 return Err(EncoderWorkspaceError::InputTooLarge {
198 maximum: self.maximum_input,
199 provided: input.len(),
200 });
201 }
202 let required = checked_output_size(input.len())?;
203 if output.len() < required {
204 return Err(EncoderWorkspaceError::OutputTooSmall {
205 required,
206 provided: output.len(),
207 });
208 }
209 self.poisoned = true;
210 let cctx = CctxParameters::from_compression_parameters(
211 self.level,
212 self.base_cctx.compression,
213 input.len() as u64,
214 );
215 if cctx.target_c_block_size != 0
218 || !FrameBlockState::supports_workspace_entropy(cctx.compression)
219 {
220 return Err(EncoderWorkspaceError::UnsupportedWorkspaceConfiguration);
221 }
222 let reusable = unsafe { ReusableVec::from_static_parts(output.as_mut_ptr(), output.len()) };
226 let (mut output_vec, lease) = reusable.lease_vec();
227 let ldm = &mut self.ldm;
228 match &mut self.matcher {
229 MatchWorkspace::Uncompressed => encode_uncompressed(input, &mut output_vec),
230 MatchWorkspace::Fast(state) => {
231 c_port::workspace_encode_fast(input, cctx, &mut self.frame, state, &mut output_vec)
232 }
233 MatchWorkspace::DFast(state) => {
234 c_port::workspace_encode_dfast(input, cctx, &mut self.frame, state, &mut output_vec)
235 }
236 MatchWorkspace::Greedy { state, depth } => c_port::workspace_encode_greedy(
237 input,
238 cctx,
239 *depth,
240 &mut self.frame,
241 state,
242 &mut output_vec,
243 ),
244 MatchWorkspace::Opt { state, strategy } => c_port::workspace_encode_opt(
245 input,
246 cctx,
247 *strategy,
248 &mut self.frame,
249 state,
250 ldm.as_mut(),
251 &mut output_vec,
252 ),
253 }?;
254 let length = output_vec.len();
255 let reusable = ReusableVec::recover_vec(output_vec, lease);
256 drop(reusable);
257 self.poisoned = false;
258 Ok(&output[..length])
259 }
260}
261
262fn encode_uncompressed(input: &[u8], output: &mut impl crate::workspace::Growable<u8>) -> Result<(), crate::workspace::CapacityError> {
263 use crate::{
264 blocks::block::BlockType,
265 encoding::{block_header::BlockHeader, frame_header::FrameHeader},
266 };
267 output.clear();
268 FrameHeader {
269 frame_content_size: Some(input.len() as u64),
270 single_segment: true,
271 content_checksum: false,
272 dictionary_id: None,
273 window_size: None,
274 }
275 .serialize(output)?;
276 if input.is_empty() {
277 BlockHeader {
278 last_block: true,
279 block_type: BlockType::Raw,
280 block_size: 0,
281 }
282 .serialize(output)?;
283 return Ok(());
284 }
285 let block_size = crate::common::MAX_BLOCK_SIZE as usize;
286 for (index, block) in input.chunks(block_size).enumerate() {
287 BlockHeader {
288 last_block: (index + 1) * block_size >= input.len(),
289 block_type: BlockType::Raw,
290 block_size: block.len() as u32,
291 }
292 .serialize(output)?;
293 output.extend_from_slice(block)?;
294 }
295 Ok(())
296}
297
298pub struct EncoderWorkspace {
300 core: EncoderCore,
301 storage: Vec<MaybeUninit<u8>>,
302}
303
304impl EncoderWorkspace {
305 pub fn new(
306 level: CompressionLevel,
307 maximum_input: usize,
308 ) -> Result<Self, EncoderWorkspaceError> {
309 let required = Self::required_size(level, maximum_input)?;
310 let mut storage = Vec::with_capacity(required);
311 storage.resize_with(required, MaybeUninit::uninit);
312 let mut arena = Arena::new(&mut storage);
313 let mut size = ArenaSize::new();
314 let cctx = EncoderCore::add_size(&mut size, level.get(), maximum_input)?;
315 let core = EncoderCore::new_in(&mut arena, level.get(), maximum_input, cctx)?;
316 Ok(Self { core, storage })
317 }
318
319 pub fn required_size(
320 level: CompressionLevel,
321 maximum_input: usize,
322 ) -> Result<usize, EncoderWorkspaceError> {
323 let mut size = ArenaSize::new();
324 EncoderCore::add_size(&mut size, level.get(), maximum_input)?;
325 Ok(size.finish())
326 }
327
328 pub fn required_output_size(maximum_input: usize) -> Result<usize, EncoderWorkspaceError> {
329 checked_output_size(maximum_input)
330 }
331
332 pub fn encode_into<'output>(
333 &mut self,
334 input: &[u8],
335 output: &'output mut [u8],
336 ) -> Result<&'output [u8], EncoderWorkspaceError> {
337 self.core.encode_into(input, output)
338 }
339
340 pub fn workspace_bytes(&self) -> usize {
341 self.storage.len()
342 }
343
344 pub const fn maximum_input(&self) -> usize {
345 self.core.maximum_input
346 }
347}
348
349#[cfg(feature = "std")]
350impl std::error::Error for EncoderWorkspaceError {}
351
352pub struct StaticEncoderWorkspace<'storage> {
354 core: EncoderCore,
355 workspace_bytes: usize,
356 marker: PhantomData<&'storage mut [MaybeUninit<u8>]>,
357}
358
359impl<'storage> StaticEncoderWorkspace<'storage> {
360 pub fn new(
361 storage: &'storage mut [u8],
362 level: CompressionLevel,
363 maximum_input: usize,
364 ) -> Result<Self, EncoderWorkspaceError> {
365 Self::new_uninit(initialized_bytes_as_uninit(storage), level, maximum_input)
366 }
367
368 pub fn new_uninit(
370 storage: &'storage mut [MaybeUninit<u8>],
371 level: CompressionLevel,
372 maximum_input: usize,
373 ) -> Result<Self, EncoderWorkspaceError> {
374 let required = EncoderWorkspace::required_size(level, maximum_input)?;
375 if storage.len() < required {
376 return Err(EncoderWorkspaceError::InsufficientWorkspace {
377 required,
378 provided: storage.len(),
379 });
380 }
381 let mut size = ArenaSize::new();
382 let cctx = EncoderCore::add_size(&mut size, level.get(), maximum_input)?;
383 let mut arena = Arena::new(storage);
384 let core = EncoderCore::new_in(&mut arena, level.get(), maximum_input, cctx)?;
385 Ok(Self {
386 core,
387 workspace_bytes: storage.len(),
388 marker: PhantomData,
389 })
390 }
391
392 pub fn encode_into<'output>(
393 &mut self,
394 input: &[u8],
395 output: &'output mut [u8],
396 ) -> Result<&'output [u8], EncoderWorkspaceError> {
397 self.core.encode_into(input, output)
398 }
399
400 pub const fn maximum_input(&self) -> usize {
401 self.core.maximum_input
402 }
403
404 pub const fn workspace_bytes(&self) -> usize {
405 self.workspace_bytes
406 }
407}
408
409fn initialized_bytes_as_uninit(storage: &mut [u8]) -> &mut [MaybeUninit<u8>] {
410 unsafe { core::slice::from_raw_parts_mut(storage.as_mut_ptr().cast(), storage.len()) }
413}
414
415fn map_arena(error: ArenaError) -> EncoderWorkspaceError {
416 match error {
417 ArenaError::Capacity(error) => EncoderWorkspaceError::Capacity(error),
418 ArenaError::InsufficientStorage { required, provided } => {
419 EncoderWorkspaceError::InsufficientWorkspace { required, provided }
420 }
421 ArenaError::CapacityExceeded { .. } | ArenaError::SizeOverflow => {
422 EncoderWorkspaceError::SizeOverflow
423 }
424 }
425}
426
427fn checked_output_size(input_size: usize) -> Result<usize, EncoderWorkspaceError> {
428 let bound = c_port::workspace_compress_bound(input_size);
429 if bound == 0 {
430 Err(EncoderWorkspaceError::SizeOverflow)
431 } else {
432 Ok(bound)
433 }
434}