use super::Aad;
use std::borrow::Cow;
pub(crate) fn encode(pieces: &[&[u8]]) -> Aad<'static> {
let total_len = 8 + pieces.iter().map(|p| 8 + p.len()).sum::<usize>();
let mut buf = Vec::with_capacity(total_len);
buf.extend_from_slice(&(pieces.len() as u64).to_le_bytes());
for piece in pieces {
buf.extend_from_slice(&(piece.len() as u64).to_le_bytes());
buf.extend_from_slice(piece);
}
debug_assert_eq!(
buf.len(),
total_len,
"PAE capacity hint must equal the encoded length"
);
Aad(Cow::Owned(buf))
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck_macros::quickcheck;
#[test]
fn empty_pieces() {
let aad = encode(&[]);
assert_eq!(aad.as_bytes(), &[0u8; 8]);
}
#[test]
fn single_empty_piece() {
let aad = encode(&[b""]);
let mut expected = vec![];
expected.extend_from_slice(&1u64.to_le_bytes());
expected.extend_from_slice(&0u64.to_le_bytes());
assert_eq!(aad.as_bytes(), &expected);
}
#[test]
fn single_piece() {
let aad = encode(&[b"test"]);
let mut expected = vec![];
expected.extend_from_slice(&1u64.to_le_bytes());
expected.extend_from_slice(&4u64.to_le_bytes());
expected.extend_from_slice(b"test");
assert_eq!(aad.as_bytes(), &expected);
}
#[test]
fn two_pieces() {
let aad = encode(&[b"foo", b"bar"]);
let mut expected = vec![];
expected.extend_from_slice(&2u64.to_le_bytes()); expected.extend_from_slice(&3u64.to_le_bytes()); expected.extend_from_slice(b"foo");
expected.extend_from_slice(&3u64.to_le_bytes()); expected.extend_from_slice(b"bar");
assert_eq!(aad.as_bytes(), &expected);
}
#[test]
fn different_splits_produce_different_encodings() {
let aad1 = encode(&[b"ab", b"cd"]);
let aad2 = encode(&[b"a", b"bcd"]);
assert_ne!(aad1.as_bytes(), aad2.as_bytes());
}
#[test]
fn trailing_empty_piece_differs_from_no_trailing() {
let aad1 = encode(&[b"foo", b"bar"]);
let aad2 = encode(&[b"foo", b"bar", b""]);
assert_ne!(aad1.as_bytes(), aad2.as_bytes());
}
#[test]
fn leading_empty_piece_differs() {
let aad1 = encode(&[b"foo"]);
let aad2 = encode(&[b"", b"foo"]);
assert_ne!(aad1.as_bytes(), aad2.as_bytes());
}
#[test]
fn single_vs_split_produces_different_encodings() {
let aad1 = encode(&[b"foobar"]);
let aad2 = encode(&[b"foo", b"bar"]);
assert_ne!(aad1.as_bytes(), aad2.as_bytes());
}
#[test]
fn embedded_fake_length_prefix_does_not_collide() {
let fake_framing = {
let mut v = Vec::new();
v.extend_from_slice(&3u64.to_le_bytes()); v.extend_from_slice(b"bar");
v
};
let aad1 = encode(&[b"foo", b"bar"]);
let aad2 = encode(&[&fake_framing]);
assert_ne!(aad1.as_bytes(), aad2.as_bytes());
}
#[test]
fn all_empty_pieces_differ_by_count() {
let aad1 = encode(&[b"", b""]);
let aad2 = encode(&[b"", b"", b""]);
assert_ne!(aad1.as_bytes(), aad2.as_bytes());
}
#[quickcheck]
fn deterministic(pieces: Vec<Vec<u8>>) -> bool {
let refs: Vec<&[u8]> = pieces.iter().map(|p| p.as_slice()).collect();
encode(&refs).as_bytes() == encode(&refs).as_bytes()
}
#[quickcheck]
fn output_length_matches_formula(pieces: Vec<Vec<u8>>) -> bool {
let refs: Vec<&[u8]> = pieces.iter().map(|p| p.as_slice()).collect();
let expected_len = 8 + pieces.iter().map(|p| 8 + p.len()).sum::<usize>();
encode(&refs).as_bytes().len() == expected_len
}
#[quickcheck]
fn shifting_boundary_is_injective(a: Vec<u8>, b: Vec<u8>) -> bool {
if a.is_empty() {
return true;
}
let split = a.len() - 1;
let a1 = &a[..split];
let mut b1 = vec![a[split]];
b1.extend_from_slice(&b);
encode(&[&a, &b]).as_bytes() != encode(&[a1, &b1]).as_bytes()
}
#[quickcheck]
fn different_piece_count_is_injective(a: Vec<u8>, b: Vec<u8>) -> bool {
let mut combined = a.clone();
combined.extend_from_slice(&b);
encode(&[&combined]).as_bytes() != encode(&[&a, &b]).as_bytes()
}
#[quickcheck]
fn appending_empty_piece_changes_encoding(pieces: Vec<Vec<u8>>) -> bool {
let refs: Vec<&[u8]> = pieces.iter().map(|p| p.as_slice()).collect();
let mut with_extra = refs.clone();
with_extra.push(b"");
encode(&refs).as_bytes() != encode(&with_extra).as_bytes()
}
}