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