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 /// # Type Parameters
969 ///
970 /// - `R`: Consumer result for an emitted value.
971 /// - `F`: Callback used to consume an emitted value and its source index.
972 ///
973 /// # Parameters
974 ///
975 /// - `source`: Optional codec error associated with the incomplete tail.
976 /// - `required_total`: Minimum total input width needed to decode a value.
977 /// - `context`: Decode cursors and the number of visible source units.
978 /// - `consume`: Callback used once when the policy emits a value.
979 ///
980 /// # Returns
981 ///
982 /// Returns the decode outcome and `Some` consumer result when the policy
983 /// emits a value; returns `None` when it skips the incomplete tail.
984 ///
985 /// # Errors
986 ///
987 /// Returns the hook error or the appropriate incomplete-input rejection,
988 /// preserving a codec error source when one was supplied.
989 fn apply_incomplete_decode<R, F>(
990 &mut self,
991 source: Option<C::DecodeError>,
992 required_total: NonZeroUsize,
993 context: DecodeContext,
994 consume: F,
995 ) -> Result<(DecodeOutcome, Option<R>), TranscodeDecodeErrorOf<C>>
996 where
997 F: FnOnce(C::Value, usize) -> R,
998 {
999 match self
1000 .hooks
1001 .handle_incomplete_decode(&mut self.codec, source.as_ref(), required_total, context)?
1002 {
1003 DecodeIncompleteAction::Reject => match source {
1004 Some(source) => Err(TranscodeDecodeError::domain_main(source, context.input_index())),
1005 None => Err(TranscodeFailure::incomplete_input(
1006 context.input_index(),
1007 required_total.get(),
1008 context.available(),
1009 )
1010 .into()),
1011 },
1012 DecodeIncompleteAction::Skip => {
1013 let read =
1014 NonZeroUsize::new(context.available()).expect("incomplete decode tail must contain source units");
1015 Ok((DecodeOutcome::skipped(read), None))
1016 }
1017 DecodeIncompleteAction::Emit { value } => {
1018 let read =
1019 NonZeroUsize::new(context.available()).expect("incomplete decode tail must contain source units");
1020 let result = consume(value, context.input_index());
1021 Ok((DecodeOutcome::emitted(read, NonZeroUsize::MIN), Some(result)))
1022 }
1023 }
1024 }
1025}
1026
1027impl<C, H> Transcoder for TranscodeDecodeEngine<C, H>
1028where
1029 C: Codec,
1030 H: TranscodeDecodeHooks<C>,
1031{
1032 /// Source units accepted by the wrapped codec.
1033 type Input = C::Unit;
1034 /// Logical values decoded by the wrapped codec.
1035 type Output = C::Value;
1036 /// Framework failures and the wrapped codec's decode errors.
1037 type Error = TranscodeDecodeErrorOf<C>;
1038
1039 /// Returns an upper bound for decoded values produced from `input_len`
1040 /// units.
1041 ///
1042 /// # Parameters
1043 ///
1044 /// - `input_len`: Number of source units to decode.
1045 ///
1046 /// # Returns
1047 ///
1048 /// Returns the conservative streaming output bound.
1049 ///
1050 /// # Errors
1051 ///
1052 /// Returns a capacity error on hook arithmetic overflow.
1053 #[inline]
1054 fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
1055 TranscodeDecodeEngine::max_transcode_output_len(self, input_len)
1056 }
1057
1058 /// Returns an upper bound for values produced by finishing codec and hook
1059 /// state.
1060 ///
1061 /// # Returns
1062 ///
1063 /// Returns the combined codec and hook finish bound.
1064 ///
1065 /// # Errors
1066 ///
1067 /// Returns a capacity error if that sum overflows.
1068 #[inline]
1069 fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
1070 TranscodeDecodeEngine::max_finish_output_len(self)
1071 }
1072
1073 /// Returns an upper bound for values emitted when resetting stream state.
1074 ///
1075 /// # Returns
1076 ///
1077 /// Returns the codec reset bound as `Ok`; this query cannot fail.
1078 #[inline]
1079 fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
1080 TranscodeDecodeEngine::max_reset_output_len(self)
1081 }
1082
1083 /// Resets hooks and codec state, emitting any stream-start values.
1084 ///
1085 /// # Parameters
1086 ///
1087 /// - `output`: Destination value slice.
1088 /// - `output_index`: First writable value index.
1089 ///
1090 /// # Returns
1091 ///
1092 /// Returns the number of stream-start values written.
1093 ///
1094 /// # Errors
1095 ///
1096 /// Returns invalid-capacity/index errors or codec reset domain errors.
1097 ///
1098 /// # Panics
1099 ///
1100 /// Panics if codec reset output exceeds its declared bound.
1101 fn reset(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
1102 TranscodeDecodeEngine::reset(self, output, output_index)
1103 }
1104
1105 /// Decodes source units into logical values.
1106 ///
1107 /// # Parameters
1108 ///
1109 /// - `input`: Source unit slice.
1110 /// - `input_index`: First source index.
1111 /// - `output`: Destination value slice.
1112 /// - `output_index`: First destination index.
1113 ///
1114 /// # Returns
1115 ///
1116 /// Returns consumed/written counts and the stop status.
1117 ///
1118 /// # Errors
1119 ///
1120 /// Returns lifecycle/index errors or rejected-input domain errors.
1121 ///
1122 /// # Panics
1123 ///
1124 /// Panics if codec or hook progress violates decode bounds.
1125 fn transcode(
1126 &mut self,
1127 input: &[C::Unit],
1128 input_index: usize,
1129 output: &mut [C::Value],
1130 output_index: usize,
1131 ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
1132 TranscodeDecodeEngine::transcode(self, input, input_index, output, output_index)
1133 }
1134
1135 /// Decodes source units after end of input is known.
1136 ///
1137 /// # Parameters
1138 ///
1139 /// - `input`: Final source unit slice.
1140 /// - `input_index`: First source index.
1141 /// - `output`: Destination value slice.
1142 /// - `output_index`: First destination index.
1143 ///
1144 /// # Returns
1145 ///
1146 /// Returns consumed/written counts and the stop status.
1147 ///
1148 /// # Errors
1149 ///
1150 /// Returns lifecycle/index errors or rejected input and incomplete-tail
1151 /// errors.
1152 ///
1153 /// # Panics
1154 ///
1155 /// Panics if codec or hook progress violates decode bounds.
1156 fn transcode_eof(
1157 &mut self,
1158 input: &[C::Unit],
1159 input_index: usize,
1160 output: &mut [C::Value],
1161 output_index: usize,
1162 ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
1163 TranscodeDecodeEngine::transcode_eof(self, input, input_index, output, output_index)
1164 }
1165
1166 /// Finishes internally retained output after EOF.
1167 ///
1168 /// # Parameters
1169 ///
1170 /// - `output`: Destination value slice.
1171 /// - `output_index`: First writable value index.
1172 ///
1173 /// # Returns
1174 ///
1175 /// Returns the number of final values written.
1176 ///
1177 /// # Errors
1178 ///
1179 /// Returns lifecycle/capacity/index errors or codec/hook finish errors.
1180 ///
1181 /// # Panics
1182 ///
1183 /// Panics if finalization output exceeds its declared bounds.
1184 fn finish(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
1185 TranscodeDecodeEngine::finish(self, output, output_index)
1186 }
1187}
1188
1189impl<C, H> TranscodeDecoder for TranscodeDecodeEngine<C, H>
1190where
1191 C: Codec,
1192 H: TranscodeDecodeHooks<C>,
1193{
1194 /// Domain error reported by the wrapped codec while decoding.
1195 type DecodeError = C::DecodeError;
1196}