1use crate::{
2 constants::DIAGNOSTIC_STRING_MAX_BYTES,
3 declaration::{AllocationDeclaration, DeclarationCollector, DeclarationSnapshot},
4 schema::SchemaMetadata,
5 slot::{
6 IC_MEMORY_AUTHORITY_OWNER, MemoryManagerAuthorityRecord, MemoryManagerIdRange,
7 MemoryManagerRangeAuthority, MemoryManagerRangeAuthorityError, MemoryManagerRangeMode,
8 is_ic_memory_stable_key,
9 },
10};
11use std::sync::{Mutex, MutexGuard};
12
13#[cfg(test)]
14pub static TEST_REGISTRY_LOCK: Mutex<()> = Mutex::new(());
15
16#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct StaticMemoryDeclaration {
28 authority: String,
29 declaration: AllocationDeclaration,
30}
31
32impl StaticMemoryDeclaration {
33 pub fn new(
35 authority: impl Into<String>,
36 declaration: AllocationDeclaration,
37 ) -> Result<Self, StaticMemoryDeclarationError> {
38 let authority = authority.into();
39 validate_external_authority(&authority)?;
40 declaration.validate()?;
41 if is_ic_memory_stable_key(declaration.stable_key().as_str()) {
42 return Err(StaticMemoryDeclarationError::ReservedStableKey {
43 stable_key: declaration.stable_key().as_str().to_string(),
44 });
45 }
46 Ok(Self {
47 authority,
48 declaration,
49 })
50 }
51
52 #[must_use]
54 pub fn authority(&self) -> &str {
55 &self.authority
56 }
57
58 #[must_use]
60 pub const fn declaration(&self) -> &AllocationDeclaration {
61 &self.declaration
62 }
63
64 #[must_use]
66 pub fn into_declaration(self) -> AllocationDeclaration {
67 self.declaration
68 }
69}
70
71#[derive(Clone, Debug, Eq, PartialEq)]
80pub struct StaticMemoryRangeDeclaration {
81 record: MemoryManagerAuthorityRecord,
82}
83
84impl StaticMemoryRangeDeclaration {
85 pub fn new(record: MemoryManagerAuthorityRecord) -> Result<Self, StaticMemoryDeclarationError> {
87 validate_external_authority(record.authority())?;
88 record.validate()?;
89 Ok(Self { record })
90 }
91
92 #[must_use]
94 pub fn authority(&self) -> &str {
95 self.record.authority()
96 }
97
98 #[must_use]
100 pub const fn record(&self) -> &MemoryManagerAuthorityRecord {
101 &self.record
102 }
103
104 #[must_use]
106 pub fn into_record(self) -> MemoryManagerAuthorityRecord {
107 self.record
108 }
109}
110
111#[non_exhaustive]
116#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
117pub enum StaticMemoryDeclarationError {
118 #[error("static memory declaration registry lock poisoned")]
120 RegistryPoisoned,
121 #[error("static memory declaration registry is already sealed")]
123 RegistrySealed,
124 #[error(transparent)]
126 Declaration(#[from] crate::DeclarationSnapshotError),
127 #[error(transparent)]
129 Range(#[from] MemoryManagerRangeAuthorityError),
130 #[error("authority {reason}")]
132 InvalidAuthority {
133 reason: &'static str,
135 },
136 #[error("authority '{authority}' is reserved for ic-memory runtime internals")]
138 ReservedAuthority {
139 authority: String,
141 },
142 #[error("stable key '{stable_key}' is reserved for ic-memory runtime internals")]
144 ReservedStableKey {
145 stable_key: String,
147 },
148}
149
150#[derive(Debug, Default)]
151struct StaticMemoryDeclarationRegistry {
152 declarations: Vec<StaticMemoryDeclaration>,
153 ranges: Vec<StaticMemoryRangeDeclaration>,
154 sealed: bool,
155}
156
157static STATIC_MEMORY_DECLARATIONS: Mutex<StaticMemoryDeclarationRegistry> =
158 Mutex::new(StaticMemoryDeclarationRegistry {
159 declarations: Vec::new(),
160 ranges: Vec::new(),
161 sealed: false,
162 });
163
164fn lock_registry()
165-> Result<MutexGuard<'static, StaticMemoryDeclarationRegistry>, StaticMemoryDeclarationError> {
166 STATIC_MEMORY_DECLARATIONS
167 .lock()
168 .map_err(|_| StaticMemoryDeclarationError::RegistryPoisoned)
169}
170
171const fn ensure_unsealed(
172 registry: &StaticMemoryDeclarationRegistry,
173) -> Result<(), StaticMemoryDeclarationError> {
174 if registry.sealed {
175 return Err(StaticMemoryDeclarationError::RegistrySealed);
176 }
177 Ok(())
178}
179
180fn with_unsealed_registry(
181 op: impl FnOnce(&mut StaticMemoryDeclarationRegistry),
182) -> Result<(), StaticMemoryDeclarationError> {
183 let mut registry = lock_registry()?;
184 ensure_unsealed(®istry)?;
185 op(&mut registry);
186 Ok(())
187}
188
189pub fn register_static_memory_declaration(
191 authority: impl Into<String>,
192 declaration: AllocationDeclaration,
193) -> Result<(), StaticMemoryDeclarationError> {
194 let registration = StaticMemoryDeclaration::new(authority, declaration)?;
195 with_unsealed_registry(|registry| {
196 registry.declarations.push(registration);
197 })
198}
199
200pub fn register_static_memory_manager_range(
202 start: u8,
203 end: u8,
204 authority: impl Into<String>,
205 mode: MemoryManagerRangeMode,
206 purpose: Option<String>,
207) -> Result<(), StaticMemoryDeclarationError> {
208 let authority = authority.into();
209 let record = MemoryManagerAuthorityRecord::new(
210 MemoryManagerIdRange::new(start, end).map_err(MemoryManagerRangeAuthorityError::Range)?,
211 authority,
212 mode,
213 purpose,
214 )?;
215 register_static_memory_range_declaration(StaticMemoryRangeDeclaration::new(record)?)
216}
217
218pub fn register_static_memory_range_declaration(
220 declaration: StaticMemoryRangeDeclaration,
221) -> Result<(), StaticMemoryDeclarationError> {
222 validate_external_authority(declaration.authority())?;
223 with_unsealed_registry(|registry| {
224 registry.ranges.push(declaration);
225 })
226}
227
228fn validate_external_authority(value: &str) -> Result<(), StaticMemoryDeclarationError> {
229 if value == IC_MEMORY_AUTHORITY_OWNER {
230 return Err(StaticMemoryDeclarationError::ReservedAuthority {
231 authority: value.to_string(),
232 });
233 }
234 if value.is_empty() {
235 return Err(StaticMemoryDeclarationError::InvalidAuthority {
236 reason: "must not be empty",
237 });
238 }
239 if value.len() > DIAGNOSTIC_STRING_MAX_BYTES {
240 return Err(StaticMemoryDeclarationError::InvalidAuthority {
241 reason: "must be at most 256 bytes",
242 });
243 }
244 if !value.is_ascii() {
245 return Err(StaticMemoryDeclarationError::InvalidAuthority {
246 reason: "must be ASCII",
247 });
248 }
249 if value.bytes().any(|byte| byte.is_ascii_control()) {
250 return Err(StaticMemoryDeclarationError::InvalidAuthority {
251 reason: "must not contain ASCII control characters",
252 });
253 }
254 Ok(())
255}
256
257pub fn register_static_memory_manager_declaration(
259 id: u8,
260 authority: impl Into<String>,
261 label: impl Into<String>,
262 stable_key: impl AsRef<str>,
263) -> Result<(), StaticMemoryDeclarationError> {
264 register_static_memory_manager_declaration_with_schema(
265 id,
266 authority,
267 label,
268 stable_key,
269 SchemaMetadata::default(),
270 )
271}
272
273pub fn register_static_memory_manager_declaration_with_schema(
275 id: u8,
276 authority: impl Into<String>,
277 label: impl Into<String>,
278 stable_key: impl AsRef<str>,
279 schema: SchemaMetadata,
280) -> Result<(), StaticMemoryDeclarationError> {
281 let declaration =
282 AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)?;
283 register_static_memory_declaration(authority, declaration)
284}
285
286pub fn static_memory_declarations()
288-> Result<Vec<StaticMemoryDeclaration>, StaticMemoryDeclarationError> {
289 Ok(lock_registry()?.declarations.clone())
290}
291
292pub fn static_memory_range_declarations()
294-> Result<Vec<StaticMemoryRangeDeclaration>, StaticMemoryDeclarationError> {
295 Ok(lock_registry()?.ranges.clone())
296}
297
298pub fn static_memory_range_authority()
300-> Result<MemoryManagerRangeAuthority, StaticMemoryDeclarationError> {
301 MemoryManagerRangeAuthority::from_records(
302 static_memory_range_declarations()?
303 .into_iter()
304 .map(StaticMemoryRangeDeclaration::into_record)
305 .collect(),
306 )
307 .map_err(StaticMemoryDeclarationError::Range)
308}
309
310pub fn seal_static_memory_registry() -> Result<(), StaticMemoryDeclarationError> {
312 let mut registry = lock_registry()?;
313 registry.sealed = true;
314 Ok(())
315}
316
317pub fn collect_static_memory_declarations(
319 collector: &mut DeclarationCollector,
320) -> Result<(), StaticMemoryDeclarationError> {
321 for registration in static_memory_declarations()? {
322 collector.push(registration.into_declaration());
323 }
324 Ok(())
325}
326
327pub fn static_memory_declaration_snapshot()
333-> Result<DeclarationSnapshot, StaticMemoryDeclarationError> {
334 let declarations = {
335 let mut registry = lock_registry()?;
336 registry.sealed = true;
337 registry
338 .declarations
339 .iter()
340 .map(|registration| registration.declaration.clone())
341 .collect()
342 };
343 DeclarationSnapshot::new(declarations).map_err(StaticMemoryDeclarationError::Declaration)
344}
345
346#[cfg(test)]
347pub fn reset_static_memory_declarations_for_tests() {
348 let mut registry = STATIC_MEMORY_DECLARATIONS
349 .lock()
350 .expect("static memory declaration registry poisoned");
351 registry.declarations.clear();
352 registry.ranges.clear();
353 registry.sealed = false;
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn registers_and_seals_static_memory_declarations() {
362 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
363 reset_static_memory_declarations_for_tests();
364
365 register_static_memory_manager_declaration(100, "icydb", "users", "icydb.users.data.v1")
366 .expect("register declaration");
367
368 let registrations = static_memory_declarations().expect("registrations");
369 assert_eq!(registrations.len(), 1);
370 assert_eq!(registrations[0].authority(), "icydb");
371 assert_eq!(
372 registrations[0].declaration().stable_key().as_str(),
373 "icydb.users.data.v1"
374 );
375
376 let snapshot = static_memory_declaration_snapshot().expect("snapshot");
377 assert_eq!(snapshot.len(), 1);
378
379 let err =
380 register_static_memory_manager_declaration(101, "icydb", "orders", "icydb.orders.v1")
381 .expect_err("late registration must fail");
382 assert_eq!(err, StaticMemoryDeclarationError::RegistrySealed);
383 }
384
385 #[test]
386 fn registers_static_memory_ranges() {
387 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
388 reset_static_memory_declarations_for_tests();
389
390 register_static_memory_manager_range(
391 100,
392 109,
393 "crate_a",
394 MemoryManagerRangeMode::Reserved,
395 Some("crate A stores".to_string()),
396 )
397 .expect("register range");
398
399 let ranges = static_memory_range_declarations().expect("ranges");
400 assert_eq!(ranges.len(), 1);
401 assert_eq!(ranges[0].authority(), "crate_a");
402 assert_eq!(ranges[0].record().range().start(), 100);
403 assert_eq!(ranges[0].record().range().end(), 109);
404 }
405
406 #[test]
407 fn static_range_declaration_uses_record_authority() {
408 let record = MemoryManagerAuthorityRecord::new(
409 MemoryManagerIdRange::new(100, 109).expect("range"),
410 "record_authority",
411 MemoryManagerRangeMode::Reserved,
412 None,
413 )
414 .expect("record");
415
416 let range = StaticMemoryRangeDeclaration::new(record).expect("external range");
417
418 assert_eq!(range.authority(), "record_authority");
419 }
420
421 #[test]
422 fn static_declaration_rejects_invalid_decoded_declaration() {
423 let mut declaration = AllocationDeclaration::memory_manager("app.users.v1", 100, "users")
424 .expect("declaration");
425 declaration.slot = crate::AllocationSlotDescriptor::memory_manager_unchecked(
426 crate::MEMORY_MANAGER_INVALID_ID,
427 );
428
429 let err = StaticMemoryDeclaration::new("app", declaration)
430 .expect_err("decoded invalid declaration must fail at the registry boundary");
431
432 assert!(matches!(
433 err,
434 StaticMemoryDeclarationError::Declaration(
435 crate::DeclarationSnapshotError::MemoryManagerSlot(
436 crate::MemoryManagerSlotError::InvalidMemoryManagerId { id }
437 )
438 ) if id == crate::MEMORY_MANAGER_INVALID_ID
439 ));
440 }
441
442 #[test]
443 fn static_range_declaration_rejects_invalid_decoded_record() {
444 let mut record = MemoryManagerAuthorityRecord::new(
445 MemoryManagerIdRange::new(100, 109).expect("range"),
446 "app",
447 MemoryManagerRangeMode::Reserved,
448 None,
449 )
450 .expect("record");
451 record.range = MemoryManagerIdRange {
452 start: 109,
453 end: 100,
454 };
455
456 let err = StaticMemoryRangeDeclaration::new(record)
457 .expect_err("decoded invalid range record must fail at the registry boundary");
458
459 assert!(matches!(
460 err,
461 StaticMemoryDeclarationError::Range(MemoryManagerRangeAuthorityError::Range(_))
462 ));
463 }
464
465 #[test]
466 fn snapshot_rejects_duplicate_static_memory_declarations() {
467 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
468 reset_static_memory_declarations_for_tests();
469
470 register_static_memory_manager_declaration(100, "icydb", "users", "icydb.users.data.v1")
471 .expect("register first declaration");
472 register_static_memory_manager_declaration(100, "icydb", "orders", "icydb.orders.v1")
473 .expect("register duplicate slot declaration");
474
475 let err = static_memory_declaration_snapshot().expect_err("duplicate slot must fail");
476 assert!(matches!(
477 err,
478 StaticMemoryDeclarationError::Declaration(
479 crate::DeclarationSnapshotError::DuplicateSlot(_)
480 )
481 ));
482 }
483
484 #[test]
485 fn external_registration_rejects_internal_stable_key_namespace() {
486 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
487 reset_static_memory_declarations_for_tests();
488
489 let err = register_static_memory_manager_declaration(
490 1,
491 "external",
492 "governance",
493 "ic_memory.spoof.v1",
494 )
495 .expect_err("internal stable key must be unavailable externally");
496
497 assert!(matches!(
498 err,
499 StaticMemoryDeclarationError::ReservedStableKey { stable_key }
500 if stable_key == "ic_memory.spoof.v1"
501 ));
502 }
503
504 #[test]
505 fn external_registration_rejects_internal_authority_identity() {
506 let _guard = TEST_REGISTRY_LOCK.lock().expect("test lock poisoned");
507 reset_static_memory_declarations_for_tests();
508
509 let declaration_err = register_static_memory_manager_declaration(
510 100,
511 IC_MEMORY_AUTHORITY_OWNER,
512 "users",
513 "app.users.v1",
514 )
515 .expect_err("internal declaration authority must be unavailable externally");
516 let range_err = register_static_memory_manager_range(
517 100,
518 109,
519 IC_MEMORY_AUTHORITY_OWNER,
520 MemoryManagerRangeMode::Reserved,
521 None,
522 )
523 .expect_err("internal range authority must be unavailable externally");
524
525 assert!(matches!(
526 declaration_err,
527 StaticMemoryDeclarationError::ReservedAuthority { .. }
528 ));
529 assert!(matches!(
530 range_err,
531 StaticMemoryDeclarationError::ReservedAuthority { .. }
532 ));
533 }
534}