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