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>
109 where
110 M: MemoryBacking,
111 {
112 Self::new_with_config(memory, StorageConfig::default())
113 }
114 pub fn new_with_config(
116 memory: M,
117 config: StorageConfig,
118 ) -> Result<Self, RuntimeConstructionError>
119 where
120 M: MemoryBacking,
121 {
122 let owner = Owner::init(memory, config).map_err(|error| match error {
123 DirectoryError::AlreadyClaimed => RuntimeConstructionError::BackingAlreadyClaimed,
124 error => RuntimeConstructionError::Directory(error),
125 })?;
126 Ok(Self {
127 owner,
128 config,
129 binding: None,
130 })
131 }
132 #[must_use]
134 pub const fn is_bootstrapped(&self) -> bool {
135 self.binding.is_some()
136 }
137 pub fn committed_generation(&self) -> Result<u64, RuntimeOpenError> {
139 if self.binding.is_none() {
140 return Err(RuntimeOpenError::NotBootstrapped);
141 }
142 Ok(self.owner.generation())
143 }
144 #[must_use]
146 pub const fn config(&self) -> StorageConfig {
147 self.config
148 }
149 pub fn bootstrap<P: RuntimeBootstrapPolicy>(
151 &mut self,
152 source: &SealedDeclarationSnapshot,
153 grants: &AllocationGrants,
154 policy: &P,
155 ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
156 if self.binding.is_some() {
157 return self.verify_bootstrap(source, grants, policy);
158 }
159 self.owner.verify()?;
160 let policy_identity = policy.runtime_bootstrap_identity();
161 let bindings = self.owner.bindings();
162 let mut admission =
163 BootstrapAdmission::new(source, grants, &bindings, self.config.allocations)?;
164 let result = policy.prepare_bootstrap(&mut admission);
165 let requests = admission.complete()?;
166 result.map_err(RuntimeBootstrapError::Policy)?;
167 for request in &requests {
168 policy
169 .validate_allocation(request.stable_key())
170 .map_err(RuntimeBootstrapError::Policy)?;
171 }
172 let admitted: BTreeSet<_> = requests
173 .into_iter()
174 .map(|request| request.stable_key().clone())
175 .collect();
176 self.owner.bind(admitted.iter().cloned().collect())?;
177 self.binding = Some(Binding {
178 source: source.clone(),
179 grants: grants.clone(),
180 policy: policy_identity,
181 admitted,
182 });
183 Ok(self.owner.generation())
184 }
185 pub fn verify_bootstrap<P: RuntimeBootstrapPolicy>(
187 &self,
188 source: &SealedDeclarationSnapshot,
189 grants: &AllocationGrants,
190 policy: &P,
191 ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
192 let binding = self
193 .binding
194 .as_ref()
195 .ok_or(RuntimeBootstrapError::NotBootstrapped)?;
196 if binding.source != *source
197 || binding.grants != *grants
198 || binding.policy != policy.runtime_bootstrap_identity()
199 {
200 return Err(RuntimeBootstrapError::BindingMismatch);
201 }
202 Ok(self.owner.generation())
203 }
204 pub fn open_memory(&self, key: &StableKey) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
209 let binding = self
210 .binding
211 .as_ref()
212 .ok_or(RuntimeOpenError::NotBootstrapped)?;
213 if !binding.admitted.contains(key) {
214 return Err(RuntimeOpenError::NotAdmitted(key.clone()));
215 }
216 let lease = self.owner.lease(key).map_err(|error| match error {
217 DirectoryError::AlreadyOpen => RuntimeOpenError::AlreadyOpen {
218 id: self.allocation_id(key).unwrap().get(),
219 },
220 error => RuntimeOpenError::Directory(error),
221 })?;
222 Ok(RuntimeMemory { lease })
223 }
224 pub fn verify_authority(
226 &self,
227 requirements: &SealedDeclarationSnapshot,
228 authority: &str,
229 ) -> Result<(), RuntimeOpenError> {
230 let binding = self
231 .binding
232 .as_ref()
233 .ok_or(RuntimeOpenError::NotBootstrapped)?;
234 for request in requirements.requests() {
235 if request.authority() != authority
236 || !binding.admitted.contains(request.stable_key())
237 || binding.grants.check(request).is_err()
238 {
239 return Err(RuntimeOpenError::NotAdmitted(request.stable_key().clone()));
240 }
241 }
242 Ok(())
243 }
244 #[must_use]
246 pub fn allocation_id(&self, key: &StableKey) -> Option<AllocationId> {
247 self.owner.allocation_id(key)
248 }
249 pub fn memory_allocations(&self) -> Result<Vec<MemoryAllocation>, DirectoryError> {
251 self.owner.verify()?;
252 Ok(self
253 .owner
254 .bindings()
255 .into_iter()
256 .map(|(stable_key, id, pages)| MemoryAllocation {
257 stable_key,
258 id,
259 pages,
260 })
261 .collect())
262 }
263}
264
265#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
271pub struct MemoryAllocation {
272 pub stable_key: StableKey,
274 pub id: AllocationId,
276 pub pages: u64,
278}
279
280pub struct RuntimeMemory<M: Memory> {
288 lease: Lease<M>,
289}
290impl<M: Memory> RuntimeMemory<M> {
291 #[must_use]
293 pub fn size(&self) -> u64 {
294 self.lease.size()
295 }
296 pub fn grow(&self, pages: u64) -> Result<u64, RuntimeGrowError> {
298 self.lease.try_grow(pages).map_err(|error| match error {
299 DirectoryError::ArithmeticOverflow => RuntimeGrowError::ArithmeticOverflow,
300 DirectoryError::Growth => RuntimeGrowError::BackingRefused {
301 additional_pages: pages,
302 },
303 DirectoryError::Budget | DirectoryError::Wire(_) => RuntimeGrowError::CapacityExceeded,
304 DirectoryError::Poisoned => RuntimeGrowError::Poisoned,
305 _ => RuntimeGrowError::InvalidDirectory,
306 })
307 }
308 pub fn read(&self, offset: u64, bytes: &mut [u8]) {
310 self.lease.read(offset, bytes);
311 }
312 pub fn write(&self, offset: u64, bytes: &[u8]) {
314 self.lease.write(offset, bytes);
315 }
316}
317impl<M: Memory> Memory for RuntimeMemory<M> {
318 fn size(&self) -> u64 {
319 self.size()
320 }
321 fn grow(&self, pages: u64) -> i64 {
322 self.grow(pages)
323 .map_or(-1, |previous| i64::try_from(previous).expect("page count"))
324 }
325 fn read(&self, offset: u64, bytes: &mut [u8]) {
326 self.read(offset, bytes);
327 }
328 fn write(&self, offset: u64, bytes: &[u8]) {
329 self.write(offset, bytes);
330 }
331}