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