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