#[cfg(feature = "unstable-v2")]
use fastpfor::FastPFor128;
use fastpfor::{AnyLenCodec as _, FastPFor256};
use usize_cast::IntoUsize as _;
use crate::decoder::FastPForKind;
use crate::{Decoder, MltError, MltResult};
pub fn decode_fastpfor(
data: &[u8],
num_values: u32,
kind: FastPForKind,
dec: &mut Decoder,
) -> MltResult<Vec<u32>> {
if num_values == 0 {
return if data.is_empty() {
Ok(vec![])
} else {
Err(MltError::InvalidFastPforByteLength(0))
};
}
let (words, rest) = data.as_chunks::<4>();
if !rest.is_empty() {
return Err(MltError::InvalidFastPforByteLength(data.len()));
}
dec.consume_items::<u32>(words.len())?;
let mut result = Vec::new();
match kind {
FastPForKind::Block256Be => {
let input: Vec<u32> = words.iter().copied().map(u32::from_be_bytes).collect();
FastPFor256::default().decode(&input, &mut result, Some(num_values))?;
}
#[cfg(feature = "unstable-v2")]
FastPForKind::Block128Le => {
let input: Vec<u32> = words.iter().copied().map(u32::from_le_bytes).collect();
FastPFor128::default().decode(&input, &mut result, Some(num_values))?;
}
}
let adjustment = result
.len()
.checked_sub(num_values.into_usize())
.and_then(|v| u32::try_from(v).ok())
.expect("infallible: the codec rejects a short decode, so the excess is block padding");
dec.adjust(adjustment);
result.truncate(num_values.into_usize());
Ok(result)
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use rstest::rstest;
use super::*;
use crate::test_helpers::{dec, starved_dec};
fn encode(kind: FastPForKind, data: &[u32]) -> Vec<u8> {
let mut words = Vec::new();
match kind {
FastPForKind::Block256Be => {
FastPFor256::default().encode(data, &mut words).unwrap();
words.iter().flat_map(|w| w.to_be_bytes()).collect()
}
#[cfg(feature = "unstable-v2")]
FastPForKind::Block128Le => {
FastPFor128::default().encode(data, &mut words).unwrap();
words.iter().flat_map(|w| w.to_le_bytes()).collect()
}
}
}
proptest! {
#[test]
fn test_fastpfor_roundtrip(data: Vec<u32>, block128: bool) {
prop_assume!(!data.is_empty());
#[cfg(feature = "unstable-v2")]
let kind = if block128 { FastPForKind::Block128Le } else { FastPForKind::Block256Be };
#[cfg(not(feature = "unstable-v2"))]
let kind = { let _ = block128; FastPForKind::Block256Be };
let encoded = encode(kind, &data);
let decoded = decode_fastpfor(&encoded, data.len().try_into().unwrap(), kind, &mut dec()).unwrap();
prop_assert_eq!(data, decoded);
}
}
#[rstest]
#[case(FastPForKind::Block256Be)]
#[cfg_attr(feature = "unstable-v2", case(FastPForKind::Block128Le))]
fn test_decode_fastpfor_empty(#[case] kind: FastPForKind) {
let decoded = decode_fastpfor(&[], 0, kind, &mut dec()).unwrap();
assert_eq!(decoded, [] as [u32; 0]);
}
#[cfg(feature = "unstable-v2")]
#[rstest]
fn test_word_order_is_not_interchangeable(
#[values(FastPForKind::Block256Be, FastPForKind::Block128Le)] kind: FastPForKind,
) {
let other = match kind {
FastPForKind::Block256Be => FastPForKind::Block128Le,
FastPForKind::Block128Le => FastPForKind::Block256Be,
};
let data: Vec<u32> = (0..500).map(|i| i * 7 + 3).collect();
let encoded = encode(kind, &data);
let num_values = u32::try_from(data.len()).unwrap();
assert_ne!(
decode_fastpfor(&encoded, num_values, other, &mut dec()).ok(),
Some(data)
);
}
#[rstest]
#[case(FastPForKind::Block256Be)]
#[cfg_attr(feature = "unstable-v2", case(FastPForKind::Block128Le))]
fn a_zero_value_stream_carrying_a_payload_is_rejected(#[case] kind: FastPForKind) {
let err = decode_fastpfor(&[0, 0, 0, 0], 0, kind, &mut dec()).unwrap_err();
assert!(
matches!(err, MltError::InvalidFastPforByteLength(0)),
"{err:?}"
);
}
#[rstest]
#[case(FastPForKind::Block256Be)]
#[cfg_attr(feature = "unstable-v2", case(FastPForKind::Block128Le))]
fn a_payload_that_is_not_whole_words_is_rejected(#[case] kind: FastPForKind) {
let err = decode_fastpfor(&[0, 0, 0], 1, kind, &mut dec()).unwrap_err();
assert!(
matches!(err, MltError::InvalidFastPforByteLength(3)),
"{err:?}"
);
}
#[rstest]
#[case(FastPForKind::Block256Be)]
#[cfg_attr(feature = "unstable-v2", case(FastPForKind::Block128Le))]
fn a_declared_count_the_codec_cannot_meet_is_rejected(#[case] kind: FastPForKind) {
let data: Vec<u32> = (0..500).map(|i| i * 7 + 3).collect();
let encoded = encode(kind, &data);
let err = decode_fastpfor(&encoded, 600, kind, &mut dec()).unwrap_err();
assert!(
matches!(err, MltError::FastPfor(_)),
"the codec must reject a short decode itself: {err:?}"
);
}
#[rstest]
#[case(FastPForKind::Block256Be)]
#[cfg_attr(feature = "unstable-v2", case(FastPForKind::Block128Le))]
fn decoding_past_the_memory_budget_is_rejected(#[case] kind: FastPForKind) {
let data: Vec<u32> = (0..500).map(|i| i * 7 + 3).collect();
let encoded = encode(kind, &data);
let err = decode_fastpfor(&encoded, 500, kind, &mut starved_dec()).unwrap_err();
assert!(
matches!(err, MltError::MemoryLimitExceeded { .. }),
"{err:?}"
);
}
}