Skip to main content

zstd_complete/decoding/
workspace.rs

1//! Prepared, reusable decompression workspaces.
2
3use core::{fmt, marker::PhantomData, mem::MaybeUninit};
4
5use super::{errors::FrameDecoderError, frame_decoder::PreparedDictionaryBytes, FrameDecoder};
6use crate::{
7    decoding::scratch::DecoderScratch,
8    workspace::{Arena, ArenaError},
9};
10
11/// A decoder whose internal tables and history storage are allocated up front.
12///
13/// After construction, decoding a valid frame whose window does not exceed
14/// `max_window_size` into a sufficiently large caller buffer does not request
15/// additional heap storage. Dictionaries must be installed before relying on
16/// that prepared-operation guarantee.
17pub struct DecoderWorkspace {
18    decoder: FrameDecoder,
19    max_window_size: usize,
20    max_dictionary_size: usize,
21}
22
23impl DecoderWorkspace {
24    /// Allocates and prepares all storage required by a bounded decoder.
25    pub fn new(
26        max_window_size: usize,
27        max_dictionary_size: usize,
28    ) -> Result<Self, DecoderWorkspaceError> {
29        if max_window_size == 0 {
30            return Err(DecoderWorkspaceError::InvalidWindowSize);
31        }
32        let mut decoder = FrameDecoder::new();
33        decoder.prepare_workspace(max_window_size, max_dictionary_size).map_err(DecoderWorkspaceError::Capacity)?;
34        Ok(Self {
35            decoder,
36            max_window_size,
37            max_dictionary_size,
38        })
39    }
40
41    pub const fn max_window_size(&self) -> usize {
42        self.max_window_size
43    }
44
45    pub const fn max_dictionary_size(&self) -> usize {
46        self.max_dictionary_size
47    }
48
49    /// Decodes complete concatenated frames into caller-owned output storage.
50    pub fn decode_into(
51        &mut self,
52        input: &[u8],
53        output: &mut [u8],
54    ) -> Result<usize, DecoderWorkspaceError> {
55        self.decoder
56            .decode_all_with_window_limit(input, output, self.max_window_size as u64)
57            .map_err(DecoderWorkspaceError::Decode)
58    }
59
60    /// Decodes using one formatted dictionary without allocating an owning
61    /// dictionary object or registering it in the decoder.
62    pub fn decode_into_with_dictionary(
63        &mut self,
64        input: &[u8],
65        dictionary: &[u8],
66        output: &mut [u8],
67    ) -> Result<usize, DecoderWorkspaceError> {
68        ensure_dictionary_size(dictionary.len(), self.max_dictionary_size)?;
69        self.decoder
70            .decode_all_with_window_limit_and_dictionary(
71                input,
72                output,
73                self.max_window_size as u64,
74                Some(PreparedDictionaryBytes::Formatted(dictionary)),
75            )
76            .map_err(DecoderWorkspaceError::Decode)
77    }
78
79    /// Accesses the underlying decoder to install formatted dictionaries
80    /// during workspace preparation.
81    pub fn decoder_mut(&mut self) -> &mut FrameDecoder {
82        &mut self.decoder
83    }
84
85    /// Decodes a frame prepared with a raw-content dictionary.
86    pub fn decode_into_with_raw_dictionary(
87        &mut self,
88        input: &[u8],
89        dictionary: &[u8],
90        output: &mut [u8],
91    ) -> Result<usize, DecoderWorkspaceError> {
92        ensure_dictionary_size(dictionary.len(), self.max_dictionary_size)?;
93        self.decoder
94            .decode_all_with_window_limit_and_dictionary(
95                input,
96                output,
97                self.max_window_size as u64,
98                Some(PreparedDictionaryBytes::Raw(dictionary)),
99            )
100            .map_err(DecoderWorkspaceError::Decode)
101    }
102}
103
104/// Errors produced by a prepared decoder workspace.
105#[derive(Debug)]
106pub enum DecoderWorkspaceError {
107    Capacity(crate::workspace::CapacityError),
108    InvalidWindowSize,
109    InsufficientStorage { required: usize, provided: usize },
110    SizeOverflow,
111    DictionaryTooLarge { maximum: usize, provided: usize },
112    Decode(FrameDecoderError),
113}
114
115impl fmt::Display for DecoderWorkspaceError {
116    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
117        match self {
118            Self::Capacity(error) => write!(formatter, "decoder storage admission failed: {error:?}"),
119            Self::InvalidWindowSize => formatter.write_str("maximum window size must be non-zero"),
120            Self::InsufficientStorage { required, provided } => write!(
121                formatter,
122                "decoder workspace needs {required} bytes but only {provided} were provided"
123            ),
124            Self::SizeOverflow => formatter.write_str("decoder workspace size overflowed"),
125            Self::DictionaryTooLarge { maximum, provided } => write!(
126                formatter,
127                "dictionary has {provided} bytes but this workspace permits at most {maximum}"
128            ),
129            Self::Decode(error) => error.fmt(formatter),
130        }
131    }
132}
133
134#[cfg(feature = "std")]
135impl std::error::Error for DecoderWorkspaceError {
136    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
137        match self {
138            Self::Decode(error) => Some(error),
139            Self::Capacity(_)
140            | Self::InvalidWindowSize
141            | Self::InsufficientStorage { .. }
142            | Self::SizeOverflow
143            | Self::DictionaryTooLarge { .. } => None,
144        }
145    }
146}
147
148/// A decoder whose complete scratch state is placed in caller-provided bytes.
149pub struct StaticDecoderWorkspace<'storage> {
150    decoder: FrameDecoder,
151    max_window_size: usize,
152    max_dictionary_size: usize,
153    workspace_bytes: usize,
154    marker: PhantomData<&'storage mut [MaybeUninit<u8>]>,
155}
156
157impl<'storage> StaticDecoderWorkspace<'storage> {
158    pub fn required_size(
159        max_window_size: usize,
160        max_dictionary_size: usize,
161    ) -> Result<usize, DecoderWorkspaceError> {
162        if max_window_size == 0 {
163            return Err(DecoderWorkspaceError::InvalidWindowSize);
164        }
165        DecoderScratch::workspace_size(max_window_size, max_dictionary_size)
166            .map_err(map_arena_error)
167    }
168
169    pub fn new(
170        storage: &'storage mut [u8],
171        max_window_size: usize,
172        max_dictionary_size: usize,
173    ) -> Result<Self, DecoderWorkspaceError> {
174        Self::new_uninit(
175            initialized_bytes_as_uninit(storage),
176            max_window_size,
177            max_dictionary_size,
178        )
179    }
180
181    /// Constructs a decoder without initializing caller-provided bytes.
182    pub fn new_uninit(
183        storage: &'storage mut [MaybeUninit<u8>],
184        max_window_size: usize,
185        max_dictionary_size: usize,
186    ) -> Result<Self, DecoderWorkspaceError> {
187        let required = Self::required_size(max_window_size, max_dictionary_size)?;
188        if storage.len() < required {
189            return Err(DecoderWorkspaceError::InsufficientStorage {
190                required,
191                provided: storage.len(),
192            });
193        }
194        let mut arena = Arena::new(storage);
195        let scratch = DecoderScratch::new_in(&mut arena, max_window_size, max_dictionary_size)
196            .map_err(map_arena_error)?;
197        let mut decoder = FrameDecoder::new();
198        decoder.install_prepared_scratch(scratch);
199        Ok(Self {
200            decoder,
201            max_window_size,
202            max_dictionary_size,
203            workspace_bytes: storage.len(),
204            marker: PhantomData,
205        })
206    }
207
208    pub fn decode_into(
209        &mut self,
210        input: &[u8],
211        output: &mut [u8],
212    ) -> Result<usize, DecoderWorkspaceError> {
213        self.decoder
214            .decode_all_with_window_limit(input, output, self.max_window_size as u64)
215            .map_err(DecoderWorkspaceError::Decode)
216    }
217
218    /// Decodes with a formatted dictionary directly from caller storage.
219    pub fn decode_into_with_dictionary(
220        &mut self,
221        input: &[u8],
222        dictionary: &[u8],
223        output: &mut [u8],
224    ) -> Result<usize, DecoderWorkspaceError> {
225        ensure_dictionary_size(dictionary.len(), self.max_dictionary_size)?;
226        self.decoder
227            .decode_all_with_window_limit_and_dictionary(
228                input,
229                output,
230                self.max_window_size as u64,
231                Some(PreparedDictionaryBytes::Formatted(dictionary)),
232            )
233            .map_err(DecoderWorkspaceError::Decode)
234    }
235
236    /// Decodes a frame prepared with a raw-content dictionary.
237    pub fn decode_into_with_raw_dictionary(
238        &mut self,
239        input: &[u8],
240        dictionary: &[u8],
241        output: &mut [u8],
242    ) -> Result<usize, DecoderWorkspaceError> {
243        ensure_dictionary_size(dictionary.len(), self.max_dictionary_size)?;
244        self.decoder
245            .decode_all_with_window_limit_and_dictionary(
246                input,
247                output,
248                self.max_window_size as u64,
249                Some(PreparedDictionaryBytes::Raw(dictionary)),
250            )
251            .map_err(DecoderWorkspaceError::Decode)
252    }
253
254    pub const fn max_window_size(&self) -> usize {
255        self.max_window_size
256    }
257
258    pub const fn max_dictionary_size(&self) -> usize {
259        self.max_dictionary_size
260    }
261
262    pub const fn workspace_bytes(&self) -> usize {
263        self.workspace_bytes
264    }
265}
266
267fn initialized_bytes_as_uninit(storage: &mut [u8]) -> &mut [MaybeUninit<u8>] {
268    // SAFETY: `MaybeUninit<u8>` has the same layout as `u8`, and treating
269    // initialized bytes as possibly uninitialized only weakens the invariant.
270    unsafe { core::slice::from_raw_parts_mut(storage.as_mut_ptr().cast(), storage.len()) }
271}
272
273fn ensure_dictionary_size(provided: usize, maximum: usize) -> Result<(), DecoderWorkspaceError> {
274    if provided > maximum {
275        Err(DecoderWorkspaceError::DictionaryTooLarge { maximum, provided })
276    } else {
277        Ok(())
278    }
279}
280
281fn map_arena_error(error: ArenaError) -> DecoderWorkspaceError {
282    match error {
283        ArenaError::Capacity(error) => DecoderWorkspaceError::Capacity(error),
284        ArenaError::InsufficientStorage { required, provided } => {
285            DecoderWorkspaceError::InsufficientStorage { required, provided }
286        }
287        ArenaError::CapacityExceeded { capacity } => DecoderWorkspaceError::InsufficientStorage {
288            required: capacity.saturating_add(1),
289            provided: capacity,
290        },
291        ArenaError::SizeOverflow => DecoderWorkspaceError::SizeOverflow,
292    }
293}