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);
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    InvalidWindowSize,
108    InsufficientStorage { required: usize, provided: usize },
109    SizeOverflow,
110    DictionaryTooLarge { maximum: usize, provided: usize },
111    Decode(FrameDecoderError),
112}
113
114impl fmt::Display for DecoderWorkspaceError {
115    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            Self::InvalidWindowSize => formatter.write_str("maximum window size must be non-zero"),
118            Self::InsufficientStorage { required, provided } => write!(
119                formatter,
120                "decoder workspace needs {required} bytes but only {provided} were provided"
121            ),
122            Self::SizeOverflow => formatter.write_str("decoder workspace size overflowed"),
123            Self::DictionaryTooLarge { maximum, provided } => write!(
124                formatter,
125                "dictionary has {provided} bytes but this workspace permits at most {maximum}"
126            ),
127            Self::Decode(error) => error.fmt(formatter),
128        }
129    }
130}
131
132#[cfg(feature = "std")]
133impl std::error::Error for DecoderWorkspaceError {
134    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
135        match self {
136            Self::Decode(error) => Some(error),
137            Self::InvalidWindowSize
138            | Self::InsufficientStorage { .. }
139            | Self::SizeOverflow
140            | Self::DictionaryTooLarge { .. } => None,
141        }
142    }
143}
144
145/// A decoder whose complete scratch state is placed in caller-provided bytes.
146pub struct StaticDecoderWorkspace<'storage> {
147    decoder: FrameDecoder,
148    max_window_size: usize,
149    max_dictionary_size: usize,
150    workspace_bytes: usize,
151    marker: PhantomData<&'storage mut [MaybeUninit<u8>]>,
152}
153
154impl<'storage> StaticDecoderWorkspace<'storage> {
155    pub fn required_size(
156        max_window_size: usize,
157        max_dictionary_size: usize,
158    ) -> Result<usize, DecoderWorkspaceError> {
159        if max_window_size == 0 {
160            return Err(DecoderWorkspaceError::InvalidWindowSize);
161        }
162        DecoderScratch::workspace_size(max_window_size, max_dictionary_size)
163            .map_err(map_arena_error)
164    }
165
166    pub fn new(
167        storage: &'storage mut [u8],
168        max_window_size: usize,
169        max_dictionary_size: usize,
170    ) -> Result<Self, DecoderWorkspaceError> {
171        Self::new_uninit(
172            initialized_bytes_as_uninit(storage),
173            max_window_size,
174            max_dictionary_size,
175        )
176    }
177
178    /// Constructs a decoder without initializing caller-provided bytes.
179    pub fn new_uninit(
180        storage: &'storage mut [MaybeUninit<u8>],
181        max_window_size: usize,
182        max_dictionary_size: usize,
183    ) -> Result<Self, DecoderWorkspaceError> {
184        let required = Self::required_size(max_window_size, max_dictionary_size)?;
185        if storage.len() < required {
186            return Err(DecoderWorkspaceError::InsufficientStorage {
187                required,
188                provided: storage.len(),
189            });
190        }
191        let mut arena = Arena::new(storage);
192        let scratch = DecoderScratch::new_in(&mut arena, max_window_size, max_dictionary_size)
193            .map_err(map_arena_error)?;
194        let mut decoder = FrameDecoder::new();
195        decoder.install_prepared_scratch(scratch);
196        Ok(Self {
197            decoder,
198            max_window_size,
199            max_dictionary_size,
200            workspace_bytes: storage.len(),
201            marker: PhantomData,
202        })
203    }
204
205    pub fn decode_into(
206        &mut self,
207        input: &[u8],
208        output: &mut [u8],
209    ) -> Result<usize, DecoderWorkspaceError> {
210        self.decoder
211            .decode_all_with_window_limit(input, output, self.max_window_size as u64)
212            .map_err(DecoderWorkspaceError::Decode)
213    }
214
215    /// Decodes with a formatted dictionary directly from caller storage.
216    pub fn decode_into_with_dictionary(
217        &mut self,
218        input: &[u8],
219        dictionary: &[u8],
220        output: &mut [u8],
221    ) -> Result<usize, DecoderWorkspaceError> {
222        ensure_dictionary_size(dictionary.len(), self.max_dictionary_size)?;
223        self.decoder
224            .decode_all_with_window_limit_and_dictionary(
225                input,
226                output,
227                self.max_window_size as u64,
228                Some(PreparedDictionaryBytes::Formatted(dictionary)),
229            )
230            .map_err(DecoderWorkspaceError::Decode)
231    }
232
233    /// Decodes a frame prepared with a raw-content dictionary.
234    pub fn decode_into_with_raw_dictionary(
235        &mut self,
236        input: &[u8],
237        dictionary: &[u8],
238        output: &mut [u8],
239    ) -> Result<usize, DecoderWorkspaceError> {
240        ensure_dictionary_size(dictionary.len(), self.max_dictionary_size)?;
241        self.decoder
242            .decode_all_with_window_limit_and_dictionary(
243                input,
244                output,
245                self.max_window_size as u64,
246                Some(PreparedDictionaryBytes::Raw(dictionary)),
247            )
248            .map_err(DecoderWorkspaceError::Decode)
249    }
250
251    pub const fn max_window_size(&self) -> usize {
252        self.max_window_size
253    }
254
255    pub const fn max_dictionary_size(&self) -> usize {
256        self.max_dictionary_size
257    }
258
259    pub const fn workspace_bytes(&self) -> usize {
260        self.workspace_bytes
261    }
262}
263
264fn initialized_bytes_as_uninit(storage: &mut [u8]) -> &mut [MaybeUninit<u8>] {
265    // SAFETY: `MaybeUninit<u8>` has the same layout as `u8`, and treating
266    // initialized bytes as possibly uninitialized only weakens the invariant.
267    unsafe { core::slice::from_raw_parts_mut(storage.as_mut_ptr().cast(), storage.len()) }
268}
269
270fn ensure_dictionary_size(provided: usize, maximum: usize) -> Result<(), DecoderWorkspaceError> {
271    if provided > maximum {
272        Err(DecoderWorkspaceError::DictionaryTooLarge { maximum, provided })
273    } else {
274        Ok(())
275    }
276}
277
278fn map_arena_error(error: ArenaError) -> DecoderWorkspaceError {
279    match error {
280        ArenaError::InsufficientStorage { required, provided } => {
281            DecoderWorkspaceError::InsufficientStorage { required, provided }
282        }
283        ArenaError::CapacityExceeded { capacity } => DecoderWorkspaceError::InsufficientStorage {
284            required: capacity.saturating_add(1),
285            provided: capacity,
286        },
287        ArenaError::SizeOverflow => DecoderWorkspaceError::SizeOverflow,
288    }
289}