use core::{
cell::Cell,
num::NonZeroUsize,
};
use std::rc::Rc;
use qubit_codec::engine::{
DecodeContext,
DecodeIncompleteAction,
DecodeInvalidAction,
TranscodeDecodeEngine,
TranscodeDecodeHooks,
};
use qubit_codec::{
CapacityError,
Codec,
TranscodeDecodeError,
TranscodeDecoder,
TranscodeFailure,
TranscodeStatus,
Transcoder,
};
fn non_zero_consumed(consumed: usize) -> NonZeroUsize {
NonZeroUsize::new(consumed)
.expect("decode policy must consume at least one source unit")
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct PrefixCodec;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum PrefixDecodeError {
Invalid { consumed: usize },
}
impl Codec for PrefixCodec {
type Value = u8;
type Unit = u8;
type DecodeError = PrefixDecodeError;
type EncodeError = core::convert::Infallible;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
const MAX_DECODE_UNITS_PER_VALUE: usize = 2;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<
(u8, core::num::NonZeroUsize),
qubit_codec::DecodeFailure<Self::DecodeError>,
> {
debug_assert!(input_index < input.len());
let first = unsafe { *input.as_ptr().add(input_index) };
match first {
0xfe if input.len() - input_index < 2 => {
Err(qubit_codec::DecodeFailure::incomplete_with_source(
PrefixDecodeError::Invalid { consumed: 1 },
crate::nz(2),
))
}
0xfe => {
let value = unsafe { *input.as_ptr().add(input_index + 1) };
Ok((value, crate::nz(2)))
}
0xff => Err(qubit_codec::DecodeFailure::invalid(
PrefixDecodeError::Invalid { consumed: 1 },
core::num::NonZeroUsize::MIN,
)),
value => Ok((value, core::num::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;
}
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct EofAwarePrefixCodec;
impl Codec for EofAwarePrefixCodec {
type Value = u8;
type Unit = u8;
type DecodeError = core::convert::Infallible;
type EncodeError = core::convert::Infallible;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
const MAX_DECODE_UNITS_PER_VALUE: usize = 2;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), qubit_codec::DecodeFailure<Self::DecodeError>>
{
if input[input_index] == 0xfe && input.len() - input_index == 1 {
return Err(qubit_codec::DecodeFailure::incomplete(crate::nz(2)));
}
Ok((input[input_index], NonZeroUsize::MIN))
}
unsafe fn decode_eof(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), qubit_codec::DecodeFailure<Self::DecodeError>>
{
if input[input_index] == 0xfe && input.len() - input_index == 1 {
return Ok((b'?', NonZeroUsize::MIN));
}
unsafe { self.decode(input, input_index) }
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
output[output_index] = *value;
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct EofAwareHooks;
impl TranscodeDecodeHooks<EofAwarePrefixCodec> for EofAwareHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut EofAwarePrefixCodec,
error: &core::convert::Infallible,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<EofAwarePrefixCodec>,
> {
match *error {}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct HintOnlyCodec;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum HintOnlyDecodeError {
Invalid,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct UnknownInvalidCodec;
impl Codec for UnknownInvalidCodec {
type Value = u8;
type Unit = u8;
type DecodeError = PrefixDecodeError;
type EncodeError = core::convert::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), qubit_codec::DecodeFailure<Self::DecodeError>>
{
Err(qubit_codec::DecodeFailure::invalid_unknown(
PrefixDecodeError::Invalid { consumed: 0 },
))
}
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 HintOnlyCodec {
type Value = u8;
type Unit = u8;
type DecodeError = HintOnlyDecodeError;
type EncodeError = core::convert::Infallible;
const MIN_UNITS_PER_VALUE: usize = 1;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
const MAX_DECODE_UNITS_PER_VALUE: usize = 2;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<(u8, NonZeroUsize), qubit_codec::DecodeFailure<Self::DecodeError>>
{
debug_assert!(input_index < input.len());
match input[input_index] {
0xaa => Err(qubit_codec::DecodeFailure::invalid(
HintOnlyDecodeError::Invalid,
crate::nz(2),
)),
value => 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());
output[output_index] = *value;
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverconsumingCodec;
impl Codec for OverconsumingCodec {
type Value = u8;
type Unit = u8;
type DecodeError = core::convert::Infallible;
type EncodeError = core::convert::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, core::num::NonZeroUsize),
qubit_codec::DecodeFailure<Self::DecodeError>,
> {
debug_assert!(input_index < input.len());
Ok((input[input_index], unsafe {
core::num::NonZeroUsize::new_unchecked(2)
}))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
debug_assert!(output_index < output.len());
output[output_index] = *value;
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverlongIncompleteCodec;
impl Codec for OverlongIncompleteCodec {
type Value = u8;
type Unit = u8;
type DecodeError = core::convert::Infallible;
type EncodeError = core::convert::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), qubit_codec::DecodeFailure<Self::DecodeError>>
{
Err(qubit_codec::DecodeFailure::incomplete(crate::nz(2)))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
output[output_index] = *value;
Ok(1)
}
}
#[derive(Debug, Eq, PartialEq)]
struct DropTrackedValue {
value: u8,
drops: Rc<Cell<usize>>,
}
impl DropTrackedValue {
fn new(value: u8, drops: Rc<Cell<usize>>) -> Self {
Self { value, drops }
}
}
impl Drop for DropTrackedValue {
fn drop(&mut self) {
self.drops.set(self.drops.get() + 1);
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct DropTrackedCodec {
drops: Rc<Cell<usize>>,
}
impl Codec for DropTrackedCodec {
type Value = DropTrackedValue;
type Unit = u8;
type DecodeError = core::convert::Infallible;
type EncodeError = core::convert::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<
(DropTrackedValue, core::num::NonZeroUsize),
qubit_codec::DecodeFailure<Self::DecodeError>,
> {
Ok((
DropTrackedValue::new(input[input_index], self.drops.clone()),
core::num::NonZeroUsize::MIN,
))
}
unsafe fn encode(
&mut self,
value: &DropTrackedValue,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
output[output_index] = value.value;
Ok(1)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct DropTrackedHooks;
impl TranscodeDecodeHooks<DropTrackedCodec> for DropTrackedHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut DropTrackedCodec,
error: &core::convert::Infallible,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<DropTrackedValue>,
qubit_codec::TranscodeDecodeErrorOf<DropTrackedCodec>,
> {
match *error {}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct ReplacingHooks;
impl TranscodeDecodeHooks<PrefixCodec> for ReplacingHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
match error {
PrefixDecodeError::Invalid { consumed } => {
Ok(DecodeInvalidAction::Emit {
value: 99,
consumed: non_zero_consumed(*consumed),
})
}
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct EofReplacingHooks;
impl TranscodeDecodeHooks<PrefixCodec> for EofReplacingHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
_error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
Ok(DecodeInvalidAction::Reject)
}
fn handle_incomplete_decode(
&mut self,
_codec: &mut PrefixCodec,
source: Option<&PrefixDecodeError>,
required_total: NonZeroUsize,
context: DecodeContext,
) -> Result<
DecodeIncompleteAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
assert_eq!(Some(&PrefixDecodeError::Invalid { consumed: 1 }), source,);
assert_eq!(crate::nz(2), required_total);
assert_eq!(1, context.available());
Ok(DecodeIncompleteAction::Emit { value: 99 })
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct OverconsumingHooks;
impl TranscodeDecodeHooks<OverconsumingCodec> for OverconsumingHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut OverconsumingCodec,
error: &core::convert::Infallible,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<OverconsumingCodec>,
> {
match *error {}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct OverlongIncompleteHooks;
impl TranscodeDecodeHooks<OverlongIncompleteCodec> for OverlongIncompleteHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut OverlongIncompleteCodec,
error: &core::convert::Infallible,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<OverlongIncompleteCodec>,
> {
match *error {}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct SkippingHooks;
impl TranscodeDecodeHooks<PrefixCodec> for SkippingHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
match error {
PrefixDecodeError::Invalid { consumed } => {
Ok(DecodeInvalidAction::Skip {
consumed: non_zero_consumed(*consumed),
})
}
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct HintOnlySkippingHooks;
impl TranscodeDecodeHooks<HintOnlyCodec> for HintOnlySkippingHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut HintOnlyCodec,
error: &HintOnlyDecodeError,
consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<HintOnlyCodec>,
> {
match error {
HintOnlyDecodeError::Invalid => Ok(DecodeInvalidAction::Skip {
consumed: consumed.expect("codec should report invalid width"),
}),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct FinishHooks {
pending_suffix: bool,
finish_bound: usize,
}
impl Default for FinishHooks {
fn default() -> Self {
Self {
pending_suffix: true,
finish_bound: 1,
}
}
}
impl TranscodeDecodeHooks<PrefixCodec> for FinishHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
match error {
PrefixDecodeError::Invalid { consumed } => {
Ok(DecodeInvalidAction::Skip {
consumed: non_zero_consumed(*consumed),
})
}
}
}
fn max_finish_output_len(&self, _codec: &PrefixCodec) -> usize {
self.finish_bound
}
fn finish_hooks(
&mut self,
_codec: &mut PrefixCodec,
output: &mut [u8],
output_index: usize,
) -> Result<usize, qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>> {
if !self.pending_suffix {
return Ok(0);
}
output[output_index] = 0xee;
self.pending_suffix = false;
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum InvalidDecodeInvalidActionKind {
Skip,
Emit,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct InvalidDecodeInvalidActionHooks {
kind: InvalidDecodeInvalidActionKind,
}
impl TranscodeDecodeHooks<PrefixCodec> for InvalidDecodeInvalidActionHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
_error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
match self.kind {
InvalidDecodeInvalidActionKind::Skip => {
Ok(DecodeInvalidAction::Skip {
consumed: non_zero_consumed(context.available() + 1),
})
}
InvalidDecodeInvalidActionKind::Emit => {
Ok(DecodeInvalidAction::Emit {
value: 77,
consumed: non_zero_consumed(context.available() + 1),
})
}
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverwritingFinishHooks;
impl TranscodeDecodeHooks<PrefixCodec> for OverwritingFinishHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
match error {
PrefixDecodeError::Invalid { consumed } => {
Ok(DecodeInvalidAction::Skip {
consumed: non_zero_consumed(*consumed),
})
}
}
}
fn max_finish_output_len(&self, _codec: &PrefixCodec) -> usize {
1
}
fn finish_hooks(
&mut self,
_codec: &mut PrefixCodec,
output: &mut [u8],
output_index: usize,
) -> Result<usize, qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>> {
output[output_index] = 0xee;
output[output_index + 1] = 0xdd;
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverreportingFinishHooks;
impl TranscodeDecodeHooks<PrefixCodec> for OverreportingFinishHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
match error {
PrefixDecodeError::Invalid { consumed } => {
Ok(DecodeInvalidAction::Skip {
consumed: non_zero_consumed(*consumed),
})
}
}
}
fn max_finish_output_len(&self, _codec: &PrefixCodec) -> usize {
1
}
fn finish_hooks(
&mut self,
_codec: &mut PrefixCodec,
output: &mut [u8],
output_index: usize,
) -> Result<usize, qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>> {
output[output_index] = 0xee;
Ok(2)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct MinTwoCodec;
impl Codec for MinTwoCodec {
type Value = u8;
type Unit = u8;
type DecodeError = PrefixDecodeError;
type EncodeError = core::convert::Infallible;
const MIN_UNITS_PER_VALUE: usize = 2;
const MAX_ENCODE_UNITS_PER_VALUE: usize = 2;
const MAX_DECODE_UNITS_PER_VALUE: usize = 2;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<
(u8, core::num::NonZeroUsize),
qubit_codec::DecodeFailure<Self::DecodeError>,
> {
debug_assert!(input_index + 1 < input.len());
Ok((
input[input_index].wrapping_add(input[input_index + 1]),
unsafe { core::num::NonZeroUsize::new_unchecked(2) },
))
}
unsafe fn encode(
&mut self,
value: &u8,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::EncodeError> {
debug_assert!(output_index < output.len());
output[output_index] = *value;
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverflowFlushCodec;
impl Codec for OverflowFlushCodec {
type Value = u8;
type Unit = u8;
type DecodeError = core::convert::Infallible;
type EncodeError = core::convert::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_DECODE_FINISH_VALUES: usize = usize::MAX;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<
(u8, core::num::NonZeroUsize),
qubit_codec::DecodeFailure<Self::DecodeError>,
> {
Ok((input[input_index], core::num::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)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverflowFinishHooks;
impl TranscodeDecodeHooks<OverflowFlushCodec> for OverflowFinishHooks {
fn max_finish_output_len(&self, _codec: &OverflowFlushCodec) -> usize {
1
}
fn handle_invalid_decode(
&mut self,
_codec: &mut OverflowFlushCodec,
error: &core::convert::Infallible,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<OverflowFlushCodec>,
> {
match *error {}
}
}
impl TranscodeDecodeHooks<MinTwoCodec> for ReplacingHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut MinTwoCodec,
error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<MinTwoCodec>,
> {
match error {
PrefixDecodeError::Invalid { consumed } => {
Ok(DecodeInvalidAction::Emit {
value: 99,
consumed: non_zero_consumed(*consumed),
})
}
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct EofSkippingHooks;
impl TranscodeDecodeHooks<MinTwoCodec> for EofSkippingHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut MinTwoCodec,
_error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<MinTwoCodec>,
> {
Ok(DecodeInvalidAction::Reject)
}
fn handle_incomplete_decode(
&mut self,
_codec: &mut MinTwoCodec,
source: Option<&PrefixDecodeError>,
required_total: NonZeroUsize,
context: DecodeContext,
) -> Result<
DecodeIncompleteAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<MinTwoCodec>,
> {
assert_eq!(None, source);
assert_eq!(crate::nz(2), required_total);
assert_eq!(1, context.available());
Ok(DecodeIncompleteAction::Skip)
}
}
#[test]
fn test_transcode_decode_engine_exposes_codec_hooks_and_parts() {
let mut engine = TranscodeDecodeEngine::new(PrefixCodec, ReplacingHooks);
assert_eq!(&PrefixCodec, engine.codec());
assert_eq!(&ReplacingHooks, engine.hooks());
*engine.codec_mut() = PrefixCodec;
*engine.hooks_mut() = ReplacingHooks;
let (codec, hooks) = engine.into_parts();
assert_eq!(PrefixCodec, codec);
assert_eq!(ReplacingHooks, hooks);
}
#[test]
fn test_transcode_decode_engine_reports_finish_bound_overflow() {
let mut decoder = TranscodeDecodeEngine::<_, _>::new(
OverflowFlushCodec,
OverflowFinishHooks,
);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
assert_eq!(
Err(CapacityError::OutputLengthOverflow),
decoder.max_finish_output_len(),
);
assert_eq!(
Err(CapacityError::OutputLengthOverflow),
decoder.max_total_output_len(0),
);
let error = decoder
.finish(&mut output, 0)
.expect_err("finish should report capacity overflow before writing");
assert_eq!(
qubit_codec::TranscodeDecodeError::Failure(
qubit_codec::TranscodeFailure::output_length_overflow()
),
error
);
}
#[test]
fn test_transcode_decode_engine_drops_replaced_output_value() {
let drops = Rc::new(Cell::new(0));
let codec = DropTrackedCodec {
drops: drops.clone(),
};
let mut decoder = TranscodeDecodeEngine::new(codec, DropTrackedHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [DropTrackedValue::new(99, drops.clone())];
let progress = decoder
.transcode(&[7], 0, &mut output, 0)
.expect("tracked value should decode into output");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(1, progress.read());
assert_eq!(1, progress.written());
assert_eq!(7, output[0].value);
assert_eq!(
1,
drops.get(),
"decoded output should replace and drop the previous initialized value",
);
drop(output);
assert_eq!(2, drops.get());
}
#[test]
fn test_transcode_decode_engine_reports_finish_bounds() {
type Decoder = TranscodeDecodeEngine<PrefixCodec, ReplacingHooks>;
type TranscodeCompleteIntoFn =
fn(
&mut Decoder,
&[u8],
&mut [u8],
) -> Result<usize, TranscodeDecodeError<PrefixDecodeError>>;
let mut decoder =
TranscodeDecodeEngine::<_, _>::new(PrefixCodec, ReplacingHooks);
let max_total_output_len: fn(
&Decoder,
usize,
) -> Result<usize, CapacityError> = Decoder::max_total_output_len;
let transcode_complete_into: TranscodeCompleteIntoFn =
Decoder::transcode_complete_into;
let mut output = [0_u8; 1];
assert_eq!(Ok(3), decoder.max_transcode_output_len(3));
assert_eq!(Ok(3), max_total_output_len(&decoder, 3));
assert_eq!(Ok(0), decoder.max_finish_output_len());
let mut all_output = [0_u8; 3];
let written =
transcode_complete_into(&mut decoder, &[1, 2, 3], &mut all_output)
.expect("complete decode should fit the planned output");
assert_eq!(3, written);
assert_eq!(&[1, 2, 3], &all_output[..written]);
decoder.reset(&mut [], 0).expect("reset");
let written = decoder
.finish(&mut output, 0)
.expect("generic decoder finish is a no-op");
assert_eq!(0, written);
}
#[test]
fn test_transcode_decode_engine_delegates_finish_to_hooks() {
let mut decoder =
TranscodeDecodeEngine::<_, _>::new(PrefixCodec, FinishHooks::default());
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
assert_eq!(Ok(1), decoder.max_finish_output_len());
let error = decoder.finish(&mut [], 0).expect_err(
"finish should reject insufficient output before calling hooks",
);
assert_eq!(
qubit_codec::TranscodeDecodeError::Failure(
qubit_codec::TranscodeFailure::insufficient_output(0, 1, 0)
),
error,
);
assert_eq!(Ok(1), decoder.max_finish_output_len());
let written = decoder
.finish(&mut output, 0)
.expect("hook should write final output");
assert_eq!(1, written);
assert_eq!([0xee], output);
assert_eq!(Ok(1), decoder.max_finish_output_len());
}
#[test]
fn test_transcode_decode_engine_finish_passes_full_output_to_hooks() {
let mut decoder =
TranscodeDecodeEngine::<_, _>::new(PrefixCodec, OverwritingFinishHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 2];
let written = decoder
.finish(&mut output, 0)
.expect("hook should receive the caller-provided output slice");
assert_eq!(1, written);
assert_eq!([0xee, 0xdd], output);
}
#[test]
#[should_panic(
expected = "TranscodeDecodeEngine hook wrote beyond its finish bound"
)]
fn test_transcode_decode_engine_finish_panics_when_hook_overreports_bound() {
let mut decoder = TranscodeDecodeEngine::<_, _>::new(
PrefixCodec,
OverreportingFinishHooks,
);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 2];
let _ = decoder.finish(&mut output, 0);
}
#[test]
fn test_transcode_decode_engine_finish_reports_output_index_beyond_buffer() {
let mut decoder =
TranscodeDecodeEngine::<_, _>::new(PrefixCodec, FinishHooks::default());
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [];
let error = decoder
.finish(&mut output, 1)
.expect_err("out-of-range finish output index should be rejected");
assert_eq!(
qubit_codec::TranscodeDecodeError::Failure(
qubit_codec::TranscodeFailure::invalid_output_index(1, 0)
),
error,
);
}
#[test]
fn test_transcode_decode_engine_default_finish_reports_output_index_beyond_buffer()
{
let mut decoder =
TranscodeDecodeEngine::<_, _>::new(PrefixCodec, ReplacingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [];
let error = decoder
.finish(&mut output, 1)
.expect_err("default finish should reject out-of-range output index");
assert_eq!(
qubit_codec::TranscodeDecodeError::Failure(
qubit_codec::TranscodeFailure::invalid_output_index(1, 0)
),
error,
);
}
#[test]
fn test_buffered_decode_hooks_default_finish_is_noop() {
let mut hooks = ReplacingHooks;
let mut output = [];
let written = TranscodeDecodeHooks::<PrefixCodec>::finish_hooks(
&mut hooks,
&mut PrefixCodec,
&mut output,
1,
)
.expect("default hook finish should be a no-op");
assert_eq!(0, written);
}
#[test]
fn test_transcode_decode_engine_leaves_incomplete_input_to_caller() {
let mut decoder = TranscodeDecodeEngine::new(PrefixCodec, ReplacingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let progress = decoder
.transcode(&[0xfe], 0, &mut output, 0)
.expect("incomplete prefix should be reported");
assert_eq!(
TranscodeStatus::NeedInput {
required: crate::nz(2),
},
progress.status(),
);
assert_eq!(0, progress.read());
assert_eq!(0, progress.written());
let progress = decoder
.transcode(&[0xfe, 7], 0, &mut output, 0)
.expect("caller-refilled prefix should decode");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(2, progress.read());
assert_eq!(1, progress.written());
assert_eq!([7], output);
}
#[test]
fn test_transcode_decode_engine_preserves_incomplete_source_at_eof() {
let mut decoder = TranscodeDecodeEngine::new(PrefixCodec, ReplacingHooks);
decoder.reset(&mut [], 0).expect("initialize stream");
let mut output = [0_u8; 1];
let error = decoder
.transcode_eof(&[0xfe], 0, &mut output, 0)
.expect_err("EOF must expose the codec incomplete source");
assert_eq!(
TranscodeDecodeError::domain_main(
PrefixDecodeError::Invalid { consumed: 1 },
0,
),
error,
);
}
#[test]
fn test_transcode_decode_engine_applies_emit_policy_to_incomplete_eof() {
let mut decoder =
TranscodeDecodeEngine::new(PrefixCodec, EofReplacingHooks);
decoder.reset(&mut [], 0).expect("initialize stream");
let mut output = [0_u8; 1];
let progress = decoder
.transcode_eof(&[0xfe], 0, &mut output, 0)
.expect("EOF incomplete policy should emit replacement");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(1, progress.read());
assert_eq!(1, progress.written());
assert_eq!([99], output);
}
#[test]
fn test_transcode_decode_engine_applies_skip_policy_to_short_eof_tail() {
let mut decoder = TranscodeDecodeEngine::new(MinTwoCodec, EofSkippingHooks);
decoder.reset(&mut [], 0).expect("initialize stream");
let mut output = [];
let progress = decoder
.transcode_eof(&[7], 0, &mut output, 0)
.expect("EOF short-tail policy should skip input");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(1, progress.read());
assert_eq!(0, progress.written());
}
#[test]
fn test_transcode_decode_engine_rejects_short_eof_tail_by_default() {
let mut decoder = TranscodeDecodeEngine::new(MinTwoCodec, ReplacingHooks);
decoder.reset(&mut [], 0).expect("initialize stream");
let mut output = [];
let error = decoder
.transcode_eof(&[7], 0, &mut output, 0)
.expect_err("default incomplete policy should reject the EOF tail");
assert_eq!(
TranscodeDecodeError::Failure(TranscodeFailure::incomplete_input(
0, 2, 1
)),
error,
);
}
#[test]
fn test_transcode_decode_engine_keeps_incomplete_eof_tail_when_emit_needs_output()
{
let mut decoder =
TranscodeDecodeEngine::new(PrefixCodec, EofReplacingHooks);
decoder.reset(&mut [], 0).expect("initialize stream");
let mut output = [];
let progress = decoder
.transcode_eof(&[0xfe], 0, &mut output, 0)
.expect("replacement should wait for output capacity");
assert_eq!(
TranscodeStatus::NeedOutput {
required: NonZeroUsize::MIN,
},
progress.status(),
);
assert_eq!(0, progress.read());
assert_eq!(0, progress.written());
}
#[test]
fn test_transcode_decode_engine_uses_codec_eof_decode_path() {
let mut decoder =
TranscodeDecodeEngine::new(EofAwarePrefixCodec, EofAwareHooks);
decoder
.reset(&mut [], 0)
.expect("reset before EOF-aware decode");
let mut output = [0_u8; 1];
let progress = decoder
.transcode_eof(&[0xfe], 0, &mut output, 0)
.expect("EOF-aware decode should resolve the trailing prefix");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(1, progress.read());
assert_eq!(1, progress.written());
assert_eq!([b'?'], output);
}
#[test]
fn test_transcode_decode_engine_reports_short_minimum_input_without_consuming_tail()
{
let mut decoder = TranscodeDecodeEngine::new(MinTwoCodec, ReplacingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let progress = decoder
.transcode(&[7], 0, &mut output, 0)
.expect("short input should request another unit");
assert_eq!(
TranscodeStatus::NeedInput {
required: crate::nz(2),
},
progress.status(),
);
assert_eq!(0, progress.read());
assert_eq!(0, progress.written());
}
#[test]
fn test_transcode_decode_engine_reports_incomplete_input_before_missing_output()
{
let mut decoder = TranscodeDecodeEngine::new(MinTwoCodec, ReplacingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [];
let progress = decoder.transcode(&[7], 0, &mut output, 0).expect(
"short input should request another unit before output capacity",
);
assert_eq!(
TranscodeStatus::NeedInput {
required: crate::nz(2),
},
progress.status(),
);
assert_eq!(0, progress.read());
assert_eq!(0, progress.written());
}
#[test]
fn test_transcode_decode_engine_allows_policy_emit_for_invalid_input() {
let mut decoder = TranscodeDecodeEngine::new(PrefixCodec, ReplacingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 2];
let progress = decoder
.transcode(&[0xff, 1], 0, &mut output, 0)
.expect("invalid input should be replaced");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(2, progress.read());
assert_eq!(2, progress.written());
assert_eq!([99, 1], output);
}
#[test]
fn test_transcode_decode_engine_reports_need_output_before_policy_emit() {
let mut decoder = TranscodeDecodeEngine::new(PrefixCodec, ReplacingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [];
let progress = decoder
.transcode(&[0xff], 0, &mut output, 0)
.expect("replacement policy should stop before writing without output");
assert_eq!(
TranscodeStatus::NeedOutput {
required: crate::nz(1),
},
progress.status(),
);
assert_eq!(0, progress.read());
assert_eq!(0, progress.written());
}
#[test]
fn test_transcode_decode_engine_allows_policy_skip_for_invalid_input() {
let mut decoder = TranscodeDecodeEngine::new(PrefixCodec, SkippingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let progress = decoder
.transcode(&[0xff, 1], 0, &mut output, 0)
.expect("invalid input should be skipped");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(2, progress.read());
assert_eq!(1, progress.written());
assert_eq!([1], output);
}
#[test]
fn test_transcode_decode_engine_passes_invalid_consumed_hint_to_hooks() {
let mut decoder =
TranscodeDecodeEngine::new(HintOnlyCodec, HintOnlySkippingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let progress = decoder
.transcode(&[0xaa, 0xbb, 1], 0, &mut output, 0)
.expect("policy should skip invalid width from failure hint");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(3, progress.read());
assert_eq!(1, progress.written());
assert_eq!([1], output);
}
#[test]
#[should_panic(
expected = "DecodeInvalidAction consumed units must not exceed available input"
)]
fn test_transcode_decode_engine_panics_on_invalid_skip_action() {
let mut decoder = TranscodeDecodeEngine::new(
PrefixCodec,
InvalidDecodeInvalidActionHooks {
kind: InvalidDecodeInvalidActionKind::Skip,
},
);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let _ = decoder.transcode(&[0xff], 0, &mut output, 0);
}
#[test]
#[should_panic(
expected = "DecodeInvalidAction consumed units must not exceed available input"
)]
fn test_transcode_decode_engine_panics_on_invalid_emit_action() {
let mut decoder = TranscodeDecodeEngine::new(
PrefixCodec,
InvalidDecodeInvalidActionHooks {
kind: InvalidDecodeInvalidActionKind::Emit,
},
);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let _ = decoder.transcode(&[0xff], 0, &mut output, 0);
}
#[test]
fn test_transcode_decode_engine_reports_output_bounds_without_consuming_input()
{
let mut decoder = TranscodeDecodeEngine::new(PrefixCodec, ReplacingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [];
let progress = decoder
.transcode(&[1], 0, &mut output, 0)
.expect("empty output should request capacity");
assert_eq!(
TranscodeStatus::NeedOutput {
required: crate::nz(1),
},
progress.status(),
);
assert_eq!(0, progress.read());
assert_eq!(0, progress.written());
let error = decoder
.transcode(&[1], 0, &mut output, 1)
.expect_err("out-of-range output index should fail");
assert_eq!(
qubit_codec::TranscodeDecodeError::Failure(
qubit_codec::TranscodeFailure::invalid_output_index(1, 0)
),
error,
);
}
#[test]
#[should_panic(expected = "Codec::decode consumed beyond available input")]
fn test_transcode_decode_engine_panics_when_codec_consumes_beyond_available_input()
{
let mut decoder =
TranscodeDecodeEngine::new(OverconsumingCodec, OverconsumingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let _ = decoder.transcode(&[1], 0, &mut output, 0);
}
#[test]
#[should_panic(
expected = "Codec::decode consumed beyond Codec::MAX_DECODE_UNITS_PER_VALUE"
)]
fn test_transcode_decode_engine_panics_when_codec_consumes_beyond_decode_maximum()
{
let mut decoder =
TranscodeDecodeEngine::new(OverconsumingCodec, OverconsumingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let _ = decoder.transcode(&[1, 2], 0, &mut output, 0);
}
#[test]
#[should_panic(
expected = "Codec::decode incomplete required_total exceeded Codec::MAX_DECODE_UNITS_PER_VALUE"
)]
fn test_transcode_decode_engine_panics_when_incomplete_hint_exceeds_codec_maximum()
{
let mut decoder = TranscodeDecodeEngine::new(
OverlongIncompleteCodec,
OverlongIncompleteHooks,
);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let _ = decoder.transcode(&[1], 0, &mut output, 0);
}
#[test]
fn test_transcode_decode_engine_uses_hooks_for_invalid_input_index() {
let mut decoder = TranscodeDecodeEngine::new(PrefixCodec, ReplacingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [];
let error = decoder
.transcode(&[1], 2, &mut output, 0)
.expect_err("invalid input index should be rejected");
assert_eq!(
qubit_codec::TranscodeDecodeError::Failure(
qubit_codec::TranscodeFailure::invalid_input_index(2, 1)
),
error,
);
}
#[test]
fn test_transcode_decode_engine_implements_buffered_transcoder() {
type Decoder = TranscodeDecodeEngine<PrefixCodec, ReplacingHooks>;
fn assert_decoder<T: TranscodeDecoder<Input = u8, Output = u8>>() {}
assert_decoder::<Decoder>();
let mut decoder = Decoder::new(PrefixCodec, ReplacingHooks);
decoder
.reset(&mut [], 0)
.expect("reset before trait transcode");
let available = <Decoder as Transcoder>::max_transcode_output_len(
&decoder, 1,
)
.expect("max_transcode_output_len should be callable through trait");
assert_eq!(1, available);
let mut output = [0_u8; 1];
let progress = <Decoder as Transcoder>::transcode(
&mut decoder,
&[0xfe, 7],
0,
&mut output,
0,
)
.expect("trait transcode should decode a prefixed value");
assert_eq!(TranscodeStatus::Complete, progress.status());
assert_eq!(2, progress.read());
assert_eq!(1, progress.written());
let finish = Transcoder::finish(&mut decoder, &mut output, 0)
.expect("trait finish should delegate to hooks");
assert_eq!(0, finish);
let finish_output_len =
<Decoder as Transcoder>::max_finish_output_len(&decoder)
.expect("max_finish_output_len should be callable through trait");
assert_eq!(0, finish_output_len);
assert_eq!(7, output[0]);
Transcoder::reset(&mut decoder, &mut output, 0).expect("reset");
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct FlushFailCodec;
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
#[error("flush failed")]
struct FlushFailError;
impl Codec for FlushFailCodec {
type Value = u8;
type Unit = u8;
type DecodeError = FlushFailError;
type EncodeError = core::convert::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_DECODE_FINISH_VALUES: usize = 1;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<
(u8, core::num::NonZeroUsize),
qubit_codec::DecodeFailure<Self::DecodeError>,
> {
Ok((input[input_index], core::num::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 decode_finish(
&mut self,
_output: &mut [u8],
_output_index: usize,
) -> Result<usize, Self::DecodeError> {
Err(FlushFailError)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct FlushMappingHooks;
impl TranscodeDecodeHooks<FlushFailCodec> for FlushMappingHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut FlushFailCodec,
error: &FlushFailError,
_consumed: Option<NonZeroUsize>,
context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<FlushFailCodec>,
> {
Err(TranscodeDecodeError::domain_main(
*error,
context.input_index(),
))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct ResetObservingHooks {
called: std::rc::Rc<Cell<bool>>,
}
impl TranscodeDecodeHooks<PrefixCodec> for ResetObservingHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
Err(TranscodeDecodeError::domain_main(
*error,
context.input_index(),
))
}
fn reset_hooks(&mut self, _codec: &mut PrefixCodec) {
self.called.set(true);
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ResetFailCodec {
fail_reset: bool,
}
impl Default for ResetFailCodec {
fn default() -> Self {
Self { fail_reset: true }
}
}
impl Codec for ResetFailCodec {
type Value = u8;
type Unit = u8;
type DecodeError = PrefixDecodeError;
type EncodeError = core::convert::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_DECODE_RESET_VALUES: usize = 1;
unsafe fn decode(
&mut self,
_input: &[u8],
_input_index: usize,
) -> Result<
(u8, core::num::NonZeroUsize),
qubit_codec::DecodeFailure<Self::DecodeError>,
> {
Ok((0u8, core::num::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.get_unchecked_mut(output_index) = 0;
}
Ok(1)
}
unsafe fn decode_reset(
&mut self,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::DecodeError> {
if self.fail_reset {
return Err(PrefixDecodeError::Invalid { consumed: 1 });
}
output[output_index] = 0xaa;
Ok(1)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct ResetErrorMappingHooks;
impl TranscodeDecodeHooks<ResetFailCodec> for ResetErrorMappingHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut ResetFailCodec,
error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<ResetFailCodec>,
> {
Err(TranscodeDecodeError::domain_main(
*error,
context.input_index(),
))
}
}
#[test]
fn test_transcode_decode_engine_reports_max_reset_output_len() {
let decoder = TranscodeDecodeEngine::<PrefixCodec, ReplacingHooks>::new(
PrefixCodec,
ReplacingHooks,
);
assert_eq!(Ok(0), decoder.max_reset_output_len());
assert_eq!(Ok(0), Transcoder::max_reset_output_len(&decoder));
}
#[test]
fn test_transcode_decode_engine_reset_rejects_invalid_output_index() {
let mut decoder = TranscodeDecodeEngine::<PrefixCodec, ReplacingHooks>::new(
PrefixCodec,
ReplacingHooks,
);
let error = decoder
.reset(&mut [], 1)
.expect_err("reset should reject invalid output index");
assert_eq!(
qubit_codec::TranscodeDecodeError::Failure(
qubit_codec::TranscodeFailure::invalid_output_index(1, 0)
),
error,
);
}
#[test]
fn test_transcode_decode_engine_reset_calls_hook_before_reset() {
let called = std::rc::Rc::new(Cell::new(false));
let hooks = ResetObservingHooks {
called: called.clone(),
};
let mut decoder = TranscodeDecodeEngine::<_, _>::new(PrefixCodec, hooks);
decoder.reset(&mut [], 0).expect("reset should succeed");
assert!(called.get(), "decode hook before_reset should be called");
}
#[test]
fn test_transcode_decode_engine_finish_converts_decode_finish_errors() {
let mut decoder =
TranscodeDecodeEngine::<_, _>::new(FlushFailCodec, FlushMappingHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let error = decoder.finish(&mut output, 0).expect_err(
"flush errors should be converted through the hook error type",
);
assert_eq!(TranscodeDecodeError::domain_finish(FlushFailError), error,);
}
#[test]
fn test_transcode_decode_engine_reset_converts_decode_reset_errors() {
let mut decoder = TranscodeDecodeEngine::<_, _>::new(
ResetFailCodec::default(),
ResetErrorMappingHooks,
);
let mut output = [0_u8; 1];
let error = decoder.reset(&mut output, 0).expect_err(
"decode reset errors should be converted through the hook error type",
);
assert_eq!(
TranscodeDecodeError::domain_reset(PrefixDecodeError::Invalid {
consumed: 1
}),
error,
);
}
#[test]
fn test_transcode_decode_engine_configurable_reset_supports_success_and_capacity_errors()
{
let mut decoder = TranscodeDecodeEngine::<_, _>::new(
ResetFailCodec { fail_reset: false },
ResetErrorMappingHooks,
);
let mut output = [0_u8; 1];
assert_eq!(
1,
decoder.reset(&mut output, 0).expect("reset should succeed")
);
assert_eq!([0xaa], output);
let error = decoder
.reset(&mut [], 0)
.expect_err("reset should enforce its declared output bound");
assert_eq!(
qubit_codec::TranscodeDecodeError::Failure(
qubit_codec::TranscodeFailure::insufficient_output(0, 1, 0)
),
error
);
}
fn new_stateless_finish_engine()
-> TranscodeDecodeEngine<PrefixCodec, FinishHooks> {
TranscodeDecodeEngine::<_, _>::new(
PrefixCodec,
FinishHooks {
pending_suffix: false,
finish_bound: 0,
},
)
}
#[test]
fn test_transcode_decode_engine_lifecycle_rejects_double_finish() {
let mut engine = new_stateless_finish_engine();
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 on a stateless decoder");
assert_eq!(
Err(TranscodeDecodeError::Failure(
TranscodeFailure::FinishAfterFinish,
)),
engine.finish(&mut output, 0),
);
}
#[test]
fn test_transcode_decode_engine_lifecycle_rejects_transcode_after_finish() {
let mut engine = new_stateless_finish_engine();
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("finish closes the logical stream");
let mut grown = [0_u8; 1];
assert_eq!(
Err(TranscodeDecodeError::Failure(
TranscodeFailure::TranscodeAfterFinish,
)),
engine.transcode(&[0x10], 0, &mut grown, 0),
);
}
#[test]
fn test_transcode_decode_engine_lifecycle_allows_finish_without_transcode() {
let mut engine = new_stateless_finish_engine();
let mut reset_output = [];
engine
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 0];
let written = engine
.finish(&mut output, 0)
.expect("fresh stateless engine may finalize an empty stream");
assert_eq!(0, written);
}
#[test]
fn test_transcode_decode_engine_lifecycle_allows_finish_retry_after_capacity_failure()
{
let mut engine =
TranscodeDecodeEngine::<_, _>::new(PrefixCodec, FinishHooks::default());
let mut reset_output = [];
engine
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut tiny = [0_u8; 0];
let _ = engine
.finish(&mut tiny, 0)
.expect_err("finish should reject insufficient output");
let mut output = [0_u8; 1];
let written = engine
.finish(&mut output, 0)
.expect("retry after capacity failure must succeed");
assert_eq!(1, written);
}
#[test]
fn test_transcode_decode_engine_lifecycle_allows_reuse_after_reset() {
let mut engine = new_stateless_finish_engine();
let mut reset_output = [];
engine
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut buf = [0_u8; 4];
engine
.finish(&mut buf, 0)
.expect("close the first logical stream");
engine
.reset(&mut buf, 0)
.expect("reset must reopen the engine for a new logical stream");
let progress = engine
.transcode(&[0x10, 0x20], 0, &mut buf, 0)
.expect("transcode should resume after reset");
assert_eq!(2, progress.read());
engine
.finish(&mut buf, 2)
.expect("finish must close the second logical stream");
}
#[test]
fn test_transcode_decode_engine_lifecycle_allows_multiple_resets() {
let mut engine = new_stateless_finish_engine();
let mut buf = [0_u8; 0];
for _ in 0..3 {
engine
.reset(&mut buf, 0)
.expect("reset must always be legal");
}
}
#[test]
fn test_transcode_decode_engine_failed_reset_preserves_finished_state() {
let mut engine = TranscodeDecodeEngine::<_, _>::new(
ResetEmittingDecodeCodec,
ResetEmittingDecodePassthroughHooks,
);
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!(
qubit_codec::TranscodeDecodeError::Failure(
qubit_codec::TranscodeFailure::insufficient_output(0, 1, 0)
),
error
);
assert_eq!(
Err(TranscodeDecodeError::Failure(
TranscodeFailure::TranscodeAfterFinish,
)),
engine.transcode(&[1_u8], 0, &mut output, 0),
);
}
#[test]
fn test_transcode_decode_engine_failed_finish_poisoned_until_reset() {
let mut engine =
TranscodeDecodeEngine::<_, _>::new(FlushFailCodec, FlushMappingHooks);
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("codec finish should fail");
assert_eq!(TranscodeDecodeError::domain_finish(FlushFailError), error);
assert_eq!(
Err(TranscodeDecodeError::Failure(
TranscodeFailure::LifecyclePoisoned,
)),
engine.finish(&mut output, 0),
);
assert_eq!(
Err(TranscodeDecodeError::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());
}
#[test]
fn test_transcode_decode_engine_failed_reset_poisoned_until_successful_reset() {
let mut engine = TranscodeDecodeEngine::<_, _>::new(
ResetFailCodec::default(),
ResetErrorMappingHooks,
);
let mut output = [0_u8; 1];
engine
.reset(&mut output, 0)
.expect_err("codec reset should fail after reset execution starts");
assert_eq!(
Err(TranscodeDecodeError::Failure(
TranscodeFailure::LifecyclePoisoned,
)),
engine.transcode(&[1_u8], 0, &mut output, 0),
);
engine.codec_mut().fail_reset = false;
engine
.reset(&mut output, 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 InvalidHookErrorHooks;
impl TranscodeDecodeHooks<PrefixCodec> for InvalidHookErrorHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
Err(TranscodeDecodeError::domain_main(
*error,
context.input_index(),
))
}
}
#[test]
fn test_transcode_decode_engine_propagates_invalid_decode_hook_errors() {
let mut decoder =
TranscodeDecodeEngine::new(PrefixCodec, InvalidHookErrorHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let mut output = [0_u8; 1];
let error = decoder
.transcode(&[0xff, 1], 0, &mut output, 0)
.expect_err("invalid decode hook errors should propagate");
assert_eq!(
TranscodeDecodeError::domain_main(
PrefixDecodeError::Invalid { consumed: 1 },
0
),
error,
);
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct OverflowPlanningDecodeHooks;
impl TranscodeDecodeHooks<PrefixCodec> for OverflowPlanningDecodeHooks {
fn max_transcode_output_len(
&self,
_codec: &PrefixCodec,
_input_len: usize,
) -> Result<usize, CapacityError> {
Err(CapacityError::OutputLengthOverflow)
}
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
Err(TranscodeDecodeError::domain_main(
*error,
context.input_index(),
))
}
}
#[test]
fn test_transcode_decode_engine_reports_capacity_failures() {
type Decoder = TranscodeDecodeEngine<PrefixCodec, ReplacingHooks>;
let decoder = Decoder::new(PrefixCodec, ReplacingHooks);
assert_eq!(
Ok(3),
TranscodeDecodeEngine::<PrefixCodec, ReplacingHooks>::max_total_output_len(&decoder, 3,),
);
let overflow_decoder = TranscodeDecodeEngine::<
PrefixCodec,
OverflowPlanningDecodeHooks,
>::new(PrefixCodec, OverflowPlanningDecodeHooks);
assert_eq!(
Err(CapacityError::OutputLengthOverflow),
Transcoder::max_transcode_output_len(&overflow_decoder, 1),
);
assert_eq!(
Err(CapacityError::OutputLengthOverflow),
overflow_decoder.max_total_output_len(1),
);
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct RejectingDecodeHooks;
impl TranscodeDecodeHooks<PrefixCodec> for RejectingDecodeHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut PrefixCodec,
_error: &PrefixDecodeError,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<PrefixCodec>,
> {
Ok(DecodeInvalidAction::Reject)
}
}
impl TranscodeDecodeHooks<UnknownInvalidCodec> for RejectingDecodeHooks {
fn handle_invalid_decode(
&mut self,
_codec: &mut UnknownInvalidCodec,
_error: &PrefixDecodeError,
consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<UnknownInvalidCodec>,
> {
assert_eq!(None, consumed);
Ok(DecodeInvalidAction::Reject)
}
}
#[test]
fn test_transcode_decode_engine_rejects_invalid_input_via_hooks() {
let mut decoder =
TranscodeDecodeEngine::new(PrefixCodec, RejectingDecodeHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let input = [0xff_u8];
let mut output = [0_u8; 1];
let error = decoder
.transcode(&input, 0, &mut output, 0)
.expect_err("reject policy should surface the invalid input");
assert_eq!(
TranscodeDecodeError::domain_main_with_consumed(
PrefixDecodeError::Invalid { consumed: 1 },
0,
Some(crate::nz(1)),
),
error,
);
}
#[test]
fn test_transcode_decode_engine_rejects_invalid_unknown_input_via_hooks() {
let mut decoder =
TranscodeDecodeEngine::new(UnknownInvalidCodec, RejectingDecodeHooks);
let mut reset_output = [];
decoder
.reset(&mut reset_output, 0)
.expect("initialize stream");
let input = [0xff_u8];
let mut output = [0_u8; 1];
let error = decoder
.transcode(&input, 0, &mut output, 0)
.expect_err("reject policy should surface invalid unknown input");
assert_eq!(
TranscodeDecodeError::domain_main(
PrefixDecodeError::Invalid { consumed: 0 },
0
),
error,
);
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct ResetEmittingDecodeCodec;
impl Codec for ResetEmittingDecodeCodec {
type Value = u8;
type Unit = u8;
type DecodeError = core::convert::Infallible;
type EncodeError = core::convert::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_DECODE_RESET_VALUES: usize = 1;
unsafe fn decode(
&mut self,
input: &[u8],
input_index: usize,
) -> Result<
(u8, core::num::NonZeroUsize),
qubit_codec::DecodeFailure<Self::DecodeError>,
> {
Ok((input[input_index], core::num::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 decode_reset(
&mut self,
output: &mut [u8],
output_index: usize,
) -> Result<usize, Self::DecodeError> {
output[output_index] = 0xaa;
Ok(1)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct ResetEmittingDecodePassthroughHooks;
impl TranscodeDecodeHooks<ResetEmittingDecodeCodec>
for ResetEmittingDecodePassthroughHooks
{
fn handle_invalid_decode(
&mut self,
_codec: &mut ResetEmittingDecodeCodec,
error: &core::convert::Infallible,
_consumed: Option<NonZeroUsize>,
_context: DecodeContext,
) -> Result<
DecodeInvalidAction<u8>,
qubit_codec::TranscodeDecodeErrorOf<ResetEmittingDecodeCodec>,
> {
match *error {}
}
}
#[test]
fn test_transcode_decode_engine_max_total_output_len_reports_sum_overflow() {
let decoder = TranscodeDecodeEngine::<
ResetEmittingDecodeCodec,
ResetEmittingDecodePassthroughHooks,
>::new(
ResetEmittingDecodeCodec,
ResetEmittingDecodePassthroughHooks,
);
assert_eq!(
Err(CapacityError::OutputLengthOverflow),
decoder.max_total_output_len(usize::MAX),
);
}