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