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