use std::borrow::Cow;
use std::fmt;
use super::{Aad, IntoAad};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum AadPiece<'a> {
Text(Cow<'a, str>),
Bytes(Cow<'a, [u8]>),
U8(u8),
U16(u16),
U32(u32),
U64(u64),
U128(u128),
I8(i8),
I16(i16),
I32(i32),
I64(i64),
I128(i128),
List(Vec<AadPiece<'a>>),
}
impl<'a> AadPiece<'a> {
pub fn into_owned(self) -> AadPiece<'static> {
match self {
AadPiece::Text(text) => AadPiece::Text(Cow::Owned(text.into_owned())),
AadPiece::Bytes(bytes) => AadPiece::Bytes(Cow::Owned(bytes.into_owned())),
AadPiece::U8(v) => AadPiece::U8(v),
AadPiece::U16(v) => AadPiece::U16(v),
AadPiece::U32(v) => AadPiece::U32(v),
AadPiece::U64(v) => AadPiece::U64(v),
AadPiece::U128(v) => AadPiece::U128(v),
AadPiece::I8(v) => AadPiece::I8(v),
AadPiece::I16(v) => AadPiece::I16(v),
AadPiece::I32(v) => AadPiece::I32(v),
AadPiece::I64(v) => AadPiece::I64(v),
AadPiece::I128(v) => AadPiece::I128(v),
AadPiece::List(parts) => {
AadPiece::List(parts.into_iter().map(AadPiece::into_owned).collect())
}
}
}
pub fn leaves(&self) -> impl Iterator<Item = &AadPiece<'a>> {
fn walk<'p, 'a>(piece: &'p AadPiece<'a>, out: &mut Vec<&'p AadPiece<'a>>) {
match piece {
AadPiece::List(parts) => parts.iter().for_each(|part| walk(part, out)),
leaf => out.push(leaf),
}
}
let mut out = Vec::new();
walk(self, &mut out);
out.into_iter()
}
fn encoded_len(&self) -> usize {
match self {
AadPiece::Text(text) => text.len(),
AadPiece::Bytes(bytes) => bytes.len(),
AadPiece::U8(_) | AadPiece::I8(_) => 1,
AadPiece::U16(_) | AadPiece::I16(_) => 2,
AadPiece::U32(_) | AadPiece::I32(_) => 4,
AadPiece::U64(_) | AadPiece::I64(_) => 8,
AadPiece::U128(_) | AadPiece::I128(_) => 16,
AadPiece::List(parts) => {
8 + parts
.iter()
.map(|part| 8 + part.encoded_len())
.sum::<usize>()
}
}
}
pub(crate) fn pae_after(&self, head: &[u8]) -> Aad<'static> {
let tail_len = self.encoded_len();
let len = 8 + (8 + head.len()) + (8 + tail_len);
let mut buf = Vec::with_capacity(len);
buf.extend_from_slice(&2u64.to_le_bytes());
buf.extend_from_slice(&(head.len() as u64).to_le_bytes());
buf.extend_from_slice(head);
buf.extend_from_slice(&(tail_len as u64).to_le_bytes());
self.write_into(&mut buf);
debug_assert_eq!(buf.len(), len, "encoded_len must equal the bytes written");
Aad::new_owned(buf)
}
fn write_into(&self, buf: &mut Vec<u8>) {
match self {
AadPiece::Text(text) => buf.extend_from_slice(text.as_bytes()),
AadPiece::Bytes(bytes) => buf.extend_from_slice(bytes),
AadPiece::U8(v) => buf.extend_from_slice(&v.to_le_bytes()),
AadPiece::U16(v) => buf.extend_from_slice(&v.to_le_bytes()),
AadPiece::U32(v) => buf.extend_from_slice(&v.to_le_bytes()),
AadPiece::U64(v) => buf.extend_from_slice(&v.to_le_bytes()),
AadPiece::U128(v) => buf.extend_from_slice(&v.to_le_bytes()),
AadPiece::I8(v) => buf.extend_from_slice(&v.to_le_bytes()),
AadPiece::I16(v) => buf.extend_from_slice(&v.to_le_bytes()),
AadPiece::I32(v) => buf.extend_from_slice(&v.to_le_bytes()),
AadPiece::I64(v) => buf.extend_from_slice(&v.to_le_bytes()),
AadPiece::I128(v) => buf.extend_from_slice(&v.to_le_bytes()),
AadPiece::List(parts) => {
buf.extend_from_slice(&(parts.len() as u64).to_le_bytes());
for part in parts {
let length_word = buf.len();
buf.extend_from_slice(&[0u8; 8]);
let start = buf.len();
part.write_into(buf);
let written = (buf.len() - start) as u64;
buf[length_word..start].copy_from_slice(&written.to_le_bytes());
}
}
}
}
}
impl<'a> IntoAad<'a> for AadPiece<'a> {
fn into_aad_piece(self) -> AadPiece<'a> {
self
}
fn into_aad(self) -> Aad<'a> {
match self {
AadPiece::Text(Cow::Borrowed(text)) => text.into_aad(),
AadPiece::Text(Cow::Owned(text)) => text.into_aad(),
AadPiece::Bytes(bytes) => bytes.into_aad(),
AadPiece::U8(v) => v.into_aad(),
AadPiece::U16(v) => v.into_aad(),
AadPiece::U32(v) => v.into_aad(),
AadPiece::U64(v) => v.into_aad(),
AadPiece::U128(v) => v.into_aad(),
AadPiece::I8(v) => v.into_aad(),
AadPiece::I16(v) => v.into_aad(),
AadPiece::I32(v) => v.into_aad(),
AadPiece::I64(v) => v.into_aad(),
AadPiece::I128(v) => v.into_aad(),
list @ AadPiece::List(_) => {
let len = list.encoded_len();
let mut buf = Vec::with_capacity(len);
list.write_into(&mut buf);
debug_assert_eq!(buf.len(), len, "encoded_len must equal the bytes written");
Aad::new_owned(buf)
}
}
}
}
impl fmt::Display for AadPiece<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AadPiece::Text(text) => write!(f, "{text:?}"),
AadPiece::Bytes(bytes) => {
f.write_str("0x")?;
bytes.iter().try_for_each(|byte| write!(f, "{byte:02x}"))
}
AadPiece::U8(v) => write!(f, "{v}u8"),
AadPiece::U16(v) => write!(f, "{v}u16"),
AadPiece::U32(v) => write!(f, "{v}u32"),
AadPiece::U64(v) => write!(f, "{v}u64"),
AadPiece::U128(v) => write!(f, "{v}u128"),
AadPiece::I8(v) => write!(f, "{v}i8"),
AadPiece::I16(v) => write!(f, "{v}i16"),
AadPiece::I32(v) => write!(f, "{v}i32"),
AadPiece::I64(v) => write!(f, "{v}i64"),
AadPiece::I128(v) => write!(f, "{v}i128"),
AadPiece::List(parts) => {
f.write_str("(")?;
for (i, part) in parts.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
fmt::Display::fmt(part, f)?;
}
f.write_str(")")
}
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use quickcheck_macros::quickcheck;
use vitaminc_protected::NonEmpty;
use super::*;
fn agrees<'a, T>(value: T) -> bool
where
T: IntoAad<'a> + Clone,
{
value.clone().into_aad_piece().into_aad().as_bytes() == value.into_aad().as_bytes()
}
#[quickcheck]
fn text_agrees(s: String) -> bool {
agrees(s.as_str()) && agrees(s)
}
#[quickcheck]
fn bytes_agree(b: Vec<u8>) -> bool {
agrees(b.as_slice()) && agrees(Cow::Borrowed(b.as_slice())) && agrees(b)
}
#[quickcheck]
fn unsigned_integers_agree(a: u8, b: u16, c: u32, d: u64, e: u128) -> bool {
agrees(a) && agrees(b) && agrees(c) && agrees(d) && agrees(e)
}
#[quickcheck]
fn signed_integers_agree(a: i8, b: i16, c: i32, d: i64, e: i128) -> bool {
agrees(a) && agrees(b) && agrees(c) && agrees(d) && agrees(e)
}
#[quickcheck]
fn composites_agree(s: String, n: u64, o: Option<String>, b: Vec<u8>) -> bool {
agrees((s.as_str(), n))
&& agrees(o.clone())
&& agrees(((s.as_str(), n), b.as_slice()))
&& agrees(Some((o, n)))
&& agrees((s, ()))
}
#[test]
fn fixed_shapes_agree() {
assert!(agrees(()));
assert!(agrees([1u8, 2, 3]));
assert!(agrees(&[4u8, 5, 6]));
assert!(agrees(Aad::from_slice(b"raw")));
assert!(agrees(Option::<&str>::None));
assert!(agrees(NonEmpty::new("users/email").unwrap()));
assert!(agrees(NonEmpty::new(("users/email", 7u64)).unwrap()));
}
#[test]
fn a_composite_context_names_its_parts() {
let piece = ("users/email", 7u64).into_aad_piece();
assert_eq!(
piece,
AadPiece::List(vec![
AadPiece::Text(Cow::Borrowed("users/email")),
AadPiece::U64(7),
])
);
assert_eq!(piece.to_string(), "(\"users/email\", 7u64)");
}
#[test]
fn integers_keep_their_type() {
assert_eq!(7u32.into_aad_piece(), AadPiece::U32(7));
assert_eq!((-7i16).into_aad_piece(), AadPiece::I16(-7));
assert_ne!(
7u32.into_aad_piece().into_aad().as_bytes(),
7u64.into_aad_piece().into_aad().as_bytes()
);
}
#[test]
fn options_are_lists() {
assert_eq!(
Option::<&str>::None.into_aad_piece(),
AadPiece::List(vec![])
);
assert_eq!(
Some("a").into_aad_piece(),
AadPiece::List(vec![AadPiece::Text(Cow::Borrowed("a"))])
);
assert_eq!(Option::<&str>::None.into_aad_piece().to_string(), "()");
assert_eq!(Some("a").into_aad_piece().to_string(), "(\"a\")");
}
#[test]
fn the_empty_aad_is_no_bytes_not_an_empty_list() {
assert_eq!(().into_aad_piece(), AadPiece::Bytes(Cow::Borrowed(&[])));
assert_ne!(().into_aad_piece(), Option::<()>::None.into_aad_piece());
assert_eq!(().into_aad_piece().to_string(), "0x");
}
#[test]
fn equality_is_tree_identity_not_encoding_identity() {
let pairs: [(AadPiece<'_>, AadPiece<'_>); 2] = [
("ab".into_aad_piece(), b"ab".as_slice().into_aad_piece()),
(7u64.into_aad_piece(), 7i64.into_aad_piece()),
];
for (left, right) in pairs {
assert_ne!(left, right);
assert_eq!(left.into_aad().as_bytes(), right.into_aad().as_bytes());
}
}
#[test]
fn non_empty_is_transparent() {
assert_eq!(
NonEmpty::new(("users/email", 7u64))
.unwrap()
.into_aad_piece(),
("users/email", 7u64).into_aad_piece()
);
}
#[test]
fn bytes_display_as_hex() {
assert_eq!(
[0xdeu8, 0xad, 0xbe, 0xef].into_aad_piece().to_string(),
"0xdeadbeef"
);
assert_eq!(
Aad::from_slice(b"\x01\x02").into_aad_piece().to_string(),
"0x0102"
);
}
#[test]
fn leaves_walk_in_encoding_order_and_drop_nesting() {
let piece = ((("a", 1u8), Option::<&str>::None), (Some("b"), [9u8])).into_aad_piece();
let leaves: Vec<String> = piece.leaves().map(ToString::to_string).collect();
assert_eq!(leaves, ["\"a\"", "1u8", "\"b\"", "0x09"]);
assert_eq!("x".into_aad_piece().leaves().count(), 1);
assert_eq!(Option::<&str>::None.into_aad_piece().leaves().count(), 0);
let left = (("a", 1u8), "b").into_aad_piece();
let right = ("a", (1u8, "b")).into_aad_piece();
assert!(left.leaves().eq(right.leaves()));
assert_ne!(left.into_aad().as_bytes(), right.into_aad().as_bytes());
}
#[test]
fn display_is_injective_where_it_used_to_collide() {
let pairs: [(AadPiece<'_>, AadPiece<'_>); 5] = [
(7u64.into_aad_piece(), 7i64.into_aad_piece()),
(("x", 7u64).into_aad_piece(), ("x", "7").into_aad_piece()),
("0xdead".into_aad_piece(), [0xdeu8, 0xad].into_aad_piece()),
(
Some("").into_aad_piece(),
Option::<&str>::None.into_aad_piece(),
),
(
("a, b", "c").into_aad_piece(),
("a", "b, c").into_aad_piece(),
),
];
for (left, right) in pairs {
assert_ne!(left.to_string(), right.to_string());
}
assert_eq!(
("a, b", (7u8, [1u8])).into_aad_piece().to_string(),
"(\"a, b\", (7u8, 0x01))"
);
assert_eq!(Some("").into_aad_piece().to_string(), "(\"\")");
assert_eq!((-7i16).into_aad_piece().to_string(), "-7i16");
}
#[derive(Debug, Clone)]
struct Tree(AadPiece<'static>);
impl quickcheck::Arbitrary for Tree {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
fn gen(g: &mut quickcheck::Gen, depth: u8) -> AadPiece<'static> {
let kinds = if depth == 0 { 12 } else { 13 };
match u8::arbitrary(g) % kinds {
0 => AadPiece::Text(Cow::Owned(String::arbitrary(g))),
1 => AadPiece::Bytes(Cow::Owned(Vec::arbitrary(g))),
2 => AadPiece::U8(u8::arbitrary(g)),
3 => AadPiece::U16(u16::arbitrary(g)),
4 => AadPiece::U32(u32::arbitrary(g)),
5 => AadPiece::U64(u64::arbitrary(g)),
6 => AadPiece::U128(u128::arbitrary(g)),
7 => AadPiece::I8(i8::arbitrary(g)),
8 => AadPiece::I16(i16::arbitrary(g)),
9 => AadPiece::I32(i32::arbitrary(g)),
10 => AadPiece::I64(i64::arbitrary(g)),
11 => AadPiece::I128(i128::arbitrary(g)),
_ => {
let n = usize::arbitrary(g) % 4;
AadPiece::List((0..n).map(|_| gen(g, depth - 1)).collect())
}
}
}
Tree(gen(g, 3))
}
}
fn every_kind() -> Vec<AadPiece<'static>> {
vec![
AadPiece::Text(Cow::Borrowed("t")),
AadPiece::Bytes(Cow::Borrowed(b"b")),
AadPiece::U8(1),
AadPiece::U16(2),
AadPiece::U32(3),
AadPiece::U64(4),
AadPiece::U128(5),
AadPiece::I8(-1),
AadPiece::I16(-2),
AadPiece::I32(-3),
AadPiece::I64(-4),
AadPiece::I128(-5),
AadPiece::List(vec![AadPiece::U8(9)]),
]
}
#[test]
fn every_kind_round_trips_through_into_owned_and_encodes_its_own_bytes() {
for piece in every_kind() {
let owned = piece.clone().into_owned();
assert_eq!(owned, piece, "into_owned must not change the tree");
assert_eq!(
owned.into_aad().as_bytes(),
piece.clone().into_aad().as_bytes(),
"into_owned must not change the bytes"
);
let mut buf = Vec::new();
piece.write_into(&mut buf);
assert_eq!(buf.len(), piece.encoded_len());
assert_eq!(buf, piece.clone().into_aad().as_bytes());
}
let rendered: Vec<String> = every_kind().iter().map(ToString::to_string).collect();
assert_eq!(
rendered,
[
"\"t\"", "0x62", "1u8", "2u16", "3u32", "4u64", "5u128", "-1i8", "-2i16", "-3i32",
"-4i64", "-5i128", "(9u8)",
]
);
}
#[quickcheck]
fn into_owned_preserves_any_tree(tree: Tree) -> bool {
let owned = tree.0.clone().into_owned();
owned == tree.0 && owned.into_aad().as_bytes() == tree.0.into_aad().as_bytes()
}
#[quickcheck]
fn display_is_injective(a: Tree, b: Tree) -> bool {
a.0 == b.0 || a.0.to_string() != b.0.to_string()
}
#[quickcheck]
fn list_encodes_as_pae_of_its_parts(tree: Tree) -> bool {
fn via_pae(piece: &AadPiece<'static>) -> Aad<'static> {
match piece {
AadPiece::List(parts) => {
let encoded: Vec<Aad<'static>> = parts.iter().map(via_pae).collect();
let refs: Vec<&[u8]> = encoded.iter().map(Aad::as_bytes).collect();
Aad::pae(&refs)
}
leaf => leaf.clone().into_aad().into_owned(),
}
}
let expected = via_pae(&tree.0);
let piece = tree.0;
let len = piece.encoded_len();
let actual = piece.into_aad();
actual.as_bytes() == expected.as_bytes() && actual.as_bytes().len() == len
}
#[quickcheck]
fn pae_after_is_the_two_element_list(head: Vec<u8>, tree: Tree) -> bool {
let expected =
AadPiece::List(vec![AadPiece::Bytes(Cow::Borrowed(&head)), tree.0.clone()]).into_aad();
tree.0.pae_after(&head).as_bytes() == expected.as_bytes()
}
#[test]
fn deep_left_nested_lists_encode_as_pae() {
let mut piece = AadPiece::U8(1);
for i in 0..=255u8 {
piece = AadPiece::List(vec![piece, AadPiece::U8(i)]);
}
let mut expected = 1u8.into_aad().into_owned();
for i in 0..=255u8 {
let leaf = i.into_aad();
expected = Aad::pae(&[expected.as_bytes(), leaf.as_bytes()]);
}
let len = piece.encoded_len();
let actual = piece.into_aad();
assert_eq!(actual.as_bytes(), expected.as_bytes());
assert_eq!(actual.as_bytes().len(), len);
}
#[test]
fn into_owned_preserves_the_tree_and_the_bytes() {
let text = String::from("users/email");
let piece = (text.as_str(), 7u64).into_aad_piece();
let owned: AadPiece<'static> = piece.clone().into_owned();
assert_eq!(owned, piece);
assert_eq!(
owned.into_aad().as_bytes(),
(text.as_str(), 7u64).into_aad().as_bytes()
);
}
#[test]
fn a_piece_is_an_aad_wherever_one_is_taken() {
let from_value = ("users/email", 7u64).into_aad();
let from_piece = ("users/email", 7u64).into_aad_piece().into_aad();
assert_eq!(from_value.as_bytes(), from_piece.as_bytes());
let hand_built = AadPiece::List(vec![
AadPiece::Text(Cow::Borrowed("users/email")),
AadPiece::U64(7),
]);
assert_eq!(hand_built.into_aad().as_bytes(), from_value.as_bytes());
}
}