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/// # Example
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 fn transcode_with_eof(
624 &mut self,
625 input: &[D::Unit],
626 input_index: usize,
627 output: &mut [E::Unit],
628 output_index: usize,
629 end_of_input: bool,
630 ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
631 self.lifecycle.on_transcode()?;
632 TranscodeFailure::ensure_transcode_indices(input.len(), input_index, output.len(), output_index)?;
633
634 let mut state = ConvertState::new(input, input_index, output, output_index);
635
636 // A retained decoded value must be written before consuming more input,
637 // otherwise callers could observe output reordered across buffer turns.
638 if let Some(progress) = self.drain_pending(&mut state)? {
639 return Ok(progress);
640 }
641
642 let min_input_units =
643 NonZeroUsize::new(D::MIN_UNITS_PER_VALUE).expect("Codec::MIN_UNITS_PER_VALUE is non-zero");
644 let min_input_len = min_input_units.get();
645 while state.has_input() {
646 let available = state.available_input();
647 if available < min_input_len && !end_of_input {
648 return Ok(state.need_input_progress(min_input_units));
649 }
650
651 let previous_read = state.read();
652 // Each hot-path step decodes one source value and immediately tries
653 // to encode it, preserving backpressure at the target output.
654 if let Some(progress) = self.convert_next(&mut state, end_of_input)? {
655 return Ok(progress);
656 }
657 debug_assert!(
658 state.read() > previous_read,
659 "TranscodeConvertEngine conversion step must consume input or stop",
660 );
661 }
662
663 Ok(state.complete_progress())
664 }
665
666 /// Finishes retained output after EOF.
667 ///
668 /// Finalization drains a pending decoded value first, then lets the
669 /// source-side decode hooks emit final values, encodes those values through
670 /// the target-side encode hooks, and finally finishes target-side encode
671 /// hook state. The decode-finish value buffer used for this cold path
672 /// requires `D::Value: Default`; the normal [`Self::transcode`] loop does
673 /// not.
674 ///
675 /// # Parameters
676 ///
677 /// - `output`: Complete output unit slice visible to the converter.
678 /// - `output_index`: Absolute output index where writing starts.
679 ///
680 /// # Returns
681 ///
682 /// Returns the number of target units written during finalization.
683 ///
684 /// # Errors
685 ///
686 /// Returns a converter error when output capacity checks fail or when
687 /// hook finalization fails. Returns
688 /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
689 /// completed its first reset. Returns
690 /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
691 /// already finished and has not been reset, or
692 /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
693 /// failed after execution started. Capacity and index failures occur before
694 /// finish execution and remain retryable; later failures poison the
695 /// converter until reset succeeds.
696 ///
697 /// # Panics
698 ///
699 /// Panics when a retained pending value or decode-finish value cannot be
700 /// encoded within the planned finish capacity.
701 pub fn finish(
702 &mut self,
703 output: &mut [E::Unit],
704 output_index: usize,
705 ) -> Result<usize, TranscodeConvertErrorOf<D, E>>
706 where
707 D::Value: Default,
708 {
709 self.lifecycle.on_finish_attempt()?;
710 let required = self.current_finish_output_len()?;
711 TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
712 self.lifecycle.on_finish_start();
713
714 let empty_input: &[D::Unit] = &[];
715 let mut state = ConvertState::new(empty_input, 0, output, output_index);
716 // Finish keeps the same priority as transcode: output any retained
717 // decoded value before asking source-side hooks for final values.
718 let progress = self.drain_pending(&mut state)?;
719 assert_reserved_output_drained(progress, "converter finish bound must reserve space for pending values");
720
721 // Source-side finish may emit one or more final values. Drain them into
722 // the target encoder before finishing target-side hook state.
723 self.drain_decoder_finish(&mut state)?;
724
725 let output_cursor = state.output_cursor();
726 let written = self.encode_engine.finish(state.output_mut(), output_cursor)?;
727 state.advance_output(written);
728 self.lifecycle.on_finish_success();
729 Ok(state.written())
730 }
731
732 /// Runs a complete one-shot `reset -> transcode -> finish` conversion.
733 ///
734 /// The complete input is supplied as `input`, and output starts at index
735 /// `0` in `output`. Callers that need subranges should slice their
736 /// buffers before calling this method. Downstream one-shot converter
737 /// helpers should call this engine method instead of reproducing the
738 /// reset, transcode, and finish sequence themselves.
739 ///
740 /// # Parameters
741 ///
742 /// - `input`: Complete source unit slice.
743 /// - `output`: Target unit slice for the whole converted stream.
744 ///
745 /// # Returns
746 ///
747 /// Returns the number of target units written.
748 ///
749 /// # Errors
750 ///
751 /// Returns framework errors for insufficient output, capacity overflow, or
752 /// an incomplete EOF tail, and domain errors from reset, conversion, or
753 /// finish.
754 #[inline]
755 pub fn transcode_complete_into(
756 &mut self,
757 input: &[D::Unit],
758 output: &mut [E::Unit],
759 ) -> Result<usize, TranscodeConvertErrorOf<D, E>>
760 where
761 D::Value: Default,
762 {
763 <Self as Transcoder>::transcode_complete_into(self, input, output)
764 }
765
766 /// Drains source-side decode reset output and encodes emitted reset
767 /// values.
768 ///
769 /// Stateless decoders still call [`TranscodeDecodeEngine::reset`] so hook
770 /// teardown side effects run even when no reset values are emitted.
771 ///
772 /// # Parameters
773 ///
774 /// - `state`: Current conversion cursors and output buffer.
775 ///
776 /// # Returns
777 ///
778 /// Returns `Ok(())` after all decode-reset values have been encoded.
779 ///
780 /// # Errors
781 ///
782 /// Returns a converter error when decode reset or encode reset handling
783 /// fails.
784 ///
785 /// # Panics
786 ///
787 /// Panics when a decode-reset value cannot be encoded within the capacity
788 /// reserved by [`Self::max_reset_output_len`].
789 fn drain_decoder_reset(
790 &mut self,
791 state: &mut ConvertState<'_, D::Unit, E::Unit>,
792 ) -> Result<(), TranscodeConvertErrorOf<D, E>>
793 where
794 D::Value: Default,
795 {
796 let value_count = D::MAX_DECODE_RESET_VALUES;
797 if value_count == 0 {
798 // Stateless decoder: still call decode_reset so codecs whose
799 // hooks own teardown side effects (e.g. clearing accumulators)
800 // run them. The empty slice is safe because the capacity check
801 // inside `reset` accepts `required == 0` against any slice.
802 self.decode_engine.reset(&mut [], 0)?;
803 return Ok(());
804 }
805 // `D::Value: Default` is only consulted when the decoder declares
806 // reset output. Stateless codecs never reach this branch.
807 let mut reset_values: Vec<D::Value> = (0..value_count).map(|_| D::Value::default()).collect();
808 let written = self.decode_engine.reset(&mut reset_values, 0)?;
809 for value in reset_values.into_iter().take(written) {
810 let pending = PendingValue::new(value, 0);
811 let progress = self.encode_pending(pending, state)?;
812 assert_reserved_output_drained(
813 progress,
814 "converter reset bound must reserve space for decode reset values",
815 );
816 }
817 Ok(())
818 }
819
820 /// Converts one source value from the current state cursors.
821 ///
822 /// Decodes one value through the source engine, then immediately attempts
823 /// to encode it through the target engine.
824 ///
825 /// # Parameters
826 ///
827 /// - `state`: Current conversion cursors and output buffer.
828 ///
829 /// # Returns
830 ///
831 /// Returns conversion progress when the step stops early, or `None` when
832 /// the value was fully consumed and encoded.
833 ///
834 /// # Errors
835 ///
836 /// Returns a converter error when decode or encode handling fails.
837 #[inline(always)]
838 fn convert_next(
839 &mut self,
840 state: &mut ConvertState<'_, D::Unit, E::Unit>,
841 end_of_input: bool,
842 ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
843 let (outcome, pending) =
844 self.decode_engine
845 .decode_one(state.input(), state.decode_context(), end_of_input, PendingValue::new)?;
846 if let Some(pending) = pending {
847 self.pending.put(pending);
848 }
849 if let Some(progress) = state.apply_decode_outcome(outcome) {
850 return Ok(Some(progress));
851 }
852 let Some(pending) = self.pending.take() else {
853 return Ok(None);
854 };
855 self.encode_pending(pending, state)
856 }
857
858 /// Writes a retained decoded value before new input is consumed.
859 ///
860 /// # Parameters
861 ///
862 /// - `state`: Current conversion cursors and output buffer.
863 ///
864 /// # Returns
865 ///
866 /// Returns conversion progress when the pending value needs more output
867 /// capacity, or `None` when the pending value was fully encoded.
868 ///
869 /// # Errors
870 ///
871 /// Returns a converter error when encode handling fails.
872 #[inline(always)]
873 fn drain_pending(
874 &mut self,
875 state: &mut ConvertState<'_, D::Unit, E::Unit>,
876 ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
877 let Some(pending) = self.pending.take() else {
878 return Ok(None);
879 };
880 self.encode_pending(pending, state)
881 }
882
883 /// Drains source-side decode finish output and encodes emitted final
884 /// values.
885 ///
886 /// When the decoder declares no finish output, still calls
887 /// [`TranscodeDecodeEngine::finish`] so codec finish and hook teardown can
888 /// run and fail even when zero values are emitted.
889 ///
890 /// # Parameters
891 ///
892 /// - `state`: Current conversion cursors and output buffer.
893 ///
894 /// # Returns
895 ///
896 /// Returns `Ok(())` after all decode-finish values have been encoded.
897 ///
898 /// # Errors
899 ///
900 /// Returns a converter error when decode finish or encode handling fails.
901 ///
902 /// # Panics
903 ///
904 /// Panics when a decode-finish value cannot be encoded within the
905 /// capacity reserved by [`Self::max_finish_output_len`].
906 fn drain_decoder_finish(
907 &mut self,
908 state: &mut ConvertState<'_, D::Unit, E::Unit>,
909 ) -> Result<(), TranscodeConvertErrorOf<D, E>>
910 where
911 D::Value: Default,
912 {
913 let value_count = self.decode_engine.max_finish_output_len()?;
914 if value_count == 0 {
915 // Skip the Vec allocation when the decoder declares no finish
916 // output. We still call finish() so that
917 // codec.decode_finish and hooks.finish_hooks both run —
918 // hooks may do validation or teardown (e.g. checksum
919 // verification) that can fail even when emitting zero
920 // values. Passing an empty slice is safe here because the capacity
921 // check inside finish() accepts required == 0 against any slice.
922 self.decode_engine.finish(&mut [], 0)?;
923 return Ok(());
924 }
925 // D::Value: Default is required only when value_count > 0. The bound
926 // remains on the method signature for the general case; stateless
927 // codecs never reach this branch.
928 let mut decoded: Vec<D::Value> = (0..value_count).map(|_| D::Value::default()).collect();
929 let written = self.decode_engine.finish(&mut decoded, 0)?;
930 for value in decoded.into_iter().take(written) {
931 let pending = PendingValue::new(value, 0);
932 let progress = self.encode_pending(pending, state)?;
933 assert_reserved_output_drained(
934 progress,
935 "converter finish bound must reserve space for decode finish values",
936 );
937 }
938 Ok(())
939 }
940
941 /// Encodes one pending value and applies output/pending state changes.
942 ///
943 /// When the target buffer lacks capacity, the value is put back into the
944 /// pending slot and progress reports
945 /// [`crate::TranscodeStatus::NeedOutput`].
946 ///
947 /// # Parameters
948 ///
949 /// - `pending`: Decoded value waiting for target output capacity.
950 /// - `state`: Current conversion cursors and output buffer.
951 ///
952 /// # Returns
953 ///
954 /// Returns conversion progress when the value needs more output capacity,
955 /// or `None` when the value was fully encoded.
956 ///
957 /// # Errors
958 ///
959 /// Returns a converter error when encode hook handling fails.
960 fn encode_pending(
961 &mut self,
962 pending: PendingValue<D::Value>,
963 state: &mut ConvertState<'_, D::Unit, E::Unit>,
964 ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
965 let input_index = pending.input_index();
966 let output_index = state.output_cursor();
967 let attempt = EncodeAttempt::new(pending.value(), input_index, state.output_mut(), output_index);
968 let outcome = match self.encode_engine.encode_one(attempt) {
969 Ok(outcome) => outcome,
970 Err(error) => {
971 return Err(TranscodeConvertError::from_encode_error_with_value(
972 error,
973 pending.into_value(),
974 ));
975 }
976 };
977 let progress = state.apply_encode_outcome(outcome);
978 if progress.is_some() {
979 self.pending.put(pending);
980 }
981 Ok(progress)
982 }
983}
984
985impl<D, E, DH, EH> Default for TranscodeConvertEngine<D, E, DH, EH>
986where
987 D: Codec + Default,
988 E: Codec<Value = D::Value> + Default,
989 DH: TranscodeDecodeHooks<D> + Default,
990 EH: TranscodeEncodeHooks<E> + Default,
991{
992 /// Creates a default buffered converter engine.
993 ///
994 /// # Returns
995 ///
996 /// Returns a converter engine constructed from default codecs and hooks.
997 #[inline(always)]
998 fn default() -> Self {
999 Self::new(D::default(), E::default(), DH::default(), EH::default())
1000 }
1001}
1002
1003impl<D, E, DH, EH> Transcoder for TranscodeConvertEngine<D, E, DH, EH>
1004where
1005 D: Codec,
1006 E: Codec<Value = D::Value>,
1007 D::Value: Default,
1008 DH: TranscodeDecodeHooks<D>,
1009 EH: TranscodeEncodeHooks<E>,
1010{
1011 type Input = D::Unit;
1012 type Output = E::Unit;
1013 type Error = TranscodeConvertErrorOf<D, E>;
1014
1015 /// Returns an upper bound for target units produced from `input_len`
1016 /// units.
1017 #[inline(always)]
1018 fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
1019 TranscodeConvertEngine::max_transcode_output_len(self, input_len)
1020 }
1021
1022 /// Returns an upper bound for target units emitted by finishing retained
1023 /// state.
1024 #[inline(always)]
1025 fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
1026 TranscodeConvertEngine::max_finish_output_len(self)
1027 }
1028
1029 /// Returns an upper bound for target units emitted when resetting stream
1030 /// state.
1031 #[inline(always)]
1032 fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
1033 TranscodeConvertEngine::max_reset_output_len(self)
1034 }
1035
1036 /// Clears retained conversion state and emits target reset output.
1037 #[inline(always)]
1038 fn reset(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>> {
1039 TranscodeConvertEngine::reset(self, output, output_index)
1040 }
1041
1042 /// Converts source units into target units.
1043 #[inline(always)]
1044 fn transcode(
1045 &mut self,
1046 input: &[D::Unit],
1047 input_index: usize,
1048 output: &mut [E::Unit],
1049 output_index: usize,
1050 ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
1051 TranscodeConvertEngine::transcode(self, input, input_index, output, output_index)
1052 }
1053
1054 #[inline(always)]
1055 fn transcode_eof(
1056 &mut self,
1057 input: &[D::Unit],
1058 input_index: usize,
1059 output: &mut [E::Unit],
1060 output_index: usize,
1061 ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
1062 TranscodeConvertEngine::transcode_eof(self, input, input_index, output, output_index)
1063 }
1064
1065 /// Finishes retained converter output after EOF.
1066 #[inline(always)]
1067 fn finish(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>> {
1068 TranscodeConvertEngine::finish(self, output, output_index)
1069 }
1070}
1071
1072impl<D, E, DH, EH> TranscodeConverter for TranscodeConvertEngine<D, E, DH, EH>
1073where
1074 D: Codec,
1075 E: Codec<Value = D::Value>,
1076 D::Value: Default,
1077 DH: TranscodeDecodeHooks<D>,
1078 EH: TranscodeEncodeHooks<E>,
1079{
1080 type DecodeError = D::DecodeError;
1081 type EncodeError = E::EncodeError;
1082 type Value = D::Value;
1083}