1use crate::{
2 declaration::{AllocationDeclaration, DeclarationSnapshot},
3 schema::SchemaMetadata,
4 slot::{
5 IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_LEDGER_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
6 MEMORY_MANAGER_LEDGER_ID, is_ic_memory_stable_key,
7 },
8 text::validate_diagnostic_text,
9};
10use serde::{Deserialize, Serialize};
11use std::{
12 borrow::Cow,
13 panic::{AssertUnwindSafe, catch_unwind},
14 sync::{Arc, Mutex, MutexGuard},
15 thread::ThreadId,
16};
17
18#[cfg(test)]
19pub static TEST_REGISTRY_LOCK: Mutex<()> = Mutex::new(());
20
21#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
29pub struct MemoryRequest {
30 authority: String,
31 stable_key: crate::StableKey,
32 schema: SchemaMetadata,
33}
34
35impl<'de> Deserialize<'de> for MemoryRequest {
36 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
37 use serde::de::Error as _;
38 #[derive(Deserialize)]
39 #[serde(deny_unknown_fields)]
40 struct Request {
41 authority: String,
42 stable_key: crate::StableKey,
43 schema: SchemaMetadata,
44 }
45 let request = Request::deserialize(deserializer)?;
46 Self::new(
47 request.authority,
48 request.stable_key.as_str(),
49 request.schema,
50 )
51 .map_err(D::Error::custom)
52 }
53}
54
55impl MemoryRequest {
56 pub fn new(
58 authority: impl Into<String>,
59 stable_key: &str,
60 schema: SchemaMetadata,
61 ) -> Result<Self, StaticMemoryDeclarationError> {
62 let authority = authority.into();
63 validate_external_authority(&authority)?;
64 let stable_key =
65 crate::StableKey::parse(stable_key).map_err(crate::DeclarationSnapshotError::Key)?;
66 if is_ic_memory_stable_key(stable_key.as_str()) {
67 return Err(StaticMemoryDeclarationError::ReservedStableKey {
68 stable_key: stable_key.as_str().to_string(),
69 });
70 }
71 Ok(Self {
72 authority,
73 stable_key,
74 schema,
75 })
76 }
77
78 pub(crate) const fn with_schema(mut self, schema: SchemaMetadata) -> Self {
80 self.schema = schema;
81 self
82 }
83
84 #[must_use]
86 pub const fn stable_key(&self) -> &crate::StableKey {
87 &self.stable_key
88 }
89
90 #[must_use]
92 pub const fn schema(&self) -> &SchemaMetadata {
93 &self.schema
94 }
95
96 #[must_use]
98 pub fn authority(&self) -> &str {
99 &self.authority
100 }
101}
102
103pub fn register_memory_request(request: MemoryRequest) -> Result<(), StaticMemoryDeclarationError> {
105 with_unsealed_registry(|registry| registry.requests.push(request))
106}
107
108#[non_exhaustive]
113#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
114pub enum StaticMemoryDeclarationError {
115 #[error("at most 254 external requests are supported")]
116 TooManyDeclarations,
117 #[error("duplicate requested stable key {stable_key}")]
118 DuplicateRequest { stable_key: crate::StableKey },
119 #[error("static memory declaration registry lock poisoned")]
121 RegistryPoisoned,
122 #[error("static memory declaration registry is already sealed")]
124 RegistrySealed,
125 #[error("static memory declaration snapshot sealing is already active on this thread")]
127 ReentrantSealing,
128 #[error("static memory declaration eager-init hook panicked")]
130 EagerInitPanicked,
131 #[error(transparent)]
133 Declaration(#[from] crate::DeclarationSnapshotError),
134 #[error("authority {reason}")]
136 InvalidAuthority {
137 reason: &'static str,
139 },
140 #[error("authority '{authority}' is reserved for ic-memory runtime internals")]
142 ReservedAuthority {
143 authority: String,
145 },
146 #[error("stable key '{stable_key}' is reserved for ic-memory runtime internals")]
148 ReservedStableKey {
149 stable_key: String,
151 },
152}
153
154#[derive(Clone, Debug, Eq, PartialEq)]
166pub struct SealedDeclarationSnapshot {
167 inner: Arc<SealedDeclarationSnapshotInner>,
168}
169
170#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
182#[serde(deny_unknown_fields)]
183pub struct SealedDeclarationFingerprint {
184 algorithm_version: u8,
185 value: u64,
186}
187
188impl SealedDeclarationFingerprint {
189 #[must_use]
191 pub const fn algorithm_version(&self) -> u8 {
192 self.algorithm_version
193 }
194
195 #[must_use]
197 pub const fn value(&self) -> u64 {
198 self.value
199 }
200}
201
202#[derive(Debug, Eq, PartialEq)]
203struct SealedDeclarationSnapshotInner {
204 requests: Vec<MemoryRequest>,
205 fingerprint: SealedDeclarationFingerprint,
206}
207
208impl SealedDeclarationSnapshot {
209 pub fn new(requests: &[MemoryRequest]) -> Result<Self, StaticMemoryDeclarationError> {
211 build_snapshot(Cow::Borrowed(requests))
212 }
213
214 #[must_use]
216 pub fn requests(&self) -> &[MemoryRequest] {
217 &self.inner.requests
218 }
219
220 pub(crate) fn resolve(
221 &self,
222 ledger: &crate::AllocationLedger,
223 historical: Vec<MemoryRequest>,
224 pool: &crate::MemoryAllocationPool,
225 ) -> Result<DeclarationSnapshot, crate::MemoryResolutionError> {
226 if self.requests().len() + historical.len() > 254 {
227 return Err(StaticMemoryDeclarationError::TooManyDeclarations.into());
228 }
229 let mut declarations = Vec::with_capacity(self.requests().len() + historical.len() + 1);
230 declarations.push(internal_ledger_declaration());
231 let mut occupied = [false; 255];
232 for record in ledger.records() {
233 occupied[usize::from(record.slot().id())] = true;
234 }
235 for request in self
240 .requests()
241 .iter()
242 .map(Cow::Borrowed)
243 .chain(historical.into_iter().map(Cow::Owned))
244 {
245 let historical = ledger
246 .records()
247 .iter()
248 .find(|record| record.stable_key() == &request.stable_key);
249 pool.validate_authority(&request.stable_key, &request.authority)?;
250 let id = if let Some(record) = historical {
251 pool.validate_id(record.slot().id())?;
254 record.slot().id()
255 } else {
256 (0..crate::MEMORY_MANAGER_INVALID_ID)
257 .find(|id| pool.contains(*id) && !occupied[usize::from(*id)])
258 .ok_or_else(|| crate::MemoryResolutionError::Exhausted {
259 stable_key: request.stable_key.clone(),
260 authority: request.authority.clone(),
261 })?
262 };
263 let slot = crate::MemoryManagerSlot::new(id).expect("usable id");
264 occupied[usize::from(id)] = true;
265 let request = request.into_owned();
270 declarations.push(AllocationDeclaration {
271 stable_key: request.stable_key,
272 slot,
273 label: None,
274 schema: request.schema,
275 });
276 }
277 declarations.sort_unstable_by(|a, b| a.stable_key().cmp(b.stable_key()));
278 Ok(DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::from)?)
279 }
280
281 #[must_use]
283 pub fn fingerprint(&self) -> SealedDeclarationFingerprint {
284 self.inner.fingerprint
285 }
286
287 #[cfg(test)]
288 pub(crate) fn shares_storage_with(&self, other: &Self) -> bool {
289 Arc::ptr_eq(&self.inner, &other.inner)
290 }
291}
292
293type StaticRegistrationHook = fn() -> Result<(), StaticMemoryDeclarationError>;
294
295#[derive(Debug)]
296struct StaticMemoryDeclarationRegistry {
297 requests: Vec<MemoryRequest>,
298 registration_hooks: Vec<StaticRegistrationHook>,
299 eager_init_hooks: Vec<fn()>,
300 lifecycle: StaticRegistryLifecycle,
301}
302
303impl StaticMemoryDeclarationRegistry {
304 fn finish_sealing(
305 &mut self,
306 result: Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError>,
307 ) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
308 self.requests = Vec::new();
310 self.registration_hooks = Vec::new();
311 self.eager_init_hooks = Vec::new();
312 self.lifecycle = match &result {
313 Ok(snapshot) => StaticRegistryLifecycle::Sealed(snapshot.clone()),
314 Err(error) => StaticRegistryLifecycle::Failed(error.clone()),
315 };
316 result
317 }
318}
319
320#[derive(Debug)]
321enum StaticRegistryLifecycle {
322 Open,
323 Sealing {
324 owner: ThreadId,
325 deferred_error: Option<StaticMemoryDeclarationError>,
326 },
327 Sealed(SealedDeclarationSnapshot),
328 Failed(StaticMemoryDeclarationError),
329}
330
331static STATIC_MEMORY_DECLARATIONS: Mutex<StaticMemoryDeclarationRegistry> =
332 Mutex::new(StaticMemoryDeclarationRegistry {
333 requests: Vec::new(),
334 registration_hooks: Vec::new(),
335 eager_init_hooks: Vec::new(),
336 lifecycle: StaticRegistryLifecycle::Open,
337 });
338
339static STATIC_MEMORY_SEAL: Mutex<()> = Mutex::new(());
340
341fn lock_registry()
342-> Result<MutexGuard<'static, StaticMemoryDeclarationRegistry>, StaticMemoryDeclarationError> {
343 STATIC_MEMORY_DECLARATIONS
344 .lock()
345 .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
346}
347
348fn ensure_registration_open(
349 registry: &StaticMemoryDeclarationRegistry,
350) -> Result<(), StaticMemoryDeclarationError> {
351 match ®istry.lifecycle {
352 StaticRegistryLifecycle::Open => Ok(()),
353 StaticRegistryLifecycle::Sealing { owner, .. } if *owner == std::thread::current().id() => {
354 Ok(())
355 }
356 StaticRegistryLifecycle::Sealing { .. }
357 | StaticRegistryLifecycle::Sealed(_)
358 | StaticRegistryLifecycle::Failed(_) => Err(StaticMemoryDeclarationError::RegistrySealed),
359 }
360}
361
362fn with_unsealed_registry(
363 op: impl FnOnce(&mut StaticMemoryDeclarationRegistry),
364) -> Result<(), StaticMemoryDeclarationError> {
365 let mut registry = lock_registry()?;
366 ensure_registration_open(®istry)?;
367 op(&mut registry);
368 Ok(())
369}
370
371#[doc(hidden)]
376pub fn defer_static_memory_registration(hook: StaticRegistrationHook) {
377 defer_constructor_registration(|registry| {
378 registry.registration_hooks.push(hook);
379 });
380}
381
382#[doc(hidden)]
387pub fn defer_eager_init(hook: fn()) {
388 defer_constructor_registration(|registry| {
389 registry.eager_init_hooks.push(hook);
390 });
391}
392
393fn defer_constructor_registration(op: impl FnOnce(&mut StaticMemoryDeclarationRegistry)) {
394 let Ok(mut registry) = STATIC_MEMORY_DECLARATIONS.lock() else {
395 return;
398 };
399 if matches!(registry.lifecycle, StaticRegistryLifecycle::Open) {
400 op(&mut registry);
401 return;
402 }
403 match &mut registry.lifecycle {
404 StaticRegistryLifecycle::Sealing { deferred_error, .. } => {
405 if deferred_error.is_none() {
406 *deferred_error = Some(StaticMemoryDeclarationError::RegistrySealed);
407 }
408 }
409 StaticRegistryLifecycle::Sealed(_) => {
410 registry.lifecycle =
411 StaticRegistryLifecycle::Failed(StaticMemoryDeclarationError::RegistrySealed);
412 }
413 StaticRegistryLifecycle::Failed(_) | StaticRegistryLifecycle::Open => {}
414 }
415}
416
417fn validate_external_authority(value: &str) -> Result<(), StaticMemoryDeclarationError> {
418 reject_internal_authority(value)?;
419 validate_diagnostic_text(value).map_err(|error| {
420 StaticMemoryDeclarationError::InvalidAuthority {
421 reason: error.reason(),
422 }
423 })
424}
425
426fn reject_internal_authority(value: &str) -> Result<(), StaticMemoryDeclarationError> {
427 if value == IC_MEMORY_AUTHORITY_OWNER {
428 return Err(StaticMemoryDeclarationError::ReservedAuthority {
429 authority: value.to_string(),
430 });
431 }
432 Ok(())
433}
434
435pub fn sealed_declaration_snapshot()
446-> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
447 {
448 let registry = lock_registry()?;
449 match ®istry.lifecycle {
450 StaticRegistryLifecycle::Sealed(snapshot) => return Ok(snapshot.clone()),
451 StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
452 StaticRegistryLifecycle::Sealing { owner, .. }
453 if *owner == std::thread::current().id() =>
454 {
455 return Err(StaticMemoryDeclarationError::ReentrantSealing);
456 }
457 StaticRegistryLifecycle::Open | StaticRegistryLifecycle::Sealing { .. } => {}
458 }
459 }
460
461 let _seal = STATIC_MEMORY_SEAL
462 .lock()
463 .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)?;
464 let (registration_hooks, eager_init_hooks) = {
465 let mut registry = lock_registry()?;
466 match ®istry.lifecycle {
467 StaticRegistryLifecycle::Sealed(snapshot) => return Ok(snapshot.clone()),
468 StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
469 StaticRegistryLifecycle::Sealing { .. } => {
470 return Err(StaticMemoryDeclarationError::ReentrantSealing);
471 }
472 StaticRegistryLifecycle::Open => {}
473 }
474 registry.lifecycle = StaticRegistryLifecycle::Sealing {
475 owner: std::thread::current().id(),
476 deferred_error: None,
477 };
478 (
479 std::mem::take(&mut registry.registration_hooks),
480 std::mem::take(&mut registry.eager_init_hooks),
481 )
482 };
483
484 for hook in registration_hooks {
485 let result = catch_unwind(AssertUnwindSafe(hook))
486 .map_err(|_| StaticMemoryDeclarationError::EagerInitPanicked)
487 .and_then(std::convert::identity);
488 if let Err(err) = result {
489 return fail_sealing(err);
490 }
491 }
492 for hook in eager_init_hooks {
493 if catch_unwind(AssertUnwindSafe(hook)).is_err() {
494 return fail_sealing(StaticMemoryDeclarationError::EagerInitPanicked);
495 }
496 }
497
498 let mut registry = lock_registry()?;
499 let deferred_error = match ®istry.lifecycle {
500 StaticRegistryLifecycle::Sealing { deferred_error, .. } => deferred_error.clone(),
501 StaticRegistryLifecycle::Failed(err) => return Err(err.clone()),
502 StaticRegistryLifecycle::Open | StaticRegistryLifecycle::Sealed(_) => {
503 unreachable!("seal lock preserves the in-progress registry lifecycle");
504 }
505 };
506 let result = match deferred_error {
507 Some(error) => Err(error),
508 None => build_snapshot(Cow::Owned(std::mem::take(&mut registry.requests))),
509 };
510 registry.finish_sealing(result)
511}
512
513fn fail_sealing(
514 err: StaticMemoryDeclarationError,
515) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
516 let mut registry = lock_registry()?;
517 let failure = match ®istry.lifecycle {
518 StaticRegistryLifecycle::Sealing {
519 deferred_error: Some(deferred_error),
520 ..
521 } => deferred_error.clone(),
522 StaticRegistryLifecycle::Open
523 | StaticRegistryLifecycle::Sealing {
524 deferred_error: None,
525 ..
526 }
527 | StaticRegistryLifecycle::Sealed(_) => err,
528 StaticRegistryLifecycle::Failed(failure) => failure.clone(),
529 };
530 registry.finish_sealing(Err(failure))
531}
532
533fn build_snapshot(
534 requests: Cow<'_, [MemoryRequest]>,
535) -> Result<SealedDeclarationSnapshot, StaticMemoryDeclarationError> {
536 if requests.len() > 254 {
537 return Err(StaticMemoryDeclarationError::TooManyDeclarations);
538 }
539 let mut requests = requests.into_owned();
540 requests.sort_unstable_by(|a, b| a.stable_key.cmp(&b.stable_key));
541 for pair in requests.windows(2) {
542 if pair[0].stable_key == pair[1].stable_key {
543 return Err(StaticMemoryDeclarationError::DuplicateRequest {
544 stable_key: pair[1].stable_key.clone(),
545 });
546 }
547 }
548 let fingerprint = sealed_declaration_fingerprint(&requests);
549 Ok(SealedDeclarationSnapshot {
550 inner: Arc::new(SealedDeclarationSnapshotInner {
551 requests,
552 fingerprint,
553 }),
554 })
555}
556
557#[derive(Serialize)]
558struct SealedDeclarationFingerprintMaterial<'a> {
559 format: &'static str,
560 requests: &'a [MemoryRequest],
561}
562
563struct FingerprintWriter(u64);
566
567impl std::io::Write for FingerprintWriter {
568 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
569 self.0 = crate::hash::fnv64(self.0, bytes);
570 Ok(bytes.len())
571 }
572
573 fn flush(&mut self) -> std::io::Result<()> {
574 Ok(())
575 }
576}
577
578fn sealed_declaration_fingerprint(requests: &[MemoryRequest]) -> SealedDeclarationFingerprint {
579 let material = SealedDeclarationFingerprintMaterial {
580 format: "ic-memory.sealed-declaration-fingerprint.v1",
581 requests,
582 };
583 let mut writer = FingerprintWriter(crate::hash::FNV_OFFSET);
584 ciborium::into_writer(&material, &mut writer)
586 .expect("sealed declaration fingerprint encodes into hash");
587
588 SealedDeclarationFingerprint {
589 algorithm_version: SEALED_DECLARATION_FINGERPRINT_VERSION,
590 value: writer.0,
591 }
592}
593
594const SEALED_DECLARATION_FINGERPRINT_VERSION: u8 = 1;
595
596fn internal_ledger_declaration() -> AllocationDeclaration {
597 AllocationDeclaration::memory_manager(
598 IC_MEMORY_LEDGER_STABLE_KEY,
599 MEMORY_MANAGER_LEDGER_ID,
600 IC_MEMORY_LEDGER_LABEL,
601 )
602 .unwrap_or_else(|_| unreachable!("built-in ledger declaration constants are valid"))
603}
604
605#[cfg(test)]
606pub fn reset_static_memory_declarations_for_tests() {
607 let mut registry = STATIC_MEMORY_DECLARATIONS
608 .lock()
609 .expect("static memory declaration registry poisoned");
610 registry.requests.clear();
611 registry.registration_hooks.clear();
612 registry.eager_init_hooks.clear();
613 registry.lifecycle = StaticRegistryLifecycle::Open;
614}
615
616#[cfg(test)]
617mod tests;