Skip to main content

qubit_codec/transcode/engine/
transcode_convert_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 converter engine for codec-backed transcoding.
9//!
10//! Bridges a source [`crate::engine::TranscodeDecodeEngine`] and a target
11//! [`crate::engine::TranscodeEncodeEngine`] into one unit-to-unit conversion
12//! pipeline.
13
14use core::num::NonZeroUsize;
15
16use super::super::internal::convert_state::ConvertState;
17use super::super::internal::encode_attempt::EncodeAttempt;
18use super::super::internal::lifecycle_guard::LifecycleGuard;
19use super::super::internal::pending_value::PendingValue;
20use super::super::internal::pending_value_slot::PendingValueSlot;
21use super::TranscodeDecodeHooks;
22use super::TranscodeEncodeHooks;
23use super::transcode_decode_engine::TranscodeDecodeEngine;
24use super::transcode_encode_engine::TranscodeEncodeEngine;
25use crate::CapacityError;
26use crate::Codec;
27use crate::TranscodeConvertError;
28use crate::TranscodeConvertErrorOf;
29use crate::TranscodeConverter;
30use crate::TranscodeFailure;
31use crate::TranscodeProgress;
32use crate::Transcoder;
33use crate::codec::assert_unit_bounds;
34
35/// Adds two independent target-output capacity bounds.
36///
37/// # Parameters
38///
39/// - `first`: First independent output bound.
40/// - `second`: Second independent output bound.
41///
42/// # Returns
43///
44/// The checked sum of the supplied target-unit bounds.
45///
46/// # Errors
47///
48/// Returns [`CapacityError::OutputLengthOverflow`] when the sum exceeds
49/// `usize`.
50#[inline]
51fn add_convert_output_bounds(first: usize, second: usize) -> Result<usize, CapacityError> {
52    first.checked_add(second).ok_or(CapacityError::OutputLengthOverflow)
53}
54
55/// Adds three independent target-output capacity bounds.
56///
57/// # Parameters
58///
59/// - `first`: First independent target-output bound.
60/// - `second`: Second independent target-output bound.
61/// - `third`: Third independent target-output bound.
62///
63/// # Returns
64///
65/// The checked sum of the supplied target-unit bounds.
66///
67/// # Errors
68///
69/// Returns [`CapacityError::OutputLengthOverflow`] when the sum exceeds
70/// `usize`.
71#[inline]
72fn sum_convert_output_bounds(first: usize, second: usize, third: usize) -> Result<usize, CapacityError> {
73    let partial = add_convert_output_bounds(first, second)?;
74    add_convert_output_bounds(partial, third)
75}
76
77/// Asserts that a pre-reserved conversion phase did not need more output.
78///
79/// # Parameters
80///
81/// - `progress`: `None` after draining, or `Some` when output is still needed.
82/// - `message`: Invariant explanation included in a panic.
83///
84/// # Panics
85///
86/// Panics with `message` if `progress` is `Some`.
87#[inline]
88fn assert_reserved_output_drained(progress: Option<TranscodeProgress>, message: &'static str) {
89    assert!(progress.is_none(), "{message}");
90}
91
92/// Reusable buffered conversion engine for codec-backed converters.
93///
94/// The engine owns reusable buffered decode and encode engines. It keeps
95/// common converter control flow private: index validation, pending-value
96/// retention, finish draining, decode-error policy dispatch, encode attempts,
97/// output-capacity checks, and [`crate::TranscodeStatus`] reporting.
98///
99/// Use this type to build a streaming converter over two one-value [`Codec`]
100/// implementations that share the same logical value type. Each hot-path step
101/// decodes one source unit sequence into a value, then immediately tries to
102/// encode that value into the target output buffer. If the target buffer lacks
103/// capacity, the decoded value is retained in an internal pending slot and
104/// must be drained before more source input is consumed, preserving output
105/// order across buffer turns.
106///
107/// `TranscodeConvertEngine` is intentionally batch-oriented. Its public
108/// [`Self::transcode`] method drives a source/output buffer loop and reuses the
109/// same unchecked codec and hook primitives as
110/// [`crate::engine::TranscodeDecodeEngine`] and
111/// [`crate::engine::TranscodeEncodeEngine`]. It does not call one-value public
112/// transcoders in the hot path.
113///
114/// For strict codec-backed conversion with default decode and encode policies,
115/// use [`crate::CodecTranscodeConverter`]. Use `TranscodeConvertEngine`
116/// directly when either side needs custom malformed-input repair, encode
117/// planning, skipped values, or finish-time output.
118///
119/// The engine follows the same lifecycle as [`crate::Transcoder`]:
120/// `reset → transcode* → finish → reset`. Call [`Self::reset`] before starting
121/// a new logical stream and [`Self::finish`] after EOF once any incomplete
122/// source tail has been handled.
123///
124/// # Examples
125///
126/// ```rust
127/// use core::convert::Infallible;
128/// use core::num::NonZeroUsize;
129/// use qubit_codec::Codec;
130/// use qubit_codec::TranscodeDecodeErrorOf;
131/// use qubit_codec::TranscodeEncodeErrorOf;
132/// use qubit_codec::DecodeFailure;
133/// use qubit_codec::TranscodeStatus;
134/// use qubit_codec::engine::DecodeContext;
135/// use qubit_codec::engine::EncodeContext;
136/// use qubit_codec::engine::EncodeUnencodableAction;
137/// use qubit_codec::engine::TranscodeConvertEngine;
138/// use qubit_codec::engine::TranscodeDecodeHooks;
139/// use qubit_codec::engine::TranscodeEncodeHooks;
140///
141/// #[derive(Clone, Copy)]
142/// struct SourceCodec;
143///
144/// #[derive(Clone, Copy)]
145/// struct TargetCodec;
146///
147/// impl Codec for SourceCodec {
148///     type Value = u8;
149///     type Unit = u8;
150///     type DecodeError = Infallible;
151///     type EncodeError = Infallible;
152///
153///     const MIN_UNITS_PER_VALUE: usize = 1;
154///     const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
155///     const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
156///
157///     unsafe fn decode(
158///         &mut self,
159///         input: &[u8],
160///         index: usize,
161///     ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
162///         Ok((input[index].wrapping_add(1), NonZeroUsize::MIN))
163///     }
164///
165///     unsafe fn encode(
166///         &mut self,
167///         value: &u8,
168///         output: &mut [u8],
169///         index: usize,
170///     ) -> Result<usize, Self::EncodeError> {
171///         output[index] = *value;
172///         Ok(1)
173///     }
174/// }
175///
176/// impl Codec for TargetCodec {
177///     type Value = u8;
178///     type Unit = u8;
179///     type DecodeError = Infallible;
180///     type EncodeError = Infallible;
181///
182///     const MIN_UNITS_PER_VALUE: usize = 1;
183///     const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
184///     const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
185///
186///     unsafe fn decode(
187///         &mut self,
188///         input: &[u8],
189///         index: usize,
190///     ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
191///         Ok((input[index], NonZeroUsize::MIN))
192///     }
193///
194///     unsafe fn encode(
195///         &mut self,
196///         value: &u8,
197///         output: &mut [u8],
198///         index: usize,
199///     ) -> Result<usize, Self::EncodeError> {
200///         output[index] = *value;
201///         Ok(1)
202///     }
203/// }
204///
205/// struct StrictDecodeHooks;
206///
207/// impl TranscodeDecodeHooks<SourceCodec> for StrictDecodeHooks {
208///     fn handle_invalid_decode(
209///         &mut self,
210///         _codec: &mut SourceCodec,
211///         error: &Infallible,
212///         _consumed: Option<NonZeroUsize>,
213///         _context: DecodeContext,
214///     ) -> Result<qubit_codec::engine::DecodeInvalidAction<u8>, TranscodeDecodeErrorOf<SourceCodec>> {
215///         match *error {}
216///     }
217/// }
218///
219/// struct StrictEncodeHooks;
220///
221/// impl TranscodeEncodeHooks<TargetCodec> for StrictEncodeHooks {
222///     fn handle_unencodable_encode(
223///         &mut self,
224///         _codec: &mut TargetCodec,
225///         _context: &EncodeContext<'_, u8>,
226///     ) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<TargetCodec>> {
227///         unreachable!("TargetCodec accepts every u8")
228///     }
229/// }
230///
231/// let mut engine = TranscodeConvertEngine::new(
232///     SourceCodec,
233///     TargetCodec,
234///     StrictDecodeHooks,
235///     StrictEncodeHooks,
236/// );
237/// let input = [1_u8, 2, 3];
238/// let mut output = [0_u8; 2];
239/// let mut reset_output = [];
240/// engine.reset(&mut reset_output, 0)?;
241///
242/// let progress = engine.transcode(&input, 0, &mut output, 0)?;
243/// match progress.status() {
244///     TranscodeStatus::NeedOutput { .. } => {
245///         assert_eq!(2, progress.written());
246///         assert_eq!([2, 3], output);
247///         // Drain `output[..progress.written()]`, then resume at
248///         // `progress.read()` with fresh output capacity.
249///     }
250///     TranscodeStatus::Complete => unreachable!("output is intentionally short"),
251///     TranscodeStatus::NeedInput { .. } => unreachable!("input is complete"),
252/// }
253/// # Ok::<(), qubit_codec::TranscodeConvertErrorOf<SourceCodec, TargetCodec>>(())
254/// ```
255///
256/// # Type Parameters
257///
258/// - `D`: Source-side decoder codec.
259/// - `E`: Target-side encoder codec.
260/// - `DH`: Source-side decode hooks.
261/// - `EH`: Target-side encode hooks.
262#[derive(Debug)]
263pub struct TranscodeConvertEngine<D, E, DH, EH>
264where
265    D: Codec,
266    E: Codec<Value = D::Value>,
267    DH: TranscodeDecodeHooks<D>,
268    EH: TranscodeEncodeHooks<E>,
269{
270    /// Source-side buffered decoder engine.
271    decode_engine: TranscodeDecodeEngine<D, DH>,
272    /// Target-side buffered encoder engine.
273    encode_engine: TranscodeEncodeEngine<E, EH>,
274    /// Decoded value waiting for target output capacity.
275    pending: PendingValueSlot<D::Value>,
276    /// Guard for the `reset → transcode* → finish` lifecycle in every build
277    /// profile. The converter owns its own guard rather than delegating to the
278    /// inner decode/encode engines, because lifecycle events here describe the
279    /// converter as a whole.
280    lifecycle: LifecycleGuard,
281}
282
283impl<D, E, DH, EH> TranscodeConvertEngine<D, E, DH, EH>
284where
285    D: Codec,
286    E: Codec<Value = D::Value>,
287    DH: TranscodeDecodeHooks<D>,
288    EH: TranscodeEncodeHooks<E>,
289{
290    /// Creates a buffered converter engine.
291    ///
292    /// The caller supplies decode hooks and encode hooks directly.
293    ///
294    /// # Parameters
295    ///
296    /// - `decoder`: Low-level codec used for source decoding.
297    /// - `encoder`: Low-level codec used for target encoding.
298    /// - `decode_hooks`: Decode-side policy hooks.
299    /// - `encode_hooks`: Encode-side policy hooks.
300    ///
301    /// # Returns
302    ///
303    /// Returns a buffered converter engine.
304    ///
305    /// # Compile-Time Checks
306    ///
307    /// Fails to compile when either codec declares a zero decode unit bound or
308    /// when [`Codec::MIN_UNITS_PER_VALUE`] exceeds
309    /// [`Codec::MAX_DECODE_UNITS_PER_VALUE`].
310    #[inline]
311    #[must_use]
312    pub fn new(decoder: D, encoder: E, decode_hooks: DH, encode_hooks: EH) -> Self {
313        assert_unit_bounds::<D>();
314        assert_unit_bounds::<E>();
315        Self {
316            decode_engine: TranscodeDecodeEngine::new(decoder, decode_hooks),
317            encode_engine: TranscodeEncodeEngine::new(encoder, encode_hooks),
318            pending: PendingValueSlot::empty(),
319            lifecycle: LifecycleGuard::new(),
320        }
321    }
322
323    /// Returns the source-side codec.
324    ///
325    /// # Returns
326    ///
327    /// Returns a shared reference to the decoder codec owned by this engine.
328    #[inline]
329    #[must_use]
330    pub fn source_codec(&self) -> &D {
331        self.decode_engine.codec()
332    }
333
334    /// Returns the source-side codec mutably.
335    ///
336    /// # Returns
337    ///
338    /// Returns a mutable reference to the decoder codec owned by this engine.
339    #[inline]
340    #[must_use]
341    pub fn source_codec_mut(&mut self) -> &mut D {
342        self.decode_engine.codec_mut()
343    }
344
345    /// Returns the target-side codec.
346    ///
347    /// # Returns
348    ///
349    /// Returns a shared reference to the encoder codec owned by this engine.
350    #[inline]
351    #[must_use]
352    pub fn target_codec(&self) -> &E {
353        self.encode_engine.codec()
354    }
355
356    /// Returns the target-side codec mutably.
357    ///
358    /// # Returns
359    ///
360    /// Returns a mutable reference to the encoder codec owned by this engine.
361    #[inline]
362    #[must_use]
363    pub fn target_codec_mut(&mut self) -> &mut E {
364        self.encode_engine.codec_mut()
365    }
366
367    /// Returns the decode hooks used by this engine.
368    ///
369    /// # Returns
370    ///
371    /// Returns a shared reference to the decode hooks owned by this engine.
372    #[inline]
373    #[must_use]
374    pub const fn decode_hooks(&self) -> &DH {
375        self.decode_engine.hooks()
376    }
377
378    /// Returns the decode hooks mutably.
379    ///
380    /// Mutating the returned hooks does not reset the converter or clear a
381    /// pending value. The replacement hooks must continue to satisfy their
382    /// global output-capacity contract for every reachable converter state.
383    ///
384    /// # Returns
385    ///
386    /// Returns a mutable reference to the decode hooks owned by this engine.
387    #[inline]
388    #[must_use]
389    pub fn decode_hooks_mut(&mut self) -> &mut DH {
390        self.decode_engine.hooks_mut()
391    }
392
393    /// Returns the encode hooks used by this engine.
394    ///
395    /// # Returns
396    ///
397    /// Returns a shared reference to the encode hooks owned by this engine.
398    #[inline]
399    #[must_use]
400    pub const fn encode_hooks(&self) -> &EH {
401        self.encode_engine.hooks()
402    }
403
404    /// Returns the encode hooks mutably.
405    ///
406    /// Mutating the returned hooks does not reset the converter or clear a
407    /// pending value. The replacement hooks must continue to satisfy their
408    /// global output-capacity contract for every reachable converter state.
409    ///
410    /// # Returns
411    ///
412    /// Returns a mutable reference to the encode hooks owned by this engine.
413    #[inline]
414    #[must_use]
415    pub fn encode_hooks_mut(&mut self) -> &mut EH {
416        self.encode_engine.hooks_mut()
417    }
418
419    /// Consumes the engine and returns its codecs and hooks.
420    ///
421    /// Any pending value and lifecycle state owned by the converter are
422    /// discarded. Callers should use this only when no further conversion state
423    /// needs to be preserved.
424    ///
425    /// # Returns
426    ///
427    /// Returns the source codec, target codec, decode hooks, and encode hooks.
428    #[inline]
429    #[must_use]
430    pub fn into_parts(self) -> (D, E, DH, EH) {
431        let Self {
432            decode_engine,
433            encode_engine,
434            ..
435        } = self;
436        let (source, decode_hooks) = decode_engine.into_parts();
437        let (target, encode_hooks) = encode_engine.into_parts();
438        (source, target, decode_hooks, encode_hooks)
439    }
440
441    /// Returns an upper bound for target units produced from `input_len` units.
442    ///
443    /// The bound sums three parts: one possible retained pending value, the
444    /// maximum decoded values from the source side, and the maximum target
445    /// units for those values on the encode side. It covers only the streaming
446    /// convert phase and remains valid even when no value is currently pending.
447    /// Downstream converters must use this engine-level API for capacity
448    /// planning instead of recomputing the bound from the source or target
449    /// [`Codec`] constants.
450    ///
451    /// # Parameters
452    ///
453    /// - `input_len`: Number of source units the caller plans to convert.
454    ///
455    /// # Returns
456    ///
457    /// Returns a conservative upper bound, or a capacity error on arithmetic
458    /// overflow.
459    ///
460    /// # Errors
461    ///
462    /// Returns [`CapacityError`] when a component bound or their sum overflows
463    /// `usize`.
464    #[must_use = "capacity planning can fail on overflow"]
465    pub fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
466        let pending_units = self.encode_engine.max_transcode_output_len(1)?;
467        let decoded_values = self.decode_engine.max_transcode_output_len(input_len)?;
468        let converted_units = self.encode_engine.max_transcode_output_len(decoded_values)?;
469        add_convert_output_bounds(converted_units, pending_units)
470    }
471
472    /// Returns the global maximum target units emitted when resetting stream
473    /// state.
474    ///
475    /// Covers decode-side reset values (encoded to target units) plus
476    /// encode-side reset units. Most codecs are stateless and return `0`
477    /// for [`Codec::MAX_DECODE_RESET_VALUES`]; in that case this equals the
478    /// encode reset bound only.
479    ///
480    /// # Returns
481    ///
482    /// Returns the combined decode-reset and encode-reset output bound, or a
483    /// capacity error on arithmetic overflow.
484    ///
485    /// # Errors
486    ///
487    /// Returns [`CapacityError`] when a component bound or their sum overflows
488    /// `usize`.
489    #[must_use = "capacity planning can fail on overflow"]
490    pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
491        let decode_reset_units = self
492            .encode_engine
493            .max_transcode_output_len(D::MAX_DECODE_RESET_VALUES)?;
494        let encode_reset_units = E::MAX_ENCODE_RESET_UNITS;
495        add_convert_output_bounds(decode_reset_units, encode_reset_units)
496    }
497
498    /// Returns the maximum target units emitted by finishing retained state.
499    ///
500    /// The bound covers one possible retained pending value, the global
501    /// decode-side finish-value bound (encoded to target units), and the
502    /// global encode-side finish-unit bound. It is independent of whether a
503    /// pending value currently exists.
504    ///
505    /// # Returns
506    ///
507    /// Returns the combined pending, decode-finish, and encode-finish output
508    /// bound, or a capacity error on arithmetic overflow.
509    ///
510    /// # Errors
511    ///
512    /// Returns [`CapacityError`] when a component bound or their sum overflows
513    /// `usize`.
514    #[must_use = "capacity planning can fail on overflow"]
515    pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
516        let pending_units = self.encode_engine.max_transcode_output_len(1)?;
517        let decoder_finish_values = self.decode_engine.max_finish_output_len()?;
518        let decoder_finish_units = self.encode_engine.max_transcode_output_len(decoder_finish_values)?;
519        let encoder_finish_units = self.encode_engine.max_finish_output_len()?;
520        sum_convert_output_bounds(pending_units, decoder_finish_units, encoder_finish_units)
521    }
522
523    /// Returns the maximum target units needed by a complete one-shot
524    /// conversion.
525    ///
526    /// The returned bound covers conversion reset output, the streaming convert
527    /// phase for `input_len` source units, and finish output. Its components
528    /// are global across transient state. Higher-level complete conversion
529    /// helpers should use this engine-level bound instead of recomputing
530    /// capacity from the source or target codec constants, because decode
531    /// and encode hooks may change streaming or finish output.
532    ///
533    /// # Parameters
534    ///
535    /// - `input_len`: Number of source units in the complete stream.
536    ///
537    /// # Returns
538    ///
539    /// Returns the complete-stream target-output bound, or a capacity error on
540    /// arithmetic overflow.
541    ///
542    /// # Errors
543    ///
544    /// Returns [`CapacityError`] when a component bound or their sum overflows
545    /// `usize`.
546    #[must_use = "capacity planning can fail on overflow"]
547    pub fn max_total_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
548        let reset = self.max_reset_output_len()?;
549        let transcode = self.max_transcode_output_len(input_len)?;
550        let finish = self.max_finish_output_len()?;
551        sum_convert_output_bounds(reset, transcode, finish)
552    }
553
554    /// Clears retained conversion state, runs before-reset hooks, and emits
555    /// stream-start encode output.
556    ///
557    /// Reset clears any retained pending value, resets the target encoder, then
558    /// drains decode-side reset values through that reset encoder state. The
559    /// caller must provide enough output capacity for
560    /// [`Self::max_reset_output_len`].
561    ///
562    /// # Parameters
563    ///
564    /// - `output`: Complete output unit slice visible to the converter.
565    /// - `output_index`: Absolute output index where writing starts.
566    ///
567    /// # Returns
568    ///
569    /// Returns the number of target units written while resetting stream state.
570    ///
571    /// # Errors
572    ///
573    /// Returns a converter error if reset validation or target reset output
574    /// emission fails. Capacity and index failures occur before converter state
575    /// is changed. Once reset execution starts, an error poisons the converter
576    /// until a later reset succeeds.
577    ///
578    /// # Panics
579    ///
580    /// Panics when decode-reset values cannot be encoded within the capacity
581    /// reserved by [`Self::max_reset_output_len`].
582    pub fn reset(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>>
583    where
584        D::Value: Default,
585    {
586        let required = self.max_reset_output_len()?;
587        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
588        self.lifecycle.on_reset_start();
589
590        self.pending.clear();
591
592        // Reset the target first because source-side reset values must be
593        // encoded under the target's new-stream state. The reset bound reserves
594        // space for both target-owned output and the encoded source-reset
595        // values, so encode_pending should never report `NeedOutput` here.
596        let empty_input: &[D::Unit] = &[];
597        let mut state = ConvertState::new(empty_input, 0, output, output_index);
598        let output_cursor = state.output_cursor();
599        let encoder_written = self.encode_engine.reset(state.output_mut(), output_cursor)?;
600        state.advance_output(encoder_written);
601        self.drain_decoder_reset(&mut state)?;
602        self.lifecycle.on_reset_success();
603        Ok(state.written())
604    }
605
606    /// Converts source units into target units.
607    ///
608    /// The engine drains any retained pending value before consuming new input.
609    /// Each loop iteration decodes one source value and immediately attempts to
610    /// encode it. Conversion stops when the input tail is incomplete, when the
611    /// output buffer cannot hold the next encoded value, or when the visible
612    /// input is exhausted.
613    ///
614    /// # Parameters
615    ///
616    /// - `input`: Complete input unit slice visible to the converter.
617    /// - `input_index`: Absolute input index where conversion starts.
618    /// - `output`: Complete output unit slice visible to the converter.
619    /// - `output_index`: Absolute output index where writing starts.
620    ///
621    /// # Returns
622    ///
623    /// Returns conversion progress describing input units consumed, target
624    /// units written, and why conversion stopped.
625    ///
626    /// # Errors
627    ///
628    /// Returns hook errors when indices are invalid or concrete conversion
629    /// fails. Invalid output indices are reported through the encode-side
630    /// error path. Returns [`TranscodeFailure::TranscodeBeforeReset`] when the
631    /// engine has not completed its first reset. Returns
632    /// [`TranscodeFailure::TranscodeAfterFinish`] when the logical stream was
633    /// already finished and has not been reset, or
634    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
635    /// failed after execution started.
636    #[inline]
637    pub fn transcode(
638        &mut self,
639        input: &[D::Unit],
640        input_index: usize,
641        output: &mut [E::Unit],
642        output_index: usize,
643    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
644        self.transcode_with_eof(input, input_index, output, output_index, false)
645    }
646
647    /// Converts source units after the caller has established end of input.
648    ///
649    /// # Parameters
650    ///
651    /// - `input`: Source units visible to this call.
652    /// - `input_index`: Starting index in `input`.
653    /// - `output`: Destination for target units.
654    /// - `output_index`: Starting index in `output`.
655    ///
656    /// # Returns
657    ///
658    /// Consumed source units, written target units, and the stopping reason.
659    ///
660    /// # Errors
661    ///
662    /// Returns an error for invalid indices, a missing reset, a finished or
663    /// poisoned stream, or codec/hook conversion failure.
664    #[inline]
665    pub fn transcode_eof(
666        &mut self,
667        input: &[D::Unit],
668        input_index: usize,
669        output: &mut [E::Unit],
670        output_index: usize,
671    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
672        self.transcode_with_eof(input, input_index, output, output_index, true)
673    }
674
675    /// Finishes retained output after EOF.
676    ///
677    /// Finalization drains a pending decoded value first, then lets the
678    /// source-side decode hooks emit final values, encodes those values through
679    /// the target-side encode hooks, and finally finishes target-side encode
680    /// hook state. The decode-finish value buffer used for this cold path
681    /// requires `D::Value: Default`; the normal [`Self::transcode`] loop does
682    /// not.
683    ///
684    /// # Parameters
685    ///
686    /// - `output`: Complete output unit slice visible to the converter.
687    /// - `output_index`: Absolute output index where writing starts.
688    ///
689    /// # Returns
690    ///
691    /// Returns the number of target units written during finalization.
692    ///
693    /// # Errors
694    ///
695    /// Returns a converter error when output capacity checks fail or when
696    /// hook finalization fails. Returns
697    /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
698    /// completed its first reset. Returns
699    /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
700    /// already finished and has not been reset, or
701    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
702    /// failed after execution started. Capacity and index failures occur before
703    /// finish execution and remain retryable; later failures poison the
704    /// converter until reset succeeds.
705    ///
706    /// # Panics
707    ///
708    /// Panics when a retained pending value or decode-finish value cannot be
709    /// encoded within the planned finish capacity.
710    pub fn finish(
711        &mut self,
712        output: &mut [E::Unit],
713        output_index: usize,
714    ) -> Result<usize, TranscodeConvertErrorOf<D, E>>
715    where
716        D::Value: Default,
717    {
718        self.lifecycle.on_finish_attempt()?;
719        let required = self.current_finish_output_len()?;
720        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
721        self.lifecycle.on_finish_start();
722
723        let empty_input: &[D::Unit] = &[];
724        let mut state = ConvertState::new(empty_input, 0, output, output_index);
725        // Finish keeps the same priority as transcode: output any retained
726        // decoded value before asking source-side hooks for final values.
727        let progress = self.drain_pending(&mut state)?;
728        assert_reserved_output_drained(progress, "converter finish bound must reserve space for pending values");
729
730        // Source-side finish may emit one or more final values. Drain them into
731        // the target encoder before finishing target-side hook state.
732        self.drain_decoder_finish(&mut state)?;
733
734        let output_cursor = state.output_cursor();
735        let written = self.encode_engine.finish(state.output_mut(), output_cursor)?;
736        state.advance_output(written);
737        self.lifecycle.on_finish_success();
738        Ok(state.written())
739    }
740
741    /// Runs a complete one-shot `reset -> transcode -> finish` conversion.
742    ///
743    /// The complete input is supplied as `input`, and output starts at index
744    /// `0` in `output`. Callers that need subranges should slice their
745    /// buffers before calling this method. Downstream one-shot converter
746    /// helpers should call this engine method instead of reproducing the
747    /// reset, transcode, and finish sequence themselves.
748    ///
749    /// # Parameters
750    ///
751    /// - `input`: Complete source unit slice.
752    /// - `output`: Target unit slice for the whole converted stream.
753    ///
754    /// # Returns
755    ///
756    /// Returns the number of target units written.
757    ///
758    /// # Errors
759    ///
760    /// Returns framework errors for insufficient output, capacity overflow, or
761    /// an incomplete EOF tail, and domain errors from reset, conversion, or
762    /// finish.
763    ///
764    /// # Panics
765    ///
766    /// Panics if codec/hook lifecycle output exceeds its declared capacity
767    /// bound.
768    #[inline]
769    pub fn transcode_complete_into(
770        &mut self,
771        input: &[D::Unit],
772        output: &mut [E::Unit],
773    ) -> Result<usize, TranscodeConvertErrorOf<D, E>>
774    where
775        D::Value: Default,
776    {
777        <Self as Transcoder>::transcode_complete_into(self, input, output)
778    }
779
780    /// Returns the finish-output bound for the converter's current pending
781    /// state.
782    ///
783    /// Public capacity methods expose global bounds across every transient
784    /// state. A concrete finish call can use this narrower checked bound while
785    /// still relying on global component bounds for decoder and encoder
786    /// finalization.
787    ///
788    /// # Returns
789    ///
790    /// Returns the current finish-output bound, or a capacity error on
791    /// arithmetic overflow.
792    ///
793    /// # Errors
794    ///
795    /// Returns [`CapacityError`] when a component bound or their sum overflows
796    /// `usize`.
797    fn current_finish_output_len(&self) -> Result<usize, CapacityError> {
798        let pending_units = self.pending.current_output_len(&self.encode_engine)?;
799        let decoder_finish_values = self.decode_engine.max_finish_output_len()?;
800        let decoder_finish_units = self.encode_engine.max_transcode_output_len(decoder_finish_values)?;
801        let encoder_finish_units = self.encode_engine.max_finish_output_len()?;
802        sum_convert_output_bounds(pending_units, decoder_finish_units, encoder_finish_units)
803    }
804
805    /// Runs one conversion step with an explicit end-of-input flag.
806    ///
807    /// The helper owns the shared lifecycle and cursor checks used by both
808    /// streaming and EOF-aware public entry points.
809    ///
810    /// # Parameters
811    ///
812    /// - `input`: Source units visible to this call.
813    /// - `input_index`: Starting index in `input`.
814    /// - `output`: Destination for target units.
815    /// - `output_index`: Starting index in `output`.
816    /// - `end_of_input`: Whether the source is closed and EOF decode rules
817    ///   apply.
818    ///
819    /// # Returns
820    ///
821    /// Consumed source units, written target units, and the stopping reason.
822    ///
823    /// # Errors
824    ///
825    /// Returns an error for invalid indices, a missing reset, a finished or
826    /// poisoned stream, or codec/hook conversion failure.
827    fn transcode_with_eof(
828        &mut self,
829        input: &[D::Unit],
830        input_index: usize,
831        output: &mut [E::Unit],
832        output_index: usize,
833        end_of_input: bool,
834    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
835        self.lifecycle.on_transcode()?;
836        TranscodeFailure::ensure_transcode_indices(input.len(), input_index, output.len(), output_index)?;
837
838        let mut state = ConvertState::new(input, input_index, output, output_index);
839
840        // A retained decoded value must be written before consuming more input,
841        // otherwise callers could observe output reordered across buffer turns.
842        if let Some(progress) = self.drain_pending(&mut state)? {
843            return Ok(progress);
844        }
845
846        let min_input_units =
847            NonZeroUsize::new(D::MIN_UNITS_PER_VALUE).expect("Codec::MIN_UNITS_PER_VALUE is non-zero");
848        let min_input_len = min_input_units.get();
849        while state.has_input() {
850            let available = state.available_input();
851            if available < min_input_len && !end_of_input {
852                return Ok(state.need_input_progress(min_input_units));
853            }
854
855            let previous_read = state.read();
856            // Each hot-path step decodes one source value and immediately tries
857            // to encode it, preserving backpressure at the target output.
858            if let Some(progress) = self.convert_next(&mut state, end_of_input)? {
859                return Ok(progress);
860            }
861            debug_assert!(
862                state.read() > previous_read,
863                "TranscodeConvertEngine conversion step must consume input or stop",
864            );
865        }
866
867        Ok(state.complete_progress())
868    }
869
870    /// Drains source-side decode reset output and encodes emitted reset
871    /// values.
872    ///
873    /// Stateless decoders still call [`TranscodeDecodeEngine::reset`] so hook
874    /// teardown side effects run even when no reset values are emitted.
875    ///
876    /// # Parameters
877    ///
878    /// - `state`: Current conversion cursors and output buffer.
879    ///
880    /// # Returns
881    ///
882    /// Returns `Ok(())` after all decode-reset values have been encoded.
883    ///
884    /// # Errors
885    ///
886    /// Returns a converter error when decode reset or encode reset handling
887    /// fails.
888    ///
889    /// # Panics
890    ///
891    /// Panics when a decode-reset value cannot be encoded within the capacity
892    /// reserved by [`Self::max_reset_output_len`].
893    fn drain_decoder_reset(
894        &mut self,
895        state: &mut ConvertState<'_, D::Unit, E::Unit>,
896    ) -> Result<(), TranscodeConvertErrorOf<D, E>>
897    where
898        D::Value: Default,
899    {
900        let value_count = D::MAX_DECODE_RESET_VALUES;
901        if value_count == 0 {
902            // Stateless decoder: still call decode_reset so codecs whose
903            // hooks own teardown side effects (e.g. clearing accumulators)
904            // run them. The empty slice is safe because the capacity check
905            // inside `reset` accepts `required == 0` against any slice.
906            self.decode_engine.reset(&mut [], 0)?;
907            return Ok(());
908        }
909        // `D::Value: Default` is only consulted when the decoder declares
910        // reset output. Stateless codecs never reach this branch.
911        let mut reset_values: Vec<D::Value> = (0..value_count).map(|_| D::Value::default()).collect();
912        let written = self.decode_engine.reset(&mut reset_values, 0)?;
913        for value in reset_values.into_iter().take(written) {
914            let pending = PendingValue::new(value, 0);
915            let progress = self.encode_pending(pending, state)?;
916            assert_reserved_output_drained(
917                progress,
918                "converter reset bound must reserve space for decode reset values",
919            );
920        }
921        Ok(())
922    }
923
924    /// Converts one source value from the current state cursors.
925    ///
926    /// Decodes one value through the source engine, then immediately attempts
927    /// to encode it through the target engine.
928    ///
929    /// # Parameters
930    ///
931    /// - `state`: Current conversion cursors and output buffer.
932    /// - `end_of_input`: Whether to apply EOF-aware decode rules.
933    ///
934    /// # Returns
935    ///
936    /// Returns conversion progress when the step stops early, or `None` when
937    /// the value was consumed and either encoded or skipped by policy.
938    ///
939    /// # Errors
940    ///
941    /// Returns a converter error when decode or encode handling fails.
942    #[inline]
943    fn convert_next(
944        &mut self,
945        state: &mut ConvertState<'_, D::Unit, E::Unit>,
946        end_of_input: bool,
947    ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
948        let (outcome, pending) =
949            self.decode_engine
950                .decode_one(state.input(), state.decode_context(), end_of_input, PendingValue::new)?;
951        if let Some(pending) = pending {
952            self.pending.put(pending);
953        }
954        if let Some(progress) = state.apply_decode_outcome(outcome) {
955            return Ok(Some(progress));
956        }
957        let Some(pending) = self.pending.take() else {
958            return Ok(None);
959        };
960        self.encode_pending(pending, state)
961    }
962
963    /// Writes a retained decoded value before new input is consumed.
964    ///
965    /// # Parameters
966    ///
967    /// - `state`: Current conversion cursors and output buffer.
968    ///
969    /// # Returns
970    ///
971    /// Returns conversion progress when the pending value needs more output
972    /// capacity, or `None` when no value was pending or the value was encoded
973    /// or skipped by policy.
974    ///
975    /// # Errors
976    ///
977    /// Returns a converter error when encode handling fails.
978    #[inline]
979    fn drain_pending(
980        &mut self,
981        state: &mut ConvertState<'_, D::Unit, E::Unit>,
982    ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
983        let Some(pending) = self.pending.take() else {
984            return Ok(None);
985        };
986        self.encode_pending(pending, state)
987    }
988
989    /// Drains source-side decode finish output and encodes emitted final
990    /// values.
991    ///
992    /// When the decoder declares no finish output, still calls
993    /// [`TranscodeDecodeEngine::finish`] so codec finish and hook teardown can
994    /// run and fail even when zero values are emitted.
995    ///
996    /// # Parameters
997    ///
998    /// - `state`: Current conversion cursors and output buffer.
999    ///
1000    /// # Returns
1001    ///
1002    /// Returns `Ok(())` after all decode-finish values have been encoded.
1003    ///
1004    /// # Errors
1005    ///
1006    /// Returns a converter error when decode finish or encode handling fails.
1007    ///
1008    /// # Panics
1009    ///
1010    /// Panics when a decode-finish value cannot be encoded within the
1011    /// capacity reserved by [`Self::max_finish_output_len`].
1012    fn drain_decoder_finish(
1013        &mut self,
1014        state: &mut ConvertState<'_, D::Unit, E::Unit>,
1015    ) -> Result<(), TranscodeConvertErrorOf<D, E>>
1016    where
1017        D::Value: Default,
1018    {
1019        let value_count = self.decode_engine.max_finish_output_len()?;
1020        if value_count == 0 {
1021            // Skip the Vec allocation when the decoder declares no finish
1022            // output. We still call finish() so that
1023            // codec.decode_finish and hooks.finish_hooks both run —
1024            // hooks may do validation or teardown (e.g. checksum
1025            // verification) that can fail even when emitting zero
1026            // values. Passing an empty slice is safe here because the capacity
1027            // check inside finish() accepts required == 0 against any slice.
1028            self.decode_engine.finish(&mut [], 0)?;
1029            return Ok(());
1030        }
1031        // D::Value: Default is required only when value_count > 0. The bound
1032        // remains on the method signature for the general case; stateless
1033        // codecs never reach this branch.
1034        let mut decoded: Vec<D::Value> = (0..value_count).map(|_| D::Value::default()).collect();
1035        let written = self.decode_engine.finish(&mut decoded, 0)?;
1036        for value in decoded.into_iter().take(written) {
1037            let pending = PendingValue::new(value, 0);
1038            let progress = self.encode_pending(pending, state)?;
1039            assert_reserved_output_drained(
1040                progress,
1041                "converter finish bound must reserve space for decode finish values",
1042            );
1043        }
1044        Ok(())
1045    }
1046
1047    /// Encodes one pending value and applies output/pending state changes.
1048    ///
1049    /// When the target buffer lacks capacity, the value is put back into the
1050    /// pending slot and progress reports
1051    /// [`crate::TranscodeStatus::NeedOutput`].
1052    ///
1053    /// # Parameters
1054    ///
1055    /// - `pending`: Decoded value waiting for target output capacity.
1056    /// - `state`: Current conversion cursors and output buffer.
1057    ///
1058    /// # Returns
1059    ///
1060    /// Returns conversion progress when the value needs more output capacity,
1061    /// or `None` when the value was encoded or skipped by policy.
1062    ///
1063    /// # Errors
1064    ///
1065    /// Returns a converter error when encode hook handling fails.
1066    fn encode_pending(
1067        &mut self,
1068        pending: PendingValue<D::Value>,
1069        state: &mut ConvertState<'_, D::Unit, E::Unit>,
1070    ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
1071        let input_index = pending.input_index();
1072        let output_index = state.output_cursor();
1073        let attempt = EncodeAttempt::new(pending.value(), input_index, state.output_mut(), output_index);
1074        let outcome = match self.encode_engine.encode_one(attempt) {
1075            Ok(outcome) => outcome,
1076            Err(error) => {
1077                return Err(TranscodeConvertError::from_encode_error_with_value(
1078                    error,
1079                    pending.into_value(),
1080                ));
1081            }
1082        };
1083        let progress = state.apply_encode_outcome(outcome);
1084        if progress.is_some() {
1085            self.pending.put(pending);
1086        }
1087        Ok(progress)
1088    }
1089}
1090
1091impl<D, E, DH, EH> Default for TranscodeConvertEngine<D, E, DH, EH>
1092where
1093    D: Codec + Default,
1094    E: Codec<Value = D::Value> + Default,
1095    DH: TranscodeDecodeHooks<D> + Default,
1096    EH: TranscodeEncodeHooks<E> + Default,
1097{
1098    /// Creates a default buffered converter engine.
1099    ///
1100    /// # Returns
1101    ///
1102    /// Returns a converter engine constructed from default codecs and hooks.
1103    fn default() -> Self {
1104        Self::new(D::default(), E::default(), DH::default(), EH::default())
1105    }
1106}
1107
1108impl<D, E, DH, EH> Transcoder for TranscodeConvertEngine<D, E, DH, EH>
1109where
1110    D: Codec,
1111    E: Codec<Value = D::Value>,
1112    D::Value: Default,
1113    DH: TranscodeDecodeHooks<D>,
1114    EH: TranscodeEncodeHooks<E>,
1115{
1116    /// Source representation units accepted by the decoder.
1117    type Input = D::Unit;
1118    /// Target representation units emitted by the encoder.
1119    type Output = E::Unit;
1120    /// Source, target, contract, and capacity failures in the pipeline.
1121    type Error = TranscodeConvertErrorOf<D, E>;
1122
1123    /// Returns an upper bound for target units produced from `input_len`
1124    /// units.
1125    ///
1126    /// # Parameters
1127    ///
1128    /// - `input_len`: Number of source units to include in capacity planning.
1129    ///
1130    /// # Returns
1131    ///
1132    /// The target-unit capacity bound, including the applicable retained state.
1133    ///
1134    /// # Errors
1135    ///
1136    /// Returns [`CapacityError`] when a component bound or their sum overflows
1137    /// `usize`.
1138    #[inline]
1139    fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
1140        TranscodeConvertEngine::max_transcode_output_len(self, input_len)
1141    }
1142
1143    /// Returns an upper bound for target units emitted by finishing retained
1144    /// state.
1145    ///
1146    /// # Returns
1147    ///
1148    /// The target-unit capacity bound, including the applicable retained state.
1149    ///
1150    /// # Errors
1151    ///
1152    /// Returns [`CapacityError`] when a component bound or their sum overflows
1153    /// `usize`.
1154    #[inline]
1155    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
1156        TranscodeConvertEngine::max_finish_output_len(self)
1157    }
1158
1159    /// Returns an upper bound for target units emitted when resetting stream
1160    /// state.
1161    ///
1162    /// # Returns
1163    ///
1164    /// The target-unit capacity bound, including the applicable retained state.
1165    ///
1166    /// # Errors
1167    ///
1168    /// Returns [`CapacityError`] when a component bound or their sum overflows
1169    /// `usize`.
1170    #[inline]
1171    fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
1172        TranscodeConvertEngine::max_reset_output_len(self)
1173    }
1174
1175    /// Clears retained conversion state and emits target reset output.
1176    ///
1177    /// # Parameters
1178    ///
1179    /// - `output`: Destination for lifecycle output units.
1180    /// - `output_index`: Starting index in `output`.
1181    ///
1182    /// # Returns
1183    ///
1184    /// The number of lifecycle output units written.
1185    ///
1186    /// # Errors
1187    ///
1188    /// Returns the engine error for invalid indices, insufficient capacity,
1189    /// an invalid lifecycle transition, or codec/hook failure. See the inherent
1190    /// [`TranscodeConvertEngine::reset`] method for retry and poisoning
1191    /// semantics.
1192    ///
1193    /// # Panics
1194    ///
1195    /// Panics if a codec/hook output bound fails to reserve enough space for
1196    /// emitted values.
1197    #[inline]
1198    fn reset(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>> {
1199        TranscodeConvertEngine::reset(self, output, output_index)
1200    }
1201
1202    /// Converts source units into target units.
1203    ///
1204    /// # Parameters
1205    ///
1206    /// - `input`: Source units visible to this call.
1207    /// - `input_index`: Starting index in `input`.
1208    /// - `output`: Destination for target units.
1209    /// - `output_index`: Starting index in `output`.
1210    ///
1211    /// # Returns
1212    ///
1213    /// Consumed source units, written target units, and the stopping reason.
1214    ///
1215    /// # Errors
1216    ///
1217    /// Returns an error for invalid indices, a missing reset, a finished or
1218    /// poisoned stream, or codec/hook conversion failure.
1219    #[inline]
1220    fn transcode(
1221        &mut self,
1222        input: &[D::Unit],
1223        input_index: usize,
1224        output: &mut [E::Unit],
1225        output_index: usize,
1226    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
1227        TranscodeConvertEngine::transcode(self, input, input_index, output, output_index)
1228    }
1229
1230    /// Transcodes an input segment while marking it as the end of the stream.
1231    ///
1232    /// This forwarding implementation delegates to the engine's EOF-aware
1233    /// operation so the decoder can resolve trailing input using EOF rules.
1234    /// Call `finish` separately to drain retained lifecycle output.
1235    ///
1236    /// # Parameters
1237    ///
1238    /// - `input`: Source units visible to this call.
1239    /// - `input_index`: Starting index in `input`.
1240    /// - `output`: Destination for target units.
1241    /// - `output_index`: Starting index in `output`.
1242    ///
1243    /// # Returns
1244    ///
1245    /// Consumed source units, written target units, and the stopping reason.
1246    ///
1247    /// # Errors
1248    ///
1249    /// Returns an error for invalid indices, a missing reset, a finished or
1250    /// poisoned stream, or codec/hook conversion failure.
1251    #[inline]
1252    fn transcode_eof(
1253        &mut self,
1254        input: &[D::Unit],
1255        input_index: usize,
1256        output: &mut [E::Unit],
1257        output_index: usize,
1258    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
1259        TranscodeConvertEngine::transcode_eof(self, input, input_index, output, output_index)
1260    }
1261
1262    /// Finishes retained converter output after EOF.
1263    ///
1264    /// # Parameters
1265    ///
1266    /// - `output`: Destination for lifecycle output units.
1267    /// - `output_index`: Starting index in `output`.
1268    ///
1269    /// # Returns
1270    ///
1271    /// The number of lifecycle output units written.
1272    ///
1273    /// # Errors
1274    ///
1275    /// Returns the engine error for invalid indices, insufficient capacity,
1276    /// an invalid lifecycle transition, or codec/hook failure. See the inherent
1277    /// [`TranscodeConvertEngine::finish`] method for retry and poisoning
1278    /// semantics.
1279    ///
1280    /// # Panics
1281    ///
1282    /// Panics if a codec/hook output bound fails to reserve enough space for
1283    /// emitted values.
1284    #[inline]
1285    fn finish(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>> {
1286        TranscodeConvertEngine::finish(self, output, output_index)
1287    }
1288}
1289
1290impl<D, E, DH, EH> TranscodeConverter for TranscodeConvertEngine<D, E, DH, EH>
1291where
1292    D: Codec,
1293    E: Codec<Value = D::Value>,
1294    D::Value: Default,
1295    DH: TranscodeDecodeHooks<D>,
1296    EH: TranscodeEncodeHooks<E>,
1297{
1298    /// Source codec failures retained in conversion errors.
1299    type DecodeError = D::DecodeError;
1300    /// Target codec failures retained in conversion errors.
1301    type EncodeError = E::EncodeError;
1302    /// Shared logical value transferred between the two codecs.
1303    type Value = D::Value;
1304}