1mod admission;
3mod custody;
4mod directory_model;
5
6use crate::{Memory, StableKey};
7pub use admission::{
8 AdmissionError, AllocationGrants, BootstrapAdmission, GenericAllocationPolicy, MemoryAuthority,
9 MemoryRequest, PolicyIdentity, RuntimeBootstrapPolicy, SealedDeclarationSnapshot,
10};
11pub use custody::{BackingIdentity, MemoryBacking};
12pub use directory_model::Error as DirectoryError;
13use directory_model::leases::{Lease, Owner};
14pub use directory_model::{AllocationId, StorageConfig};
15use std::collections::BTreeSet;
16
17#[derive(Debug, thiserror::Error)]
19pub enum RuntimeConstructionError {
20 #[error("backing already claimed")]
22 BackingAlreadyClaimed,
23 #[error(transparent)]
25 Directory(#[from] DirectoryError),
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
30pub enum RuntimeGrowError {
31 #[error("allocation page count overflow")]
33 ArithmeticOverflow,
34 #[error("allocation capacity exceeded")]
36 CapacityExceeded,
37 #[error("backing refused growth by {additional_pages} pages")]
39 BackingRefused { additional_pages: u64 },
40 #[error("allocation owner is poisoned")]
42 Poisoned,
43 #[error("allocation directory validation failed")]
45 InvalidDirectory,
46}
47
48#[derive(Debug, thiserror::Error)]
50pub enum RuntimeOpenError {
51 #[error("runtime is not bootstrapped")]
53 NotBootstrapped,
54 #[error("key is not admitted: {0}")]
56 NotAdmitted(StableKey),
57 #[error("allocation {id} already open")]
59 AlreadyOpen { id: u64 },
60 #[error(transparent)]
62 Directory(#[from] DirectoryError),
63}
64
65#[derive(Debug, thiserror::Error)]
67pub enum RuntimeBootstrapError<E> {
68 #[error(transparent)]
70 Admission(#[from] AdmissionError),
71 #[error("host policy rejected allocation")]
73 Policy(E),
74 #[error("bootstrap binding mismatch")]
76 BindingMismatch,
77 #[error("runtime is not bootstrapped")]
79 NotBootstrapped,
80 #[error(transparent)]
82 Directory(#[from] DirectoryError),
83}
84
85struct Binding {
86 source: SealedDeclarationSnapshot,
87 grants: AllocationGrants,
88 policy: PolicyIdentity,
89 admitted: BTreeSet<StableKey>,
90}
91
92pub struct MemoryRuntime<M: Memory> {
101 owner: Owner<M>,
102 config: StorageConfig,
103 binding: Option<Binding>,
104}
105
106impl<M: Memory> MemoryRuntime<M> {
107 pub fn new(memory: M) -> Result<Self, RuntimeConstructionError>
112 where
113 M: MemoryBacking,
114 {
115 Self::new_with_config(memory, StorageConfig::default())
116 }
117 pub fn new_with_config(
122 memory: M,
123 config: StorageConfig,
124 ) -> Result<Self, RuntimeConstructionError>
125 where
126 M: MemoryBacking,
127 {
128 let owner = Owner::init(memory, config).map_err(|error| match error {
129 DirectoryError::AlreadyClaimed => RuntimeConstructionError::BackingAlreadyClaimed,
130 error => RuntimeConstructionError::Directory(error),
131 })?;
132 Ok(Self {
133 owner,
134 config,
135 binding: None,
136 })
137 }
138 #[must_use]
140 pub const fn is_bootstrapped(&self) -> bool {
141 self.binding.is_some()
142 }
143 pub fn committed_generation(&self) -> Result<u64, RuntimeOpenError> {
148 if self.binding.is_none() {
149 return Err(RuntimeOpenError::NotBootstrapped);
150 }
151 Ok(self.owner.generation())
152 }
153 #[must_use]
155 pub const fn config(&self) -> StorageConfig {
156 self.config
157 }
158 pub fn bootstrap<P: RuntimeBootstrapPolicy>(
163 &mut self,
164 source: &SealedDeclarationSnapshot,
165 grants: &AllocationGrants,
166 policy: &P,
167 ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
168 if self.binding.is_some() {
169 return self.verify_bootstrap(source, grants, policy);
170 }
171 self.owner.verify()?;
172 let policy_identity = policy.runtime_bootstrap_identity();
173 let bindings = self.owner.bindings();
174 let mut admission =
175 BootstrapAdmission::new(source, grants, &bindings, self.config.allocations)?;
176 let result = policy.prepare_bootstrap(&mut admission);
177 let requests = admission.complete()?;
178 result.map_err(RuntimeBootstrapError::Policy)?;
179 for request in &requests {
180 policy
181 .validate_allocation(request.stable_key())
182 .map_err(RuntimeBootstrapError::Policy)?;
183 }
184 let admitted: BTreeSet<_> = requests
185 .into_iter()
186 .map(|request| request.stable_key().clone())
187 .collect();
188 self.owner.bind(admitted.iter().cloned().collect())?;
189 self.binding = Some(Binding {
190 source: source.clone(),
191 grants: grants.clone(),
192 policy: policy_identity,
193 admitted,
194 });
195 Ok(self.owner.generation())
196 }
197 pub fn verify_bootstrap<P: RuntimeBootstrapPolicy>(
202 &self,
203 source: &SealedDeclarationSnapshot,
204 grants: &AllocationGrants,
205 policy: &P,
206 ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
207 let binding = self
208 .binding
209 .as_ref()
210 .ok_or(RuntimeBootstrapError::NotBootstrapped)?;
211 if binding.source != *source
212 || binding.grants != *grants
213 || binding.policy != policy.runtime_bootstrap_identity()
214 {
215 return Err(RuntimeBootstrapError::BindingMismatch);
216 }
217 Ok(self.owner.generation())
218 }
219 pub fn open_memory(&self, key: &StableKey) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
227 let binding = self
228 .binding
229 .as_ref()
230 .ok_or(RuntimeOpenError::NotBootstrapped)?;
231 if !binding.admitted.contains(key) {
232 return Err(RuntimeOpenError::NotAdmitted(key.clone()));
233 }
234 let lease = self.owner.lease(key).map_err(|error| match error {
235 DirectoryError::AlreadyOpen => RuntimeOpenError::AlreadyOpen {
236 id: self.allocation_id(key).unwrap().get(),
237 },
238 error => RuntimeOpenError::Directory(error),
239 })?;
240 Ok(RuntimeMemory { lease })
241 }
242 pub fn verify_authority(
247 &self,
248 requirements: &SealedDeclarationSnapshot,
249 authority: &str,
250 ) -> Result<(), RuntimeOpenError> {
251 let binding = self
252 .binding
253 .as_ref()
254 .ok_or(RuntimeOpenError::NotBootstrapped)?;
255 for request in requirements.requests() {
256 if request.authority() != authority
257 || !binding.admitted.contains(request.stable_key())
258 || binding.grants.check(request).is_err()
259 {
260 return Err(RuntimeOpenError::NotAdmitted(request.stable_key().clone()));
261 }
262 }
263 Ok(())
264 }
265 #[must_use]
267 pub fn allocation_id(&self, key: &StableKey) -> Option<AllocationId> {
268 self.owner.allocation_id(key)
269 }
270 pub fn memory_allocations(&self) -> Result<Vec<MemoryAllocation>, DirectoryError> {
275 self.owner.verify()?;
276 Ok(self
277 .owner
278 .bindings()
279 .into_iter()
280 .map(|(stable_key, id, pages)| MemoryAllocation {
281 stable_key,
282 id,
283 pages,
284 })
285 .collect())
286 }
287
288 pub fn diagnostics(&self) -> Result<StorageDiagnostics, DirectoryError> {
295 let mut report = self.owner.diagnostics()?;
296 report.admitted_allocations = self.binding.as_ref().map_or(0, |b| b.admitted.len());
297 Ok(report)
298 }
299}
300
301#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize)]
311pub struct StorageDiagnostics {
312 pub generation: u64,
314 pub allocations: usize,
316 pub admitted_allocations: usize,
318 pub live_leases: usize,
320 pub extents: usize,
322 pub data_pages: u64,
324 pub metadata_pages: u64,
326 pub directory_bytes: u64,
328 pub physical_pages: u64,
330 pub unassigned_pages: u64,
332}
333
334#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
340pub struct MemoryAllocation {
341 pub stable_key: StableKey,
343 pub id: AllocationId,
345 pub pages: u64,
347}
348
349pub struct RuntimeMemory<M: Memory> {
357 lease: Lease<M>,
358}
359impl<M: Memory> RuntimeMemory<M> {
360 #[cfg(not(target_arch = "wasm32"))]
361 pub(crate) fn poison_on_unwind(&self) -> directory_model::leases::PanicGuard<M> {
362 self.lease.poison_on_unwind()
363 }
364 #[must_use]
366 pub fn size(&self) -> u64 {
367 self.lease.size()
368 }
369 pub fn grow(&self, pages: u64) -> Result<u64, RuntimeGrowError> {
374 self.lease.try_grow(pages).map_err(|error| match error {
375 DirectoryError::ArithmeticOverflow => RuntimeGrowError::ArithmeticOverflow,
376 DirectoryError::Growth => RuntimeGrowError::BackingRefused {
377 additional_pages: pages,
378 },
379 DirectoryError::Budget | DirectoryError::Wire(_) => RuntimeGrowError::CapacityExceeded,
380 DirectoryError::Poisoned => RuntimeGrowError::Poisoned,
381 _ => RuntimeGrowError::InvalidDirectory,
382 })
383 }
384 pub fn read(&self, offset: u64, bytes: &mut [u8]) {
386 self.lease.read(offset, bytes);
387 }
388 pub fn write(&self, offset: u64, bytes: &[u8]) {
390 self.lease.write(offset, bytes);
391 }
392}
393impl<M: Memory> Memory for RuntimeMemory<M> {
394 fn size(&self) -> u64 {
395 self.size()
396 }
397 fn grow(&self, pages: u64) -> i64 {
398 self.grow(pages)
399 .map_or(-1, |previous| i64::try_from(previous).expect("page count"))
400 }
401 fn read(&self, offset: u64, bytes: &mut [u8]) {
402 self.read(offset, bytes);
403 }
404 #[expect(
405 unsafe_code,
406 reason = "Forward the raw-read contract through the governed lease"
407 )]
408 unsafe fn read_unsafe(&self, offset: u64, dst: *mut u8, count: usize) {
409 unsafe { self.lease.read_unsafe(offset, dst, count) }
411 }
412 fn write(&self, offset: u64, bytes: &[u8]) {
413 self.write(offset, bytes);
414 }
415}