use armour_core::GetType;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
pub const UNIQUE_OWNER_KEY_CAP: usize = 56;
#[derive(
Clone, Copy, Debug, PartialEq, Eq, GetType, FromBytes, IntoBytes, Immutable, KnownLayout,
)]
#[repr(C)]
pub struct UniqueRow {
expires_at: u64,
collection_id: u64,
key_len: u8,
_pad: [u8; 7],
key: [u8; UNIQUE_OWNER_KEY_CAP],
}
const _: () = assert!(size_of::<UniqueRow>() == 80);
pub const fn collection_tag(name: &str) -> u64 {
let bytes = name.as_bytes();
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
let mut i = 0;
while i < bytes.len() {
hash ^= bytes[i] as u64;
hash = hash.wrapping_mul(0x1000_0000_01b3);
i += 1;
}
hash
}
impl UniqueRow {
pub(crate) fn new(owner: UniqueOwner) -> Self {
Self {
expires_at: owner.expires_at,
collection_id: owner.tag,
key_len: owner.key_len,
_pad: [0; 7],
key: owner.key,
}
}
pub(crate) fn owner(&self) -> UniqueOwner {
UniqueOwner {
tag: self.collection_id,
key_len: self.key_len,
key: self.key,
expires_at: self.expires_at,
}
}
pub(crate) fn is_well_formed(&self) -> bool {
(self.key_len as usize) <= UNIQUE_OWNER_KEY_CAP
}
pub fn deadline(&self) -> Option<u64> {
(self.expires_at != 0).then_some(self.expires_at)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UniqueOwner {
tag: u64,
key_len: u8,
key: [u8; UNIQUE_OWNER_KEY_CAP],
expires_at: u64,
}
impl UniqueOwner {
pub(crate) fn new(tag: u64, key: &[u8], expires_at: u64) -> Option<Self> {
if key.len() > UNIQUE_OWNER_KEY_CAP {
return None;
}
let mut buf = [0u8; UNIQUE_OWNER_KEY_CAP];
buf[..key.len()].copy_from_slice(key);
Some(Self {
tag,
key_len: key.len() as u8,
key: buf,
expires_at,
})
}
pub fn collection_id(&self) -> u64 {
self.tag
}
pub fn is_reservation(&self) -> bool {
self.expires_at != 0
}
pub fn expires_at(&self) -> Option<u64> {
(self.expires_at != 0).then_some(self.expires_at)
}
pub(crate) fn same_holder(&self, other: &UniqueOwner) -> bool {
self.tag == other.tag && self.key_len == other.key_len && self.key == other.key
}
pub(crate) fn with_deadline(&self, deadline_ms: u64) -> Self {
Self {
expires_at: deadline_ms,
..*self
}
}
pub(crate) fn key_bytes(&self) -> &[u8] {
&self.key[..self.key_len as usize]
}
pub fn belongs_to<T: crate::CollectionMeta>(&self) -> Option<T::SelfId> {
if self.tag != collection_tag(T::NAME) {
return None;
}
if self.key_len as usize != size_of::<T::SelfId>() {
return None;
}
let mut out = <T::SelfId as crate::Key>::zeroed();
<T::SelfId as crate::Key>::as_bytes_mut(&mut out).copy_from_slice(self.key_bytes());
Some(out)
}
}
#[doc(hidden)]
pub fn unique_owner_for<T: crate::CollectionMeta>(key_bytes: &[u8]) -> UniqueOwner {
UniqueOwner::new(collection_tag(T::NAME), key_bytes, 0)
.expect("unique_owner_for: key_bytes must fit UNIQUE_OWNER_KEY_CAP")
}
#[cfg(any(test, feature = "test-faults"))]
#[doc(hidden)]
pub fn corrupt_unique_row() -> UniqueRow {
let mut row = UniqueRow::new(UniqueOwner::new(1, b"x", 0).expect("fits UNIQUE_OWNER_KEY_CAP"));
row.key_len = (UNIQUE_OWNER_KEY_CAP + 1) as u8;
row
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CollectionMeta;
#[test]
fn row_size_is_pinned() {
assert_eq!(size_of::<UniqueRow>(), 80);
assert_eq!(UNIQUE_OWNER_KEY_CAP, 56);
}
#[test]
fn row_owner_roundtrip_preserves_fields() {
let owner = UniqueOwner::new(0xdead_beef, b"hello", 42).unwrap();
let row = UniqueRow::new(owner);
assert!(row.is_well_formed());
assert_eq!(row.deadline(), Some(42));
let back = row.owner();
assert_eq!(back, owner);
assert!(back.same_holder(&owner));
}
#[test]
fn permanent_row_has_no_deadline() {
let owner = UniqueOwner::new(1, b"k", 0).unwrap();
let row = UniqueRow::new(owner);
assert_eq!(row.deadline(), None);
assert!(!row.owner().is_reservation());
}
#[test]
fn owner_new_rejects_oversized_key() {
let key = vec![0u8; UNIQUE_OWNER_KEY_CAP + 1];
assert!(UniqueOwner::new(1, &key, 0).is_none());
}
#[test]
fn is_well_formed_rejects_out_of_range_key_len() {
let owner = UniqueOwner::new(1, b"k", 0).unwrap();
let mut row = UniqueRow::new(owner);
row.key_len = (UNIQUE_OWNER_KEY_CAP + 1) as u8;
assert!(!row.is_well_formed());
}
#[test]
fn corrupt_unique_row_is_actually_corrupt() {
assert!(!corrupt_unique_row().is_well_formed());
}
#[test]
fn with_deadline_changes_only_the_deadline() {
let owner = UniqueOwner::new(7, b"hello", 0).unwrap();
let reserved = owner.with_deadline(42);
assert_eq!(reserved.expires_at(), Some(42));
assert!(reserved.is_reservation());
assert!(
reserved.same_holder(&owner),
"a reservation is still the same holder as the permanent owner it came from"
);
assert_eq!(reserved.collection_id(), owner.collection_id());
}
#[test]
fn collection_tag_is_const_evaluable_and_stable() {
const TAG: u64 = collection_tag("usernames");
assert_eq!(TAG, collection_tag("usernames"));
assert_ne!(TAG, collection_tag("other"));
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
zerocopy::FromBytes,
zerocopy::IntoBytes,
zerocopy::Immutable,
zerocopy::KnownLayout,
)]
#[repr(C, align(16))]
struct OverAlignedKey {
bytes: [u8; 16],
}
crate::impl_key_zerocopy!(
OverAlignedKey,
armour_core::KeyScheme::Typed(&[armour_core::KeyType::Array(16)])
);
struct OverAlignedOwner;
impl armour_core::GetType for OverAlignedOwner {
const TYPE: armour_core::Typ = armour_core::Typ::Custom("over_aligned_owner_test", &[]);
}
impl crate::CollectionMeta for OverAlignedOwner {
type SelfId = OverAlignedKey;
const NAME: &'static str = "over_aligned_owner_test";
}
#[test]
fn belongs_to_decodes_an_over_aligned_key_without_unaligned_read() {
assert!(align_of::<OverAlignedKey>() > align_of::<UniqueOwner>());
let key = OverAlignedKey { bytes: [7u8; 16] };
let owner = UniqueOwner::new(
collection_tag(OverAlignedOwner::NAME),
crate::Key::as_bytes(&key),
0,
)
.unwrap();
let decoded = owner
.belongs_to::<OverAlignedOwner>()
.expect("tag and length match");
assert_eq!(decoded, key);
}
#[test]
fn belongs_to_rejects_wrong_tag_and_wrong_length() {
let key = OverAlignedKey { bytes: [1u8; 16] };
let wrong_tag = UniqueOwner::new(
collection_tag("someone_else"),
crate::Key::as_bytes(&key),
0,
)
.unwrap();
assert!(wrong_tag.belongs_to::<OverAlignedOwner>().is_none());
let wrong_len =
UniqueOwner::new(collection_tag(OverAlignedOwner::NAME), b"short", 0).unwrap();
assert!(wrong_len.belongs_to::<OverAlignedOwner>().is_none());
}
}