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