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/// # Example
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 fn transcode_with_eof(
446 &mut self,
447 input: &[C::Unit],
448 input_index: usize,
449 output: &mut [C::Value],
450 output_index: usize,
451 end_of_input: bool,
452 ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
453 self.lifecycle.on_transcode()?;
454 TranscodeFailure::ensure_transcode_indices(input.len(), input_index, output.len(), output_index)?;
455
456 let min_units = NonZeroUsize::new(C::MIN_UNITS_PER_VALUE).expect("Codec::MIN_UNITS_PER_VALUE is non-zero");
457 let min_units_len = min_units.get();
458 let mut state = DecodeState::new(input, input_index, output, output_index);
459 while state.has_input() {
460 let context = state.context();
461 let available = context.available();
462 if available < min_units_len {
463 if !end_of_input {
464 return Ok(state.need_input_progress_with(min_units));
465 }
466 match self
467 .hooks
468 .handle_incomplete_decode(&mut self.codec, None, min_units, context)?
469 {
470 DecodeIncompleteAction::Reject => {
471 return Err(TranscodeFailure::incomplete_input(
472 context.input_index(),
473 min_units.get(),
474 available,
475 )
476 .into());
477 }
478 DecodeIncompleteAction::Skip => {
479 let read =
480 NonZeroUsize::new(available).expect("incomplete decode tail must contain source units");
481 if let Some(progress) = state.apply_decode_outcome(DecodeOutcome::skipped(read)) {
482 return Ok(progress);
483 }
484 continue;
485 }
486 DecodeIncompleteAction::Emit { value } => {
487 if state.needs_output() {
488 return Ok(state.need_output_progress());
489 }
490 let output_index = state.output_cursor();
491 // SAFETY: `needs_output()` returned false, so the
492 // output cursor points at a writable initialized slot.
493 unsafe {
494 *state.output_mut().get_unchecked_mut(output_index) = value;
495 }
496 let read =
497 NonZeroUsize::new(available).expect("incomplete decode tail must contain source units");
498 if let Some(progress) =
499 state.apply_decode_outcome(DecodeOutcome::emitted(read, NonZeroUsize::MIN))
500 {
501 return Ok(progress);
502 }
503 continue;
504 }
505 }
506 }
507 if state.needs_output() {
508 return Ok(state.need_output_progress());
509 }
510 let output_index = state.output_cursor();
511 let output = state.output_mut();
512 let (outcome, _) = self.decode_one(input, context, end_of_input, |value, _input_index| {
513 // SAFETY: `needs_output()` returned false, so the output
514 // cursor points at a writable initialized slot.
515 unsafe {
516 *output.get_unchecked_mut(output_index) = value;
517 }
518 })?;
519 if let Some(progress) = state.apply_decode_outcome(outcome) {
520 return Ok(progress);
521 }
522 }
523
524 Ok(state.complete_progress())
525 }
526
527 /// Finishes codec and hook-owned output after EOF.
528 ///
529 /// Finalization first finishes decode-side codec state through
530 /// [`Codec::decode_finish`], then lets hook implementations finish their
531 /// own retained state. The caller must provide enough output capacity for
532 /// [`TranscodeDecodeEngine::max_finish_output_len`], which includes both
533 /// the codec finish bound and the hook-owned finish bound.
534 ///
535 /// # Parameters
536 ///
537 /// - `output`: Complete output value slice visible to the decoder.
538 /// - `output_index`: Absolute output value index where writing starts.
539 ///
540 /// # Returns
541 ///
542 /// Returns the number of values written by finalization.
543 ///
544 /// # Errors
545 ///
546 /// Returns framework errors when the caller provides invalid or
547 /// insufficient output capacity. Returns domain errors when codec finish or
548 /// hook finalization fails. Returns
549 /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
550 /// completed its first reset. Returns
551 /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
552 /// already finished and has not been reset, or
553 /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
554 /// failed after execution started. Capacity and index failures occur before
555 /// finish execution and remain retryable; later failures poison the engine
556 /// until reset succeeds.
557 ///
558 /// # Panics
559 ///
560 /// Panics when the codec finish writes beyond
561 /// [`Codec::MAX_DECODE_FINISH_VALUES`] or when the combined codec and hook
562 /// finalization writes beyond
563 /// [`TranscodeDecodeEngine::max_finish_output_len`].
564 pub fn finish(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
565 self.lifecycle.on_finish_attempt()?;
566 let required = self.max_finish_output_len()?;
567 TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
568 self.lifecycle.on_finish_start();
569 let finished =
570 unsafe { self.codec.decode_finish(output, output_index) }.map_err(TranscodeDecodeError::domain_finish)?;
571 assert!(
572 finished <= C::MAX_DECODE_FINISH_VALUES,
573 "Codec::decode_finish wrote beyond its finish bound",
574 );
575 let written = self
576 .hooks
577 .finish_hooks(&mut self.codec, output, output_index + finished)?;
578 assert!(
579 finished + written <= required,
580 "TranscodeDecodeEngine hook wrote beyond its finish bound",
581 );
582 self.lifecycle.on_finish_success();
583 Ok(finished + written)
584 }
585
586 /// Runs a complete one-shot `reset -> transcode -> finish` decode stream.
587 ///
588 /// The complete input is supplied as `input`, and output starts at index
589 /// `0` in `output`. Callers that need subranges should slice their
590 /// buffers before calling this method. Downstream one-shot decoder
591 /// helpers should call this engine method instead of reproducing the
592 /// reset, transcode, and finish sequence themselves.
593 ///
594 /// # Parameters
595 ///
596 /// - `input`: Complete source unit slice.
597 /// - `output`: Output value slice for the whole decoded stream.
598 ///
599 /// # Returns
600 ///
601 /// Returns the number of output values written.
602 ///
603 /// # Errors
604 ///
605 /// Returns framework errors for insufficient output, capacity overflow, or
606 /// an incomplete EOF tail, and domain errors from reset, decode, or
607 /// finish.
608 #[inline(always)]
609 pub fn transcode_complete_into(
610 &mut self,
611 input: &[C::Unit],
612 output: &mut [C::Value],
613 ) -> Result<usize, TranscodeDecodeErrorOf<C>> {
614 <Self as Transcoder>::transcode_complete_into(self, input, output)
615 }
616
617 /// Decodes one source value attempt and delivers emitted values.
618 ///
619 /// # Parameters
620 ///
621 /// - `input`: Complete input unit slice visible to the caller.
622 /// - `context`: Decode context describing the current source and output
623 /// cursors.
624 /// - `consume`: Callback invoked exactly once when this attempt emits a
625 /// logical value.
626 ///
627 /// # Type Parameters
628 ///
629 /// - `R`: Value returned by the consumer when a decoded value is emitted.
630 /// - `F`: Consumer callback type.
631 ///
632 /// # Returns
633 ///
634 /// Returns the decode outcome and the consumer result when a value was
635 /// emitted.
636 ///
637 /// # Errors
638 ///
639 /// Returns hook errors when the decode policy rejects the input.
640 ///
641 /// # Panics
642 ///
643 /// Panics when the codec reports consumption beyond the available input,
644 /// an incomplete-input requirement beyond
645 /// [`Codec::MAX_DECODE_UNITS_PER_VALUE`], or hooks return an action that
646 /// consumes beyond the available input.
647 pub(crate) fn decode_one<R, F>(
648 &mut self,
649 input: &[C::Unit],
650 context: DecodeContext,
651 end_of_input: bool,
652 consume: F,
653 ) -> Result<(DecodeOutcome, Option<R>), TranscodeDecodeErrorOf<C>>
654 where
655 F: FnOnce(C::Value, usize) -> R,
656 {
657 debug_assert!(
658 context.available() > 0,
659 "decode_one requires at least one source input unit",
660 );
661
662 if context.available() < C::MIN_UNITS_PER_VALUE {
663 let required_total =
664 NonZeroUsize::new(C::MIN_UNITS_PER_VALUE).expect("Codec::MIN_UNITS_PER_VALUE is non-zero");
665 if !end_of_input {
666 return Ok((DecodeOutcome::need_input(required_total), None));
667 }
668 return match self
669 .hooks
670 .handle_incomplete_decode(&mut self.codec, None, required_total, context)?
671 {
672 DecodeIncompleteAction::Reject => Err(TranscodeFailure::incomplete_input(
673 context.input_index(),
674 required_total.get(),
675 context.available(),
676 )
677 .into()),
678 DecodeIncompleteAction::Skip => {
679 let read = NonZeroUsize::new(context.available())
680 .expect("incomplete decode tail must contain source units");
681 Ok((DecodeOutcome::skipped(read), None))
682 }
683 DecodeIncompleteAction::Emit { value } => {
684 let read = NonZeroUsize::new(context.available())
685 .expect("incomplete decode tail must contain source units");
686 let consumed_value = consume(value, context.input_index());
687 Ok((DecodeOutcome::emitted(read, NonZeroUsize::MIN), Some(consumed_value)))
688 }
689 };
690 }
691
692 // SAFETY: The context reports at least `MIN_UNITS_PER_VALUE` source
693 // units available from `context.input_index()`.
694 let result = unsafe {
695 if end_of_input {
696 self.codec.decode_eof(input, context.input_index())
697 } else {
698 self.codec.decode(input, context.input_index())
699 }
700 };
701 match result {
702 Ok((value, consumed)) => {
703 assert!(
704 consumed.get() <= context.available(),
705 "Codec::decode consumed beyond available input",
706 );
707 assert!(
708 consumed.get() <= C::MAX_DECODE_UNITS_PER_VALUE,
709 "Codec::decode consumed beyond Codec::MAX_DECODE_UNITS_PER_VALUE",
710 );
711 let consumed_value = consume(value, context.input_index());
712 Ok((
713 DecodeOutcome::emitted(consumed, NonZeroUsize::MIN),
714 Some(consumed_value),
715 ))
716 }
717 Err(DecodeFailure::Incomplete { source, required_total }) => {
718 assert!(
719 required_total.get() > context.available(),
720 "Codec::decode incomplete required_total must exceed available input",
721 );
722 assert!(
723 required_total.get() <= C::MAX_DECODE_UNITS_PER_VALUE,
724 "Codec::decode incomplete required_total exceeded Codec::MAX_DECODE_UNITS_PER_VALUE",
725 );
726 if !end_of_input {
727 return Ok((DecodeOutcome::need_input(required_total), None));
728 }
729 match self
730 .hooks
731 .handle_incomplete_decode(&mut self.codec, source.as_ref(), required_total, context)?
732 {
733 DecodeIncompleteAction::Reject => match source {
734 Some(source) => Err(TranscodeDecodeError::domain_main(source, context.input_index())),
735 None => Err(TranscodeFailure::incomplete_input(
736 context.input_index(),
737 required_total.get(),
738 context.available(),
739 )
740 .into()),
741 },
742 DecodeIncompleteAction::Skip => {
743 let read = NonZeroUsize::new(context.available())
744 .expect("incomplete decode tail must contain source units");
745 Ok((DecodeOutcome::skipped(read), None))
746 }
747 DecodeIncompleteAction::Emit { value } => {
748 let read = NonZeroUsize::new(context.available())
749 .expect("incomplete decode tail must contain source units");
750 let consumed_value = consume(value, context.input_index());
751 Ok((DecodeOutcome::emitted(read, NonZeroUsize::MIN), Some(consumed_value)))
752 }
753 }
754 }
755 Err(DecodeFailure::Invalid { source, consumed }) => {
756 let action = match self
757 .hooks
758 .handle_invalid_decode(&mut self.codec, &source, consumed, context)?
759 {
760 DecodeInvalidAction::Reject => {
761 return Err(TranscodeDecodeError::domain_main_with_consumed(
762 source,
763 context.input_index(),
764 consumed,
765 ));
766 }
767 DecodeInvalidAction::Skip { consumed } => AppliedDecodeInvalidAction::Skip { consumed },
768 DecodeInvalidAction::Emit { value, consumed } => {
769 AppliedDecodeInvalidAction::Emit { value, consumed }
770 }
771 };
772 Ok(Self::apply_invalid_decode_action(action, context, consume))
773 }
774 }
775 }
776
777 /// Applies a hook-selected invalid-decode action after reject handling.
778 fn apply_invalid_decode_action<R, F>(
779 action: AppliedDecodeInvalidAction<C::Value>,
780 context: DecodeContext,
781 consume: F,
782 ) -> (DecodeOutcome, Option<R>)
783 where
784 F: FnOnce(C::Value, usize) -> R,
785 {
786 match action {
787 AppliedDecodeInvalidAction::Skip { consumed } => {
788 let read = DecodeInvalidAction::<C::Value>::bound_consumed(consumed, context.available());
789 (DecodeOutcome::skipped(read), None)
790 }
791 AppliedDecodeInvalidAction::Emit { value, consumed } => {
792 let read = DecodeInvalidAction::<C::Value>::bound_consumed(consumed, context.available());
793 let consumed_value = consume(value, context.input_index());
794 (DecodeOutcome::emitted(read, NonZeroUsize::MIN), Some(consumed_value))
795 }
796 }
797 }
798}
799
800impl<C, H> Transcoder for TranscodeDecodeEngine<C, H>
801where
802 C: Codec,
803 H: TranscodeDecodeHooks<C>,
804{
805 type Input = C::Unit;
806 type Output = C::Value;
807 type Error = TranscodeDecodeErrorOf<C>;
808
809 /// Returns an upper bound for decoded values produced from `input_len`
810 /// units.
811 #[inline(always)]
812 fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
813 TranscodeDecodeEngine::max_transcode_output_len(self, input_len)
814 }
815
816 /// Returns an upper bound for values produced by finishing codec and hook
817 /// state.
818 #[inline(always)]
819 fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
820 TranscodeDecodeEngine::max_finish_output_len(self)
821 }
822
823 /// Returns an upper bound for values emitted when resetting stream state.
824 #[inline(always)]
825 fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
826 TranscodeDecodeEngine::max_reset_output_len(self)
827 }
828
829 /// Runs hook-owned cleanup before a logical decoder reset.
830 #[inline(always)]
831 fn reset(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
832 TranscodeDecodeEngine::reset(self, output, output_index)
833 }
834
835 /// Decodes source units into logical values.
836 #[inline(always)]
837 fn transcode(
838 &mut self,
839 input: &[C::Unit],
840 input_index: usize,
841 output: &mut [C::Value],
842 output_index: usize,
843 ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
844 TranscodeDecodeEngine::transcode(self, input, input_index, output, output_index)
845 }
846
847 /// Decodes source units after end of input is known.
848 #[inline(always)]
849 fn transcode_eof(
850 &mut self,
851 input: &[C::Unit],
852 input_index: usize,
853 output: &mut [C::Value],
854 output_index: usize,
855 ) -> Result<TranscodeProgress, TranscodeDecodeErrorOf<C>> {
856 TranscodeDecodeEngine::transcode_eof(self, input, input_index, output, output_index)
857 }
858
859 /// Finishes internally retained output after EOF.
860 #[inline(always)]
861 fn finish(&mut self, output: &mut [C::Value], output_index: usize) -> Result<usize, TranscodeDecodeErrorOf<C>> {
862 TranscodeDecodeEngine::finish(self, output, output_index)
863 }
864}
865
866impl<C, H> TranscodeDecoder for TranscodeDecodeEngine<C, H>
867where
868 C: Codec,
869 H: TranscodeDecodeHooks<C>,
870{
871 type DecodeError = C::DecodeError;
872}