use crate::error::CacheError;
use crate::identity::CacheContext;
use crate::key::Key;
use crate::telemetry::KeyIdentity;
const MANAGER_KEY_BUDGET: usize = 256;
#[derive(Debug, Clone)]
pub struct CensusKey<K> {
pub key: K,
pub framed: Vec<u8>,
pub identity: KeyIdentity,
}
pub struct KeyCensus<K> {
context: CacheContext,
entries: std::sync::RwLock<Vec<CensusKey<K>>>,
}
impl<K: Key> KeyCensus<K> {
#[must_use]
pub fn new(context: CacheContext) -> Self {
KeyCensus {
context,
entries: std::sync::RwLock::new(Vec::new()),
}
}
pub fn register(&self, key: K) -> Result<CensusKey<K>, CacheError> {
let mut raw = [0_u8; MANAGER_KEY_BUDGET];
let encoded = key.encode(&mut raw)?;
let tenant = self.context.identity().tenant.as_str();
let mut framed = [0_u8; MANAGER_KEY_BUDGET];
let len = crate::key::frame_tenant_key(tenant, &raw[..encoded], &mut framed)?;
let framed = framed[..len].to_vec();
let identity = KeyIdentity::of(&framed);
let entry = CensusKey {
key,
framed,
identity,
};
match self.entries.write() {
Ok(mut entries) => {
entries.push(entry.clone());
Ok(entry)
}
Err(_) => Err(CacheError::ConfigurationError),
}
}
#[must_use]
pub fn entries(&self) -> Vec<CensusKey<K>> {
self.entries.read().map(|e| e.clone()).unwrap_or_default()
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.read().map_or(0, |e| e.len())
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn context(&self) -> &CacheContext {
&self.context
}
}
impl<K: Key> Default for KeyCensus<K> {
fn default() -> Self {
KeyCensus::new(CacheContext::anonymous())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx() -> CacheContext {
CacheContext::new(crate::identity::IdentityContext::new(
"governor-ops".to_string(),
vec!["maintenance".to_string()],
"tenant-a".to_string(),
))
}
#[test]
fn registration_frames_the_key_and_matches_telemetry_identity() {
testkit::proves!("autonomy.movement.has_no_key_of_its_own");
let census = KeyCensus::<String>::new(ctx());
let entry = census.register("order-42".to_string()).expect("register");
let mut expected = [0_u8; MANAGER_KEY_BUDGET];
let len =
crate::key::frame_tenant_key("tenant-a", b"order-42", &mut expected).expect("frame");
assert_eq!(entry.framed, expected[..len]);
assert_eq!(entry.identity, KeyIdentity::of(&entry.framed));
}
#[test]
fn two_tenants_do_not_share_a_census_identity() {
let a = KeyCensus::<String>::new(ctx());
let b_ctx = CacheContext::new(crate::identity::IdentityContext::new(
"governor-ops".to_string(),
vec!["maintenance".to_string()],
"tenant-b".to_string(),
));
let b = KeyCensus::<String>::new(b_ctx);
let ea = a.register("k".to_string()).expect("a");
let eb = b.register("k".to_string()).expect("b");
assert_ne!(ea.identity, eb.identity, "the tenant frames the identity");
assert_ne!(ea.framed, eb.framed);
}
#[test]
fn an_oversized_key_is_refused_not_silently_admitted() {
let census = KeyCensus::<String>::new(ctx());
let oversized = "x".repeat(MANAGER_KEY_BUDGET);
assert_eq!(
census.register(oversized).map(|_| ()).unwrap_err(),
CacheError::ConfigurationError,
"a frame the manager itself could not encode must not enter the census"
);
assert_eq!(census.len(), 0);
}
#[test]
fn entries_preserve_registration_order() {
let census = KeyCensus::<String>::new(ctx());
for i in 0..5 {
census.register(format!("k{i}")).expect("register");
}
let entries = census.entries();
assert_eq!(entries.len(), 5);
assert_eq!(entries[0].key, "k0");
assert_eq!(entries[4].key, "k4");
}
}