//! Bounded service-name ownership model. `caller` must come from the trusted //! process boundary; request bytes must never supply their own caller ID. pub const MAX_NAME: usize = 24; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { InvalidName, InvalidEndpoint, Access, Occupied, NotFound } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Name { bytes: [u8; MAX_NAME], len: u8 } impl Name { pub fn parse(input: &[u8]) -> Result { if input.is_empty() || input.len() > MAX_NAME || !input[0].is_ascii_lowercase() || input.iter().any(|byte| !byte.is_ascii_lowercase() && !byte.is_ascii_digit() && *byte != b'-') || input.last() == Some(&b'-') { return Err(Error::InvalidName); } let mut bytes = [0; MAX_NAME]; bytes[..input.len()].copy_from_slice(input); Ok(Self {bytes, len: input.len() as u8}) } pub fn as_bytes(&self) -> &[u8] { &self.bytes[..self.len as usize] } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PublisherId { slot: u32, generation: u64 } impl PublisherId { pub fn new(slot: u32, generation: u64) -> Result { if slot == 0 || generation == 0 { return Err(Error::Access); } Ok(Self {slot, generation}) } pub const fn slot(self) -> u32 { self.slot } pub const fn generation(self) -> u64 { self.generation } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Binding { pub name: Name, pub publisher: PublisherId, pub endpoint: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Grant { pub name: Name, pub publisher: PublisherId } impl Grant { pub const fn new(name: Name, publisher: PublisherId) -> Self { Self {name, publisher} } } #[derive(Clone, Copy)] struct Slot { grant: Grant, endpoint: Option } pub struct Registry { slots: [Slot; N] } impl Registry { /// `grants` are supplied by the trusted startup policy, not a service request. pub fn with_grants(grants: [Grant; N]) -> Result { for (index, grant) in grants.iter().enumerate() { if grants[..index].iter().any(|prior| prior.name == grant.name) { return Err(Error::Occupied); } } Ok(Self {slots: grants.map(|grant| Slot {grant, endpoint: None})}) } pub fn grants(&self) -> [Grant; N] { core::array::from_fn(|index| self.slots[index].grant) } pub fn snapshot(&self) -> [Option; N] { core::array::from_fn(|index| { let slot = self.slots[index]; slot.endpoint.map(|endpoint| Binding { name: slot.grant.name, publisher: slot.grant.publisher, endpoint, }) }) } pub fn count(&self) -> usize { self.slots.iter().filter(|slot| slot.endpoint.is_some()).count() } pub fn lookup(&self, name: Name) -> Result { self.snapshot().into_iter().flatten() .find(|entry| entry.name == name).ok_or(Error::NotFound) } pub fn publish(&mut self, caller: PublisherId, name: Name, endpoint: u64) -> Result { let slot = self.slots.iter_mut().find(|slot| slot.grant.name == name) .ok_or(Error::Access)?; if slot.grant.publisher != caller { return Err(Error::Access); } if endpoint == 0 { return Err(Error::InvalidEndpoint); } if slot.endpoint.is_some() { return Err(Error::Occupied); } let binding = Binding {name, publisher: caller, endpoint}; slot.endpoint = Some(endpoint); Ok(binding) } pub fn unpublish(&mut self, caller: PublisherId, name: Name) -> Result { let slot = self.slots.iter_mut().find(|slot| slot.grant.name == name) .ok_or(Error::NotFound)?; if slot.grant.publisher != caller { return Err(Error::Access); } let endpoint = slot.endpoint.take().ok_or(Error::NotFound)?; let binding = Binding {name, publisher: caller, endpoint}; Ok(binding) } }