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