Skip to main content

structured_zstd/decoding/
workspace.rs

1//! Streaming decoder that runs entirely in caller-owned memory.
2//!
3//! [`WorkspaceDecoder`] decodes a stream of zstd frames chunk by chunk without
4//! touching the heap: the window, the entropy tables, the literals and the
5//! staging for blocks split across input chunks all live in one byte slice the
6//! caller supplies. [`workspace_bytes`] says how large that slice must be for a
7//! given window limit, so the caller can admit the memory in its own accounting
8//! before decoding starts, and keep the charge for as long as the slice lives.
9//!
10//! Every step is bounded three ways, all chosen by the caller: the window a
11//! frame may declare and the total output ([`DecodeLimits`]), and the work a
12//! step may do ([`WorkBudget`]). A refusal is reported apart from malformed
13//! input, an unsupported feature, and input that ends mid-frame, and it ends
14//! the stream: every later step fails with [`WorkspaceDecodeError::Failed`]
15//! until [`WorkspaceDecoder::reset`].
16//!
17//! Frames that name a dictionary are refused with
18//! [`Unsupported::Dictionary`]; this decoder keeps no dictionary registry.
19//! With the `legacy` feature, v0.5-v0.7 frames are refused with
20//! `Unsupported::LegacyFrame`, since their decoders allocate.
21//!
22//! The decoding itself is the same code the other decoders run, so output is
23//! identical to theirs.
24//!
25//! # Examples
26//!
27//! ```
28//! use structured_zstd::decoding::workspace::{
29//!     workspace_bytes, DecodeLimits, StepState, WorkBudget, WorkspaceDecoder,
30//! };
31//! use structured_zstd::encoding::{compress_slice_to_vec, CompressionLevel};
32//!
33//! let data = b"the same words, the same words, the same words".repeat(100);
34//! let compressed = compress_slice_to_vec(&data, CompressionLevel::Default);
35//!
36//! let limits = DecodeLimits::new(1 << 20).with_max_output(1 << 20);
37//! let mut workspace = vec![0u8; workspace_bytes(limits.max_window_size).unwrap()];
38//! let mut decoder = WorkspaceDecoder::new(&mut workspace, limits).unwrap();
39//! let mut work = WorkBudget::new(1 << 24);
40//!
41//! let mut decoded = Vec::new();
42//! let mut out = [0u8; 512];
43//! let mut input = &compressed[..];
44//! loop {
45//!     // Feed at most 100 bytes at a time; say when nothing more will come.
46//!     let chunk = &input[..input.len().min(100)];
47//!     let last = chunk.len() == input.len();
48//!     let progress = decoder.step(chunk, last, &mut out, &mut work).unwrap();
49//!     decoded.extend_from_slice(&out[..progress.produced]);
50//!     input = &input[progress.consumed..];
51//!     if progress.state == StepState::Done {
52//!         break;
53//!     }
54//! }
55//! assert_eq!(decoded, data);
56//! ```
57
58use super::block_decoder::{self, BlockDecoder};
59use super::buffer_backend::{BufferBackend, WILDCOPY_OVERLENGTH};
60use super::decode_buffer::DecodeBuffer;
61use super::errors::{
62    BlockHeaderReadError, DecodeBlockContentError, FrameHeaderError, ReadFrameHeaderError,
63};
64use super::frame;
65use super::ringbuffer::RingBuffer;
66use super::scratch::{ByteScratch, FSEScratch, HuffmanScratch, Workspace, WorkspaceRef};
67use super::user_slice_buf::UserSliceBackend;
68use crate::blocks::block::{BlockHeader, BlockType};
69use crate::common::{MAGIC_NUM, MAX_DECODER_WINDOW_SIZE, MIN_WINDOW_SIZE};
70use core::marker::PhantomData;
71use core::ptr::NonNull;
72
73/// Work units charged before a compressed block decodes, on top of its input
74/// bytes and its output bound: the most its entropy-table builds can cost
75/// (one Huffman table of `1 << 12` entries and the literal-length,
76/// match-length and offset tables at `1 << 9`, `1 << 9` and `1 << 8`).
77pub const COMPRESSED_BLOCK_UNITS: u64 = (1 << crate::huff0::MAX_MAX_NUM_BITS)
78    + (1 << super::sequence_section_decoder::LL_MAX_LOG)
79    + (1 << super::sequence_section_decoder::ML_MAX_LOG)
80    + (1 << super::sequence_section_decoder::OF_MAX_LOG);
81
82/// Largest frame header: magic, descriptor, window byte, 4-byte dictionary id
83/// and 8-byte content size (RFC 8878 3.1.1.1).
84const MAX_FRAME_HEADER: usize = 18;
85
86/// Alignment the entropy tables need at the start of the workspace.
87const TABLE_ALIGN: usize = {
88    let fse = core::mem::align_of::<FSEScratch>();
89    let huf = core::mem::align_of::<HuffmanScratch>();
90    if fse > huf { fse } else { huf }
91};
92
93/// Bytes of workspace a [`WorkspaceDecoder`] needs to decode frames whose
94/// window is at most `max_window_size`.
95///
96/// The figure covers every byte the decoder writes: the window with room for
97/// the block being decoded, the entropy tables, the literals, the staging for a
98/// block split across input chunks, and alignment slack, so any sub-slice of
99/// that length works whatever its address.
100///
101/// # Errors
102///
103/// [`WorkspaceConfigError::WindowLimitOutOfRange`] when `max_window_size` lies
104/// outside 1 KiB ..= [`MAX_DECODER_WINDOW_SIZE`],
105/// and [`WorkspaceConfigError::SizeOverflow`] when the size does not fit in
106/// `usize` on this target.
107///
108/// # Examples
109///
110/// ```
111/// use structured_zstd::decoding::workspace::workspace_bytes;
112/// let small = workspace_bytes(64 * 1024).unwrap();
113/// let large = workspace_bytes(8 << 20).unwrap();
114/// assert!(small < large);
115/// assert!(workspace_bytes(100).is_err());
116/// ```
117pub fn workspace_bytes(max_window_size: u64) -> Result<usize, WorkspaceConfigError> {
118    Layout::for_window(max_window_size).map(|layout| layout.total)
119}
120
121/// Where each part of the workspace sits, relative to its aligned start.
122struct Layout {
123    huf_offset: usize,
124    tables_len: usize,
125    ring_cap: usize,
126    literals_len: usize,
127    staging_len: usize,
128    total: usize,
129}
130
131impl Layout {
132    fn for_window(max_window_size: u64) -> Result<Layout, WorkspaceConfigError> {
133        use WorkspaceConfigError::SizeOverflow;
134        if !(MIN_WINDOW_SIZE..=MAX_DECODER_WINDOW_SIZE).contains(&max_window_size) {
135            return Err(WorkspaceConfigError::WindowLimitOutOfRange {
136                requested: max_window_size,
137            });
138        }
139        let window = usize::try_from(max_window_size).map_err(|_| SizeOverflow)?;
140        let block_max = block_decoder::block_maximum(window);
141        let huf_offset = core::mem::size_of::<FSEScratch>()
142            .next_multiple_of(core::mem::align_of::<HuffmanScratch>());
143        let tables_len = huf_offset + core::mem::size_of::<HuffmanScratch>();
144        // The window, plus two block maxima: a block's matches stop at one
145        // block past the window (the per-block ceiling), and literals copied
146        // after its last match can add up to one more before the block-size
147        // check rejects it. One more slot is the ring's empty/full sentinel.
148        let ring_cap = window
149            .checked_add(2 * block_max)
150            .and_then(|n| n.checked_add(1))
151            .ok_or(SizeOverflow)?;
152        let literals_len = block_max + WILDCOPY_OVERLENGTH;
153        let staging_len = block_max;
154        let total = [
155            tables_len,
156            ring_cap,
157            WILDCOPY_OVERLENGTH,
158            literals_len,
159            staging_len,
160        ]
161        .iter()
162        .try_fold(TABLE_ALIGN - 1, |sum, &part| sum.checked_add(part))
163        .ok_or(SizeOverflow)?;
164        Ok(Layout {
165            huf_offset,
166            tables_len,
167            ring_cap,
168            literals_len,
169            staging_len,
170            total,
171        })
172    }
173}
174
175/// Limits a [`WorkspaceDecoder`] holds every frame of the stream to.
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub struct DecodeLimits {
178    /// Largest window a frame may declare. Also sizes the workspace; see
179    /// [`workspace_bytes`].
180    pub max_window_size: u64,
181    /// Most decompressed bytes the whole stream may produce.
182    pub max_output: u64,
183}
184
185impl DecodeLimits {
186    /// Limits with the given window ceiling and no output limit.
187    pub const fn new(max_window_size: u64) -> Self {
188        Self {
189            max_window_size,
190            max_output: u64::MAX,
191        }
192    }
193
194    /// The same limits with the total output capped at `max_output` bytes.
195    pub const fn with_max_output(self, max_output: u64) -> Self {
196        Self { max_output, ..self }
197    }
198}
199
200/// Work a caller allows its decode steps, in units charged before the work
201/// they pay for runs.
202///
203/// One unit is charged per input byte consumed (frame and block headers,
204/// block contents, skippable frames, checksums), and one per byte a block may
205/// produce, before it is produced: a Raw block's bytes as each part of it is
206/// copied, an RLE block's stated size, and a compressed block's frame block
207/// maximum plus [`COMPRESSED_BLOCK_UNITS`] for its table builds. A charge the
208/// budget cannot cover refuses the step before that work starts.
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210pub struct WorkBudget {
211    remaining: u64,
212}
213
214impl WorkBudget {
215    /// A budget of `units`.
216    pub const fn new(units: u64) -> Self {
217        Self { remaining: units }
218    }
219
220    /// Units still available.
221    pub const fn remaining(&self) -> u64 {
222        self.remaining
223    }
224
225    /// Add `units` to the budget, stopping at `u64::MAX`, which is already
226    /// more work than any stream can ask for.
227    pub fn add(&mut self, units: u64) {
228        self.remaining = self.remaining.saturating_add(units);
229    }
230
231    fn charge(&mut self, units: u64) -> Result<(), WorkspaceDecodeError> {
232        match self.remaining.checked_sub(units) {
233            Some(left) => {
234                self.remaining = left;
235                Ok(())
236            }
237            None => Err(WorkspaceDecodeError::Limit(LimitExceeded::Work {
238                needed: units,
239                remaining: self.remaining,
240            })),
241        }
242    }
243}
244
245/// What one [`WorkspaceDecoder::step`] did.
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub struct Progress {
248    /// Input bytes consumed; the next step starts after them.
249    pub consumed: usize,
250    /// Bytes written to the front of the output slice.
251    pub produced: usize,
252    /// What the decoder needs next.
253    pub state: StepState,
254}
255
256/// Why a [`WorkspaceDecoder::step`] returned.
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258#[non_exhaustive]
259pub enum StepState {
260    /// Every input byte was consumed; call again with more, or with the
261    /// final-input flag set once there is no more.
262    NeedInput,
263    /// The output slice is full; call again with room.
264    NeedOutput,
265    /// The input was final and every frame in it is decoded and delivered.
266    Done,
267}
268
269/// A workspace size or limit [`workspace_bytes`] or [`WorkspaceDecoder::new`]
270/// refused.
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272#[non_exhaustive]
273pub enum WorkspaceConfigError {
274    /// The window limit lies outside 1 KiB ..=
275    /// [`MAX_DECODER_WINDOW_SIZE`].
276    WindowLimitOutOfRange {
277        /// The limit asked for.
278        requested: u64,
279    },
280    /// The workspace size does not fit in `usize` on this target.
281    SizeOverflow,
282    /// The workspace slice is shorter than [`workspace_bytes`] asks for.
283    WorkspaceTooSmall {
284        /// Bytes the limits require.
285        needed: usize,
286        /// Bytes supplied.
287        provided: usize,
288    },
289}
290
291impl core::fmt::Display for WorkspaceConfigError {
292    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
293        match self {
294            Self::WindowLimitOutOfRange { requested } => write!(
295                f,
296                "window limit {requested} lies outside {MIN_WINDOW_SIZE}..={MAX_DECODER_WINDOW_SIZE}"
297            ),
298            Self::SizeOverflow => write!(f, "workspace size does not fit in usize"),
299            Self::WorkspaceTooSmall { needed, provided } => write!(
300                f,
301                "workspace of {provided} bytes is smaller than the {needed} the limits require"
302            ),
303        }
304    }
305}
306
307#[cfg(feature = "std")]
308impl std::error::Error for WorkspaceConfigError {}
309
310/// Why a [`WorkspaceDecoder::step`] failed. Every variant ends the stream:
311/// later steps return [`Self::Failed`] until [`WorkspaceDecoder::reset`].
312#[derive(Debug)]
313#[non_exhaustive]
314pub enum WorkspaceDecodeError {
315    /// A caller-chosen limit refused the stream.
316    Limit(LimitExceeded),
317    /// The input is not a valid zstd stream.
318    Malformed(MalformedInput),
319    /// The input is valid but needs something this decoder does not do.
320    Unsupported(Unsupported),
321    /// The input was marked final in the middle of a frame.
322    Truncated,
323    /// An earlier step failed; call [`WorkspaceDecoder::reset`] first.
324    Failed,
325}
326
327/// A limit from [`DecodeLimits`] or [`WorkBudget`] that refused the stream.
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329#[non_exhaustive]
330pub enum LimitExceeded {
331    /// A frame declared a window above [`DecodeLimits::max_window_size`].
332    Window {
333        /// The window the frame declared.
334        requested: u64,
335        /// The limit.
336        limit: u64,
337    },
338    /// The stream would produce more than [`DecodeLimits::max_output`].
339    Output {
340        /// The limit.
341        limit: u64,
342    },
343    /// The next unit of work costs more than the budget has left.
344    Work {
345        /// Units the refused work costs.
346        needed: u64,
347        /// Units the budget had.
348        remaining: u64,
349    },
350}
351
352/// Something this decoder does not do, met in otherwise valid input.
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354#[non_exhaustive]
355pub enum Unsupported {
356    /// The frame needs dictionary `id`, and this decoder takes none.
357    Dictionary {
358        /// The dictionary id the frame header names.
359        id: u32,
360    },
361    /// A v0.5, v0.6 or v0.7 frame, which the owned decoders read and this
362    /// one does not: their decoders allocate. Upstream's static context
363    /// refuses them likewise.
364    #[cfg(feature = "legacy")]
365    LegacyFrame {
366        /// The format of the frame.
367        version: crate::decoding::LegacyVersion,
368    },
369}
370
371/// How the input breaks the zstd format.
372#[derive(Debug)]
373#[non_exhaustive]
374pub enum MalformedInput {
375    /// A frame header, or the magic number before it, is invalid.
376    FrameHeader(ReadFrameHeaderError),
377    /// The frame header declares an invalid window.
378    Window(FrameHeaderError),
379    /// A block header is invalid.
380    BlockHeader(BlockHeaderReadError),
381    /// A block states a size above its frame's block maximum
382    /// (RFC 8878 3.1.1.2.4).
383    BlockTooLarge {
384        /// The size the block header states.
385        size: usize,
386        /// The frame's block maximum.
387        maximum: usize,
388    },
389    /// A block's content does not decode.
390    Block(DecodeBlockContentError),
391    /// The frame's blocks do not add up to its declared content size.
392    ContentSize {
393        /// The size the frame header declares.
394        declared: u64,
395        /// The size its blocks produce (so far, when they overrun it).
396        produced: u64,
397    },
398    /// The content checksum does not match the decoded output.
399    Checksum {
400        /// The checksum the frame carries.
401        expected: u32,
402        /// The low 32 bits of the XXH64 of the decoded frame.
403        calculated: u32,
404    },
405}
406
407impl core::fmt::Display for WorkspaceDecodeError {
408    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
409        match self {
410            Self::Limit(limit) => match limit {
411                LimitExceeded::Window { requested, limit } => write!(
412                    f,
413                    "frame window of {requested} bytes exceeds the {limit}-byte limit"
414                ),
415                LimitExceeded::Output { limit } => {
416                    write!(f, "output exceeds the {limit}-byte limit")
417                }
418                LimitExceeded::Work { needed, remaining } => write!(
419                    f,
420                    "work of {needed} units exceeds the {remaining} remaining"
421                ),
422            },
423            Self::Malformed(malformed) => match malformed {
424                MalformedInput::FrameHeader(e) => write!(f, "invalid frame header: {e}"),
425                MalformedInput::Window(e) => write!(f, "invalid frame window: {e}"),
426                MalformedInput::BlockHeader(e) => write!(f, "invalid block header: {e}"),
427                MalformedInput::BlockTooLarge { size, maximum } => write!(
428                    f,
429                    "block of {size} bytes exceeds the frame's block maximum of {maximum}"
430                ),
431                MalformedInput::Block(e) => write!(f, "invalid block: {e}"),
432                MalformedInput::ContentSize { declared, produced } => write!(
433                    f,
434                    "frame declares {declared} bytes but its blocks produce {produced}"
435                ),
436                MalformedInput::Checksum {
437                    expected,
438                    calculated,
439                } => write!(
440                    f,
441                    "content checksum {calculated:#010x} does not match the stored {expected:#010x}"
442                ),
443            },
444            Self::Unsupported(Unsupported::Dictionary { id }) => {
445                write!(
446                    f,
447                    "frame needs dictionary {id}, which this decoder does not take"
448                )
449            }
450            #[cfg(feature = "legacy")]
451            Self::Unsupported(Unsupported::LegacyFrame { version }) => write!(
452                f,
453                "v0.{} frame, which this decoder does not read",
454                version.minor()
455            ),
456            Self::Truncated => write!(f, "input ended in the middle of a frame"),
457            Self::Failed => write!(f, "an earlier step failed; reset the decoder"),
458        }
459    }
460}
461
462#[cfg(feature = "std")]
463impl std::error::Error for WorkspaceDecodeError {}
464
465/// A fixed region of the workspace standing in for the `Vec` the owned
466/// decoders keep for literals.
467///
468/// A push past the end is dropped but still counted, so the literals decoder's
469/// own count check rejects the block as it would with a growable buffer. Every
470/// other write is sized by the caller below the region's length; the asserts
471/// are what keeps a broken size argument from writing outside it.
472struct SliceScratch<'w> {
473    buf: &'w mut [u8],
474    len: usize,
475}
476
477impl ByteScratch for SliceScratch<'_> {
478    #[inline(always)]
479    fn len(&self) -> usize {
480        self.len
481    }
482    #[inline(always)]
483    fn clear(&mut self) {
484        self.len = 0;
485    }
486    #[inline(always)]
487    fn truncate(&mut self, len: usize) {
488        self.len = self.len.min(len);
489    }
490    #[inline(always)]
491    fn reserve(&mut self, additional: usize) {
492        assert!(
493            self.len
494                .checked_add(additional)
495                .is_some_and(|end| end <= self.buf.len()),
496            "literals exceed the workspace region sized for a block"
497        );
498    }
499    #[inline(always)]
500    fn push(&mut self, byte: u8) {
501        if let Some(slot) = self.buf.get_mut(self.len) {
502            *slot = byte;
503        }
504        self.len += 1;
505    }
506    fn resize(&mut self, new_len: usize, value: u8) {
507        if new_len > self.len {
508            self.buf[self.len..new_len].fill(value);
509        }
510        self.len = new_len;
511    }
512    fn extend_from_slice(&mut self, bytes: &[u8]) {
513        let end = self.len + bytes.len();
514        self.buf[self.len..end].copy_from_slice(bytes);
515        self.len = end;
516    }
517    #[inline(always)]
518    fn as_mut_ptr(&mut self) -> *mut u8 {
519        self.buf.as_mut_ptr()
520    }
521    #[inline(always)]
522    unsafe fn set_len(&mut self, new_len: usize) {
523        assert!(new_len <= self.buf.len());
524        self.len = new_len;
525    }
526    #[inline(always)]
527    fn as_slice(&self) -> &[u8] {
528        &self.buf[..self.len.min(self.buf.len())]
529    }
530    #[inline(always)]
531    fn as_mut_slice(&mut self) -> &mut [u8] {
532        let len = self.len.min(self.buf.len());
533        &mut self.buf[..len]
534    }
535}
536
537/// The current frame's window over the workspace's window region, chosen per
538/// frame as `FrameDecoder` chooses its backend: a single-segment frame's whole
539/// content is its window, so it decodes linearly into the region, the shape
540/// `decode_all` writes into a caller's slice; any other frame decodes into a
541/// ring.
542enum Window<'w> {
543    /// No frame has started since construction or reset.
544    Idle,
545    Ring(DecodeBuffer<RingBuffer>),
546    Linear(DecodeBuffer<UserSliceBackend<'w>>),
547}
548
549/// Run `$body` with `$buffer` bound to the active window's buffer, or yield
550/// `$idle` when no frame has started.
551macro_rules! with_window {
552    ($window:expr, $buffer:ident => $body:expr, idle => $idle:expr) => {
553        match $window {
554            Window::Ring($buffer) => $body,
555            Window::Linear($buffer) => $body,
556            Window::Idle => $idle,
557        }
558    };
559}
560
561/// Output bytes `buffer` has produced this frame. The linear backend's inline
562/// sequence path skips the running counter, so its write cursor counts there;
563/// the ring keeps the counter, its cursor wrapping.
564fn produced_so_far<B: BufferBackend>(buffer: &DecodeBuffer<B>) -> u64 {
565    if B::INLINE_EXEC_MAINTAINS_OUTPUT_COUNTER {
566        buffer.total_output()
567    } else {
568        buffer.buffer_ref().tail() as u64
569    }
570}
571
572/// The state the shared block decoder works on for one block: the tables and
573/// literals in the workspace, and the active window's buffer.
574struct BlockScratch<'a, 'w, B: BufferBackend> {
575    huf: &'a mut HuffmanScratch,
576    fse: &'a mut FSEScratch,
577    buffer: &'a mut DecodeBuffer<B>,
578    offset_hist: &'a mut [u32; 3],
579    literals: &'a mut SliceScratch<'w>,
580    /// The Read-sourced block path's buffer; this decoder stages blocks
581    /// itself and hands them over as slices, so it stays empty.
582    block_content: &'a mut SliceScratch<'w>,
583}
584
585impl<'w, B: BufferBackend> Workspace for BlockScratch<'_, 'w, B> {
586    type Backend = B;
587    type Bytes = SliceScratch<'w>;
588    fn split(&mut self) -> WorkspaceRef<'_, B, SliceScratch<'w>> {
589        WorkspaceRef {
590            huf: &mut *self.huf,
591            fse: &mut *self.fse,
592            buffer: &mut *self.buffer,
593            offset_hist: &mut *self.offset_hist,
594            literals_buffer: &mut *self.literals,
595            block_content_buffer: &mut *self.block_content,
596        }
597    }
598}
599
600/// Decode one block whose whole content is `content`, returning the bytes it
601/// produced.
602fn run_block<B: BufferBackend>(
603    decoder: &mut BlockDecoder,
604    scratch: &mut BlockScratch<'_, '_, B>,
605    header: &BlockHeader,
606    mut content: &[u8],
607) -> Result<u64, DecodeBlockContentError> {
608    let before = produced_so_far(scratch.buffer);
609    decoder.decode_block_content_from_slice(header, scratch, None, &mut content)?;
610    Ok(produced_so_far(scratch.buffer) - before)
611}
612
613/// What the decoder reads next.
614#[derive(Clone, Copy, PartialEq, Eq, Debug)]
615enum Phase {
616    /// The 4-byte magic number of the next frame.
617    Magic,
618    /// The descriptor byte, which gives the header's length.
619    Descriptor,
620    /// The rest of the frame header.
621    FrameHeader,
622    /// The 4-byte length of a skippable frame.
623    SkippableSize,
624    /// The payload of a skippable frame.
625    Skipping { remaining: u64 },
626    /// A 3-byte block header.
627    BlockHeader,
628    /// A block's content.
629    BlockBody(PendingBlock),
630    /// The 4-byte content checksum.
631    Checksum,
632}
633
634/// A block whose header was read, carried while its content arrives.
635#[derive(Clone, Copy, PartialEq, Eq, Debug)]
636struct PendingBlock {
637    last_block: bool,
638    block_type: BlockType,
639    decompressed_size: u32,
640    content_size: u32,
641}
642
643impl PendingBlock {
644    fn header(self) -> BlockHeader {
645        BlockHeader {
646            last_block: self.last_block,
647            block_type: self.block_type,
648            decompressed_size: self.decompressed_size,
649            content_size: self.content_size,
650        }
651    }
652}
653
654/// The frame being decoded.
655#[derive(Clone, Copy)]
656struct FrameState {
657    declared_size: Option<u64>,
658    has_checksum: bool,
659    block_maximum: usize,
660    produced: u64,
661}
662
663/// Allocation-free streaming zstd decoder over a caller-owned workspace.
664///
665/// See the [module documentation](self) for the contract and an example.
666pub struct WorkspaceDecoder<'w> {
667    huf: &'w mut HuffmanScratch,
668    fse: &'w mut FSEScratch,
669    offset_hist: [u32; 3],
670    literals: SliceScratch<'w>,
671    block_content: SliceScratch<'w>,
672    window: Window<'w>,
673    /// The workspace region every frame's window is laid over, held as a
674    /// pointer because frames alternate between a ring (which addresses it by
675    /// pointer) and a linear buffer (which borrows it), never both at once.
676    region: NonNull<u8>,
677    /// `region`'s length: the ring's capacity plus the copiers' slack.
678    region_len: usize,
679    ring_cap: usize,
680    _region: PhantomData<&'w mut [u8]>,
681    staging: &'w mut [u8],
682    staged: usize,
683    block_decoder: BlockDecoder,
684    kernel: crate::cpu_kernel::CpuKernelTag,
685    limits: DecodeLimits,
686    phase: Phase,
687    header: [u8; MAX_FRAME_HEADER],
688    header_len: usize,
689    header_target: usize,
690    frame: FrameState,
691    /// Every block of the current frame is decoded, so all of the window is
692    /// output to deliver rather than history to keep.
693    blocks_done: bool,
694    stream_produced: u64,
695    failed: bool,
696}
697
698// SAFETY: `region` points into the `&'w mut [u8]` workspace this decoder
699// borrows exclusively for `'w` (`_region` records that borrow), and every other
700// field is `Send`. Moving the decoder moves that exclusive borrow with it, as
701// moving the `&mut [u8]` itself would.
702unsafe impl Send for WorkspaceDecoder<'_> {}
703
704impl<'w> WorkspaceDecoder<'w> {
705    /// Build a decoder in `workspace`, which must hold at least
706    /// [`workspace_bytes`]`(limits.max_window_size)` bytes. Nothing is
707    /// allocated, now or later; the workspace stays borrowed for the decoder's
708    /// life.
709    ///
710    /// # Errors
711    ///
712    /// [`WorkspaceConfigError`] when the window limit is out of range or the
713    /// workspace is too small for it.
714    pub fn new(
715        workspace: &'w mut [u8],
716        limits: DecodeLimits,
717    ) -> Result<Self, WorkspaceConfigError> {
718        let layout = Layout::for_window(limits.max_window_size)?;
719        let provided = workspace.len();
720        if provided < layout.total {
721            return Err(WorkspaceConfigError::WorkspaceTooSmall {
722                needed: layout.total,
723                provided,
724            });
725        }
726        // `layout.total` holds `TABLE_ALIGN - 1` bytes of slack, and the
727        // offset a byte pointer needs to reach an alignment is below it.
728        let skip = workspace.as_ptr().align_offset(TABLE_ALIGN);
729        debug_assert!(skip < TABLE_ALIGN);
730        let rest = &mut workspace[skip..];
731        let (tables, rest) = rest.split_at_mut(layout.tables_len);
732        let (ring, rest) = rest.split_at_mut(layout.ring_cap + WILDCOPY_OVERLENGTH);
733        let (literals, rest) = rest.split_at_mut(layout.literals_len);
734        let staging = &mut rest[..layout.staging_len];
735
736        let tables = tables.as_mut_ptr();
737        let fse_ptr = tables.cast::<FSEScratch>();
738        // SAFETY: `tables` starts at a `TABLE_ALIGN` boundary and spans
739        // `tables_len` bytes: the FSE scratch at its start and the Huffman
740        // scratch at `huf_offset`, a multiple of its alignment, both in bounds.
741        // Each is fully initialised in place before a reference to it exists,
742        // and the bytes are reached through nothing else while `workspace`
743        // stays borrowed for `'w`. Neither type owns heap memory under this
744        // feature, so never running their destructors leaks nothing.
745        let (fse, huf) = unsafe {
746            let huf_ptr = tables.add(layout.huf_offset).cast::<HuffmanScratch>();
747            FSEScratch::init_in_place(fse_ptr);
748            HuffmanScratch::init_in_place(huf_ptr);
749            (&mut *fse_ptr, &mut *huf_ptr)
750        };
751        let region_len = ring.len();
752        let region = NonNull::from(ring).cast::<u8>();
753
754        let kernel = crate::cpu_kernel::detect_cpu_kernel();
755        Ok(WorkspaceDecoder {
756            huf,
757            fse,
758            offset_hist: [1, 4, 8],
759            literals: SliceScratch {
760                buf: literals,
761                len: 0,
762            },
763            block_content: SliceScratch {
764                buf: &mut [],
765                len: 0,
766            },
767            window: Window::Idle,
768            region,
769            region_len,
770            ring_cap: layout.ring_cap,
771            _region: PhantomData,
772            staging,
773            staged: 0,
774            block_decoder: block_decoder::with_kernel(kernel),
775            kernel,
776            limits,
777            phase: Phase::Magic,
778            header: [0; MAX_FRAME_HEADER],
779            header_len: 0,
780            header_target: 4,
781            frame: FrameState {
782                declared_size: None,
783                has_checksum: false,
784                block_maximum: 0,
785                produced: 0,
786            },
787            blocks_done: false,
788            stream_produced: 0,
789            failed: false,
790        })
791    }
792
793    /// Start a new stream: forget the current one, including a failure, and
794    /// expect a frame's magic number next. The workspace is reused as it is.
795    pub fn reset(&mut self) {
796        self.window = Window::Idle;
797        self.literals.clear();
798        self.staged = 0;
799        self.block_decoder = block_decoder::with_kernel(self.kernel);
800        self.phase = Phase::Magic;
801        self.header_len = 0;
802        self.header_target = 4;
803        self.blocks_done = false;
804        self.stream_produced = 0;
805        self.failed = false;
806    }
807
808    /// Decompressed bytes the stream has produced so far, delivered or not.
809    pub fn total_output(&self) -> u64 {
810        self.stream_produced
811    }
812
813    /// Decode from `input` into `output` until one of them runs out or the
814    /// stream ends.
815    ///
816    /// `input_is_final` says no input follows this slice. Without it, running
817    /// out of input asks for more ([`StepState::NeedInput`]); with it, input
818    /// that ends between frames finishes the stream ([`StepState::Done`]) and
819    /// input that ends inside one is [`WorkspaceDecodeError::Truncated`].
820    /// Unconsumed input must be passed again, first, on the next call.
821    ///
822    /// Concatenated frames decode one after another, and skippable frames are
823    /// consumed without output.
824    ///
825    /// # Errors
826    ///
827    /// See [`WorkspaceDecodeError`]. Any error ends the stream; the output
828    /// already written by this call is then not counted in a [`Progress`].
829    pub fn step(
830        &mut self,
831        input: &[u8],
832        input_is_final: bool,
833        output: &mut [u8],
834        work: &mut WorkBudget,
835    ) -> Result<Progress, WorkspaceDecodeError> {
836        if self.failed {
837            return Err(WorkspaceDecodeError::Failed);
838        }
839        let result = self.step_inner(input, input_is_final, output, work);
840        if result.is_err() {
841            self.failed = true;
842        }
843        result
844    }
845
846    fn step_inner(
847        &mut self,
848        input: &[u8],
849        input_is_final: bool,
850        output: &mut [u8],
851        work: &mut WorkBudget,
852    ) -> Result<Progress, WorkspaceDecodeError> {
853        let mut consumed = 0;
854        let mut produced = 0;
855        loop {
856            produced += self.deliver(&mut output[produced..]);
857            if self.pending() != 0 {
858                return Ok(Progress {
859                    consumed,
860                    produced,
861                    state: StepState::NeedOutput,
862                });
863            }
864            let rest = &input[consumed..];
865            match self.phase {
866                Phase::Skipping { remaining } => {
867                    if remaining == 0 {
868                        self.expect_frame();
869                        continue;
870                    }
871                    if rest.is_empty() {
872                        return self.out_of_input(input_is_final, consumed, produced);
873                    }
874                    let take = rest
875                        .len()
876                        .min(usize::try_from(remaining).unwrap_or(usize::MAX));
877                    work.charge(take as u64)?;
878                    consumed += take;
879                    self.phase = Phase::Skipping {
880                        remaining: remaining - take as u64,
881                    };
882                }
883                Phase::BlockBody(block) if block.block_type == BlockType::Raw => {
884                    // A Raw block's content is its output: it goes straight
885                    // into the window as it arrives, never through staging.
886                    // `staged` counts the bytes written so far.
887                    let remaining = block.content_size as usize - self.staged;
888                    if remaining == 0 {
889                        self.finish_block(block)?;
890                        continue;
891                    }
892                    if rest.is_empty() {
893                        return self.out_of_input(input_is_final, consumed, produced);
894                    }
895                    let take = rest.len().min(remaining);
896                    // One unit for the input byte, one for the output byte.
897                    work.charge(2 * take as u64)?;
898                    self.push_raw(&rest[..take])?;
899                    self.staged += take;
900                    consumed += take;
901                }
902                Phase::BlockBody(block) => {
903                    let size = block.content_size as usize;
904                    if self.staged == 0 && rest.len() >= size {
905                        work.charge(size as u64)?;
906                        consumed += size;
907                        self.decode_block(block, &rest[..size], work)?;
908                        continue;
909                    }
910                    if rest.is_empty() {
911                        return self.out_of_input(input_is_final, consumed, produced);
912                    }
913                    let take = rest.len().min(size - self.staged);
914                    work.charge(take as u64)?;
915                    self.staging[self.staged..self.staged + take].copy_from_slice(&rest[..take]);
916                    self.staged += take;
917                    consumed += take;
918                    if self.staged == size {
919                        self.staged = 0;
920                        // The staging region is a separate field from the
921                        // state the block decoder writes, so it lends the
922                        // content in place.
923                        let staging = core::mem::take(&mut self.staging);
924                        let decoded = self.decode_block(block, &staging[..size], work);
925                        self.staging = staging;
926                        decoded?;
927                    }
928                }
929                _ => {
930                    if self.header_len < self.header_target {
931                        if rest.is_empty() {
932                            return self.out_of_input(input_is_final, consumed, produced);
933                        }
934                        let take = rest.len().min(self.header_target - self.header_len);
935                        work.charge(take as u64)?;
936                        self.header[self.header_len..self.header_len + take]
937                            .copy_from_slice(&rest[..take]);
938                        self.header_len += take;
939                        consumed += take;
940                    }
941                    if self.header_len == self.header_target {
942                        self.header_complete()?;
943                    }
944                }
945            }
946        }
947    }
948
949    /// Input ran out. Only between frames does final input end the stream.
950    fn out_of_input(
951        &self,
952        input_is_final: bool,
953        consumed: usize,
954        produced: usize,
955    ) -> Result<Progress, WorkspaceDecodeError> {
956        if !input_is_final {
957            return Ok(Progress {
958                consumed,
959                produced,
960                state: StepState::NeedInput,
961            });
962        }
963        if self.phase == Phase::Magic && self.header_len == 0 {
964            return Ok(Progress {
965                consumed,
966                produced,
967                state: StepState::Done,
968            });
969        }
970        Err(WorkspaceDecodeError::Truncated)
971    }
972
973    /// Decoded bytes not delivered yet, whether or not the frame is finished.
974    fn pending(&self) -> usize {
975        with_window!(&self.window, buffer => buffer.undelivered(), idle => 0)
976    }
977
978    fn deliver(&mut self, output: &mut [u8]) -> usize {
979        if output.is_empty() {
980            return 0;
981        }
982        let blocks_done = self.blocks_done;
983        let drained = with_window!(&mut self.window, buffer => if blocks_done {
984            buffer.read_all(output)
985        } else {
986            buffer.read_reporting_pending(output).map(|(read, _)| read)
987        }, idle => Ok(0));
988        // Draining into a slice cannot fail: the copy reports no error.
989        drained.unwrap_or(0)
990    }
991
992    fn expect_frame(&mut self) {
993        self.phase = Phase::Magic;
994        self.header_len = 0;
995        self.header_target = 4;
996    }
997
998    fn expect_header(&mut self, phase: Phase, target: usize) {
999        self.phase = phase;
1000        self.header_target = target;
1001    }
1002
1003    /// The bytes the current phase collects are all in `header`.
1004    fn header_complete(&mut self) -> Result<(), WorkspaceDecodeError> {
1005        use WorkspaceDecodeError::Malformed;
1006        match self.phase {
1007            Phase::Magic => {
1008                let magic = u32::from_le_bytes([
1009                    self.header[0],
1010                    self.header[1],
1011                    self.header[2],
1012                    self.header[3],
1013                ]);
1014                if (0x184D2A50..=0x184D2A5F).contains(&magic) {
1015                    self.expect_header(Phase::SkippableSize, 8);
1016                } else if magic == MAGIC_NUM {
1017                    self.expect_header(Phase::Descriptor, 5);
1018                } else {
1019                    #[cfg(feature = "legacy")]
1020                    if let Some(version) = crate::decoding::LegacyVersion::of(&magic.to_le_bytes())
1021                    {
1022                        return Err(WorkspaceDecodeError::Unsupported(
1023                            Unsupported::LegacyFrame { version },
1024                        ));
1025                    }
1026                    return Err(Malformed(MalformedInput::FrameHeader(
1027                        ReadFrameHeaderError::BadMagicNumber(magic),
1028                    )));
1029                }
1030            }
1031            Phase::SkippableSize => {
1032                let length = u32::from_le_bytes([
1033                    self.header[4],
1034                    self.header[5],
1035                    self.header[6],
1036                    self.header[7],
1037                ]);
1038                self.phase = Phase::Skipping {
1039                    remaining: u64::from(length),
1040                };
1041            }
1042            Phase::Descriptor => {
1043                let length = frame::measure_header_from_slice(&self.header[..5])
1044                    .map_err(|e| Malformed(MalformedInput::FrameHeader(e)))?;
1045                self.expect_header(Phase::FrameHeader, length);
1046            }
1047            Phase::FrameHeader => self.start_frame()?,
1048            Phase::BlockHeader => self.start_block()?,
1049            Phase::Checksum => {
1050                self.check_checksum()?;
1051                self.expect_frame();
1052            }
1053            Phase::Skipping { .. } | Phase::BlockBody(_) => {
1054                unreachable!("phases without a header buffer are handled by the step loop")
1055            }
1056        }
1057        Ok(())
1058    }
1059
1060    fn start_frame(&mut self) -> Result<(), WorkspaceDecodeError> {
1061        use WorkspaceDecodeError::{Limit, Malformed};
1062        let mut source = &self.header[..self.header_target];
1063        let (header, _) = frame::read_frame_header_from_slice(&mut source, false)
1064            .map_err(|e| Malformed(MalformedInput::FrameHeader(e)))?;
1065        if let Some(id) = header.dictionary_id() {
1066            return Err(WorkspaceDecodeError::Unsupported(Unsupported::Dictionary {
1067                id,
1068            }));
1069        }
1070        let window = header
1071            .window_size()
1072            .map_err(|e| Malformed(MalformedInput::Window(e)))?;
1073        if window > self.limits.max_window_size {
1074            return Err(Limit(LimitExceeded::Window {
1075                requested: window,
1076                limit: self.limits.max_window_size,
1077            }));
1078        }
1079        let declared_size = header.fcs_declared().then(|| header.frame_content_size());
1080        if let Some(declared) = declared_size
1081            && self
1082                .stream_produced
1083                .checked_add(declared)
1084                .is_none_or(|total| total > self.limits.max_output)
1085        {
1086            return Err(Limit(LimitExceeded::Output {
1087                limit: self.limits.max_output,
1088            }));
1089        }
1090        // Fits `usize`: it is at most the limit, which `Layout` converted.
1091        let window = window as usize;
1092        let has_checksum = header.descriptor.content_checksum_flag();
1093
1094        // End the previous frame's view of the region before laying out the
1095        // next one, so the two never coexist.
1096        self.window = Window::Idle;
1097        self.window = if header.descriptor.single_segment_flag() {
1098            // SAFETY: `region` is `region_len` initialised bytes borrowed for
1099            // `'w`, and no other view of it is alive: the previous window was
1100            // dropped just above.
1101            let region =
1102                unsafe { core::slice::from_raw_parts_mut(self.region.as_ptr(), self.region_len) };
1103            Window::Linear(DecodeBuffer::from_backend(
1104                UserSliceBackend::from_slice(region),
1105                window,
1106            ))
1107        } else {
1108            // SAFETY: as above; the ring addresses `ring_cap` bytes of the
1109            // region plus the copiers' slack, `region_len` in all, and
1110            // `ring_cap >= 1`.
1111            let ring = unsafe { RingBuffer::from_borrowed(self.region, self.ring_cap) };
1112            Window::Ring(DecodeBuffer::from_backend(ring, window))
1113        };
1114        with_window!(&mut self.window, buffer => {
1115            buffer.set_declared_content(declared_size);
1116            #[cfg(feature = "hash")]
1117            buffer.set_compute_hash(has_checksum);
1118        }, idle => {});
1119        self.offset_hist = [1, 4, 8];
1120        self.literals.clear();
1121        super::scratch::reset_entropy(self.huf, self.fse);
1122
1123        self.frame = FrameState {
1124            declared_size,
1125            has_checksum,
1126            block_maximum: block_decoder::block_maximum(window),
1127            produced: 0,
1128        };
1129        self.blocks_done = false;
1130        self.header_len = 0;
1131        self.expect_header(Phase::BlockHeader, 3);
1132        Ok(())
1133    }
1134
1135    fn start_block(&mut self) -> Result<(), WorkspaceDecodeError> {
1136        use WorkspaceDecodeError::Malformed;
1137        let (header, _) = self
1138            .block_decoder
1139            .read_block_header(&self.header[..3])
1140            .map_err(|e| Malformed(MalformedInput::BlockHeader(e)))?;
1141        // RFC 8878 3.1.1.2.4: Block_Size is at most Block_Maximum_Size for
1142        // every block type; for RLE it is the regenerated size.
1143        let size = match header.block_type {
1144            BlockType::RLE => header.decompressed_size,
1145            _ => header.content_size,
1146        } as usize;
1147        let maximum = self.frame.block_maximum;
1148        if size > maximum {
1149            return Err(Malformed(MalformedInput::BlockTooLarge { size, maximum }));
1150        }
1151        self.header_len = 0;
1152        self.staged = 0;
1153        self.phase = Phase::BlockBody(PendingBlock {
1154            last_block: header.last_block,
1155            block_type: header.block_type,
1156            decompressed_size: header.decompressed_size,
1157            content_size: header.content_size,
1158        });
1159        Ok(())
1160    }
1161
1162    /// Decode one block whose whole content is `content`, charging its work
1163    /// first and holding its output to the limits.
1164    fn decode_block(
1165        &mut self,
1166        block: PendingBlock,
1167        content: &[u8],
1168        work: &mut WorkBudget,
1169    ) -> Result<(), WorkspaceDecodeError> {
1170        use WorkspaceDecodeError::{Limit, Malformed};
1171        let output_bound = match block.block_type {
1172            BlockType::Compressed => self.frame.block_maximum as u64 + COMPRESSED_BLOCK_UNITS,
1173            _ => u64::from(block.decompressed_size),
1174        };
1175        work.charge(output_bound)?;
1176        let output_limit = Limit(LimitExceeded::Output {
1177            limit: self.limits.max_output,
1178        });
1179        if block.block_type != BlockType::Compressed
1180            && self
1181                .stream_produced
1182                .checked_add(u64::from(block.decompressed_size))
1183                .is_none_or(|total| total > self.limits.max_output)
1184        {
1185            return Err(output_limit);
1186        }
1187
1188        let header = block.header();
1189        let produced = with_window!(&mut self.window, buffer => run_block(
1190            &mut self.block_decoder,
1191            &mut BlockScratch {
1192                huf: &mut *self.huf,
1193                fse: &mut *self.fse,
1194                buffer,
1195                offset_hist: &mut self.offset_hist,
1196                literals: &mut self.literals,
1197                block_content: &mut self.block_content,
1198            },
1199            &header,
1200            content,
1201        ), idle => unreachable!("a block is decoded only inside a started frame"))
1202        .map_err(|e| Malformed(MalformedInput::Block(e)))?;
1203
1204        self.stream_produced += produced;
1205        self.frame.produced += produced;
1206        if self.stream_produced > self.limits.max_output {
1207            return Err(output_limit);
1208        }
1209        if let Some(declared) = self.frame.declared_size
1210            && self.frame.produced > declared
1211        {
1212            return Err(Malformed(MalformedInput::ContentSize {
1213                declared,
1214                produced: self.frame.produced,
1215            }));
1216        }
1217        self.finish_block(block)
1218    }
1219
1220    /// Append the next part of a Raw block to the window, after holding it to
1221    /// the output limit and to the frame's declared size.
1222    fn push_raw(&mut self, bytes: &[u8]) -> Result<(), WorkspaceDecodeError> {
1223        use crate::cpu_kernel::{BaselineKernel, CpuKernelTag, ScalarKernel};
1224        use WorkspaceDecodeError::{Limit, Malformed};
1225        let len = bytes.len() as u64;
1226        let stream_total = self.stream_produced + len;
1227        if stream_total > self.limits.max_output {
1228            return Err(Limit(LimitExceeded::Output {
1229                limit: self.limits.max_output,
1230            }));
1231        }
1232        let frame_total = self.frame.produced + len;
1233        if let Some(declared) = self.frame.declared_size
1234            && frame_total > declared
1235        {
1236            return Err(Malformed(MalformedInput::ContentSize {
1237                declared,
1238                produced: frame_total,
1239            }));
1240        }
1241        // One copy per piece, under the kernel a Raw block takes elsewhere:
1242        // the scalar tier stays portable.
1243        let scalar = self.kernel == CpuKernelTag::Scalar;
1244        let pushed = with_window!(&mut self.window, buffer => if scalar {
1245            buffer.try_push::<ScalarKernel>(bytes)
1246        } else {
1247            buffer.try_push::<BaselineKernel>(bytes)
1248        }, idle => unreachable!("a block is decoded only inside a started frame"));
1249        // The block was held to the block maximum and the window region holds
1250        // a window and two block maxima, so only a frame that outruns its own
1251        // window can reach here.
1252        pushed.map_err(|_| {
1253            Malformed(MalformedInput::Block(
1254                DecodeBlockContentError::BackendOverflow {
1255                    step: BlockType::Raw,
1256                },
1257            ))
1258        })?;
1259        self.stream_produced = stream_total;
1260        self.frame.produced = frame_total;
1261        Ok(())
1262    }
1263
1264    /// The block's content is all decoded: check a last block against the
1265    /// declared size and read what follows it.
1266    fn finish_block(&mut self, block: PendingBlock) -> Result<(), WorkspaceDecodeError> {
1267        if let Some(declared) = self.frame.declared_size
1268            && block.last_block
1269            && self.frame.produced != declared
1270        {
1271            return Err(WorkspaceDecodeError::Malformed(
1272                MalformedInput::ContentSize {
1273                    declared,
1274                    produced: self.frame.produced,
1275                },
1276            ));
1277        }
1278        self.staged = 0;
1279        self.header_len = 0;
1280        if !block.last_block {
1281            self.expect_header(Phase::BlockHeader, 3);
1282        } else {
1283            self.blocks_done = true;
1284            if self.frame.has_checksum {
1285                self.expect_header(Phase::Checksum, 4);
1286            } else {
1287                self.expect_frame();
1288            }
1289        }
1290        Ok(())
1291    }
1292
1293    /// Compare the stored checksum with the decoded frame's. The step loop
1294    /// reaches this only once the whole frame was delivered, which is when the
1295    /// running digest covers all of it. Without the `hash` feature the
1296    /// checksum is read and not checked.
1297    fn check_checksum(&mut self) -> Result<(), WorkspaceDecodeError> {
1298        #[cfg(feature = "hash")]
1299        {
1300            use core::hash::Hasher;
1301            let expected = u32::from_le_bytes([
1302                self.header[0],
1303                self.header[1],
1304                self.header[2],
1305                self.header[3],
1306            ]);
1307            let calculated =
1308                with_window!(&self.window, buffer => buffer.hash.finish(), idle => 0) as u32;
1309            if expected != calculated {
1310                return Err(WorkspaceDecodeError::Malformed(MalformedInput::Checksum {
1311                    expected,
1312                    calculated,
1313                }));
1314            }
1315        }
1316        Ok(())
1317    }
1318}
1319
1320#[cfg(test)]
1321mod tests;