mod pae;
mod piece;
use std::borrow::Cow;
pub use piece::AadPiece;
use vitaminc_protected::NonEmpty;
#[derive(Clone)]
pub struct Aad<'a>(Cow<'a, [u8]>);
impl<'a> Aad<'a> {
pub fn empty() -> Self {
Aad(Cow::Borrowed(&[]))
}
pub fn new_owned<I>(aad: I) -> Self
where
I: IntoIterator<Item = u8>,
{
let aad: Vec<u8> = aad.into_iter().collect();
Aad(Cow::Owned(aad))
}
pub fn from_slice(slice: &'a [u8]) -> Self {
Aad(Cow::Borrowed(slice))
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_ref()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn into_owned(self) -> Aad<'static> {
match self.0 {
Cow::Borrowed(slice) => Aad(Cow::Owned(slice.to_vec())),
Cow::Owned(owned) => Aad(Cow::Owned(owned)),
}
}
pub fn pae(pieces: &[&[u8]]) -> Self {
pae::encode(pieces)
}
pub fn for_map_entry(&self, key: &str) -> Aad<'static> {
const MAP_ENTRY_DOMAIN: &[u8] = b"vitaminc/aead/map-entry/v1";
pae::encode(&[MAP_ENTRY_DOMAIN, self.as_bytes(), key.as_bytes()])
}
fn for_marker(&self, kind: &[u8]) -> Aad<'static> {
const MARKER_DOMAIN: &[u8] = b"vitaminc/aead/marker/v1";
pae::encode(&[MARKER_DOMAIN, self.as_bytes(), kind])
}
pub fn for_leaf(&self, version: u8) -> Aad<'static> {
const LEAF_DOMAIN: &[u8] = b"vitaminc/aead/leaf";
pae::encode(&[LEAF_DOMAIN, &[version], self.as_bytes()])
}
pub fn for_sequence_element(&self) -> Aad<'static> {
const SEQ_ELEMENT_DOMAIN: &[u8] = b"vitaminc/aead/seq-element/v1";
pae::encode(&[SEQ_ELEMENT_DOMAIN, self.as_bytes(), b"element"])
}
pub fn for_empty_sequence(&self) -> Aad<'static> {
self.for_marker(b"empty-sequence")
}
pub fn for_empty_map(&self) -> Aad<'static> {
self.for_marker(b"empty-map")
}
pub fn for_none(&self) -> Aad<'static> {
self.for_marker(b"none")
}
}
pub trait IntoAad<'a> {
fn into_aad(self) -> Aad<'a>
where
Self: Sized;
fn into_aad_piece(self) -> AadPiece<'a>
where
Self: Sized,
{
AadPiece::Bytes(self.into_aad().0)
}
}
impl<'a, T> IntoAad<'a> for NonEmpty<T>
where
T: IntoAad<'a>,
{
fn into_aad(self) -> Aad<'a> {
self.into_inner().into_aad()
}
fn into_aad_piece(self) -> AadPiece<'a> {
self.into_inner().into_aad_piece()
}
}
impl<'a> IntoAad<'a> for Aad<'a> {
fn into_aad(self) -> Aad<'a> {
self
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::Bytes(self.0)
}
}
impl<'a> IntoAad<'a> for () {
fn into_aad(self) -> Aad<'a> {
Aad::from_slice(&[])
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::Bytes(Cow::Borrowed(&[]))
}
}
impl<'a> IntoAad<'a> for &'a [u8] {
fn into_aad(self) -> Aad<'a> {
Aad::from_slice(self)
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::Bytes(Cow::Borrowed(self))
}
}
impl<'a> IntoAad<'a> for Vec<u8> {
fn into_aad(self) -> Aad<'a> {
Aad::new_owned(self)
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::Bytes(Cow::Owned(self))
}
}
impl<'a, const N: usize> IntoAad<'a> for [u8; N] {
fn into_aad(self) -> Aad<'a> {
Aad::new_owned(self)
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::Bytes(Cow::Owned(self.to_vec()))
}
}
impl<'a, const N: usize> IntoAad<'a> for &'a [u8; N] {
fn into_aad(self) -> Aad<'a> {
Aad::from_slice(self.as_slice())
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::Bytes(Cow::Borrowed(self.as_slice()))
}
}
impl<'a> IntoAad<'a> for String {
fn into_aad(self) -> Aad<'a> {
Aad::new_owned(self.into_bytes())
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::Text(Cow::Owned(self))
}
}
impl<'a> IntoAad<'a> for Cow<'a, [u8]> {
fn into_aad(self) -> Aad<'a> {
Aad(self)
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::Bytes(self)
}
}
impl<'a> IntoAad<'a> for &'a str {
fn into_aad(self) -> Aad<'a> {
Aad::from_slice(self.as_bytes())
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::Text(Cow::Borrowed(self))
}
}
macro_rules! integer_aad {
($($ty:ty => $variant:ident),+ $(,)?) => {$(
impl<'a> IntoAad<'a> for $ty {
fn into_aad(self) -> Aad<'a> {
Aad::new_owned(self.to_le_bytes())
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::$variant(self)
}
}
)+};
}
integer_aad!(
u8 => U8, u16 => U16, u32 => U32, u64 => U64, u128 => U128,
i8 => I8, i16 => I16, i32 => I32, i64 => I64, i128 => I128,
);
impl<'a, T> IntoAad<'a> for Option<T>
where
T: IntoAad<'a>,
{
fn into_aad(self) -> Aad<'a> {
match self {
Some(value) => Aad::pae(&[value.into_aad().as_bytes()]),
None => Aad::pae(&[]),
}
}
fn into_aad_piece(self) -> AadPiece<'a> {
AadPiece::List(self.into_iter().map(T::into_aad_piece).collect())
}
}
impl<'a, A, B> IntoAad<'a> for (A, B)
where
A: IntoAad<'a>,
B: IntoAad<'a>,
{
fn into_aad(self) -> Aad<'a> {
let (a, b) = self;
let a = a.into_aad();
let b = b.into_aad();
Aad::pae(&[a.as_bytes(), b.as_bytes()])
}
fn into_aad_piece(self) -> AadPiece<'a> {
let (a, b) = self;
AadPiece::List(vec![a.into_aad_piece(), b.into_aad_piece()])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn non_empty_is_transparent_to_the_encoding() {
assert_eq!(
NonEmpty::new("users/email")
.expect("non-empty")
.into_aad()
.as_bytes(),
"users/email".into_aad().as_bytes()
);
assert_eq!(
Some(NonEmpty::new("users/email").expect("non-empty"))
.into_aad()
.as_bytes(),
Some("users/email").into_aad().as_bytes()
);
assert_eq!(
NonEmpty::new(("users", "email"))
.expect("non-empty")
.into_aad()
.as_bytes(),
("users", "email").into_aad().as_bytes()
);
assert_eq!(
vitaminc_protected::nonempty!("users/email")
.with(42u64)
.into_aad()
.as_bytes(),
("users/email", 42u64).into_aad().as_bytes()
);
assert_eq!(
vitaminc_protected::nonempty!("users/email")
.into_aad()
.as_bytes(),
b"users/email"
);
}
#[test]
fn encoded_aad_reports_emptiness_of_its_bytes_only() {
assert!(Aad::empty().is_empty());
assert!("".into_aad().is_empty());
assert!(!Aad::from_slice(b"raw").is_empty());
assert!(!Some("").into_aad().is_empty());
}
#[test]
fn test_aad() {
let aad = Aad::new_owned(vec![1, 2, 3]);
assert_eq!(aad.as_bytes(), &[1, 2, 3]);
assert!(!aad.is_empty());
let aad_borrowed = Aad::from_slice(&[4, 5, 6]);
assert_eq!(aad_borrowed.as_bytes(), &[4, 5, 6]);
assert!(!aad_borrowed.is_empty());
}
#[test]
fn test_str_aad() {
let aad = "hello".into_aad();
assert_eq!(aad.as_bytes(), b"hello");
assert!(!aad.is_empty());
}
#[test]
fn test_string_aad() {
let aad = String::from("world").into_aad();
assert_eq!(aad.as_bytes(), b"world");
assert!(!aad.is_empty());
}
#[test]
fn test_u64_aad() {
let aad = 42u64.into_aad();
assert_eq!(aad.as_bytes(), &[42, 0, 0, 0, 0, 0, 0, 0]);
assert!(!aad.is_empty());
}
#[test]
fn every_integer_type_encodes_as_little_endian_bytes() {
assert_eq!(7u8.into_aad().as_bytes(), &7u8.to_le_bytes());
assert_eq!(7u16.into_aad().as_bytes(), &7u16.to_le_bytes());
assert_eq!(7u32.into_aad().as_bytes(), &7u32.to_le_bytes());
assert_eq!(7u64.into_aad().as_bytes(), &7u64.to_le_bytes());
assert_eq!(7u128.into_aad().as_bytes(), &7u128.to_le_bytes());
assert_eq!((-7i8).into_aad().as_bytes(), &(-7i8).to_le_bytes());
assert_eq!((-7i16).into_aad().as_bytes(), &(-7i16).to_le_bytes());
assert_eq!((-7i32).into_aad().as_bytes(), &(-7i32).to_le_bytes());
assert_eq!((-7i64).into_aad().as_bytes(), &(-7i64).to_le_bytes());
assert_eq!((-7i128).into_aad().as_bytes(), &(-7i128).to_le_bytes());
assert_ne!(7u32.into_aad().as_bytes(), 7u64.into_aad().as_bytes());
}
#[test]
fn test_tuple_aad() {
let aad = ("foo", "bar").into_aad();
let expected = Aad::pae(&[b"foo", b"bar"]);
assert_eq!(aad.as_bytes(), expected.as_bytes());
assert!(!aad.is_empty());
}
#[test]
fn test_option_none_differs_from_some_empty() {
let none_aad = Option::<&str>::None.into_aad();
let some_empty_aad = Some("").into_aad();
assert_ne!(none_aad.as_bytes(), some_empty_aad.as_bytes());
}
#[test]
fn test_option_none_differs_from_unit() {
let none_aad = Option::<&str>::None.into_aad();
let unit_aad = ().into_aad();
assert_ne!(none_aad.as_bytes(), unit_aad.as_bytes());
}
#[test]
fn test_option_some_roundtrips_value() {
let some_aad = Some("hello").into_aad();
let expected = Aad::pae(&[b"hello"]);
assert_eq!(some_aad.as_bytes(), expected.as_bytes());
}
#[test]
fn test_byte_array_aad() {
let aad = [1u8, 2, 3].into_aad();
assert_eq!(aad.as_bytes(), &[1, 2, 3]);
assert!(!aad.is_empty());
}
#[test]
fn test_byte_array_ref_aad() {
let arr = [4u8, 5, 6];
let aad = (&arr).into_aad();
assert_eq!(aad.as_bytes(), &[4, 5, 6]);
}
#[test]
fn test_option_byte_array_aad() {
let some_aad = Some([1u8, 2, 3]).into_aad();
let expected = Aad::pae(&[&[1, 2, 3]]);
assert_eq!(some_aad.as_bytes(), expected.as_bytes());
let none_aad = Option::<[u8; 3]>::None.into_aad();
assert_eq!(none_aad.as_bytes(), &[0u8; 8]);
assert_ne!(none_aad.as_bytes(), some_aad.as_bytes());
}
#[test]
fn for_map_entry_pins_encoding() {
let bound = Aad::from_slice(b"ctx").for_map_entry("name");
let expected = Aad::pae(&[b"vitaminc/aead/map-entry/v1", b"ctx", b"name"]);
assert_eq!(bound.as_bytes(), expected.as_bytes());
}
#[test]
fn for_map_entry_differs_from_tuple_aad() {
let bound = Aad::from_slice(b"ctx").for_map_entry("name");
let tuple = (Aad::from_slice(b"ctx"), "name").into_aad();
assert_ne!(bound.as_bytes(), tuple.as_bytes());
}
#[test]
fn marker_aads_pin_encoding() {
let aad = Aad::from_slice(b"ctx");
for (derived, kind) in [
(aad.for_empty_sequence(), b"empty-sequence".as_slice()),
(aad.for_empty_map(), b"empty-map"),
(aad.for_none(), b"none"),
] {
let expected = Aad::pae(&[b"vitaminc/aead/marker/v1", b"ctx", kind]);
assert_eq!(derived.as_bytes(), expected.as_bytes());
}
}
#[test]
fn marker_aads_differ_from_tuple_aad_and_each_other() {
let aad = Aad::from_slice(b"ctx");
let markers = [
aad.for_empty_sequence(),
aad.for_empty_map(),
aad.for_none(),
];
for (i, m) in markers.iter().enumerate() {
let tuple = (
b"vitaminc/aead/marker/v1".as_slice(),
Aad::from_slice(b"ctx"),
)
.into_aad();
assert_ne!(m.as_bytes(), tuple.as_bytes());
for other in &markers[i + 1..] {
assert_ne!(m.as_bytes(), other.as_bytes());
}
}
}
#[test]
fn for_leaf_pins_encoding() {
let derived = Aad::from_slice(b"ctx").for_leaf(1);
let expected = Aad::pae(&[b"vitaminc/aead/leaf", &[1u8], b"ctx"]);
assert_eq!(derived.as_bytes(), expected.as_bytes());
}
#[test]
fn for_leaf_is_version_sensitive_and_disjoint() {
let aad = Aad::from_slice(b"ctx");
assert_ne!(aad.for_leaf(1).as_bytes(), aad.for_leaf(2).as_bytes());
assert_ne!(aad.for_leaf(1).as_bytes(), aad.as_bytes());
let tuple = (b"vitaminc/aead/leaf".as_slice(), "ctx").into_aad();
assert_ne!(aad.for_leaf(1).as_bytes(), tuple.as_bytes());
}
#[test]
fn for_sequence_element_pins_encoding() {
let derived = Aad::from_slice(b"ctx").for_sequence_element();
let expected = Aad::pae(&[b"vitaminc/aead/seq-element/v1", b"ctx", b"element"]);
assert_eq!(derived.as_bytes(), expected.as_bytes());
}
#[test]
fn for_sequence_element_differs_from_bare_aad_and_sibling_derivations() {
let aad = Aad::from_slice(b"ctx");
let elem = aad.for_sequence_element();
assert_ne!(elem.as_bytes(), aad.as_bytes());
assert_ne!(elem.as_bytes(), aad.for_map_entry("element").as_bytes());
assert_ne!(elem.as_bytes(), aad.for_empty_sequence().as_bytes());
assert_ne!(elem.as_bytes(), aad.for_none().as_bytes());
let tuple = (b"vitaminc/aead/seq-element/v1".as_slice(), "ctx").into_aad();
assert_ne!(elem.as_bytes(), tuple.as_bytes());
}
#[test]
fn for_map_entry_is_key_sensitive() {
let aad = Aad::from_slice(b"ctx");
assert_ne!(
aad.for_map_entry("a").as_bytes(),
aad.for_map_entry("b").as_bytes()
);
assert_ne!(
Aad::from_slice(b"ctxa").for_map_entry("").as_bytes(),
Aad::from_slice(b"ctx").for_map_entry("a").as_bytes()
);
}
#[test]
fn test_tuple_aad_is_injective() {
let aad1 = ("ab", "cd").into_aad();
let aad2 = ("a", "bcd").into_aad();
assert_ne!(aad1.as_bytes(), aad2.as_bytes());
let aad3 = ("foobar", ()).into_aad();
let aad4 = ("foo", "bar").into_aad();
assert_ne!(aad3.as_bytes(), aad4.as_bytes());
}
}