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