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