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 #[inline]
643 pub fn transcode(
644 &mut self,
645 input: &[D::Unit],
646 input_index: usize,
647 output: &mut [E::Unit],
648 output_index: usize,
649 ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
650 self.transcode_with_eof(input, input_index, output, output_index, false)
651 }
652
653 /// Converts source units after the caller has established end of input.
654 ///
655 /// # Parameters
656 ///
657 /// - `input`: Source units visible to this call.
658 /// - `input_index`: Starting index in `input`.
659 /// - `output`: Destination for target units.
660 /// - `output_index`: Starting index in `output`.
661 ///
662 /// # Returns
663 ///
664 /// Consumed source units, written target units, and the stopping reason.
665 ///
666 /// # Errors
667 ///
668 /// Returns an error for invalid indices, a missing reset, a finished or
669 /// poisoned stream, or codec/hook conversion failure.
670 #[inline]
671 pub fn transcode_eof(
672 &mut self,
673 input: &[D::Unit],
674 input_index: usize,
675 output: &mut [E::Unit],
676 output_index: usize,
677 ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
678 self.transcode_with_eof(input, input_index, output, output_index, true)
679 }
680
681 /// Finishes retained output after EOF.
682 ///
683 /// Finalization drains a pending decoded value first, then lets the
684 /// source-side decode hooks emit final values, encodes those values through
685 /// the target-side encode hooks, and finally finishes target-side encode
686 /// hook state. The decode-finish value buffer used for this cold path
687 /// requires `D::Value: Default`; the normal [`Self::transcode`] loop does
688 /// not.
689 ///
690 /// # Parameters
691 ///
692 /// - `output`: Complete output unit slice visible to the converter.
693 /// - `output_index`: Absolute output index where writing starts.
694 ///
695 /// # Returns
696 ///
697 /// Returns the number of target units written during finalization.
698 ///
699 /// # Errors
700 ///
701 /// Returns a converter error when output capacity checks fail or when
702 /// hook finalization fails. Returns
703 /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
704 /// completed its first reset. Returns
705 /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
706 /// already finished and has not been reset, or
707 /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
708 /// failed after execution started. Capacity and index failures occur before
709 /// finish execution and remain retryable; later failures poison the
710 /// converter until reset succeeds.
711 ///
712 /// # Panics
713 ///
714 /// Panics when a retained pending value or decode-finish value cannot be
715 /// encoded within the planned finish capacity.
716 pub fn finish(
717 &mut self,
718 output: &mut [E::Unit],
719 output_index: usize,
720 ) -> Result<usize, TranscodeConvertErrorOf<D, E>>
721 where
722 D::Value: Default,
723 {
724 self.lifecycle.on_finish_attempt()?;
725 let required = self.current_finish_output_len()?;
726 TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
727 self.lifecycle.on_finish_start();
728
729 let empty_input: &[D::Unit] = &[];
730 let mut state = ConvertState::new(empty_input, 0, output, output_index);
731 // Finish keeps the same priority as transcode: output any retained
732 // decoded value before asking source-side hooks for final values.
733 let progress = self.drain_pending(&mut state)?;
734 assert_reserved_output_drained(progress, "converter finish bound must reserve space for pending values");
735
736 // Source-side finish may emit one or more final values. Drain them into
737 // the target encoder before finishing target-side hook state.
738 self.drain_decoder_finish(&mut state)?;
739
740 let output_cursor = state.output_cursor();
741 let written = self.encode_engine.finish(state.output_mut(), output_cursor)?;
742 state.advance_output(written);
743 self.lifecycle.on_finish_success();
744 Ok(state.written())
745 }
746
747 /// Runs a complete one-shot `reset -> transcode -> finish` conversion.
748 ///
749 /// The complete input is supplied as `input`, and output starts at index
750 /// `0` in `output`. Callers that need subranges should slice their
751 /// buffers before calling this method. Downstream one-shot converter
752 /// helpers should call this engine method instead of reproducing the
753 /// reset, transcode, and finish sequence themselves.
754 ///
755 /// # Parameters
756 ///
757 /// - `input`: Complete source unit slice.
758 /// - `output`: Target unit slice for the whole converted stream.
759 ///
760 /// # Returns
761 ///
762 /// Returns the number of target units written.
763 ///
764 /// # Errors
765 ///
766 /// Returns framework errors for insufficient output, capacity overflow, or
767 /// an incomplete EOF tail, and domain errors from reset, conversion, or
768 /// finish.
769 ///
770 /// # Panics
771 ///
772 /// Panics if codec/hook lifecycle output exceeds its declared capacity
773 /// bound.
774 #[inline]
775 pub fn transcode_complete_into(
776 &mut self,
777 input: &[D::Unit],
778 output: &mut [E::Unit],
779 ) -> Result<usize, TranscodeConvertErrorOf<D, E>>
780 where
781 D::Value: Default,
782 {
783 <Self as Transcoder>::transcode_complete_into(self, input, output)
784 }
785
786 /// Returns the finish-output bound for the converter's current pending
787 /// state.
788 ///
789 /// Public capacity methods expose global bounds across every transient
790 /// state. A concrete finish call can use this narrower checked bound while
791 /// still relying on global component bounds for decoder and encoder
792 /// finalization.
793 ///
794 /// # Returns
795 ///
796 /// Returns the current finish-output bound, or a capacity error on
797 /// arithmetic overflow.
798 ///
799 /// # Errors
800 ///
801 /// Returns [`CapacityError`] when a component bound or their sum overflows
802 /// `usize`.
803 fn current_finish_output_len(&self) -> Result<usize, CapacityError> {
804 let pending_units = self.pending.current_output_len(&self.encode_engine)?;
805 let decoder_finish_values = self.decode_engine.max_finish_output_len()?;
806 let decoder_finish_units = self.encode_engine.max_transcode_output_len(decoder_finish_values)?;
807 let encoder_finish_units = self.encode_engine.max_finish_output_len()?;
808 sum_convert_output_bounds(pending_units, decoder_finish_units, encoder_finish_units)
809 }
810
811 /// Runs one conversion step with an explicit end-of-input flag.
812 ///
813 /// The helper owns the shared lifecycle and cursor checks used by both
814 /// streaming and EOF-aware public entry points.
815 ///
816 /// # Parameters
817 ///
818 /// - `input`: Source units visible to this call.
819 /// - `input_index`: Starting index in `input`.
820 /// - `output`: Destination for target units.
821 /// - `output_index`: Starting index in `output`.
822 /// - `end_of_input`: Whether the source is closed and EOF decode rules
823 /// apply.
824 ///
825 /// # Returns
826 ///
827 /// Consumed source units, written target units, and the stopping reason.
828 ///
829 /// # Errors
830 ///
831 /// Returns an error for invalid indices, a missing reset, a finished or
832 /// poisoned stream, or codec/hook conversion failure.
833 fn transcode_with_eof(
834 &mut self,
835 input: &[D::Unit],
836 input_index: usize,
837 output: &mut [E::Unit],
838 output_index: usize,
839 end_of_input: bool,
840 ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
841 self.lifecycle.on_transcode()?;
842 TranscodeFailure::ensure_transcode_indices(input.len(), input_index, output.len(), output_index)?;
843
844 let mut state = ConvertState::new(input, input_index, output, output_index);
845
846 // A retained decoded value must be written before consuming more input,
847 // otherwise callers could observe output reordered across buffer turns.
848 if let Some(progress) = self.drain_pending(&mut state)? {
849 return Ok(progress);
850 }
851
852 let min_input_units =
853 NonZeroUsize::new(D::MIN_UNITS_PER_VALUE).expect("Codec::MIN_UNITS_PER_VALUE is non-zero");
854 let min_input_len = min_input_units.get();
855 while state.has_input() {
856 let available = state.available_input();
857 if available < min_input_len && !end_of_input {
858 return Ok(state.need_input_progress(min_input_units));
859 }
860
861 let previous_read = state.read();
862 // Each hot-path step decodes one source value and immediately tries
863 // to encode it, preserving backpressure at the target output.
864 if let Some(progress) = self.convert_next(&mut state, end_of_input)? {
865 return Ok(progress);
866 }
867 debug_assert!(
868 state.read() > previous_read,
869 "TranscodeConvertEngine conversion step must consume input or stop",
870 );
871 }
872
873 Ok(state.complete_progress())
874 }
875
876 /// Drains source-side decode reset output and encodes emitted reset
877 /// values.
878 ///
879 /// Stateless decoders still call [`TranscodeDecodeEngine::reset`] so hook
880 /// teardown side effects run even when no reset values are emitted.
881 ///
882 /// # Parameters
883 ///
884 /// - `state`: Current conversion cursors and output buffer.
885 ///
886 /// # Returns
887 ///
888 /// Returns `Ok(())` after all decode-reset values have been encoded.
889 ///
890 /// # Errors
891 ///
892 /// Returns a converter error when decode reset or encode reset handling
893 /// fails.
894 ///
895 /// # Panics
896 ///
897 /// Panics when a decode-reset value cannot be encoded within the capacity
898 /// reserved by [`Self::max_reset_output_len`].
899 fn drain_decoder_reset(
900 &mut self,
901 state: &mut ConvertState<'_, D::Unit, E::Unit>,
902 ) -> Result<(), TranscodeConvertErrorOf<D, E>>
903 where
904 D::Value: Default,
905 {
906 let value_count = D::MAX_DECODE_RESET_VALUES;
907 if value_count == 0 {
908 // Stateless decoder: still call decode_reset so codecs whose
909 // hooks own teardown side effects (e.g. clearing accumulators)
910 // run them. The empty slice is safe because the capacity check
911 // inside `reset` accepts `required == 0` against any slice.
912 self.decode_engine.reset(&mut [], 0)?;
913 return Ok(());
914 }
915 // `D::Value: Default` is only consulted when the decoder declares
916 // reset output. Stateless codecs never reach this branch.
917 let mut reset_values: Vec<D::Value> = (0..value_count).map(|_| D::Value::default()).collect();
918 let written = self.decode_engine.reset(&mut reset_values, 0)?;
919 for value in reset_values.into_iter().take(written) {
920 let pending = PendingValue::new(value, 0);
921 let progress = self.encode_pending(pending, state)?;
922 assert_reserved_output_drained(
923 progress,
924 "converter reset bound must reserve space for decode reset values",
925 );
926 }
927 Ok(())
928 }
929
930 /// Converts one source value from the current state cursors.
931 ///
932 /// Decodes one value through the source engine, then immediately attempts
933 /// to encode it through the target engine.
934 ///
935 /// # Parameters
936 ///
937 /// - `state`: Current conversion cursors and output buffer.
938 /// - `end_of_input`: Whether to apply EOF-aware decode rules.
939 ///
940 /// # Returns
941 ///
942 /// Returns conversion progress when the step stops early, or `None` when
943 /// the value was consumed and either encoded or skipped by policy.
944 ///
945 /// # Errors
946 ///
947 /// Returns a converter error when decode or encode handling fails.
948 #[inline]
949 fn convert_next(
950 &mut self,
951 state: &mut ConvertState<'_, D::Unit, E::Unit>,
952 end_of_input: bool,
953 ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
954 let (outcome, pending) =
955 self.decode_engine
956 .decode_one(state.input(), state.decode_context(), end_of_input, PendingValue::new)?;
957 if let Some(pending) = pending {
958 self.pending.put(pending);
959 }
960 if let Some(progress) = state.apply_decode_outcome(outcome) {
961 return Ok(Some(progress));
962 }
963 let Some(pending) = self.pending.take() else {
964 return Ok(None);
965 };
966 self.encode_pending(pending, state)
967 }
968
969 /// Writes a retained decoded value before new input is consumed.
970 ///
971 /// # Parameters
972 ///
973 /// - `state`: Current conversion cursors and output buffer.
974 ///
975 /// # Returns
976 ///
977 /// Returns conversion progress when the pending value needs more output
978 /// capacity, or `None` when no value was pending or the value was encoded
979 /// or skipped by policy.
980 ///
981 /// # Errors
982 ///
983 /// Returns a converter error when encode handling fails.
984 #[inline]
985 fn drain_pending(
986 &mut self,
987 state: &mut ConvertState<'_, D::Unit, E::Unit>,
988 ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
989 let Some(pending) = self.pending.take() else {
990 return Ok(None);
991 };
992 self.encode_pending(pending, state)
993 }
994
995 /// Drains source-side decode finish output and encodes emitted final
996 /// values.
997 ///
998 /// When the decoder declares no finish output, still calls
999 /// [`TranscodeDecodeEngine::finish`] so codec finish and hook teardown can
1000 /// run and fail even when zero values are emitted.
1001 ///
1002 /// # Parameters
1003 ///
1004 /// - `state`: Current conversion cursors and output buffer.
1005 ///
1006 /// # Returns
1007 ///
1008 /// Returns `Ok(())` after all decode-finish values have been encoded.
1009 ///
1010 /// # Errors
1011 ///
1012 /// Returns a converter error when decode finish or encode handling fails.
1013 ///
1014 /// # Panics
1015 ///
1016 /// Panics when a decode-finish value cannot be encoded within the
1017 /// capacity reserved by [`Self::max_finish_output_len`].
1018 fn drain_decoder_finish(
1019 &mut self,
1020 state: &mut ConvertState<'_, D::Unit, E::Unit>,
1021 ) -> Result<(), TranscodeConvertErrorOf<D, E>>
1022 where
1023 D::Value: Default,
1024 {
1025 let value_count = self.decode_engine.max_finish_output_len()?;
1026 if value_count == 0 {
1027 // Skip the Vec allocation when the decoder declares no finish
1028 // output. We still call finish() so that
1029 // codec.decode_finish and hooks.finish_hooks both run —
1030 // hooks may do validation or teardown (e.g. checksum
1031 // verification) that can fail even when emitting zero
1032 // values. Passing an empty slice is safe here because the capacity
1033 // check inside finish() accepts required == 0 against any slice.
1034 self.decode_engine.finish(&mut [], 0)?;
1035 return Ok(());
1036 }
1037 // D::Value: Default is required only when value_count > 0. The bound
1038 // remains on the method signature for the general case; stateless
1039 // codecs never reach this branch.
1040 let mut decoded: Vec<D::Value> = (0..value_count).map(|_| D::Value::default()).collect();
1041 let written = self.decode_engine.finish(&mut decoded, 0)?;
1042 for value in decoded.into_iter().take(written) {
1043 let pending = PendingValue::new(value, 0);
1044 let progress = self.encode_pending(pending, state)?;
1045 assert_reserved_output_drained(
1046 progress,
1047 "converter finish bound must reserve space for decode finish values",
1048 );
1049 }
1050 Ok(())
1051 }
1052
1053 /// Encodes one pending value and applies output/pending state changes.
1054 ///
1055 /// When the target buffer lacks capacity, the value is put back into the
1056 /// pending slot and progress reports
1057 /// [`crate::TranscodeStatus::NeedOutput`].
1058 ///
1059 /// # Parameters
1060 ///
1061 /// - `pending`: Decoded value waiting for target output capacity.
1062 /// - `state`: Current conversion cursors and output buffer.
1063 ///
1064 /// # Returns
1065 ///
1066 /// Returns conversion progress when the value needs more output capacity,
1067 /// or `None` when the value was encoded or skipped by policy.
1068 ///
1069 /// # Errors
1070 ///
1071 /// Returns a converter error when encode hook handling fails.
1072 fn encode_pending(
1073 &mut self,
1074 pending: PendingValue<D::Value>,
1075 state: &mut ConvertState<'_, D::Unit, E::Unit>,
1076 ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
1077 let input_index = pending.input_index();
1078 let output_index = state.output_cursor();
1079 let attempt = EncodeAttempt::new(pending.value(), input_index, state.output_mut(), output_index);
1080 let outcome = match self.encode_engine.encode_one(attempt) {
1081 Ok(outcome) => outcome,
1082 Err(error) => {
1083 return Err(TranscodeConvertError::from_encode_error_with_value(
1084 error,
1085 pending.into_value(),
1086 ));
1087 }
1088 };
1089 let progress = state.apply_encode_outcome(outcome);
1090 if progress.is_some() {
1091 self.pending.put(pending);
1092 }
1093 Ok(progress)
1094 }
1095}
1096
1097impl<D, E, DH, EH> Default for TranscodeConvertEngine<D, E, DH, EH>
1098where
1099 D: Codec + Default,
1100 E: Codec<Value = D::Value> + Default,
1101 DH: TranscodeDecodeHooks<D> + Default,
1102 EH: TranscodeEncodeHooks<E> + Default,
1103{
1104 /// Creates a default buffered converter engine.
1105 ///
1106 /// # Returns
1107 ///
1108 /// Returns a converter engine constructed from default codecs and hooks.
1109 fn default() -> Self {
1110 Self::new(D::default(), E::default(), DH::default(), EH::default())
1111 }
1112}
1113
1114impl<D, E, DH, EH> Transcoder for TranscodeConvertEngine<D, E, DH, EH>
1115where
1116 D: Codec,
1117 E: Codec<Value = D::Value>,
1118 D::Value: Default,
1119 DH: TranscodeDecodeHooks<D>,
1120 EH: TranscodeEncodeHooks<E>,
1121{
1122 /// Source representation units accepted by the decoder.
1123 type Input = D::Unit;
1124 /// Target representation units emitted by the encoder.
1125 type Output = E::Unit;
1126 /// Source, target, contract, and capacity failures in the pipeline.
1127 type Error = TranscodeConvertErrorOf<D, E>;
1128
1129 /// Returns an upper bound for target units produced from `input_len`
1130 /// units.
1131 ///
1132 /// # Parameters
1133 ///
1134 /// - `input_len`: Number of source units to include in capacity planning.
1135 ///
1136 /// # Returns
1137 ///
1138 /// The target-unit capacity bound, including the applicable retained state.
1139 ///
1140 /// # Errors
1141 ///
1142 /// Returns [`CapacityError`] when a component bound or their sum overflows
1143 /// `usize`.
1144 #[inline]
1145 fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
1146 TranscodeConvertEngine::max_transcode_output_len(self, input_len)
1147 }
1148
1149 /// Returns an upper bound for target units emitted by finishing retained
1150 /// state.
1151 ///
1152 /// # Returns
1153 ///
1154 /// The target-unit capacity bound, including the applicable retained state.
1155 ///
1156 /// # Errors
1157 ///
1158 /// Returns [`CapacityError`] when a component bound or their sum overflows
1159 /// `usize`.
1160 #[inline]
1161 fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
1162 TranscodeConvertEngine::max_finish_output_len(self)
1163 }
1164
1165 /// Returns an upper bound for target units emitted when resetting stream
1166 /// state.
1167 ///
1168 /// # Returns
1169 ///
1170 /// The target-unit capacity bound, including the applicable retained state.
1171 ///
1172 /// # Errors
1173 ///
1174 /// Returns [`CapacityError`] when a component bound or their sum overflows
1175 /// `usize`.
1176 #[inline]
1177 fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
1178 TranscodeConvertEngine::max_reset_output_len(self)
1179 }
1180
1181 /// Clears retained conversion state and emits target reset output.
1182 ///
1183 /// # Parameters
1184 ///
1185 /// - `output`: Destination for lifecycle output units.
1186 /// - `output_index`: Starting index in `output`.
1187 ///
1188 /// # Returns
1189 ///
1190 /// The number of lifecycle output units written.
1191 ///
1192 /// # Errors
1193 ///
1194 /// Returns the engine error for invalid indices, insufficient capacity,
1195 /// an invalid lifecycle transition, or codec/hook failure. See the inherent
1196 /// [`TranscodeConvertEngine::reset`] method for retry and poisoning
1197 /// semantics.
1198 ///
1199 /// # Panics
1200 ///
1201 /// Panics if a codec/hook output bound fails to reserve enough space for
1202 /// emitted values.
1203 #[inline]
1204 fn reset(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>> {
1205 TranscodeConvertEngine::reset(self, output, output_index)
1206 }
1207
1208 /// Converts source units into target units.
1209 ///
1210 /// # Parameters
1211 ///
1212 /// - `input`: Source units visible to this call.
1213 /// - `input_index`: Starting index in `input`.
1214 /// - `output`: Destination for target units.
1215 /// - `output_index`: Starting index in `output`.
1216 ///
1217 /// # Returns
1218 ///
1219 /// Consumed source units, written target units, and the stopping reason.
1220 ///
1221 /// # Errors
1222 ///
1223 /// Returns an error for invalid indices, a missing reset, a finished or
1224 /// poisoned stream, or codec/hook conversion failure.
1225 #[inline]
1226 fn transcode(
1227 &mut self,
1228 input: &[D::Unit],
1229 input_index: usize,
1230 output: &mut [E::Unit],
1231 output_index: usize,
1232 ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
1233 TranscodeConvertEngine::transcode(self, input, input_index, output, output_index)
1234 }
1235
1236 /// Transcodes an input segment while marking it as the end of the stream.
1237 ///
1238 /// This forwarding implementation delegates to the engine's EOF-aware
1239 /// operation so the decoder can resolve trailing input using EOF rules.
1240 /// Call `finish` separately to drain retained lifecycle output.
1241 ///
1242 /// # Parameters
1243 ///
1244 /// - `input`: Source units visible to this call.
1245 /// - `input_index`: Starting index in `input`.
1246 /// - `output`: Destination for target units.
1247 /// - `output_index`: Starting index in `output`.
1248 ///
1249 /// # Returns
1250 ///
1251 /// Consumed source units, written target units, and the stopping reason.
1252 ///
1253 /// # Errors
1254 ///
1255 /// Returns an error for invalid indices, a missing reset, a finished or
1256 /// poisoned stream, or codec/hook conversion failure.
1257 #[inline]
1258 fn transcode_eof(
1259 &mut self,
1260 input: &[D::Unit],
1261 input_index: usize,
1262 output: &mut [E::Unit],
1263 output_index: usize,
1264 ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
1265 TranscodeConvertEngine::transcode_eof(self, input, input_index, output, output_index)
1266 }
1267
1268 /// Finishes retained converter output after EOF.
1269 ///
1270 /// # Parameters
1271 ///
1272 /// - `output`: Destination for lifecycle output units.
1273 /// - `output_index`: Starting index in `output`.
1274 ///
1275 /// # Returns
1276 ///
1277 /// The number of lifecycle output units written.
1278 ///
1279 /// # Errors
1280 ///
1281 /// Returns the engine error for invalid indices, insufficient capacity,
1282 /// an invalid lifecycle transition, or codec/hook failure. See the inherent
1283 /// [`TranscodeConvertEngine::finish`] method for retry and poisoning
1284 /// semantics.
1285 ///
1286 /// # Panics
1287 ///
1288 /// Panics if a codec/hook output bound fails to reserve enough space for
1289 /// emitted values.
1290 #[inline]
1291 fn finish(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>> {
1292 TranscodeConvertEngine::finish(self, output, output_index)
1293 }
1294}
1295
1296impl<D, E, DH, EH> TranscodeConverter for TranscodeConvertEngine<D, E, DH, EH>
1297where
1298 D: Codec,
1299 E: Codec<Value = D::Value>,
1300 D::Value: Default,
1301 DH: TranscodeDecodeHooks<D>,
1302 EH: TranscodeEncodeHooks<E>,
1303{
1304 /// Source codec failures retained in conversion errors.
1305 type DecodeError = D::DecodeError;
1306 /// Target codec failures retained in conversion errors.
1307 type EncodeError = E::EncodeError;
1308 /// Shared logical value transferred between the two codecs.
1309 type Value = D::Value;
1310}