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.
178///
179/// # Parameters
180///
181/// - `first`: First decoded-value bound.
182/// - `second`: Independent decoded-value bound to add.
183///
184/// # Returns
185///
186/// Returns the combined decoded-value bound.
187///
188/// # Errors
189///
190/// Returns `CapacityError::OutputLengthOverflow` if the sum exceeds
191/// `usize::MAX`.
192#[inline]
193fn add_decode_output_bounds(first: usize, second: usize) -> Result<usize, CapacityError> {
194    first.checked_add(second).ok_or(CapacityError::OutputLengthOverflow)
195}
196
197/// Adds reset, transcode, and finish decoded-value capacity bounds.
198///
199/// # Parameters
200///
201/// - `reset`: Maximum reset output.
202/// - `transcode`: Maximum streaming output.
203/// - `finish`: Maximum finalization output.
204///
205/// # Returns
206///
207/// Returns the complete-stream decoded-value bound.
208///
209/// # Errors
210///
211/// Returns `CapacityError::OutputLengthOverflow` if the sum exceeds
212/// `usize::MAX`.
213#[inline]
214fn sum_decode_output_bounds(reset: usize, transcode: usize, finish: usize) -> Result<usize, CapacityError> {
215    let before_finish = add_decode_output_bounds(reset, transcode)?;
216    add_decode_output_bounds(before_finish, finish)
217}
218
219impl<C, H> TranscodeDecodeEngine<C, H>
220where
221    C: Codec,
222    H: TranscodeDecodeHooks<C>,
223{
224    /// Creates a buffered decoder engine.
225    ///
226    /// # Parameters
227    ///
228    /// - `codec`: Low-level codec used for one-value decoding.
229    /// - `hooks`: Policy hooks used for decode failures.
230    ///
231    /// # Returns
232    ///
233    /// Returns a buffered decoder engine.
234    ///
235    /// # Compile-Time Checks
236    ///
237    /// Fails to compile when the supplied codec declares a zero decode unit
238    /// bound or when [`Codec::MIN_UNITS_PER_VALUE`] exceeds
239    /// [`Codec::MAX_DECODE_UNITS_PER_VALUE`].
240    #[inline]
241    #[must_use]
242    pub fn new(codec: C, hooks: H) -> Self {
243        assert_unit_bounds::<C>();
244        Self {
245            codec,
246            hooks,
247            lifecycle: LifecycleGuard::new(),
248        }
249    }
250
251    /// Returns the wrapped low-level codec.
252    ///
253    /// # Returns
254    ///
255    /// Returns a shared reference to the codec owned by this engine.
256    #[inline]
257    #[must_use]
258    pub const fn codec(&self) -> &C {
259        &self.codec
260    }
261
262    /// Returns the wrapped low-level codec mutably.
263    ///
264    /// # Returns
265    ///
266    /// Returns a mutable reference to the codec owned by this engine.
267    #[inline]
268    #[must_use]
269    pub fn codec_mut(&mut self) -> &mut C {
270        &mut self.codec
271    }
272
273    /// Returns the decode hooks used by this engine.
274    ///
275    /// # Returns
276    ///
277    /// Returns a shared reference to the hook object owned by this engine.
278    #[inline]
279    #[must_use]
280    pub const fn hooks(&self) -> &H {
281        &self.hooks
282    }
283
284    /// Returns the decode hooks mutably.
285    ///
286    /// # Returns
287    ///
288    /// Returns a mutable reference to the hook object owned by this engine.
289    #[inline]
290    #[must_use]
291    pub fn hooks_mut(&mut self) -> &mut H {
292        &mut self.hooks
293    }
294
295    /// Consumes the engine and returns its codec and hooks.
296    ///
297    /// Any lifecycle state owned by the engine is discarded.
298    ///
299    /// # Returns
300    ///
301    /// Returns the wrapped codec followed by the decode hooks.
302    #[inline]
303    #[must_use]
304    pub fn into_parts(self) -> (C, H) {
305        let Self { codec, hooks, .. } = self;
306        (codec, hooks)
307    }
308
309    /// Returns an upper bound for decoded values produced from `input_len`
310    /// units.
311    ///
312    /// This bound covers only the streaming decode phase. It is delegated to
313    /// [`TranscodeDecodeHooks::max_transcode_output_len`], so it includes hook
314    /// policy and is valid for every reachable transient codec and hook state.
315    /// Downstream decoders must use this engine-level API for capacity planning
316    /// instead of recomputing the bound from [`Codec`] constants.
317    ///
318    /// # Parameters
319    ///
320    /// - `input_len`: Number of source units the caller plans to decode.
321    ///
322    /// # Returns
323    ///
324    /// Returns a conservative upper bound, or a capacity error on arithmetic
325    /// overflow.
326    ///
327    /// # Errors
328    ///
329    /// Returns a capacity error when hook capacity arithmetic overflows.
330    #[inline]
331    #[must_use = "capacity planning can fail on overflow"]
332    pub fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
333        self.hooks.max_transcode_output_len(&self.codec, input_len)
334    }
335
336    /// Returns the global maximum values emitted by finishing codec state and
337    /// finishing hook-owned state.
338    ///
339    /// # Returns
340    ///
341    /// Returns the sum of [`Codec::MAX_DECODE_FINISH_VALUES`] and the
342    /// hook-provided final-output bound. The codec finish portion covers values
343    /// written by [`Codec::decode_finish`]; hook implementations must not
344    /// include that portion in
345    /// [`TranscodeDecodeHooks::max_finish_output_len`]. Both component bounds
346    /// cover every reachable transient state.
347    ///
348    /// # Errors
349    ///
350    /// Returns a capacity error when the combined finish bound exceeds
351    /// `usize::MAX`.
352    #[inline]
353    #[must_use = "capacity planning can fail on overflow"]
354    pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
355        add_decode_output_bounds(
356            C::MAX_DECODE_FINISH_VALUES,
357            self.hooks.max_finish_output_len(&self.codec),
358        )
359    }
360
361    /// Returns the global maximum values emitted when resetting stream state.
362    ///
363    /// # Returns
364    ///
365    /// Returns `Ok(Codec::MAX_DECODE_RESET_VALUES)` for the wrapped codec.
366    /// This query cannot fail.
367    /// Stateless decoders always return `0`.
368    #[inline]
369    #[must_use = "capacity planning can fail on overflow"]
370    pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
371        Ok(C::MAX_DECODE_RESET_VALUES)
372    }
373
374    /// Returns the maximum values needed by a complete one-shot decode stream.
375    ///
376    /// The returned bound covers reset output, the streaming decode phase for
377    /// `input_len` units, and finish output. Its components are global across
378    /// transient state. Higher-level complete decode helpers should use this
379    /// engine-level bound instead of recomputing capacity from [`Codec`]
380    /// constants, because hook policy may change streaming or finish output.
381    ///
382    /// # Parameters
383    ///
384    /// - `input_len`: Number of source units in the complete stream.
385    ///
386    /// # Returns
387    ///
388    /// Returns the complete-stream output bound, or a capacity error on
389    /// arithmetic overflow.
390    ///
391    /// # Errors
392    ///
393    /// Returns a capacity error when a component or their sum overflows.
394    #[inline]
395    #[must_use = "capacity planning can fail on overflow"]
396    pub fn max_total_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
397        let transcode = self.max_transcode_output_len(input_len)?;
398        let finish = self.max_finish_output_len()?;
399        sum_decode_output_bounds(C::MAX_DECODE_RESET_VALUES, transcode, finish)
400    }
401
402    /// Resets codec decode state, runs reset hooks, and emits stream-start
403    /// values.
404    ///
405    /// The sequence is: validate capacity → run `reset_hooks` → call
406    /// [`Codec::decode_reset`]. Stateless decoders (`MAX_DECODE_RESET_VALUES
407    /// == 0`) write nothing and return `Ok(0)`.
408    ///
409    /// # Parameters
410    ///
411    /// - `output`: Complete output value slice visible to the decoder.
412    /// - `output_index`: Absolute output value index where writing starts.
413    ///
414    /// # Returns
415    ///
416    /// Returns the number of reset values written.
417    ///
418    /// # Errors
419    ///
420    /// Returns framework errors when the caller provides invalid or
421    /// insufficient output capacity. Returns domain errors when codec reset
422    /// or hook reset handling fails. Capacity and index failures occur before
423    /// any reset state is changed. Once reset execution starts, an error
424    /// poisons the engine until a later reset succeeds.
425    ///
426    /// # Panics
427    ///
428    /// Panics if the codec reports more than `MAX_DECODE_RESET_VALUES` written.
429    pub fn reset(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
430        let required = C::MAX_DECODE_RESET_VALUES;
431        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
432        self.lifecycle.on_reset_start();
433        self.hooks.reset_hooks(&mut self.codec);
434        let written = unsafe {
435            // SAFETY: The capacity check above reserves the codec's declared
436            // reset-output bound at `output_index`.
437            self.codec.decode_reset(output, output_index)
438        }
439        .map_err(TranscodeDecodeError::domain_reset)?;
440        assert!(written <= required, "Codec::decode_reset wrote beyond its reset bound",);
441        self.lifecycle.on_reset_success();
442        Ok(written)
443    }
444
445    /// Decodes source units into caller-provided output values.
446    ///
447    /// # Parameters
448    ///
449    /// - `input`: Complete input unit slice visible to the decoder.
450    /// - `input_index`: Absolute input unit index where decoding starts.
451    /// - `output`: Complete output value slice visible to the decoder.
452    /// - `output_index`: Absolute output value index where writing starts.
453    ///
454    /// # Returns
455    ///
456    /// Returns progress describing input units consumed, output values written,
457    /// and why decoding stopped.
458    ///
459    /// # Errors
460    ///
461    /// Returns framework errors when either index is outside its slice, and
462    /// domain errors when a concrete policy hook rejects a value. Returns
463    /// [`TranscodeFailure::TranscodeBeforeReset`] when the engine has not
464    /// completed its first reset. Returns
465    /// [`TranscodeFailure::TranscodeAfterFinish`] when the logical stream was
466    /// already finished and has not been reset, or
467    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
468    /// failed after execution started.
469    ///
470    /// # Panics
471    ///
472    /// Panics when codec or hook progress violates declared decode bounds.
473    pub fn transcode(
474        &mut self,
475        input: &[C::Unit],
476        input_index: usize,
477        output: &mut [C::Value],
478        output_index: usize,
479    ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
480        self.transcode_with_eof(input, input_index, output, output_index, false)
481    }
482
483    /// Decodes source units after the caller has established end of input.
484    ///
485    /// This follows the normal streaming contract, except that codec attempts
486    /// use [`Codec::decode_eof`]. A codec may therefore resolve a trailing
487    /// prefix that would remain incomplete in an open stream.
488    ///
489    /// # Parameters
490    ///
491    /// - `input`: Complete source-unit slice.
492    /// - `input_index`: Absolute index of the next source unit.
493    /// - `output`: Destination for decoded values.
494    /// - `output_index`: Absolute index of the next destination slot.
495    ///
496    /// # Returns
497    ///
498    /// Returns consumed/written counts and the reason decoding stopped.
499    ///
500    /// # Errors
501    ///
502    /// Returns framework errors for invalid lifecycle state or slice indices,
503    /// and policy/domain errors for rejected input, including incomplete EOF
504    /// tails.
505    ///
506    /// # Panics
507    ///
508    /// Panics when codec or hook progress violates declared decode bounds.
509    pub fn transcode_eof(
510        &mut self,
511        input: &[C::Unit],
512        input_index: usize,
513        output: &mut [C::Value],
514        output_index: usize,
515    ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
516        self.transcode_with_eof(input, input_index, output, output_index, true)
517    }
518
519    /// Finishes codec and hook-owned output after EOF.
520    ///
521    /// Finalization first finishes decode-side codec state through
522    /// [`Codec::decode_finish`], then lets hook implementations finish their
523    /// own retained state. The caller must provide enough output capacity for
524    /// [`TranscodeDecodeEngine::max_finish_output_len`], which includes both
525    /// the codec finish bound and the hook-owned finish bound.
526    ///
527    /// # Parameters
528    ///
529    /// - `output`: Complete output value slice visible to the decoder.
530    /// - `output_index`: Absolute output value index where writing starts.
531    ///
532    /// # Returns
533    ///
534    /// Returns the number of values written by finalization.
535    ///
536    /// # Errors
537    ///
538    /// Returns framework errors when the caller provides invalid or
539    /// insufficient output capacity. Returns domain errors when codec finish or
540    /// hook finalization fails. Returns
541    /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
542    /// completed its first reset. Returns
543    /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
544    /// already finished and has not been reset, or
545    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
546    /// failed after execution started. Capacity and index failures occur before
547    /// finish execution and remain retryable; later failures poison the engine
548    /// until reset succeeds.
549    ///
550    /// # Panics
551    ///
552    /// Panics when the codec finish writes beyond
553    /// [`Codec::MAX_DECODE_FINISH_VALUES`] or when the combined codec and hook
554    /// finalization writes beyond
555    /// [`TranscodeDecodeEngine::max_finish_output_len`].
556    pub fn finish(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
557        self.lifecycle.on_finish_attempt()?;
558        let required = self.max_finish_output_len()?;
559        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
560        self.lifecycle.on_finish_start();
561        let finished =
562            unsafe { self.codec.decode_finish(output, output_index) }.map_err(TranscodeDecodeError::domain_finish)?;
563        assert!(
564            finished <= C::MAX_DECODE_FINISH_VALUES,
565            "Codec::decode_finish wrote beyond its finish bound",
566        );
567        let written = self
568            .hooks
569            .finish_hooks(&mut self.codec, output, output_index + finished)?;
570        assert!(
571            finished + written <= required,
572            "TranscodeDecodeEngine hook wrote beyond its finish bound",
573        );
574        self.lifecycle.on_finish_success();
575        Ok(finished + written)
576    }
577
578    /// Runs a complete one-shot `reset -> transcode -> finish` decode stream.
579    ///
580    /// The complete input is supplied as `input`, and output starts at index
581    /// `0` in `output`. Callers that need subranges should slice their
582    /// buffers before calling this method. Downstream one-shot decoder
583    /// helpers should call this engine method instead of reproducing the
584    /// reset, transcode, and finish sequence themselves.
585    ///
586    /// # Parameters
587    ///
588    /// - `input`: Complete source unit slice.
589    /// - `output`: Output value slice for the whole decoded stream.
590    ///
591    /// # Returns
592    ///
593    /// Returns the number of output values written.
594    ///
595    /// # Errors
596    ///
597    /// Returns framework errors for insufficient output, capacity overflow, or
598    /// an incomplete EOF tail, and domain errors from reset, decode, or
599    /// finish.
600    ///
601    /// # Panics
602    ///
603    /// Panics when codec or hook output violates reset, decode, or finish
604    /// bounds.
605    pub fn transcode_complete_into(
606        &mut self,
607        input: &[C::Unit],
608        output: &mut [C::Value],
609    ) -> Result<usize, TranscodeDecodeErrorOf<C>> {
610        <Self as Transcoder>::transcode_complete_into(self, input, output)
611    }
612
613    /// Decodes one source value attempt and delivers emitted values.
614    ///
615    /// # Parameters
616    ///
617    /// - `input`: Complete input unit slice visible to the caller.
618    /// - `context`: Decode context describing the current source and output
619    ///   cursors.
620    /// - `end_of_input`: Whether the source is final and EOF policy applies.
621    /// - `consume`: Callback invoked exactly once when this attempt emits a
622    ///   logical value.
623    ///
624    /// # Type Parameters
625    ///
626    /// - `R`: Value returned by the consumer when a decoded value is emitted.
627    /// - `F`: Consumer callback type.
628    ///
629    /// # Returns
630    ///
631    /// Returns the decode outcome and `Some` consumer result when a value was
632    /// emitted; returns `None` when input was skipped or more input is needed.
633    ///
634    /// # Errors
635    ///
636    /// Returns hook errors when the decode policy rejects the input.
637    ///
638    /// # Panics
639    ///
640    /// Panics when the codec reports consumption beyond the available input,
641    /// an incomplete-input requirement beyond
642    /// [`Codec::MAX_DECODE_UNITS_PER_VALUE`], or hooks return an action that
643    /// consumes beyond the available input. Also panics when reported consumed
644    /// width exceeds the codec maximum or an incomplete requirement does not
645    /// exceed available input. Debug builds reject an empty context.
646    pub(crate) fn decode_one<R, F>(
647        &mut self,
648        input: &[C::Unit],
649        context: DecodeContext,
650        end_of_input: bool,
651        consume: F,
652    ) -> Result<(DecodeOutcome, Option<R>), TranscodeDecodeErrorOf<C>>
653    where
654        F: FnOnce(C::Value, usize) -> R,
655    {
656        debug_assert!(
657            context.available() > 0,
658            "decode_one requires at least one source input unit",
659        );
660
661        if context.available() < C::MIN_UNITS_PER_VALUE {
662            let required_total =
663                NonZeroUsize::new(C::MIN_UNITS_PER_VALUE).expect("Codec::MIN_UNITS_PER_VALUE is non-zero");
664            if !end_of_input {
665                return Ok((DecodeOutcome::need_input(required_total), None));
666            }
667            return match self
668                .hooks
669                .handle_incomplete_decode(&mut self.codec, None, required_total, context)?
670            {
671                DecodeIncompleteAction::Reject => Err(TranscodeFailure::incomplete_input(
672                    context.input_index(),
673                    required_total.get(),
674                    context.available(),
675                )
676                .into()),
677                DecodeIncompleteAction::Skip => {
678                    let read = NonZeroUsize::new(context.available())
679                        .expect("incomplete decode tail must contain source units");
680                    Ok((DecodeOutcome::skipped(read), None))
681                }
682                DecodeIncompleteAction::Emit { value } => {
683                    let read = NonZeroUsize::new(context.available())
684                        .expect("incomplete decode tail must contain source units");
685                    let consumed_value = consume(value, context.input_index());
686                    Ok((DecodeOutcome::emitted(read, NonZeroUsize::MIN), Some(consumed_value)))
687                }
688            };
689        }
690
691        // SAFETY: The context reports at least `MIN_UNITS_PER_VALUE` source
692        // units available from `context.input_index()`.
693        let result = unsafe {
694            if end_of_input {
695                self.codec.decode_eof(input, context.input_index())
696            } else {
697                self.codec.decode(input, context.input_index())
698            }
699        };
700        match result {
701            Ok((value, consumed)) => {
702                assert!(
703                    consumed.get() <= context.available(),
704                    "Codec::decode consumed beyond available input",
705                );
706                assert!(
707                    consumed.get() <= C::MAX_DECODE_UNITS_PER_VALUE,
708                    "Codec::decode consumed beyond Codec::MAX_DECODE_UNITS_PER_VALUE",
709                );
710                let consumed_value = consume(value, context.input_index());
711                Ok((
712                    DecodeOutcome::emitted(consumed, NonZeroUsize::MIN),
713                    Some(consumed_value),
714                ))
715            }
716            Err(DecodeFailure::Incomplete { source, required_total }) => {
717                assert!(
718                    required_total.get() > context.available(),
719                    "Codec::decode incomplete required_total must exceed available input",
720                );
721                assert!(
722                    required_total.get() <= C::MAX_DECODE_UNITS_PER_VALUE,
723                    "Codec::decode incomplete required_total exceeded Codec::MAX_DECODE_UNITS_PER_VALUE",
724                );
725                if !end_of_input {
726                    return Ok((DecodeOutcome::need_input(required_total), None));
727                }
728                match self
729                    .hooks
730                    .handle_incomplete_decode(&mut self.codec, source.as_ref(), required_total, context)?
731                {
732                    DecodeIncompleteAction::Reject => match source {
733                        Some(source) => Err(TranscodeDecodeError::domain_main(source, context.input_index())),
734                        None => Err(TranscodeFailure::incomplete_input(
735                            context.input_index(),
736                            required_total.get(),
737                            context.available(),
738                        )
739                        .into()),
740                    },
741                    DecodeIncompleteAction::Skip => {
742                        let read = NonZeroUsize::new(context.available())
743                            .expect("incomplete decode tail must contain source units");
744                        Ok((DecodeOutcome::skipped(read), None))
745                    }
746                    DecodeIncompleteAction::Emit { value } => {
747                        let read = NonZeroUsize::new(context.available())
748                            .expect("incomplete decode tail must contain source units");
749                        let consumed_value = consume(value, context.input_index());
750                        Ok((DecodeOutcome::emitted(read, NonZeroUsize::MIN), Some(consumed_value)))
751                    }
752                }
753            }
754            Err(DecodeFailure::Invalid { source, consumed }) => {
755                let action = match self
756                    .hooks
757                    .handle_invalid_decode(&mut self.codec, &source, consumed, context)?
758                {
759                    DecodeInvalidAction::Reject => {
760                        return Err(TranscodeDecodeError::domain_main_with_consumed(
761                            source,
762                            context.input_index(),
763                            consumed,
764                        ));
765                    }
766                    DecodeInvalidAction::Skip { consumed } => AppliedDecodeInvalidAction::Skip { consumed },
767                    DecodeInvalidAction::Emit { value, consumed } => {
768                        AppliedDecodeInvalidAction::Emit { value, consumed }
769                    }
770                };
771                Ok(Self::apply_invalid_decode_action(action, context, consume))
772            }
773        }
774    }
775
776    /// Runs one decode step with an explicit end-of-input flag.
777    ///
778    /// The helper centralizes lifecycle validation and shared cursor handling
779    /// for the streaming and EOF-aware entry points.
780    ///
781    /// # Parameters
782    ///
783    /// - `input`: Complete source-unit slice.
784    /// - `input_index`: Absolute index of the next source unit.
785    /// - `output`: Destination for decoded values.
786    /// - `output_index`: Absolute index of the next destination slot.
787    /// - `end_of_input`: Whether to resolve incomplete tails through EOF
788    ///   policy.
789    ///
790    /// # Returns
791    ///
792    /// Returns consumed/written counts and the reason decoding stopped.
793    ///
794    /// # Errors
795    ///
796    /// Returns framework errors for invalid lifecycle state or slice indices,
797    /// and policy/domain errors for rejected input, including incomplete EOF
798    /// tails.
799    ///
800    /// # Panics
801    ///
802    /// Panics when codec or hook progress violates declared decode bounds.
803    fn transcode_with_eof(
804        &mut self,
805        input: &[C::Unit],
806        input_index: usize,
807        output: &mut [C::Value],
808        output_index: usize,
809        end_of_input: bool,
810    ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
811        self.lifecycle.on_transcode()?;
812        TranscodeFailure::ensure_transcode_indices(input.len(), input_index, output.len(), output_index)?;
813
814        let min_units = NonZeroUsize::new(C::MIN_UNITS_PER_VALUE).expect("Codec::MIN_UNITS_PER_VALUE is non-zero");
815        let min_units_len = min_units.get();
816        let mut state = DecodeState::new(input, input_index, output, output_index);
817        while state.has_input() {
818            let context = state.context();
819            let available = context.available();
820            if available < min_units_len {
821                if !end_of_input {
822                    return Ok(state.need_input_progress_with(min_units));
823                }
824                match self
825                    .hooks
826                    .handle_incomplete_decode(&mut self.codec, None, min_units, context)?
827                {
828                    DecodeIncompleteAction::Reject => {
829                        return Err(TranscodeFailure::incomplete_input(
830                            context.input_index(),
831                            min_units.get(),
832                            available,
833                        )
834                        .into());
835                    }
836                    DecodeIncompleteAction::Skip => {
837                        let read =
838                            NonZeroUsize::new(available).expect("incomplete decode tail must contain source units");
839                        if let Some(progress) = state.apply_decode_outcome(DecodeOutcome::skipped(read)) {
840                            return Ok(progress);
841                        }
842                        continue;
843                    }
844                    DecodeIncompleteAction::Emit { value } => {
845                        if state.needs_output() {
846                            return Ok(state.need_output_progress());
847                        }
848                        let output_index = state.output_cursor();
849                        // SAFETY: `needs_output()` returned false, so the
850                        // output cursor points at a writable initialized slot.
851                        unsafe {
852                            *state.output_mut().get_unchecked_mut(output_index) = value;
853                        }
854                        let read =
855                            NonZeroUsize::new(available).expect("incomplete decode tail must contain source units");
856                        if let Some(progress) =
857                            state.apply_decode_outcome(DecodeOutcome::emitted(read, NonZeroUsize::MIN))
858                        {
859                            return Ok(progress);
860                        }
861                        continue;
862                    }
863                }
864            }
865            if state.needs_output() {
866                return Ok(state.need_output_progress());
867            }
868            let output_index = state.output_cursor();
869            let output = state.output_mut();
870            let (outcome, _) = self.decode_one(input, context, end_of_input, |value, _input_index| {
871                // SAFETY: `needs_output()` returned false, so the output
872                // cursor points at a writable initialized slot.
873                unsafe {
874                    *output.get_unchecked_mut(output_index) = value;
875                }
876            })?;
877            if let Some(progress) = state.apply_decode_outcome(outcome) {
878                return Ok(progress);
879            }
880        }
881
882        Ok(state.complete_progress())
883    }
884
885    /// Applies a hook-selected invalid-decode action after reject handling.
886    ///
887    /// # Type Parameters
888    ///
889    /// - `R`: Consumer result for an emitted value.
890    /// - `F`: Callback consuming an emitted value and its source index.
891    ///
892    /// # Parameters
893    ///
894    /// - `action`: Validated policy action to skip or emit.
895    /// - `context`: Source cursor and available input.
896    /// - `consume`: Callback invoked only for an emitted value.
897    ///
898    /// # Returns
899    ///
900    /// Returns progress with `Some` callback result for Emit, or `None` for
901    /// Skip.
902    ///
903    /// # Panics
904    ///
905    /// Panics if the action consumes more units than the context makes
906    /// available.
907    fn apply_invalid_decode_action<R, F>(
908        action: AppliedDecodeInvalidAction<C::Value>,
909        context: DecodeContext,
910        consume: F,
911    ) -> (DecodeOutcome, Option<R>)
912    where
913        F: FnOnce(C::Value, usize) -> R,
914    {
915        match action {
916            AppliedDecodeInvalidAction::Skip { consumed } => {
917                let read = DecodeInvalidAction::<C::Value>::bound_consumed(consumed, context.available());
918                (DecodeOutcome::skipped(read), None)
919            }
920            AppliedDecodeInvalidAction::Emit { value, consumed } => {
921                let read = DecodeInvalidAction::<C::Value>::bound_consumed(consumed, context.available());
922                let consumed_value = consume(value, context.input_index());
923                (DecodeOutcome::emitted(read, NonZeroUsize::MIN), Some(consumed_value))
924            }
925        }
926    }
927}
928
929impl<C, H> Transcoder for TranscodeDecodeEngine<C, H>
930where
931    C: Codec,
932    H: TranscodeDecodeHooks<C>,
933{
934    /// Source units accepted by the wrapped codec.
935    type Input = C::Unit;
936    /// Logical values decoded by the wrapped codec.
937    type Output = C::Value;
938    /// Framework failures and the wrapped codec's decode errors.
939    type Error = TranscodeDecodeErrorOf<C>;
940
941    /// Returns an upper bound for decoded values produced from `input_len`
942    /// units.
943    ///
944    /// # Parameters
945    ///
946    /// - `input_len`: Number of source units to decode.
947    ///
948    /// # Returns
949    ///
950    /// Returns the conservative streaming output bound.
951    ///
952    /// # Errors
953    ///
954    /// Returns a capacity error on hook arithmetic overflow.
955    #[inline]
956    fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
957        TranscodeDecodeEngine::max_transcode_output_len(self, input_len)
958    }
959
960    /// Returns an upper bound for values produced by finishing codec and hook
961    /// state.
962    ///
963    /// # Returns
964    ///
965    /// Returns the combined codec and hook finish bound.
966    ///
967    /// # Errors
968    ///
969    /// Returns a capacity error if that sum overflows.
970    #[inline]
971    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
972        TranscodeDecodeEngine::max_finish_output_len(self)
973    }
974
975    /// Returns an upper bound for values emitted when resetting stream state.
976    ///
977    /// # Returns
978    ///
979    /// Returns the codec reset bound as `Ok`; this query cannot fail.
980    #[inline]
981    fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
982        TranscodeDecodeEngine::max_reset_output_len(self)
983    }
984
985    /// Resets hooks and codec state, emitting any stream-start values.
986    ///
987    /// # Parameters
988    ///
989    /// - `output`: Destination value slice.
990    /// - `output_index`: First writable value index.
991    ///
992    /// # Returns
993    ///
994    /// Returns the number of stream-start values written.
995    ///
996    /// # Errors
997    ///
998    /// Returns invalid-capacity/index errors or codec reset domain errors.
999    ///
1000    /// # Panics
1001    ///
1002    /// Panics if codec reset output exceeds its declared bound.
1003    fn reset(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
1004        TranscodeDecodeEngine::reset(self, output, output_index)
1005    }
1006
1007    /// Decodes source units into logical values.
1008    ///
1009    /// # Parameters
1010    ///
1011    /// - `input`: Source unit slice.
1012    /// - `input_index`: First source index.
1013    /// - `output`: Destination value slice.
1014    /// - `output_index`: First destination index.
1015    ///
1016    /// # Returns
1017    ///
1018    /// Returns consumed/written counts and the stop status.
1019    ///
1020    /// # Errors
1021    ///
1022    /// Returns lifecycle/index errors or rejected-input domain errors.
1023    ///
1024    /// # Panics
1025    ///
1026    /// Panics if codec or hook progress violates decode bounds.
1027    fn transcode(
1028        &mut self,
1029        input: &[C::Unit],
1030        input_index: usize,
1031        output: &mut [C::Value],
1032        output_index: usize,
1033    ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
1034        TranscodeDecodeEngine::transcode(self, input, input_index, output, output_index)
1035    }
1036
1037    /// Decodes source units after end of input is known.
1038    ///
1039    /// # Parameters
1040    ///
1041    /// - `input`: Final source unit slice.
1042    /// - `input_index`: First source index.
1043    /// - `output`: Destination value slice.
1044    /// - `output_index`: First destination index.
1045    ///
1046    /// # Returns
1047    ///
1048    /// Returns consumed/written counts and the stop status.
1049    ///
1050    /// # Errors
1051    ///
1052    /// Returns lifecycle/index errors or rejected input and incomplete-tail
1053    /// errors.
1054    ///
1055    /// # Panics
1056    ///
1057    /// Panics if codec or hook progress violates decode bounds.
1058    fn transcode_eof(
1059        &mut self,
1060        input: &[C::Unit],
1061        input_index: usize,
1062        output: &mut [C::Value],
1063        output_index: usize,
1064    ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
1065        TranscodeDecodeEngine::transcode_eof(self, input, input_index, output, output_index)
1066    }
1067
1068    /// Finishes internally retained output after EOF.
1069    ///
1070    /// # Parameters
1071    ///
1072    /// - `output`: Destination value slice.
1073    /// - `output_index`: First writable value index.
1074    ///
1075    /// # Returns
1076    ///
1077    /// Returns the number of final values written.
1078    ///
1079    /// # Errors
1080    ///
1081    /// Returns lifecycle/capacity/index errors or codec/hook finish errors.
1082    ///
1083    /// # Panics
1084    ///
1085    /// Panics if finalization output exceeds its declared bounds.
1086    fn finish(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
1087        TranscodeDecodeEngine::finish(self, output, output_index)
1088    }
1089}
1090
1091impl<C, H> TranscodeDecoder for TranscodeDecodeEngine<C, H>
1092where
1093    C: Codec,
1094    H: TranscodeDecodeHooks<C>,
1095{
1096    /// Domain error reported by the wrapped codec while decoding.
1097    type DecodeError = C::DecodeError;
1098}