mod admission;
mod custody;
mod directory_model;
use crate::{Memory, StableKey};
pub use admission::{
AdmissionError, AllocationGrants, BootstrapAdmission, GenericAllocationPolicy, MemoryAuthority,
MemoryRequest, PolicyIdentity, RuntimeBootstrapPolicy, SealedDeclarationSnapshot,
};
pub use custody::{BackingIdentity, MemoryBacking};
pub use directory_model::Error as DirectoryError;
use directory_model::leases::{Lease, Owner};
pub use directory_model::{AllocationId, StorageConfig};
use std::collections::BTreeSet;
#[derive(Debug, thiserror::Error)]
pub enum RuntimeConstructionError {
#[error("backing already claimed")]
BackingAlreadyClaimed,
#[error(transparent)]
Directory(#[from] DirectoryError),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum RuntimeGrowError {
#[error("allocation page count overflow")]
ArithmeticOverflow,
#[error("allocation capacity exceeded")]
CapacityExceeded,
#[error("backing refused growth by {additional_pages} pages")]
BackingRefused { additional_pages: u64 },
#[error("allocation owner is poisoned")]
Poisoned,
#[error("allocation directory validation failed")]
InvalidDirectory,
}
#[derive(Debug, thiserror::Error)]
pub enum RuntimeOpenError {
#[error("runtime is not bootstrapped")]
NotBootstrapped,
#[error("key is not admitted: {0}")]
NotAdmitted(StableKey),
#[error("allocation {id} already open")]
AlreadyOpen { id: u64 },
#[error(transparent)]
Directory(#[from] DirectoryError),
}
#[derive(Debug, thiserror::Error)]
pub enum RuntimeBootstrapError<E> {
#[error(transparent)]
Admission(#[from] AdmissionError),
#[error("host policy rejected allocation")]
Policy(E),
#[error("bootstrap binding mismatch")]
BindingMismatch,
#[error("runtime is not bootstrapped")]
NotBootstrapped,
#[error(transparent)]
Directory(#[from] DirectoryError),
}
struct Binding {
source: SealedDeclarationSnapshot,
grants: AllocationGrants,
policy: PolicyIdentity,
admitted: BTreeSet<StableKey>,
}
pub struct MemoryRuntime<M: Memory> {
owner: Owner<M>,
config: StorageConfig,
binding: Option<Binding>,
}
impl<M: Memory> MemoryRuntime<M> {
pub fn new(memory: M) -> Result<Self, RuntimeConstructionError>
where
M: MemoryBacking,
{
Self::new_with_config(memory, StorageConfig::default())
}
pub fn new_with_config(
memory: M,
config: StorageConfig,
) -> Result<Self, RuntimeConstructionError>
where
M: MemoryBacking,
{
let owner = Owner::init(memory, config).map_err(|error| match error {
DirectoryError::AlreadyClaimed => RuntimeConstructionError::BackingAlreadyClaimed,
error => RuntimeConstructionError::Directory(error),
})?;
Ok(Self {
owner,
config,
binding: None,
})
}
#[must_use]
pub const fn is_bootstrapped(&self) -> bool {
self.binding.is_some()
}
pub fn committed_generation(&self) -> Result<u64, RuntimeOpenError> {
if self.binding.is_none() {
return Err(RuntimeOpenError::NotBootstrapped);
}
Ok(self.owner.generation())
}
#[must_use]
pub const fn config(&self) -> StorageConfig {
self.config
}
pub fn bootstrap<P: RuntimeBootstrapPolicy>(
&mut self,
source: &SealedDeclarationSnapshot,
grants: &AllocationGrants,
policy: &P,
) -> Result<u64, RuntimeBootstrapError<P::Error>> {
if self.binding.is_some() {
return self.verify_bootstrap(source, grants, policy);
}
self.owner.verify()?;
let policy_identity = policy.runtime_bootstrap_identity();
let bindings = self.owner.bindings();
let mut admission =
BootstrapAdmission::new(source, grants, &bindings, self.config.allocations)?;
let result = policy.prepare_bootstrap(&mut admission);
let requests = admission.complete()?;
result.map_err(RuntimeBootstrapError::Policy)?;
for request in &requests {
policy
.validate_allocation(request.stable_key())
.map_err(RuntimeBootstrapError::Policy)?;
}
let admitted: BTreeSet<_> = requests
.into_iter()
.map(|request| request.stable_key().clone())
.collect();
self.owner.bind(admitted.iter().cloned().collect())?;
self.binding = Some(Binding {
source: source.clone(),
grants: grants.clone(),
policy: policy_identity,
admitted,
});
Ok(self.owner.generation())
}
pub fn verify_bootstrap<P: RuntimeBootstrapPolicy>(
&self,
source: &SealedDeclarationSnapshot,
grants: &AllocationGrants,
policy: &P,
) -> Result<u64, RuntimeBootstrapError<P::Error>> {
let binding = self
.binding
.as_ref()
.ok_or(RuntimeBootstrapError::NotBootstrapped)?;
if binding.source != *source
|| binding.grants != *grants
|| binding.policy != policy.runtime_bootstrap_identity()
{
return Err(RuntimeBootstrapError::BindingMismatch);
}
Ok(self.owner.generation())
}
pub fn open_memory(&self, key: &StableKey) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
let binding = self
.binding
.as_ref()
.ok_or(RuntimeOpenError::NotBootstrapped)?;
if !binding.admitted.contains(key) {
return Err(RuntimeOpenError::NotAdmitted(key.clone()));
}
let lease = self.owner.lease(key).map_err(|error| match error {
DirectoryError::AlreadyOpen => RuntimeOpenError::AlreadyOpen {
id: self.allocation_id(key).unwrap().get(),
},
error => RuntimeOpenError::Directory(error),
})?;
Ok(RuntimeMemory { lease })
}
pub fn verify_authority(
&self,
requirements: &SealedDeclarationSnapshot,
authority: &str,
) -> Result<(), RuntimeOpenError> {
let binding = self
.binding
.as_ref()
.ok_or(RuntimeOpenError::NotBootstrapped)?;
for request in requirements.requests() {
if request.authority() != authority
|| !binding.admitted.contains(request.stable_key())
|| binding.grants.check(request).is_err()
{
return Err(RuntimeOpenError::NotAdmitted(request.stable_key().clone()));
}
}
Ok(())
}
#[must_use]
pub fn allocation_id(&self, key: &StableKey) -> Option<AllocationId> {
self.owner.allocation_id(key)
}
pub fn memory_allocations(&self) -> Result<Vec<MemoryAllocation>, DirectoryError> {
self.owner.verify()?;
Ok(self
.owner
.bindings()
.into_iter()
.map(|(stable_key, id, pages)| MemoryAllocation {
stable_key,
id,
pages,
})
.collect())
}
pub fn diagnostics(&self) -> Result<StorageDiagnostics, DirectoryError> {
let mut report = self.owner.diagnostics()?;
report.admitted_allocations = self.binding.as_ref().map_or(0, |b| b.admitted.len());
Ok(report)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize)]
pub struct StorageDiagnostics {
pub generation: u64,
pub allocations: usize,
pub admitted_allocations: usize,
pub live_leases: usize,
pub extents: usize,
pub data_pages: u64,
pub metadata_pages: u64,
pub directory_bytes: u64,
pub physical_pages: u64,
pub unassigned_pages: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct MemoryAllocation {
pub stable_key: StableKey,
pub id: AllocationId,
pub pages: u64,
}
pub struct RuntimeMemory<M: Memory> {
lease: Lease<M>,
}
impl<M: Memory> RuntimeMemory<M> {
#[must_use]
pub fn size(&self) -> u64 {
self.lease.size()
}
pub fn grow(&self, pages: u64) -> Result<u64, RuntimeGrowError> {
self.lease.try_grow(pages).map_err(|error| match error {
DirectoryError::ArithmeticOverflow => RuntimeGrowError::ArithmeticOverflow,
DirectoryError::Growth => RuntimeGrowError::BackingRefused {
additional_pages: pages,
},
DirectoryError::Budget | DirectoryError::Wire(_) => RuntimeGrowError::CapacityExceeded,
DirectoryError::Poisoned => RuntimeGrowError::Poisoned,
_ => RuntimeGrowError::InvalidDirectory,
})
}
pub fn read(&self, offset: u64, bytes: &mut [u8]) {
self.lease.read(offset, bytes);
}
pub fn write(&self, offset: u64, bytes: &[u8]) {
self.lease.write(offset, bytes);
}
}
impl<M: Memory> Memory for RuntimeMemory<M> {
fn size(&self) -> u64 {
self.size()
}
fn grow(&self, pages: u64) -> i64 {
self.grow(pages)
.map_or(-1, |previous| i64::try_from(previous).expect("page count"))
}
fn read(&self, offset: u64, bytes: &mut [u8]) {
self.read(offset, bytes);
}
fn write(&self, offset: u64, bytes: &[u8]) {
self.write(offset, bytes);
}
}