use core::convert::Infallible;
use core::num::NonZeroUsize;
use qubit_codec::CapacityError;
use qubit_codec::Codec;
use qubit_codec::DecodeFailure;
use qubit_codec::TranscodeDomainError;
use qubit_codec::TranscodeEncodeError;
use qubit_codec::TranscodeEncodeErrorOf;
use qubit_codec::TranscodeEncoder;
use qubit_codec::TranscodeFailure;
use qubit_codec::TranscodeProgress;
use qubit_codec::TranscodeStatus;
use qubit_codec::Transcoder;
use qubit_codec::engine::EncodeContext;
use qubit_codec::engine::EncodeUnencodableAction;
use qubit_codec::engine::TranscodeEncodeEngine;
use qubit_codec::engine::TranscodeEncodeHooks;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct WideCodec;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct UnderfilledFinishCodec;
impl Codec for UnderfilledFinishCodec {
type Value = u8;
type Unit = u8;
type DecodeError = Infallible;
type EncodeError = Infallible;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_FINISH_UNITS: usize = 2;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
Ok((input[input_index], NonZeroUsize::MIN))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
output[output_index] = *value;
Ok(1)
}
}
impl Codec for WideCodec {
type Value = u8;
type Unit = u8;
type DecodeError = Infallible;
type EncodeError = Infallible;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 4;
const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
fn can_encode_value(&self, value: &u8) -> bool {
*value != 0
}
fn encode_len(&self, _value: &u8) -> usize {
1
}
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
debug_assert!(input_index < input.len());
let value = unsafe { *input.as_ptr().add(input_index) };
Ok((value, NonZeroUsize::MIN))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
debug_assert!(output_index < output.len());
unsafe {
*output.as_mut_ptr().add(output_index) = value.wrapping_add(10);
}
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct FullyBufferedCodec;
impl Codec for FullyBufferedCodec {
type Value = u8;
type Unit = u8;
type DecodeError = Infallible;
type EncodeError = Infallible;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 0;
const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
fn can_encode_value(&self, value: &u8) -> bool {
*value != 0
}
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
Ok((input[input_index], NonZeroUsize::MIN))
}
unsafe fn encode(
&mut self,
_value: &u8,
_output: &mut [u8],
_output_index: usize,
) -> Result<usize, Self::EncodeError> {
Ok(0)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct FullyBufferedHooks;
impl TranscodeEncodeHooks<FullyBufferedCodec> for FullyBufferedHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut FullyBufferedCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<FullyBufferedCodec>> {
Ok(EncodeUnencodableAction::Skip)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct FullyBufferedZeroWidthHooks;
impl TranscodeEncodeHooks<FullyBufferedCodec> for FullyBufferedZeroWidthHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut FullyBufferedCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<FullyBufferedCodec>> {
Ok(EncodeUnencodableAction::Replace { value: 1 })
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverlongEncodeLenCodec;
impl Codec for OverlongEncodeLenCodec {
type Value = u8;
type Unit = u8;
type DecodeError = Infallible;
type EncodeError = Infallible;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
fn can_encode_value(&self, value: &u8) -> bool {
*value != 0
}
fn encode_len(&self, _value: &u8) -> usize {
2
}
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
Ok((input[input_index], NonZeroUsize::MIN))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
output[output_index] = *value;
output[output_index + 1] = *value;
Ok(2)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
enum EngineError {
#[error("rejected input at index {input_index}")]
Rejected { input_index: usize },
}
impl From<Infallible> for EngineError {
fn from(error: Infallible) -> Self {
match error {}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct ExactWidthHooks;
impl<C> TranscodeEncodeHooks<C> for ExactWidthHooks
where
C: Codec<Value = u8, Unit = u8>,
{
fn handle_unencodable_encode(
&mut self,
_codec: &mut C,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<C>> {
Ok(EncodeUnencodableAction::Reject)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverlongReplacementHooks;
impl TranscodeEncodeHooks<OverlongEncodeLenCodec> for OverlongReplacementHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut OverlongEncodeLenCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<OverlongEncodeLenCodec>> {
Ok(EncodeUnencodableAction::replace(1))
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct SkippingHooks;
impl TranscodeEncodeHooks<WideCodec> for SkippingHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut WideCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<WideCodec>> {
Ok(EncodeUnencodableAction::Skip)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct RejectingHooks;
impl TranscodeEncodeHooks<WideCodec> for RejectingHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut WideCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<WideCodec>> {
Ok(EncodeUnencodableAction::Reject)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ReplacingHooks {
replacement: u8,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct CountedReplacingHooks {
calls: usize,
}
impl TranscodeEncodeHooks<WideCodec> for CountedReplacingHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut WideCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<WideCodec>> {
self.calls += 1;
Ok(EncodeUnencodableAction::Replace {
value: 4 + self.calls as u8,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CountedNoOutputAction {
Skip,
Reject,
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct CountedNoOutputHooks {
calls: usize,
action: CountedNoOutputAction,
}
impl TranscodeEncodeHooks<WideCodec> for CountedNoOutputHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut WideCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<WideCodec>> {
self.calls += 1;
Ok(match self.action {
CountedNoOutputAction::Skip => EncodeUnencodableAction::Skip,
CountedNoOutputAction::Reject => EncodeUnencodableAction::Reject,
})
}
}
impl TranscodeEncodeHooks<WideCodec> for ReplacingHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut WideCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<WideCodec>> {
Ok(EncodeUnencodableAction::replace(self.replacement))
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct ReplacementEncodeFailCodec;
impl Codec for ReplacementEncodeFailCodec {
type Value = u8;
type Unit = u8;
type DecodeError = Infallible;
type EncodeError = EngineError;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
fn can_encode_value(&self, value: &u8) -> bool {
*value != 0
}
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
Ok((input[input_index], NonZeroUsize::MIN))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
if *value == 7 {
return Err(EngineError::Rejected {
input_index: output_index,
});
}
output[output_index] = *value;
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct FailingReplacementHooks;
impl TranscodeEncodeHooks<ReplacementEncodeFailCodec> for FailingReplacementHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut ReplacementEncodeFailCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<ReplacementEncodeFailCodec>> {
Ok(EncodeUnencodableAction::replace(7))
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct FailingFinishHooks;
impl TranscodeEncodeHooks<ReplacementEncodeFailCodec> for FailingFinishHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut ReplacementEncodeFailCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<ReplacementEncodeFailCodec>> {
Ok(EncodeUnencodableAction::Reject)
}
fn max_finish_output_len(&self, _codec: &ReplacementEncodeFailCodec) -> usize {
1
}
fn finish_hooks(
&mut self,
_codec: &mut ReplacementEncodeFailCodec,
_output: &mut [u8],
output_index: usize,
) -> Result<usize, TranscodeEncodeErrorOf<ReplacementEncodeFailCodec>> {
Err(TranscodeEncodeError::domain_finish(EngineError::Rejected {
input_index: output_index,
}))
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct FinishFailingCodec;
impl Codec for FinishFailingCodec {
type Value = u8;
type Unit = u8;
type DecodeError = Infallible;
type EncodeError = EngineError;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
Ok((input[input_index], NonZeroUsize::MIN))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
output[output_index] = *value;
Ok(1)
}
unsafe fn encode_finish(&mut self, _output: &mut [u8], _output_index: usize) -> Result<usize, Self::EncodeError> {
Err(EngineError::Rejected { input_index: 0 })
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct FinishFailingHooks;
impl TranscodeEncodeHooks<FinishFailingCodec> for FinishFailingHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut FinishFailingCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<FinishFailingCodec>> {
Ok(EncodeUnencodableAction::Reject)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverreportingEncodeCodec;
impl Codec for OverreportingEncodeCodec {
type Value = u8;
type Unit = u8;
type DecodeError = Infallible;
type EncodeError = Infallible;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
Ok((input[input_index], NonZeroUsize::MIN))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
output[output_index] = *value;
Ok(2)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct FinishHooks {
pending_suffix: bool,
}
impl Default for FinishHooks {
fn default() -> Self {
Self { pending_suffix: true }
}
}
impl TranscodeEncodeHooks<WideCodec> for FinishHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut WideCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<WideCodec>> {
Ok(EncodeUnencodableAction::Reject)
}
fn max_finish_output_len(&self, _codec: &WideCodec) -> usize {
1
}
fn finish_hooks(
&mut self,
_codec: &mut WideCodec,
output: &mut [u8],
output_index: usize,
) -> Result<usize, TranscodeEncodeErrorOf<WideCodec>> {
if !self.pending_suffix {
return Ok(0);
}
output[output_index] = 0xee;
self.pending_suffix = false;
Ok(1)
}
fn reset_hooks(&mut self, _codec: &mut WideCodec) {
self.pending_suffix = true;
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverwritingFinishHooks;
impl TranscodeEncodeHooks<WideCodec> for OverwritingFinishHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut WideCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<WideCodec>> {
Ok(EncodeUnencodableAction::Reject)
}
fn max_finish_output_len(&self, _codec: &WideCodec) -> usize {
1
}
fn finish_hooks(
&mut self,
_codec: &mut WideCodec,
output: &mut [u8],
output_index: usize,
) -> Result<usize, TranscodeEncodeErrorOf<WideCodec>> {
assert_eq!(3, output.len());
output[output_index] = 0xee;
Ok(1)
}
}
struct UnderfilledFinishHooks {
bound: usize,
reported: usize,
}
impl TranscodeEncodeHooks<UnderfilledFinishCodec> for UnderfilledFinishHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut UnderfilledFinishCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<UnderfilledFinishCodec>> {
Ok(EncodeUnencodableAction::Reject)
}
fn max_finish_output_len(&self, _codec: &UnderfilledFinishCodec) -> usize {
self.bound
}
fn finish_hooks(
&mut self,
_codec: &mut UnderfilledFinishCodec,
_output: &mut [u8],
_output_index: usize,
) -> Result<usize, TranscodeEncodeErrorOf<UnderfilledFinishCodec>> {
Ok(self.reported)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverreportingFinishHooks;
impl TranscodeEncodeHooks<WideCodec> for OverreportingFinishHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut WideCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<WideCodec>> {
Ok(EncodeUnencodableAction::Reject)
}
fn max_finish_output_len(&self, _codec: &WideCodec) -> usize {
1
}
fn finish_hooks(
&mut self,
_codec: &mut WideCodec,
output: &mut [u8],
output_index: usize,
) -> Result<usize, TranscodeEncodeErrorOf<WideCodec>> {
output[output_index] = 0xee;
Ok(2)
}
}
#[test]
fn test_transcode_encode_engine_exposes_codec_hooks_and_parts() {
let mut engine = TranscodeEncodeEngine::new(WideCodec, ExactWidthHooks);
assert_eq!(&WideCodec, engine.codec());
assert_eq!(&ExactWidthHooks, engine.hooks());
*engine.codec_mut() = WideCodec;
*engine.hooks_mut() = ExactWidthHooks;
let (codec, hooks) = engine.into_parts();
assert_eq!(WideCodec, codec);
assert_eq!(ExactWidthHooks, hooks);
}
#[test]
#[should_panic(expected = "Codec::encode_len exceeded Codec::MAX_ENCODE_UNITS_PER_VALUE")]
fn test_buffered_encode_engine_panics_when_value_width_exceeds_codec_maximum() {
let mut encoder = TranscodeEncodeEngine::new(OverlongEncodeLenCodec, ExactWidthHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 2];
let _ = encoder.transcode(&[1], 0, &mut output, 0);
}
#[test]
#[should_panic(expected = "Codec::encode_len exceeded Codec::MAX_ENCODE_UNITS_PER_VALUE")]
fn test_buffered_encode_engine_panics_when_replacement_width_exceeds_codec_maximum() {
let mut encoder = TranscodeEncodeEngine::new(OverlongEncodeLenCodec, OverlongReplacementHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 2];
let _ = encoder.transcode(&[0], 0, &mut output, 0);
}
#[test]
fn test_buffered_encode_engine_reports_bounds_and_resets() {
type Engine = TranscodeEncodeEngine<WideCodec, ExactWidthHooks>;
type TranscodeCompleteIntoFn =
fn(&mut Engine, &[u8], &mut [u8]) -> Result<usize, TranscodeEncodeError<Infallible, u8>>;
let mut encoder = TranscodeEncodeEngine::<_, _>::new(WideCodec, ExactWidthHooks);
let max_total_output_len: fn(&Engine, usize) -> Result<usize, CapacityError> = Engine::max_total_output_len;
let transcode_complete_into: TranscodeCompleteIntoFn = Engine::transcode_complete_into;
assert_eq!(Ok(8), encoder.max_transcode_output_len(2));
assert_eq!(Ok(8), max_total_output_len(&encoder, 2));
assert_eq!(Ok(0), encoder.max_finish_output_len());
assert_eq!(Ok(0), encoder.max_reset_output_len());
assert_eq!(
Err(CapacityError::OutputLengthOverflow),
max_total_output_len(&encoder, usize::MAX),
);
assert_eq!(
Err(CapacityError::OutputLengthOverflow),
encoder.max_transcode_output_len(usize::MAX),
);
let mut output = [0_u8; 8];
let written = transcode_complete_into(&mut encoder, &[1, 2], &mut output)
.expect("complete encode should fit the planned output");
assert_eq!(2, written);
assert_eq!(&[11, 12], &output[..written]);
}
#[test]
fn test_buffered_encode_engine_allows_zero_encode_unit_bound() {
let mut engine = TranscodeEncodeEngine::new(FullyBufferedCodec, FullyBufferedHooks);
let mut reset_output = [];
engine.reset(&mut reset_output, 0).expect("initialize stream");
assert_eq!(Ok(0), engine.max_transcode_output_len(3));
let progress = engine
.transcode(&[1, 2, 3], 0, &mut [], 0)
.expect("fully buffered values should not require immediate output");
assert_eq!(3, progress.read());
assert_eq!(0, progress.written());
let progress = engine
.transcode(&[0], 0, &mut [], 0)
.expect("zero-width skip must not require output capacity");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(1, progress.read());
assert_eq!(0, progress.written());
let mut engine = TranscodeEncodeEngine::new(FullyBufferedCodec, FullyBufferedZeroWidthHooks);
engine
.reset(&mut [], 0)
.expect("initialize zero-width replacement stream");
let progress = engine
.transcode(&[0], 0, &mut [], 0)
.expect("zero-width replacement must not require nonzero capacity");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(1, progress.read());
assert_eq!(0, progress.written());
}
#[test]
fn test_buffered_encode_engine_delegates_finish_to_hooks() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(WideCodec, FinishHooks::default());
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 1];
assert_eq!(Ok(1), encoder.max_finish_output_len());
let error = encoder
.finish(&mut [], 0)
.expect_err("finish should reject insufficient output before calling hooks");
assert_eq!(
TranscodeEncodeError::Failure(TranscodeFailure::insufficient_output(0, 1, 0)),
error,
);
assert_eq!(Ok(1), encoder.max_finish_output_len());
let written = encoder.finish(&mut output, 0).expect("hook should write final output");
assert_eq!(1, written);
assert_eq!([0xee], output);
assert_eq!(Ok(1), encoder.max_finish_output_len());
let mut encoder = TranscodeEncodeEngine::<_, _>::new(WideCodec, FinishHooks::default());
encoder.reset(&mut [], 0).expect("reset");
assert_eq!(Ok(1), encoder.max_finish_output_len());
}
#[test]
fn test_buffered_encode_engine_implements_transcoder() {
type Engine = TranscodeEncodeEngine<WideCodec, ExactWidthHooks>;
fn assert_encoder<T: TranscodeEncoder<Input = u8, Output = u8>>() {}
assert_encoder::<Engine>();
type EngineResult<T> = Result<T, TranscodeEncodeError<Infallible, u8>>;
type TranscodeFn = fn(&mut Engine, &[u8], usize, &mut [u8], usize) -> EngineResult<TranscodeProgress>;
type OutputFn = fn(&mut Engine, &mut [u8], usize) -> EngineResult<usize>;
let mut encoder = TranscodeEncodeEngine::<_, _>::new(WideCodec, ExactWidthHooks);
let mut output = [0_u8; 2];
let max_transcode_output_len: fn(&Engine, usize) -> Result<usize, CapacityError> =
std::hint::black_box(<Engine as Transcoder>::max_transcode_output_len);
let max_finish_output_len: fn(&Engine) -> Result<usize, CapacityError> =
std::hint::black_box(<Engine as Transcoder>::max_finish_output_len);
let max_reset_output_len: fn(&Engine) -> Result<usize, CapacityError> =
std::hint::black_box(<Engine as Transcoder>::max_reset_output_len);
let transcode: TranscodeFn = std::hint::black_box(<Engine as Transcoder>::transcode);
let reset: OutputFn = std::hint::black_box(<Engine as Transcoder>::reset);
let finish: OutputFn = std::hint::black_box(<Engine as Transcoder>::finish);
assert_eq!(Ok(8), max_transcode_output_len(&encoder, 2));
assert_eq!(Ok(0), max_finish_output_len(&encoder));
assert_eq!(Ok(0), max_reset_output_len(&encoder));
reset(&mut encoder, &mut [], 0).expect("reset before trait transcode");
let progress =
transcode(&mut encoder, &[1, 2], 0, &mut output, 0).expect("engine should transcode through the trait");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!((2, 2), (progress.read(), progress.written()));
assert_eq!([11, 12], output);
let mut empty_output = [0_u8; 0];
let reset = reset(&mut encoder, &mut empty_output, 0).expect("engine should reset through the trait");
let finished = finish(&mut encoder, &mut empty_output, 0).expect("engine should finish through the trait");
assert_eq!(0, reset);
assert_eq!(0, finished);
}
#[test]
fn test_buffered_encode_engine_finish_maps_hook_errors() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(ReplacementEncodeFailCodec, FailingFinishHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 1];
let error = encoder
.finish(&mut output, 0)
.expect_err("finish hook error should be propagated");
assert_eq!(
TranscodeEncodeError::domain_finish(EngineError::Rejected { input_index: 0 }),
error,
);
}
#[test]
fn test_buffered_encode_engine_finish_converts_codec_finish_errors() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(FinishFailingCodec, FinishFailingHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 1];
let error = encoder
.finish(&mut output, 0)
.expect_err("finish should convert codec finish errors");
assert_eq!(
TranscodeEncodeError::domain_finish(EngineError::Rejected { input_index: 0 }),
error,
);
}
#[test]
fn test_buffered_encode_engine_finish_passes_full_output_to_hooks() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(WideCodec, OverwritingFinishHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0xa1, 0, 0xb2];
let written = encoder
.finish(&mut output, 1)
.expect("hook should receive the caller-provided output slice");
assert_eq!(1, written);
assert_eq!([0xa1, 0xee, 0xb2], output);
}
#[test]
#[should_panic(expected = "TranscodeEncodeEngine hook wrote beyond its finish bound")]
fn test_buffered_encode_engine_finish_panics_when_hook_exceeds_zero_bound() {
let hooks = UnderfilledFinishHooks { bound: 0, reported: 1 };
let mut encoder = TranscodeEncodeEngine::new(UnderfilledFinishCodec, hooks);
encoder.reset(&mut [0; 2], 0).expect("initialize stream");
let _ = encoder.finish(&mut [0; 2], 0);
}
#[test]
fn test_buffered_encode_engine_finish_accepts_hook_at_its_bound() {
let hooks = UnderfilledFinishHooks { bound: 1, reported: 1 };
let mut encoder = TranscodeEncodeEngine::new(UnderfilledFinishCodec, hooks);
encoder.reset(&mut [0; 2], 0).expect("initialize stream");
assert_eq!(1, encoder.finish(&mut [0; 3], 0).expect("finish within bound"));
}
#[test]
#[should_panic(expected = "TranscodeEncodeEngine hook wrote beyond its finish bound")]
fn test_buffered_encode_engine_finish_panics_when_hook_report_overflows() {
let hooks = UnderfilledFinishHooks {
bound: 1,
reported: usize::MAX,
};
let mut encoder = TranscodeEncodeEngine::new(UnderfilledFinishCodec, hooks);
encoder.reset(&mut [0; 2], 0).expect("initialize stream");
let _ = encoder.finish(&mut [0; 3], 0);
}
#[test]
#[should_panic(expected = "TranscodeEncodeEngine hook wrote beyond its finish bound")]
fn test_buffered_encode_engine_finish_panics_when_hook_overreports_bound() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(WideCodec, OverreportingFinishHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 2];
let _ = encoder.finish(&mut output, 0);
}
#[test]
fn test_buffered_encode_engine_finish_reports_output_index_beyond_buffer() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(WideCodec, FinishHooks::default());
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [];
let error = encoder
.finish(&mut output, 1)
.expect_err("out-of-range finish output index should be rejected");
assert_eq!(
TranscodeEncodeError::Failure(TranscodeFailure::invalid_output_index(1, 0)),
error,
);
}
#[test]
fn test_buffered_encode_engine_default_finish_reports_output_index_beyond_buffer() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(WideCodec, ExactWidthHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [];
let error = encoder
.finish(&mut output, 1)
.expect_err("default finish should reject out-of-range output index");
assert_eq!(
TranscodeEncodeError::Failure(TranscodeFailure::invalid_output_index(1, 0)),
error,
);
}
#[test]
fn test_buffered_encode_hooks_default_finish_is_noop() {
let mut hooks = ExactWidthHooks;
let mut output = [];
let written = TranscodeEncodeHooks::<WideCodec>::finish_hooks(&mut hooks, &mut WideCodec, &mut output, 1)
.expect("default hook finish should be a no-op");
assert_eq!(0, written);
}
#[test]
fn test_buffered_encode_engine_uses_exact_value_width_for_output_pressure() {
let mut encoder = TranscodeEncodeEngine::new(WideCodec, ExactWidthHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 1];
let progress = encoder
.transcode(&[1, 2], 0, &mut output, 0)
.expect("engine encoding should succeed");
assert_eq!(
TranscodeStatus::NeedOutput {
required: crate::nonzero(1),
},
progress.status(),
);
assert_eq!(1, progress.read());
assert_eq!(1, progress.written());
assert_eq!([11], output);
assert_eq!(Ok(8), encoder.max_transcode_output_len(2));
}
#[test]
fn test_buffered_encode_engine_allows_zero_width_value_to_consume_input() {
let mut encoder = TranscodeEncodeEngine::new(WideCodec, SkippingHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 4];
let progress = encoder
.transcode(&[0, 0, 0], 0, &mut output, 0)
.expect("zero-width value should not need output");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(3, progress.read());
assert_eq!(0, progress.written());
}
#[test]
fn test_buffered_encode_engine_reports_output_index_beyond_buffer() {
let mut encoder = TranscodeEncodeEngine::new(WideCodec, ExactWidthHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [];
let error = encoder
.transcode(&[1], 0, &mut output, 1)
.expect_err("out-of-range output index should fail");
assert_eq!(
TranscodeEncodeError::Failure(TranscodeFailure::invalid_output_index(1, 0)),
error,
);
}
#[test]
#[should_panic(expected = "Codec::encode wrote a different length than Codec::encode_len")]
fn test_buffered_encode_engine_panics_when_codec_reports_wrong_value_width() {
let mut encoder = TranscodeEncodeEngine::new(OverreportingEncodeCodec, ExactWidthHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 1];
let _ = encoder.transcode(&[1], 0, &mut output, 0);
}
#[test]
fn test_buffered_encode_engine_encodes_replacement_for_unencodable() {
let mut encoder = TranscodeEncodeEngine::new(WideCodec, ReplacingHooks { replacement: 5 });
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 4];
let progress = encoder
.transcode(&[0], 0, &mut output, 0)
.expect("replacement should encode through the codec");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(1, progress.read());
assert_eq!(1, progress.written());
assert_eq!([15], output[..progress.written()]);
}
#[test]
fn test_buffered_encode_engine_replacement_waits_for_output_capacity() {
let mut encoder = TranscodeEncodeEngine::new(WideCodec, ReplacingHooks { replacement: 5 });
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 4];
for available in [0, 1, 3] {
let progress = encoder
.transcode(&[0], 0, &mut output[..available], 0)
.expect("replacement should report output pressure");
assert_eq!(
TranscodeStatus::NeedOutput {
required: crate::nonzero(4),
},
progress.status(),
);
assert_eq!(0, progress.read());
assert_eq!(0, progress.written());
}
}
#[test]
fn test_buffered_encode_engine_does_not_retry_replacement_hook_under_output_pressure() {
let mut encoder = TranscodeEncodeEngine::new(WideCodec, CountedReplacingHooks::default());
encoder.reset(&mut [], 0).expect("initialize stream");
for _ in 0..2 {
let progress = encoder
.transcode(&[0], 0, &mut [], 0)
.expect("insufficient capacity should be reported");
assert_eq!(
TranscodeStatus::NeedOutput {
required: crate::nonzero(4)
},
progress.status()
);
assert_eq!(0, progress.read());
assert_eq!(0, encoder.hooks().calls);
}
let mut output = [0_u8; 4];
let progress = encoder
.transcode(&[0], 0, &mut output, 0)
.expect("replacement should encode after capacity is supplied");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(1, progress.read());
assert_eq!(1, progress.written());
assert_eq!([15], output[..progress.written()]);
assert_eq!(1, encoder.hooks().calls);
}
#[test]
fn test_buffered_encode_engine_gates_skip_and_reject_hooks_on_capacity() {
for action in [CountedNoOutputAction::Skip, CountedNoOutputAction::Reject] {
let mut encoder = TranscodeEncodeEngine::new(WideCodec, CountedNoOutputHooks { calls: 0, action });
encoder.reset(&mut [], 0).expect("initialize stream");
let mut output = [0_u8; 4];
for capacity in [0, 1, 3] {
let progress = encoder
.transcode(&[0], 0, &mut output[..capacity], 0)
.expect("insufficient output should be reported before policy");
assert_eq!(
TranscodeStatus::NeedOutput {
required: crate::nonzero(4)
},
progress.status()
);
assert_eq!((0, 0), (progress.read(), progress.written()));
assert_eq!(0, encoder.hooks().calls);
}
let result = encoder.transcode(&[0], 0, &mut output, 0);
match action {
CountedNoOutputAction::Skip => {
let progress = result.expect("Skip should consume after capacity is available");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!((1, 0), (progress.read(), progress.written()));
}
CountedNoOutputAction::Reject => {
assert_eq!(Err(TranscodeEncodeError::unencodable_without_context(0)), result,);
}
}
assert_eq!(1, encoder.hooks().calls);
}
}
#[test]
#[should_panic(expected = "EncodeUnencodableAction::Replace returned an unencodable replacement value")]
fn test_buffered_encode_engine_panics_when_replacement_is_unencodable() {
let mut encoder = TranscodeEncodeEngine::new(WideCodec, ReplacingHooks { replacement: 0 });
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 4];
let _ = encoder.transcode(&[0], 0, &mut output, 0);
}
#[test]
fn test_buffered_encode_engine_maps_replacement_encode_error() {
let mut encoder = TranscodeEncodeEngine::new(ReplacementEncodeFailCodec, FailingReplacementHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 1];
let error = encoder
.transcode(&[0], 0, &mut output, 0)
.expect_err("replacement encode failure should be mapped");
assert!(matches!(
error,
TranscodeEncodeError::Domain(TranscodeDomainError::Main {
source: EngineError::Rejected { input_index: 0 },
input_index: 0,
input_consumed: None
}),
));
assert_eq!([0], output);
}
#[test]
fn test_buffered_encode_engine_propagates_unencodable_hook_error_without_consuming_input() {
let mut encoder = TranscodeEncodeEngine::new(WideCodec, RejectingHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 4];
let error = encoder
.transcode(&[0], 0, &mut output, 0)
.expect_err("encode hook error should be propagated");
assert_eq!(TranscodeEncodeError::unencodable_without_context(0), error);
assert_eq!([0, 0, 0, 0], output);
}
#[test]
fn test_buffered_encode_engine_uses_hooks_for_invalid_input_index() {
let mut encoder = TranscodeEncodeEngine::new(WideCodec, ExactWidthHooks);
let mut reset_output = [];
encoder.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [];
let error = encoder
.transcode(&[1], 2, &mut output, 0)
.expect_err("invalid input index should be rejected");
assert_eq!(
TranscodeEncodeError::Failure(TranscodeFailure::invalid_input_index(2, 1)),
error,
);
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct ResetEmittingCodec;
impl Codec for ResetEmittingCodec {
type Value = u8;
type Unit = u8;
type DecodeError = Infallible;
type EncodeError = Infallible;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_RESET_UNITS: usize = 1;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
Ok((input[input_index], NonZeroUsize::MIN))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
output[output_index] = *value;
Ok(1)
}
unsafe fn encode_reset(&mut self, output: &mut [u8], output_index: usize) -> Result<usize, Self::EncodeError> {
output[output_index] = 0xaa;
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct ResetFailCodec;
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
#[error("reset failed")]
struct ResetFailError;
impl Codec for ResetFailCodec {
type Value = u8;
type Unit = u8;
type DecodeError = Infallible;
type EncodeError = ResetFailError;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_RESET_UNITS: usize = 1;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
Ok((input[input_index], NonZeroUsize::MIN))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
output[output_index] = *value;
Ok(1)
}
unsafe fn encode_reset(&mut self, _output: &mut [u8], _output_index: usize) -> Result<usize, Self::EncodeError> {
Err(ResetFailError)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct ResetErrorMappingHooks;
impl TranscodeEncodeHooks<ResetFailCodec> for ResetErrorMappingHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut ResetFailCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<ResetFailCodec>> {
Err(TranscodeEncodeError::domain_main(
ResetFailError,
_context.input_index(),
))
}
}
#[test]
fn test_buffered_encode_engine_default_builds_engine() {
let mut encoder = TranscodeEncodeEngine::<WideCodec, ExactWidthHooks>::default();
encoder
.reset(&mut [], 0)
.expect("reset before default engine transcode");
let mut output = [0_u8; 1];
let progress = encoder
.transcode(&[7], 0, &mut output, 0)
.expect("default engine should encode one value");
assert_eq!(1, progress.read());
assert_eq!(1, progress.written());
assert_eq!([17], output);
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct ResetPassthroughHooks;
impl TranscodeEncodeHooks<ResetEmittingCodec> for ResetPassthroughHooks {
fn handle_unencodable_encode(
&mut self,
_codec: &mut ResetEmittingCodec,
_context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<ResetEmittingCodec>> {
Ok(EncodeUnencodableAction::Reject)
}
}
#[test]
fn test_buffered_encode_engine_reset_emits_codec_reset_output() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(ResetEmittingCodec, ResetPassthroughHooks);
let mut output = [0_u8; 1];
let written = encoder
.reset(&mut output, 0)
.expect("reset should emit codec reset output");
assert_eq!(1, written);
assert_eq!([0xaa], output);
}
#[test]
fn test_buffered_encode_engine_reset_rejects_insufficient_output() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(ResetEmittingCodec, ResetPassthroughHooks);
let mut output = [];
let error = encoder
.reset(&mut output, 0)
.expect_err("reset should reject insufficient output capacity");
assert_eq!(
TranscodeEncodeError::Failure(TranscodeFailure::insufficient_output(0, 1, 0)),
error,
);
}
#[test]
fn test_buffered_encode_engine_reset_reports_output_index_beyond_buffer() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(ResetEmittingCodec, ResetPassthroughHooks);
let mut output = [0_u8; 1];
let error = encoder
.reset(&mut output, 2)
.expect_err("reset should reject an out-of-range output index");
assert_eq!(
TranscodeEncodeError::Failure(TranscodeFailure::invalid_output_index(2, 1)),
error,
);
}
#[test]
fn test_buffered_encode_engine_max_total_output_len_reports_sum_overflow() {
let encoder = TranscodeEncodeEngine::<_, _>::new(ResetEmittingCodec, ResetPassthroughHooks);
assert_eq!(
Err(CapacityError::OutputLengthOverflow),
encoder.max_total_output_len(usize::MAX),
);
}
#[test]
fn test_buffered_encode_engine_reset_converts_codec_reset_errors() {
let mut encoder = TranscodeEncodeEngine::<_, _>::new(ResetFailCodec, ResetErrorMappingHooks);
let mut output = [0_u8; 1];
let error = encoder
.reset(&mut output, 0)
.expect_err("reset should convert codec reset errors");
assert_eq!(TranscodeEncodeError::domain_reset(ResetFailError), error,);
}
#[test]
fn test_buffered_encode_engine_lifecycle_rejects_double_finish() {
let mut engine = TranscodeEncodeEngine::<_, _>::new(WideCodec, ExactWidthHooks);
let mut reset_output = [];
engine.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 0];
engine
.finish(&mut output, 0)
.expect("first finish should succeed for a stateless encoder");
assert_eq!(
Err(TranscodeEncodeError::Failure(TranscodeFailure::FinishAfterFinish,)),
engine.finish(&mut output, 0),
);
}
#[test]
fn test_buffered_encode_engine_lifecycle_rejects_transcode_after_finish() {
let mut engine = TranscodeEncodeEngine::<_, _>::new(WideCodec, ExactWidthHooks);
let mut reset_output = [];
engine.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 1];
engine.finish(&mut output, 0).expect("finish closes the logical stream");
assert_eq!(
Err(TranscodeEncodeError::Failure(TranscodeFailure::TranscodeAfterFinish,)),
engine.transcode(&[1_u8], 0, &mut output, 0),
);
}
#[test]
fn test_buffered_encode_engine_lifecycle_allows_reuse_after_reset() {
let mut engine = TranscodeEncodeEngine::<_, _>::new(WideCodec, ExactWidthHooks);
let mut reset_output = [];
engine.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 2];
engine.finish(&mut output, 0).expect("first logical stream finalizes");
engine.reset(&mut output, 0).expect("reset reopens the engine");
let progress = engine
.transcode(&[1_u8], 0, &mut output, 0)
.expect("transcode after reset");
assert_eq!(1, progress.read());
engine.finish(&mut output, 1).expect("second logical stream finalizes");
}
#[test]
fn test_buffered_encode_engine_failed_reset_preserves_finished_state() {
let mut engine = TranscodeEncodeEngine::<_, _>::new(ResetEmittingCodec, ResetPassthroughHooks);
let mut reset_output = [0_u8; 1];
engine.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 1];
engine.finish(&mut output, 0).expect("finish closes the logical stream");
let error = engine
.reset(&mut [], 0)
.expect_err("reset should reject insufficient output");
assert_eq!(
TranscodeEncodeError::Failure(TranscodeFailure::insufficient_output(0, 1, 0)),
error
);
assert_eq!(
Err(TranscodeEncodeError::Failure(TranscodeFailure::TranscodeAfterFinish,)),
engine.transcode(&[1_u8], 0, &mut output, 0),
);
}
#[test]
fn test_buffered_encode_engine_failed_finish_poisoned_until_reset() {
let mut engine = TranscodeEncodeEngine::<_, _>::new(ReplacementEncodeFailCodec, FailingFinishHooks);
let mut reset_output = [];
engine.reset(&mut reset_output, 0).expect("initialize stream");
let mut output = [0_u8; 1];
let error = engine
.finish(&mut output, 0)
.expect_err("finish hook should fail after codec finish");
assert_eq!(
TranscodeEncodeError::domain_finish(EngineError::Rejected { input_index: 0 }),
error,
);
assert_eq!(
Err(TranscodeEncodeError::Failure(TranscodeFailure::LifecyclePoisoned,)),
engine.finish(&mut output, 0),
);
assert_eq!(
Err(TranscodeEncodeError::Failure(TranscodeFailure::LifecyclePoisoned,)),
engine.transcode(&[1_u8], 0, &mut output, 0),
);
engine
.reset(&mut [], 0)
.expect("successful reset should recover a poisoned engine");
let progress = engine
.transcode(&[1_u8], 0, &mut output, 0)
.expect("transcode should resume after successful reset");
assert_eq!(1, progress.read());
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverflowPlanningEncodeHooks;
impl TranscodeEncodeHooks<WideCodec> for OverflowPlanningEncodeHooks {
fn max_transcode_output_len(&self, _codec: &WideCodec, _input_len: usize) -> Result<usize, CapacityError> {
Err(CapacityError::OutputLengthOverflow)
}
fn handle_unencodable_encode(
&mut self,
_codec: &mut WideCodec,
context: &EncodeContext<'_, u8>,
) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<WideCodec>> {
Err(TranscodeEncodeError::unencodable(
context.input_index(),
*context.input_value(),
))
}
}
#[test]
fn test_buffered_encode_engine_reports_capacity_failures() {
type Engine = TranscodeEncodeEngine<WideCodec, ExactWidthHooks>;
let encoder = Engine::new(WideCodec, ExactWidthHooks);
assert_eq!(Ok(8), Engine::max_total_output_len(&encoder, 2));
let overflow_encoder =
TranscodeEncodeEngine::<WideCodec, OverflowPlanningEncodeHooks>::new(WideCodec, OverflowPlanningEncodeHooks);
assert_eq!(
Err(CapacityError::OutputLengthOverflow),
Transcoder::max_transcode_output_len(&overflow_encoder, 1),
);
}