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