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