Skip to main content

qubit_codec/transcode/engine/
transcode_decode_engine.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Reusable buffered decoder engine.
9
10use core::num::NonZeroUsize;
11
12use super::super::internal::applied_decode_invalid_action::AppliedDecodeInvalidAction;
13use super::super::internal::decode_state::DecodeState;
14use super::super::internal::lifecycle_guard::LifecycleGuard;
15use super::DecodeContext;
16use super::DecodeIncompleteAction;
17use super::DecodeInvalidAction;
18use super::DecodeOutcome;
19use super::TranscodeDecodeHooks;
20use crate::CapacityError;
21use crate::Codec;
22use crate::DecodeFailure;
23use crate::TranscodeDecodeError;
24use crate::TranscodeDecodeErrorOf;
25use crate::TranscodeDecoder;
26use crate::TranscodeFailure;
27use crate::TranscodeProgress;
28use crate::Transcoder;
29use crate::codec::assert_unit_bounds;
30
31/// Reusable buffered decoding engine for codec-backed decoders.
32///
33/// The engine owns the low-level codec and hook object. It keeps the common
34/// buffered decoding loop private: input-index validation, output-capacity
35/// checks, calls to [`Codec::decode`], hook dispatch, and
36/// [`crate::TranscodeStatus`] reporting. Incomplete input tails are left in the
37/// caller-provided input slice; callers own input-buffer refill.
38///
39/// Use this type to build a streaming decoder over a one-value [`Codec`]. The
40/// engine decodes into a caller-provided output slice and returns
41/// [`TranscodeProgress`] instead of allocating. On success it writes decoded
42/// values directly to output. On codec errors it delegates to
43/// [`crate::engine::TranscodeDecodeHooks`], allowing a policy to skip invalid
44/// units, emit a replacement value, or fail.
45///
46/// The engine stops before reading an incomplete value when fewer than
47/// [`Codec::MIN_UNITS_PER_VALUE`] units are available. For variable-width
48/// codecs, the codec may still return an incomplete decode error after that
49/// minimum is satisfied. Both conditions return
50/// [`crate::TranscodeStatus::NeedInput`] while the stream is open; at EOF the
51/// hook selects whether to reject, skip, or replace the remaining tail.
52///
53/// For strict decoding that wraps codec errors, use
54/// [`crate::CodecTranscodeDecoder`]. Use `TranscodeDecodeEngine` directly when
55/// invalid input should be repaired, skipped, counted, or otherwise handled by
56/// policy.
57///
58/// # Examples
59///
60/// ```rust
61/// use core::num::NonZeroUsize;
62/// use qubit_codec::{
63///     Codec,
64///     TranscodeDecodeErrorOf,
65///     DecodeFailure,
66///     TranscodeStatus,
67/// };
68/// use qubit_codec::engine::{
69///     DecodeContext,
70///     DecodeInvalidAction,
71///     TranscodeDecodeEngine,
72///     TranscodeDecodeHooks,
73/// };
74///
75/// #[derive(Clone, Copy)]
76/// struct ByteCodec;
77///
78/// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
79/// enum ByteDecodeError {
80///     Malformed { consumed: NonZeroUsize },
81/// }
82///
83/// impl Codec for ByteCodec {
84///     type Value = u8;
85///     type Unit = u8;
86///     type DecodeError = ByteDecodeError;
87///     type EncodeError = core::convert::Infallible;
88///
89///     const MIN_UNITS_PER_VALUE: usize = 1;
90///     const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
91///     const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
92///
93///     unsafe fn decode(
94///         &mut self,
95///         input: &[u8],
96///         index: usize,
97///     ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
98///         match input[index] {
99///             0xff => Err(DecodeFailure::invalid(
100///                 ByteDecodeError::Malformed {
101///                     consumed: NonZeroUsize::MIN,
102///                 },
103///                 NonZeroUsize::MIN,
104///             )),
105///             value => Ok((value, NonZeroUsize::MIN)),
106///         }
107///     }
108///
109///     unsafe fn encode(
110///         &mut self,
111///         value: &u8,
112///         output: &mut [u8],
113///         index: usize,
114///     ) -> Result<usize, Self::EncodeError> {
115///         output[index] = *value;
116///         Ok(1)
117///     }
118/// }
119///
120/// struct ReplacementHooks;
121///
122/// impl TranscodeDecodeHooks<ByteCodec> for ReplacementHooks {
123///     fn handle_invalid_decode(
124///         &mut self,
125///         _codec: &mut ByteCodec,
126///         error: &ByteDecodeError,
127///         consumed: Option<NonZeroUsize>,
128///         _context: DecodeContext,
129///     ) -> Result<DecodeInvalidAction<u8>, TranscodeDecodeErrorOf<ByteCodec>> {
130///         match error {
131///             ByteDecodeError::Malformed { .. } => {
132///                 Ok(DecodeInvalidAction::Emit {
133///                     value: b'?',
134///                     consumed: consumed.expect("codec reported malformed width"),
135///                 })
136///             }
137///         }
138///     }
139/// }
140///
141/// let mut engine = TranscodeDecodeEngine::<_, _>::new(ByteCodec, ReplacementHooks);
142/// let input = [b'a', 0xff, b'b'];
143/// let mut output = [0_u8; 3];
144/// let mut reset_output = [];
145/// engine.reset(&mut reset_output, 0)?;
146///
147/// let progress = engine.transcode(&input, 0, &mut output, 0)?;
148/// match progress.status() {
149///     TranscodeStatus::Complete => assert_eq!(&output[..progress.written()], b"a?b"),
150///     TranscodeStatus::NeedInput { .. } => {
151///         // Keep `input[input_index + progress.read()..]`, append more source
152///         // units, and resume.
153///     }
154///     TranscodeStatus::NeedOutput { .. } => {
155///         // Drain `output[..progress.written()]`, then resume with more output
156///         // room.
157///     }
158/// }
159/// # Ok::<(), qubit_codec::TranscodeDecodeError<ByteDecodeError>>(())
160/// ```
161///
162/// # Type Parameters
163///
164/// - `C`: Low-level codec used by the engine.
165/// - `H`: Policy hook object used by the engine.
166#[derive(Debug, Default)]
167pub struct TranscodeDecodeEngine<C, H> {
168    /// Low-level codec used for one-value decoding.
169    pub(super) codec: C,
170    /// Policy hooks used for decode failures.
171    pub(super) hooks: H,
172    /// Guard for the `reset → transcode* → finish` lifecycle in every build
173    /// profile.
174    lifecycle: LifecycleGuard,
175}
176
177/// Adds two independent decoded-value capacity bounds.
178fn add_decode_output_bounds(first: usize, second: usize) -> Result<usize, CapacityError> {
179    first.checked_add(second).ok_or(CapacityError::OutputLengthOverflow)
180}
181
182/// Adds reset, transcode, and finish decoded-value capacity bounds.
183fn sum_decode_output_bounds(reset: usize, transcode: usize, finish: usize) -> Result<usize, CapacityError> {
184    let before_finish = add_decode_output_bounds(reset, transcode)?;
185    add_decode_output_bounds(before_finish, finish)
186}
187
188impl<C, H> TranscodeDecodeEngine<C, H>
189where
190    C: Codec,
191    H: TranscodeDecodeHooks<C>,
192{
193    /// Creates a buffered decoder engine.
194    ///
195    /// # Parameters
196    ///
197    /// - `codec`: Low-level codec used for one-value decoding.
198    /// - `hooks`: Policy hooks used for decode failures.
199    ///
200    /// # Returns
201    ///
202    /// Returns a buffered decoder engine.
203    ///
204    /// # Compile-Time Checks
205    ///
206    /// Fails to compile when the supplied codec declares a zero decode unit
207    /// bound or when [`Codec::MIN_UNITS_PER_VALUE`] exceeds
208    /// [`Codec::MAX_DECODE_UNITS_PER_VALUE`].
209    #[inline]
210    #[must_use]
211    pub fn new(codec: C, hooks: H) -> Self {
212        assert_unit_bounds::<C>();
213        Self {
214            codec,
215            hooks,
216            lifecycle: LifecycleGuard::new(),
217        }
218    }
219
220    /// Returns the wrapped low-level codec.
221    ///
222    /// # Returns
223    ///
224    /// Returns a shared reference to the codec owned by this engine.
225    #[inline(always)]
226    #[must_use]
227    pub const fn codec(&self) -> &C {
228        &self.codec
229    }
230
231    /// Returns the wrapped low-level codec mutably.
232    ///
233    /// # Returns
234    ///
235    /// Returns a mutable reference to the codec owned by this engine.
236    #[inline(always)]
237    #[must_use]
238    pub fn codec_mut(&mut self) -> &mut C {
239        &mut self.codec
240    }
241
242    /// Returns the decode hooks used by this engine.
243    ///
244    /// # Returns
245    ///
246    /// Returns a shared reference to the hook object owned by this engine.
247    #[inline(always)]
248    #[must_use]
249    pub const fn hooks(&self) -> &H {
250        &self.hooks
251    }
252
253    /// Returns the decode hooks mutably.
254    ///
255    /// # Returns
256    ///
257    /// Returns a mutable reference to the hook object owned by this engine.
258    #[inline(always)]
259    #[must_use]
260    pub fn hooks_mut(&mut self) -> &mut H {
261        &mut self.hooks
262    }
263
264    /// Consumes the engine and returns its codec and hooks.
265    ///
266    /// Any lifecycle state owned by the engine is discarded.
267    ///
268    /// # Returns
269    ///
270    /// Returns the wrapped codec followed by the decode hooks.
271    #[inline(always)]
272    #[must_use]
273    pub fn into_parts(self) -> (C, H) {
274        let Self { codec, hooks, .. } = self;
275        (codec, hooks)
276    }
277
278    /// Returns an upper bound for decoded values produced from `input_len`
279    /// units.
280    ///
281    /// This bound covers only the streaming decode phase. It is delegated to
282    /// [`TranscodeDecodeHooks::max_transcode_output_len`], so it includes hook
283    /// policy and is valid for every reachable transient codec and hook state.
284    /// Downstream decoders must use this engine-level API for capacity planning
285    /// instead of recomputing the bound from [`Codec`] constants.
286    ///
287    /// # Parameters
288    ///
289    /// - `input_len`: Number of source units the caller plans to decode.
290    ///
291    /// # Returns
292    ///
293    /// Returns a conservative upper bound, or a capacity error on arithmetic
294    /// overflow.
295    #[inline(always)]
296    #[must_use = "capacity planning can fail on overflow"]
297    pub fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
298        self.hooks.max_transcode_output_len(&self.codec, input_len)
299    }
300
301    /// Returns the global maximum values emitted by finishing codec state and
302    /// finishing hook-owned state.
303    ///
304    /// # Returns
305    ///
306    /// Returns the sum of [`Codec::MAX_DECODE_FINISH_VALUES`] and the
307    /// hook-provided final-output bound. The codec finish portion covers values
308    /// written by [`Codec::decode_finish`]; hook implementations must not
309    /// include that portion in
310    /// [`TranscodeDecodeHooks::max_finish_output_len`]. Both component bounds
311    /// cover every reachable transient state.
312    #[inline(always)]
313    #[must_use = "capacity planning can fail on overflow"]
314    pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
315        add_decode_output_bounds(
316            C::MAX_DECODE_FINISH_VALUES,
317            self.hooks.max_finish_output_len(&self.codec),
318        )
319    }
320
321    /// Returns the global maximum values emitted when resetting stream state.
322    ///
323    /// Returns [`Codec::MAX_DECODE_RESET_VALUES`] for the wrapped codec.
324    /// Stateless decoders always return `0`.
325    #[inline(always)]
326    #[must_use = "capacity planning can fail on overflow"]
327    pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
328        Ok(C::MAX_DECODE_RESET_VALUES)
329    }
330
331    /// Returns the maximum values needed by a complete one-shot decode stream.
332    ///
333    /// The returned bound covers reset output, the streaming decode phase for
334    /// `input_len` units, and finish output. Its components are global across
335    /// transient state. Higher-level complete decode helpers should use this
336    /// engine-level bound instead of recomputing capacity from [`Codec`]
337    /// constants, because hook policy may change streaming or finish output.
338    ///
339    /// # Parameters
340    ///
341    /// - `input_len`: Number of source units in the complete stream.
342    ///
343    /// # Returns
344    ///
345    /// Returns the complete-stream output bound, or a capacity error on
346    /// arithmetic overflow.
347    #[inline]
348    #[must_use = "capacity planning can fail on overflow"]
349    pub fn max_total_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
350        let transcode = self.max_transcode_output_len(input_len)?;
351        let finish = self.max_finish_output_len()?;
352        sum_decode_output_bounds(C::MAX_DECODE_RESET_VALUES, transcode, finish)
353    }
354
355    /// Resets codec decode state, runs reset hooks, and emits stream-start
356    /// values.
357    ///
358    /// The sequence is: validate capacity → run `reset_hooks` → call
359    /// [`Codec::decode_reset`]. Stateless decoders (`MAX_DECODE_RESET_VALUES
360    /// == 0`) write nothing and return `Ok(0)`.
361    ///
362    /// # Parameters
363    ///
364    /// - `output`: Complete output value slice visible to the decoder.
365    /// - `output_index`: Absolute output value index where writing starts.
366    ///
367    /// # Returns
368    ///
369    /// Returns the number of reset values written.
370    ///
371    /// # Errors
372    ///
373    /// Returns framework errors when the caller provides invalid or
374    /// insufficient output capacity. Returns domain errors when codec reset
375    /// or hook reset handling fails. Capacity and index failures occur before
376    /// any reset state is changed. Once reset execution starts, an error
377    /// poisons the engine until a later reset succeeds.
378    pub fn reset(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
379        let required = C::MAX_DECODE_RESET_VALUES;
380        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
381        self.lifecycle.on_reset_start();
382        self.hooks.reset_hooks(&mut self.codec);
383        let written = unsafe {
384            // SAFETY: The capacity check above reserves the codec's declared
385            // reset-output bound at `output_index`.
386            self.codec.decode_reset(output, output_index)
387        }
388        .map_err(TranscodeDecodeError::domain_reset)?;
389        assert!(written <= required, "Codec::decode_reset wrote beyond its reset bound",);
390        self.lifecycle.on_reset_success();
391        Ok(written)
392    }
393
394    /// Decodes source units into caller-provided output values.
395    ///
396    /// # Parameters
397    ///
398    /// - `input`: Complete input unit slice visible to the decoder.
399    /// - `input_index`: Absolute input unit index where decoding starts.
400    /// - `output`: Complete output value slice visible to the decoder.
401    /// - `output_index`: Absolute output value index where writing starts.
402    ///
403    /// # Returns
404    ///
405    /// Returns progress describing input units consumed, output values written,
406    /// and why decoding stopped.
407    ///
408    /// # Errors
409    ///
410    /// Returns hook errors when `input_index` is outside `input`, when
411    /// `output_index` is outside `output`, or when a concrete policy hook
412    /// rejects a value. Returns
413    /// [`TranscodeFailure::TranscodeBeforeReset`] when the engine has not
414    /// completed its first reset. Returns
415    /// [`TranscodeFailure::TranscodeAfterFinish`] when the logical stream was
416    /// already finished and has not been reset, or
417    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
418    /// failed after execution started.
419    pub fn transcode(
420        &mut self,
421        input: &[C::Unit],
422        input_index: usize,
423        output: &mut [C::Value],
424        output_index: usize,
425    ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
426        self.transcode_with_eof(input, input_index, output, output_index, false)
427    }
428
429    /// Decodes source units after the caller has established end of input.
430    ///
431    /// This follows the normal streaming contract, except that codec attempts
432    /// use [`Codec::decode_eof`]. A codec may therefore resolve a trailing
433    /// prefix that would remain incomplete in an open stream.
434    #[inline(always)]
435    pub fn transcode_eof(
436        &mut self,
437        input: &[C::Unit],
438        input_index: usize,
439        output: &mut [C::Value],
440        output_index: usize,
441    ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
442        self.transcode_with_eof(input, input_index, output, output_index, true)
443    }
444
445    /// Runs one decode step with an explicit end-of-input flag.
446    ///
447    /// The helper centralizes lifecycle validation and shared cursor handling
448    /// for the streaming and EOF-aware entry points.
449    fn transcode_with_eof(
450        &mut self,
451        input: &[C::Unit],
452        input_index: usize,
453        output: &mut [C::Value],
454        output_index: usize,
455        end_of_input: bool,
456    ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
457        self.lifecycle.on_transcode()?;
458        TranscodeFailure::ensure_transcode_indices(input.len(), input_index, output.len(), output_index)?;
459
460        let min_units = NonZeroUsize::new(C::MIN_UNITS_PER_VALUE).expect("Codec::MIN_UNITS_PER_VALUE is non-zero");
461        let min_units_len = min_units.get();
462        let mut state = DecodeState::new(input, input_index, output, output_index);
463        while state.has_input() {
464            let context = state.context();
465            let available = context.available();
466            if available < min_units_len {
467                if !end_of_input {
468                    return Ok(state.need_input_progress_with(min_units));
469                }
470                match self
471                    .hooks
472                    .handle_incomplete_decode(&mut self.codec, None, min_units, context)?
473                {
474                    DecodeIncompleteAction::Reject => {
475                        return Err(TranscodeFailure::incomplete_input(
476                            context.input_index(),
477                            min_units.get(),
478                            available,
479                        )
480                        .into());
481                    }
482                    DecodeIncompleteAction::Skip => {
483                        let read =
484                            NonZeroUsize::new(available).expect("incomplete decode tail must contain source units");
485                        if let Some(progress) = state.apply_decode_outcome(DecodeOutcome::skipped(read)) {
486                            return Ok(progress);
487                        }
488                        continue;
489                    }
490                    DecodeIncompleteAction::Emit { value } => {
491                        if state.needs_output() {
492                            return Ok(state.need_output_progress());
493                        }
494                        let output_index = state.output_cursor();
495                        // SAFETY: `needs_output()` returned false, so the
496                        // output cursor points at a writable initialized slot.
497                        unsafe {
498                            *state.output_mut().get_unchecked_mut(output_index) = value;
499                        }
500                        let read =
501                            NonZeroUsize::new(available).expect("incomplete decode tail must contain source units");
502                        if let Some(progress) =
503                            state.apply_decode_outcome(DecodeOutcome::emitted(read, NonZeroUsize::MIN))
504                        {
505                            return Ok(progress);
506                        }
507                        continue;
508                    }
509                }
510            }
511            if state.needs_output() {
512                return Ok(state.need_output_progress());
513            }
514            let output_index = state.output_cursor();
515            let output = state.output_mut();
516            let (outcome, _) = self.decode_one(input, context, end_of_input, |value, _input_index| {
517                // SAFETY: `needs_output()` returned false, so the output
518                // cursor points at a writable initialized slot.
519                unsafe {
520                    *output.get_unchecked_mut(output_index) = value;
521                }
522            })?;
523            if let Some(progress) = state.apply_decode_outcome(outcome) {
524                return Ok(progress);
525            }
526        }
527
528        Ok(state.complete_progress())
529    }
530
531    /// Finishes codec and hook-owned output after EOF.
532    ///
533    /// Finalization first finishes decode-side codec state through
534    /// [`Codec::decode_finish`], then lets hook implementations finish their
535    /// own retained state. The caller must provide enough output capacity for
536    /// [`TranscodeDecodeEngine::max_finish_output_len`], which includes both
537    /// the codec finish bound and the hook-owned finish bound.
538    ///
539    /// # Parameters
540    ///
541    /// - `output`: Complete output value slice visible to the decoder.
542    /// - `output_index`: Absolute output value index where writing starts.
543    ///
544    /// # Returns
545    ///
546    /// Returns the number of values written by finalization.
547    ///
548    /// # Errors
549    ///
550    /// Returns framework errors when the caller provides invalid or
551    /// insufficient output capacity. Returns domain errors when codec finish or
552    /// hook finalization fails. Returns
553    /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
554    /// completed its first reset. Returns
555    /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
556    /// already finished and has not been reset, or
557    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
558    /// failed after execution started. Capacity and index failures occur before
559    /// finish execution and remain retryable; later failures poison the engine
560    /// until reset succeeds.
561    ///
562    /// # Panics
563    ///
564    /// Panics when the codec finish writes beyond
565    /// [`Codec::MAX_DECODE_FINISH_VALUES`] or when the combined codec and hook
566    /// finalization writes beyond
567    /// [`TranscodeDecodeEngine::max_finish_output_len`].
568    pub fn finish(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
569        self.lifecycle.on_finish_attempt()?;
570        let required = self.max_finish_output_len()?;
571        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
572        self.lifecycle.on_finish_start();
573        let finished =
574            unsafe { self.codec.decode_finish(output, output_index) }.map_err(TranscodeDecodeError::domain_finish)?;
575        assert!(
576            finished <= C::MAX_DECODE_FINISH_VALUES,
577            "Codec::decode_finish wrote beyond its finish bound",
578        );
579        let written = self
580            .hooks
581            .finish_hooks(&mut self.codec, output, output_index + finished)?;
582        assert!(
583            finished + written <= required,
584            "TranscodeDecodeEngine hook wrote beyond its finish bound",
585        );
586        self.lifecycle.on_finish_success();
587        Ok(finished + written)
588    }
589
590    /// Runs a complete one-shot `reset -> transcode -> finish` decode stream.
591    ///
592    /// The complete input is supplied as `input`, and output starts at index
593    /// `0` in `output`. Callers that need subranges should slice their
594    /// buffers before calling this method. Downstream one-shot decoder
595    /// helpers should call this engine method instead of reproducing the
596    /// reset, transcode, and finish sequence themselves.
597    ///
598    /// # Parameters
599    ///
600    /// - `input`: Complete source unit slice.
601    /// - `output`: Output value slice for the whole decoded stream.
602    ///
603    /// # Returns
604    ///
605    /// Returns the number of output values written.
606    ///
607    /// # Errors
608    ///
609    /// Returns framework errors for insufficient output, capacity overflow, or
610    /// an incomplete EOF tail, and domain errors from reset, decode, or
611    /// finish.
612    #[inline(always)]
613    pub fn transcode_complete_into(
614        &mut self,
615        input: &[C::Unit],
616        output: &mut [C::Value],
617    ) -> Result<usize, TranscodeDecodeErrorOf<C>> {
618        <Self as Transcoder>::transcode_complete_into(self, input, output)
619    }
620
621    /// Decodes one source value attempt and delivers emitted values.
622    ///
623    /// # Parameters
624    ///
625    /// - `input`: Complete input unit slice visible to the caller.
626    /// - `context`: Decode context describing the current source and output
627    ///   cursors.
628    /// - `consume`: Callback invoked exactly once when this attempt emits a
629    ///   logical value.
630    ///
631    /// # Type Parameters
632    ///
633    /// - `R`: Value returned by the consumer when a decoded value is emitted.
634    /// - `F`: Consumer callback type.
635    ///
636    /// # Returns
637    ///
638    /// Returns the decode outcome and the consumer result when a value was
639    /// emitted.
640    ///
641    /// # Errors
642    ///
643    /// Returns hook errors when the decode policy rejects the input.
644    ///
645    /// # Panics
646    ///
647    /// Panics when the codec reports consumption beyond the available input,
648    /// an incomplete-input requirement beyond
649    /// [`Codec::MAX_DECODE_UNITS_PER_VALUE`], or hooks return an action that
650    /// consumes beyond the available input.
651    pub(crate) fn decode_one<R, F>(
652        &mut self,
653        input: &[C::Unit],
654        context: DecodeContext,
655        end_of_input: bool,
656        consume: F,
657    ) -> Result<(DecodeOutcome, Option<R>), TranscodeDecodeErrorOf<C>>
658    where
659        F: FnOnce(C::Value, usize) -> R,
660    {
661        debug_assert!(
662            context.available() > 0,
663            "decode_one requires at least one source input unit",
664        );
665
666        if context.available() < C::MIN_UNITS_PER_VALUE {
667            let required_total =
668                NonZeroUsize::new(C::MIN_UNITS_PER_VALUE).expect("Codec::MIN_UNITS_PER_VALUE is non-zero");
669            if !end_of_input {
670                return Ok((DecodeOutcome::need_input(required_total), None));
671            }
672            return match self
673                .hooks
674                .handle_incomplete_decode(&mut self.codec, None, required_total, context)?
675            {
676                DecodeIncompleteAction::Reject => Err(TranscodeFailure::incomplete_input(
677                    context.input_index(),
678                    required_total.get(),
679                    context.available(),
680                )
681                .into()),
682                DecodeIncompleteAction::Skip => {
683                    let read = NonZeroUsize::new(context.available())
684                        .expect("incomplete decode tail must contain source units");
685                    Ok((DecodeOutcome::skipped(read), None))
686                }
687                DecodeIncompleteAction::Emit { value } => {
688                    let read = NonZeroUsize::new(context.available())
689                        .expect("incomplete decode tail must contain source units");
690                    let consumed_value = consume(value, context.input_index());
691                    Ok((DecodeOutcome::emitted(read, NonZeroUsize::MIN), Some(consumed_value)))
692                }
693            };
694        }
695
696        // SAFETY: The context reports at least `MIN_UNITS_PER_VALUE` source
697        // units available from `context.input_index()`.
698        let result = unsafe {
699            if end_of_input {
700                self.codec.decode_eof(input, context.input_index())
701            } else {
702                self.codec.decode(input, context.input_index())
703            }
704        };
705        match result {
706            Ok((value, consumed)) => {
707                assert!(
708                    consumed.get() <= context.available(),
709                    "Codec::decode consumed beyond available input",
710                );
711                assert!(
712                    consumed.get() <= C::MAX_DECODE_UNITS_PER_VALUE,
713                    "Codec::decode consumed beyond Codec::MAX_DECODE_UNITS_PER_VALUE",
714                );
715                let consumed_value = consume(value, context.input_index());
716                Ok((
717                    DecodeOutcome::emitted(consumed, NonZeroUsize::MIN),
718                    Some(consumed_value),
719                ))
720            }
721            Err(DecodeFailure::Incomplete { source, required_total }) => {
722                assert!(
723                    required_total.get() > context.available(),
724                    "Codec::decode incomplete required_total must exceed available input",
725                );
726                assert!(
727                    required_total.get() <= C::MAX_DECODE_UNITS_PER_VALUE,
728                    "Codec::decode incomplete required_total exceeded Codec::MAX_DECODE_UNITS_PER_VALUE",
729                );
730                if !end_of_input {
731                    return Ok((DecodeOutcome::need_input(required_total), None));
732                }
733                match self
734                    .hooks
735                    .handle_incomplete_decode(&mut self.codec, source.as_ref(), required_total, context)?
736                {
737                    DecodeIncompleteAction::Reject => match source {
738                        Some(source) => Err(TranscodeDecodeError::domain_main(source, context.input_index())),
739                        None => Err(TranscodeFailure::incomplete_input(
740                            context.input_index(),
741                            required_total.get(),
742                            context.available(),
743                        )
744                        .into()),
745                    },
746                    DecodeIncompleteAction::Skip => {
747                        let read = NonZeroUsize::new(context.available())
748                            .expect("incomplete decode tail must contain source units");
749                        Ok((DecodeOutcome::skipped(read), None))
750                    }
751                    DecodeIncompleteAction::Emit { value } => {
752                        let read = NonZeroUsize::new(context.available())
753                            .expect("incomplete decode tail must contain source units");
754                        let consumed_value = consume(value, context.input_index());
755                        Ok((DecodeOutcome::emitted(read, NonZeroUsize::MIN), Some(consumed_value)))
756                    }
757                }
758            }
759            Err(DecodeFailure::Invalid { source, consumed }) => {
760                let action = match self
761                    .hooks
762                    .handle_invalid_decode(&mut self.codec, &source, consumed, context)?
763                {
764                    DecodeInvalidAction::Reject => {
765                        return Err(TranscodeDecodeError::domain_main_with_consumed(
766                            source,
767                            context.input_index(),
768                            consumed,
769                        ));
770                    }
771                    DecodeInvalidAction::Skip { consumed } => AppliedDecodeInvalidAction::Skip { consumed },
772                    DecodeInvalidAction::Emit { value, consumed } => {
773                        AppliedDecodeInvalidAction::Emit { value, consumed }
774                    }
775                };
776                Ok(Self::apply_invalid_decode_action(action, context, consume))
777            }
778        }
779    }
780
781    /// Applies a hook-selected invalid-decode action after reject handling.
782    fn apply_invalid_decode_action<R, F>(
783        action: AppliedDecodeInvalidAction<C::Value>,
784        context: DecodeContext,
785        consume: F,
786    ) -> (DecodeOutcome, Option<R>)
787    where
788        F: FnOnce(C::Value, usize) -> R,
789    {
790        match action {
791            AppliedDecodeInvalidAction::Skip { consumed } => {
792                let read = DecodeInvalidAction::<C::Value>::bound_consumed(consumed, context.available());
793                (DecodeOutcome::skipped(read), None)
794            }
795            AppliedDecodeInvalidAction::Emit { value, consumed } => {
796                let read = DecodeInvalidAction::<C::Value>::bound_consumed(consumed, context.available());
797                let consumed_value = consume(value, context.input_index());
798                (DecodeOutcome::emitted(read, NonZeroUsize::MIN), Some(consumed_value))
799            }
800        }
801    }
802}
803
804impl<C, H> Transcoder for TranscodeDecodeEngine<C, H>
805where
806    C: Codec,
807    H: TranscodeDecodeHooks<C>,
808{
809    type Input = C::Unit;
810    type Output = C::Value;
811    type Error = TranscodeDecodeErrorOf<C>;
812
813    /// Returns an upper bound for decoded values produced from `input_len`
814    /// units.
815    #[inline(always)]
816    fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
817        TranscodeDecodeEngine::max_transcode_output_len(self, input_len)
818    }
819
820    /// Returns an upper bound for values produced by finishing codec and hook
821    /// state.
822    #[inline(always)]
823    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
824        TranscodeDecodeEngine::max_finish_output_len(self)
825    }
826
827    /// Returns an upper bound for values emitted when resetting stream state.
828    #[inline(always)]
829    fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
830        TranscodeDecodeEngine::max_reset_output_len(self)
831    }
832
833    /// Runs hook-owned cleanup before a logical decoder reset.
834    #[inline(always)]
835    fn reset(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
836        TranscodeDecodeEngine::reset(self, output, output_index)
837    }
838
839    /// Decodes source units into logical values.
840    #[inline(always)]
841    fn transcode(
842        &mut self,
843        input: &[C::Unit],
844        input_index: usize,
845        output: &mut [C::Value],
846        output_index: usize,
847    ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
848        TranscodeDecodeEngine::transcode(self, input, input_index, output, output_index)
849    }
850
851    /// Decodes source units after end of input is known.
852    #[inline(always)]
853    fn transcode_eof(
854        &mut self,
855        input: &[C::Unit],
856        input_index: usize,
857        output: &mut [C::Value],
858        output_index: usize,
859    ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
860        TranscodeDecodeEngine::transcode_eof(self, input, input_index, output, output_index)
861    }
862
863    /// Finishes internally retained output after EOF.
864    #[inline(always)]
865    fn finish(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
866        TranscodeDecodeEngine::finish(self, output, output_index)
867    }
868}
869
870impl<C, H> TranscodeDecoder for TranscodeDecodeEngine<C, H>
871where
872    C: Codec,
873    H: TranscodeDecodeHooks<C>,
874{
875    type DecodeError = C::DecodeError;
876}