use crate::{AllocationId, StableKey};
use std::collections::BTreeSet;
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
pub enum AdmissionError {
#[error("invalid authority or namespace")]
Invalid,
#[error(transparent)]
Key(#[from] crate::StableKeyError),
#[error("duplicate key or overlapping namespace")]
Duplicate,
#[error("namespace is not granted to this authority")]
Unauthorized,
#[error("unknown historical allocation or declaration budget exceeded")]
Limit,
}
fn owner_valid(owner: &str) -> bool {
!owner.is_empty()
&& owner.len() <= 128
&& owner
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MemoryAuthority {
authority: String,
prefix: String,
}
impl MemoryAuthority {
pub fn new(
authority: impl Into<String>,
prefix: impl Into<String>,
) -> Result<Self, AdmissionError> {
let authority = authority.into();
let prefix = prefix.into();
if !owner_valid(&authority) || !crate::key::is_key_prefix(&prefix) {
return Err(AdmissionError::Invalid);
}
Ok(Self { authority, prefix })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AllocationGrants(Vec<MemoryAuthority>);
impl AllocationGrants {
pub fn new(mut grants: Vec<MemoryAuthority>) -> Result<Self, AdmissionError> {
grants.sort_unstable_by(|a, b| a.prefix.cmp(&b.prefix));
if grants
.windows(2)
.any(|pair| pair[1].prefix.starts_with(&pair[0].prefix))
{
return Err(AdmissionError::Duplicate);
}
Ok(Self(grants))
}
pub(super) fn check(&self, request: &MemoryRequest) -> Result<(), AdmissionError> {
if self.0.iter().any(|grant| {
grant.authority == request.authority && request.key.as_str().starts_with(&grant.prefix)
}) {
Ok(())
} else {
Err(AdmissionError::Unauthorized)
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MemoryRequest {
authority: String,
key: StableKey,
}
impl MemoryRequest {
pub fn new(authority: impl Into<String>, key: &str) -> Result<Self, AdmissionError> {
let authority = authority.into();
if !owner_valid(&authority) {
return Err(AdmissionError::Invalid);
}
Ok(Self {
authority,
key: StableKey::parse(key)?,
})
}
#[must_use]
pub fn authority(&self) -> &str {
&self.authority
}
#[must_use]
pub const fn stable_key(&self) -> &StableKey {
&self.key
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SealedDeclarationSnapshot(Vec<MemoryRequest>);
impl SealedDeclarationSnapshot {
pub fn new(mut requests: Vec<MemoryRequest>) -> Result<Self, AdmissionError> {
requests.sort_unstable_by(|a, b| a.key.cmp(&b.key));
if requests.windows(2).any(|pair| pair[0].key == pair[1].key) {
return Err(AdmissionError::Duplicate);
}
Ok(Self(requests))
}
#[must_use]
pub fn requests(&self) -> &[MemoryRequest] {
&self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PolicyIdentity([u8; 32]);
impl PolicyIdentity {
#[must_use]
pub const fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
pub struct BootstrapAdmission<'a> {
grants: &'a AllocationGrants,
recovered: &'a [(StableKey, AllocationId, u64)],
selected: Vec<MemoryRequest>,
keys: BTreeSet<StableKey>,
maximum: usize,
failure: Option<AdmissionError>,
}
impl<'a> BootstrapAdmission<'a> {
pub(super) fn new(
source: &SealedDeclarationSnapshot,
grants: &'a AllocationGrants,
recovered: &'a [(StableKey, AllocationId, u64)],
maximum: usize,
) -> Result<Self, AdmissionError> {
if source.0.len() > maximum {
return Err(AdmissionError::Limit);
}
for request in &source.0 {
grants.check(request)?;
}
Ok(Self {
grants,
recovered,
selected: source.0.clone(),
keys: source.0.iter().map(|request| request.key.clone()).collect(),
maximum,
failure: None,
})
}
#[must_use]
pub const fn recovered_allocations(&self) -> &[(StableKey, AllocationId, u64)] {
self.recovered
}
#[must_use]
pub fn is_declared(&self, key: &StableKey) -> bool {
self.keys.contains(key)
}
pub fn include_historical(&mut self, authority: &str, key: &str) -> Result<(), AdmissionError> {
if let Some(error) = &self.failure {
return Err(error.clone());
}
let result = self.include(authority, key);
if let Err(error) = &result {
self.failure = Some(error.clone());
}
result
}
fn include(&mut self, authority: &str, key: &str) -> Result<(), AdmissionError> {
let request = MemoryRequest::new(authority, key)?;
self.grants.check(&request)?;
if self.selected.len() >= self.maximum
|| !self.recovered.iter().any(|(key, _, _)| key == &request.key)
{
return Err(AdmissionError::Limit);
}
if !self.keys.insert(request.key.clone()) {
return Err(AdmissionError::Duplicate);
}
self.selected.push(request);
Ok(())
}
pub(super) fn complete(self) -> Result<Vec<MemoryRequest>, AdmissionError> {
if let Some(error) = self.failure {
Err(error)
} else {
Ok(self.selected)
}
}
}
pub trait RuntimeBootstrapPolicy {
type Error;
fn validate_allocation(&self, key: &StableKey) -> Result<(), Self::Error>;
fn prepare_bootstrap(
&self,
_admission: &mut BootstrapAdmission<'_>,
) -> Result<(), Self::Error> {
Ok(())
}
fn runtime_bootstrap_identity(&self) -> PolicyIdentity;
}
pub struct GenericAllocationPolicy;
impl RuntimeBootstrapPolicy for GenericAllocationPolicy {
type Error = std::convert::Infallible;
fn validate_allocation(&self, _key: &StableKey) -> Result<(), Self::Error> {
Ok(())
}
fn runtime_bootstrap_identity(&self) -> PolicyIdentity {
PolicyIdentity::new(*b"ic-storage.noop-policy..........")
}
}