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