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