use alloc::vec::Vec;
use core::fmt;
use crate::common::errors::CardError;
use crate::seal::slot::SlotId;
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SealedBytes {
nonce: [u8; 24],
ct: [u8; 2],
tag: [u8; 16],
}
impl SealedBytes {
pub const LEN: usize = 42;
pub(crate) const fn new(nonce: [u8; 24], ct: [u8; 2], tag: [u8; 16]) -> Self {
Self { nonce, ct, tag }
}
#[must_use]
pub fn to_bytes(&self) -> [u8; 42] {
let mut out = [0u8; 42];
out[..24].copy_from_slice(&self.nonce);
out[24..26].copy_from_slice(&self.ct);
out[26..].copy_from_slice(&self.tag);
out
}
#[must_use]
pub fn from_bytes(b: [u8; 42]) -> Self {
let mut nonce = [0u8; 24];
let mut ct = [0u8; 2];
let mut tag = [0u8; 16];
nonce.copy_from_slice(&b[..24]);
ct.copy_from_slice(&b[24..26]);
tag.copy_from_slice(&b[26..]);
Self { nonce, ct, tag }
}
#[must_use]
pub const fn nonce(&self) -> &[u8; 24] {
&self.nonce
}
#[must_use]
pub const fn ct(&self) -> &[u8; 2] {
&self.ct
}
#[must_use]
pub const fn tag(&self) -> &[u8; 16] {
&self.tag
}
}
impl fmt::Debug for SealedBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SealedBytes(")?;
for b in self.to_bytes() {
write!(f, "{b:02x}")?;
}
f.write_str(")")
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "Vec<(SlotId, SealedBytes)>"))]
pub struct Custody(Vec<(SlotId, SealedBytes)>);
impl TryFrom<Vec<(SlotId, SealedBytes)>> for Custody {
type Error = CardError;
fn try_from(pairs: Vec<(SlotId, SealedBytes)>) -> Result<Self, CardError> {
Self::from_pairs(pairs)
}
}
impl Custody {
#[must_use]
pub const fn new() -> Self {
Self(Vec::new())
}
pub fn from_pairs(pairs: Vec<(SlotId, SealedBytes)>) -> Result<Self, CardError> {
let mut c = Self::new();
for (slot, sealed) in pairs {
c.insert(slot, sealed)?;
}
Ok(c)
}
pub fn insert(&mut self, slot: SlotId, sealed: SealedBytes) -> Result<(), CardError> {
if self.get(slot).is_some() {
return Err(CardError::DuplicateSlot(slot.get()));
}
self.0.push((slot, sealed));
Ok(())
}
#[must_use]
pub fn get(&self, slot: SlotId) -> Option<&SealedBytes> {
self.0.iter().find(|(s, _)| *s == slot).map(|(_, b)| b)
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (SlotId, &SealedBytes)> + '_ {
self.0.iter().map(|(s, b)| (*s, b))
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod seal__aead__sealed_bytes_tests {
use super::*;
use alloc::format;
fn sample() -> SealedBytes {
let mut b = [0u8; 42];
for (i, x) in b.iter_mut().enumerate() {
*x = u8::try_from(i).unwrap();
}
SealedBytes::from_bytes(b)
}
#[test]
fn sealed_bytes__len_is_42() {
assert_eq!(SealedBytes::LEN, 42);
assert_eq!(sample().to_bytes().len(), 42);
}
#[test]
fn sealed_bytes__to_from_bytes_roundtrip() {
let s = sample();
assert_eq!(SealedBytes::from_bytes(s.to_bytes()), s);
}
#[test]
fn sealed_bytes__parts_are_nonce_ct_tag() {
let s = sample();
assert_eq!(s.nonce()[0], 0);
assert_eq!(s.nonce()[23], 23);
assert_eq!(s.ct(), &[24, 25]);
assert_eq!(s.tag()[0], 26);
assert_eq!(s.tag()[15], 41);
}
#[test]
fn sealed_bytes__debug_is_hex() {
let dbg = format!("{:?}", sample());
assert_eq!(
dbg,
"SealedBytes(000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627282 9)"
.replace(' ', "")
);
}
#[test]
fn sealed_bytes__is_copy() {
fn assert_copy<T: Copy>() {}
assert_copy::<SealedBytes>();
}
#[test]
fn custody__insert_get_len_iter() {
let mut c = Custody::new();
assert!(c.is_empty());
c.insert(SlotId::new(3), sample()).unwrap();
c.insert(SlotId::new(1), sample()).unwrap();
assert_eq!(c.len(), 2);
assert_eq!(c.get(SlotId::new(3)), Some(&sample()));
assert_eq!(c.get(SlotId::new(9)), None);
let slots: alloc::vec::Vec<SlotId> = c.iter().map(|(s, _)| s).collect();
assert_eq!(slots, [SlotId::new(3), SlotId::new(1)]);
}
#[test]
fn custody__rejects_duplicate_slot() {
let mut c = Custody::new();
c.insert(SlotId::new(3), sample()).unwrap();
assert_eq!(
c.insert(SlotId::new(3), sample()).unwrap_err(),
CardError::DuplicateSlot(3)
);
assert_eq!(c.len(), 1);
}
#[test]
fn custody__from_pairs_rejects_duplicate() {
let pairs = alloc::vec![(SlotId::new(1), sample()), (SlotId::new(1), sample())];
assert_eq!(
Custody::from_pairs(pairs).unwrap_err(),
CardError::DuplicateSlot(1)
);
}
#[test]
fn custody__debug_contains_only_slots_and_hex() {
let mut c = Custody::new();
c.insert(SlotId::new(3), sample()).unwrap();
let dbg = format!("{c:?}");
assert!(dbg.contains("SealedBytes(0001"), "{dbg}");
}
#[cfg(feature = "serde")]
#[test]
fn sealed_bytes__serde_roundtrip() {
let s = sample();
let json = serde_json::to_string(&s).unwrap();
assert_eq!(serde_json::from_str::<SealedBytes>(&json).unwrap(), s);
}
#[cfg(feature = "serde")]
#[test]
fn custody__serde_roundtrip_and_rejects_duplicate() {
let mut c = Custody::new();
c.insert(SlotId::new(3), sample()).unwrap();
let json = serde_json::to_string(&c).unwrap();
assert_eq!(serde_json::from_str::<Custody>(&json).unwrap(), c);
let dup = alloc::format!(
"[[3,{0}],[3,{0}]]",
serde_json::to_string(&sample()).unwrap()
);
assert!(serde_json::from_str::<Custody>(&dup).is_err());
}
}