1use crate::{
8 memory::{
9 CANIC_CONTROL_PLANE_MEMORY_AUTHORITY, CANIC_CORE_MEMORY_AUTHORITY,
10 admission::MemoryBootstrapAdmission, registry::MemoryRegistryError,
11 },
12 role_contract::allocation::{
13 CANIC_CONTROL_PLANE_MAX_ID, CANIC_CONTROL_PLANE_MIN_ID, CANIC_CORE_AUTH_MAX_ID,
14 CANIC_CORE_AUTH_MIN_ID, CANIC_CORE_LOWER_MAX_ID, CANIC_CORE_MAX_ID, CANIC_CORE_MIN_ID,
15 CANIC_CORE_UPPER_MIN_ID,
16 memory::control_plane::{
17 FIXTURE_STORE_ID, FLEET_COORDINATOR_ADMISSION_ID, FLEET_COORDINATOR_FUNDING_ID,
18 ROOT_ADMISSION_ID, ROOT_FUNDING_ID,
19 },
20 },
21};
22use ic_memory::{
23 AllocationPolicy, AllocationSlotDescriptor, MemoryManagerAuthorityRecord, MemoryManagerIdRange,
24 MemoryManagerRangeMode, MemoryManagerSlotError, PolicyIdentity, PolicyIdentityError,
25 RuntimeBootstrapPolicy, StableKey,
26};
27
28use sha2::{Digest as _, Sha256};
29
30pub const CANIC_CORE_AUTHORITY_PURPOSE: &str = "Canic core allocation authority";
31pub const CANIC_CONTROL_PLANE_AUTHORITY_PURPOSE: &str = "Canic control-plane allocation authority";
32const CANIC_MEMORY_BOOTSTRAP_POLICY_NAME: &str = "canic.memory-bootstrap-policy";
33const CANIC_MEMORY_BOOTSTRAP_POLICY_VERSION: u32 = 1;
34
35#[derive(Clone, Debug, Default)]
44pub struct CanicMemoryManagerPolicy {
45 admission: Option<MemoryBootstrapAdmission>,
46}
47
48impl CanicMemoryManagerPolicy {
49 #[must_use]
51 pub const fn new() -> Self {
52 Self { admission: None }
53 }
54
55 #[must_use]
57 pub fn with_admission(mut self, admission: MemoryBootstrapAdmission) -> Self {
58 self.admission = Some(admission);
59 self
60 }
61}
62
63impl AllocationPolicy for CanicMemoryManagerPolicy {
64 type Error = MemoryRegistryError;
65
66 fn validate_key(&self, _key: &StableKey) -> Result<(), Self::Error> {
67 Ok(())
68 }
69
70 fn validate_slot(
71 &self,
72 key: &StableKey,
73 slot: &AllocationSlotDescriptor,
74 ) -> Result<(), Self::Error> {
75 let id = slot
76 .memory_manager_id()
77 .map_err(memory_slot_error_to_registry_error)?;
78 validate_key_id_claim(id, key.as_str())
79 }
80
81 fn validate_reserved_slot(
82 &self,
83 key: &StableKey,
84 slot: &AllocationSlotDescriptor,
85 ) -> Result<(), Self::Error> {
86 let id = slot
87 .memory_manager_id()
88 .map_err(memory_slot_error_to_registry_error)?;
89 if !ic_memory::is_ic_memory_stable_key(key.as_str()) && !key.as_str().starts_with("canic.")
90 {
91 return Err(MemoryRegistryError::RangeAuthorityViolation {
92 stable_key: key.as_str().to_string(),
93 id,
94 reason: "application stable keys may not be pre-reserved by Canic",
95 });
96 }
97 validate_key_id_claim(id, key.as_str())
98 }
99}
100
101impl RuntimeBootstrapPolicy for CanicMemoryManagerPolicy {
102 fn prepare_bootstrap(
103 &self,
104 admission: &mut ic_memory::BootstrapAdmission<'_>,
105 ) -> Result<(), Self::Error> {
106 match &self.admission {
107 Some(participant) => (participant.prepare)(admission),
108 None => Ok(()),
109 }
110 }
111
112 fn runtime_bootstrap_identity(&self) -> Result<PolicyIdentity, PolicyIdentityError> {
113 let identity = PolicyIdentity::new(
114 CANIC_MEMORY_BOOTSTRAP_POLICY_NAME,
115 CANIC_MEMORY_BOOTSTRAP_POLICY_VERSION,
116 )?;
117 let Some(participant) = &self.admission else {
118 return Ok(identity);
119 };
120 let mut hash = Sha256::new();
121 hash.update(b"canic:memory-admission:v1");
122 hash.update((participant.identity.name().len() as u64).to_be_bytes());
123 hash.update(participant.identity.name().as_bytes());
124 hash.update(participant.identity.version().to_be_bytes());
125 if let Some(configuration) = participant.identity.configuration_digest() {
126 hash.update([1]);
127 hash.update(configuration);
128 } else {
129 hash.update([0]);
130 }
131 Ok(identity.with_configuration_digest(hash.finalize().into()))
132 }
133}
134
135#[must_use]
137pub fn canonical_authority_records() -> Vec<MemoryManagerAuthorityRecord> {
138 vec![
139 MemoryManagerAuthorityRecord::new(
140 ic_memory::memory_manager_governance_range(),
141 ic_memory::IC_MEMORY_AUTHORITY_OWNER,
142 MemoryManagerRangeMode::Reserved,
143 Some(ic_memory::IC_MEMORY_AUTHORITY_PURPOSE.to_string()),
144 )
145 .expect("valid ic-memory authority record"),
146 MemoryManagerAuthorityRecord::new(
147 canic_control_plane_range(),
148 CANIC_CONTROL_PLANE_MEMORY_AUTHORITY,
149 MemoryManagerRangeMode::Reserved,
150 Some(CANIC_CONTROL_PLANE_AUTHORITY_PURPOSE.to_string()),
151 )
152 .expect("valid Canic control-plane authority record"),
153 MemoryManagerAuthorityRecord::new(
154 canic_core_lower_range(),
155 CANIC_CORE_MEMORY_AUTHORITY,
156 MemoryManagerRangeMode::Reserved,
157 Some(CANIC_CORE_AUTHORITY_PURPOSE.to_string()),
158 )
159 .expect("valid Canic core authority record"),
160 MemoryManagerAuthorityRecord::new(
161 control_plane_infrastructure_range(),
162 CANIC_CONTROL_PLANE_MEMORY_AUTHORITY,
163 MemoryManagerRangeMode::Reserved,
164 Some(CANIC_CONTROL_PLANE_AUTHORITY_PURPOSE.to_string()),
165 )
166 .expect("valid infrastructure control-plane authority record"),
167 MemoryManagerAuthorityRecord::new(
168 canic_core_auth_range(),
169 CANIC_CORE_MEMORY_AUTHORITY,
170 MemoryManagerRangeMode::Reserved,
171 Some(CANIC_CORE_AUTHORITY_PURPOSE.to_string()),
172 )
173 .expect("valid Canic auth authority record"),
174 MemoryManagerAuthorityRecord::new(
175 fixture_store_range(),
176 CANIC_CONTROL_PLANE_MEMORY_AUTHORITY,
177 MemoryManagerRangeMode::Reserved,
178 Some(CANIC_CONTROL_PLANE_AUTHORITY_PURPOSE.to_string()),
179 )
180 .expect("valid fixture Store authority record"),
181 MemoryManagerAuthorityRecord::new(
182 canic_core_upper_range(),
183 CANIC_CORE_MEMORY_AUTHORITY,
184 MemoryManagerRangeMode::Reserved,
185 Some(CANIC_CORE_AUTHORITY_PURPOSE.to_string()),
186 )
187 .expect("valid Canic core authority record"),
188 ]
189}
190
191fn validate_key_id_claim(id: u8, stable_key: &str) -> Result<(), MemoryRegistryError> {
192 if ic_memory::is_ic_memory_stable_key(stable_key) {
193 return Ok(());
194 }
195
196 if stable_key.starts_with("canic.core.") {
197 return require_core_range(id, stable_key);
198 }
199
200 if stable_key.starts_with("canic.control_plane.") {
201 if stable_key == "canic.control_plane.fleet_coordinator.funding.v1" {
202 return require_range(
203 id,
204 stable_key,
205 MemoryManagerIdRange::new(
206 FLEET_COORDINATOR_FUNDING_ID,
207 FLEET_COORDINATOR_FUNDING_ID,
208 )
209 .expect("valid Coordinator funding range"),
210 "the Fleet Coordinator funding key must use reserved id 62",
211 );
212 }
213 if stable_key == "canic.control_plane.root.funding.v1" {
214 return require_range(
215 id,
216 stable_key,
217 MemoryManagerIdRange::new(ROOT_FUNDING_ID, ROOT_FUNDING_ID)
218 .expect("valid Root funding range"),
219 "the Root funding key must use reserved id 63",
220 );
221 }
222 if stable_key == "canic.control_plane.fleet_admission.v1" {
223 return require_range(
224 id,
225 stable_key,
226 MemoryManagerIdRange::new(
227 FLEET_COORDINATOR_ADMISSION_ID,
228 FLEET_COORDINATOR_ADMISSION_ID,
229 )
230 .expect("valid Coordinator admission range"),
231 "the Fleet Coordinator admission key must use reserved id 64",
232 );
233 }
234 if stable_key == "canic.control_plane.root.admission.v1" {
235 return require_range(
236 id,
237 stable_key,
238 MemoryManagerIdRange::new(ROOT_ADMISSION_ID, ROOT_ADMISSION_ID)
239 .expect("valid Root admission range"),
240 "the Root admission key must use reserved id 65",
241 );
242 }
243 if stable_key == "canic.control_plane.fixture_store.v1" {
244 return require_range(
245 id,
246 stable_key,
247 fixture_store_range(),
248 "fixture Store must use its reserved id",
249 );
250 }
251 return require_range(
252 id,
253 stable_key,
254 canic_control_plane_range(),
255 "canic.control_plane.* keys must use Canic control-plane ids 10-29",
256 );
257 }
258
259 if stable_key.starts_with("canic.") {
260 return Err(MemoryRegistryError::RangeAuthorityViolation {
261 stable_key: stable_key.to_string(),
262 id,
263 reason: "unrecognized canic.* stable key namespace",
264 });
265 }
266
267 validate_application_claim(id, stable_key)
268}
269
270fn validate_application_claim(id: u8, stable_key: &str) -> Result<(), MemoryRegistryError> {
271 if ic_memory::memory_manager_governance_range().contains(id)
272 || canic_core_lower_range().contains(id)
273 || control_plane_infrastructure_range().contains(id)
274 || canic_core_upper_range().contains(id)
275 || canic_core_auth_range().contains(id)
276 || fixture_store_range().contains(id)
277 || canic_control_plane_range().contains(id)
278 {
279 return Err(MemoryRegistryError::RangeAuthorityViolation {
280 stable_key: stable_key.to_string(),
281 id,
282 reason: "application keys may not use reserved MemoryManager IDs",
283 });
284 }
285 Ok(())
286}
287
288fn require_range(
289 id: u8,
290 stable_key: &str,
291 range: MemoryManagerIdRange,
292 reason: &'static str,
293) -> Result<(), MemoryRegistryError> {
294 if range.contains(id) {
295 Ok(())
296 } else {
297 Err(MemoryRegistryError::RangeAuthorityViolation {
298 stable_key: stable_key.to_string(),
299 id,
300 reason,
301 })
302 }
303}
304
305fn require_core_range(id: u8, stable_key: &str) -> Result<(), MemoryRegistryError> {
306 if canic_core_lower_range().contains(id)
307 || canic_core_auth_range().contains(id)
308 || canic_core_upper_range().contains(id)
309 {
310 Ok(())
311 } else {
312 Err(MemoryRegistryError::RangeAuthorityViolation {
313 stable_key: stable_key.to_string(),
314 id,
315 reason: "canic.core.* keys must use Canic core ids 30-61, 66-67 or 69-99",
316 })
317 }
318}
319
320fn canic_core_lower_range() -> MemoryManagerIdRange {
321 MemoryManagerIdRange::new(CANIC_CORE_MIN_ID, CANIC_CORE_LOWER_MAX_ID)
322 .expect("valid lower Canic core range")
323}
324
325fn control_plane_infrastructure_range() -> MemoryManagerIdRange {
326 MemoryManagerIdRange::new(FLEET_COORDINATOR_FUNDING_ID, ROOT_ADMISSION_ID)
327 .expect("valid infrastructure control-plane range")
328}
329
330fn canic_core_upper_range() -> MemoryManagerIdRange {
331 MemoryManagerIdRange::new(CANIC_CORE_UPPER_MIN_ID, CANIC_CORE_MAX_ID)
332 .expect("valid upper Canic core range")
333}
334
335fn canic_core_auth_range() -> MemoryManagerIdRange {
336 MemoryManagerIdRange::new(CANIC_CORE_AUTH_MIN_ID, CANIC_CORE_AUTH_MAX_ID)
337 .expect("valid Canic auth range")
338}
339
340fn fixture_store_range() -> MemoryManagerIdRange {
341 MemoryManagerIdRange::new(FIXTURE_STORE_ID, FIXTURE_STORE_ID)
342 .expect("valid fixture Store range")
343}
344
345fn canic_control_plane_range() -> MemoryManagerIdRange {
346 MemoryManagerIdRange::new(CANIC_CONTROL_PLANE_MIN_ID, CANIC_CONTROL_PLANE_MAX_ID)
347 .expect("valid Canic control-plane range")
348}
349
350fn memory_slot_error_to_registry_error(err: MemoryManagerSlotError) -> MemoryRegistryError {
351 match err {
352 MemoryManagerSlotError::InvalidMemoryManagerId { id } => {
353 MemoryRegistryError::InvalidDeclaration {
354 stable_key: "<slot>".to_string(),
355 reason: if id == ic_memory::MEMORY_MANAGER_INVALID_ID {
356 "MemoryManager ID 255 is not usable"
357 } else {
358 "MemoryManager ID is not usable"
359 },
360 }
361 }
362 _ => MemoryRegistryError::InvalidDeclaration {
363 stable_key: "<slot>".to_string(),
364 reason: "unsupported MemoryManager slot error",
365 },
366 }
367}
368
369#[cfg(test)]
374mod tests {
375 use super::*;
376
377 fn policy() -> CanicMemoryManagerPolicy {
378 CanicMemoryManagerPolicy::new()
379 }
380
381 #[test]
382 fn runtime_bootstrap_policy_has_explicit_v1_identity() {
383 assert_eq!(
384 policy()
385 .runtime_bootstrap_identity()
386 .expect("valid Canic policy identity"),
387 PolicyIdentity::new("canic.memory-bootstrap-policy", 1)
388 .expect("valid expected policy identity")
389 );
390 }
391
392 fn key(value: &str) -> StableKey {
393 StableKey::parse(value).expect("stable key")
394 }
395
396 fn slot(id: u8) -> AllocationSlotDescriptor {
397 AllocationSlotDescriptor::memory_manager(id).expect("usable MemoryManager id")
398 }
399
400 #[test]
401 fn rejects_memory_manager_sentinel_id_through_ic_memory() {
402 let err = AllocationSlotDescriptor::memory_manager(ic_memory::MEMORY_MANAGER_INVALID_ID)
403 .expect_err("ID 255 is the unallocated-bucket sentinel");
404 std::assert_matches!(
405 err,
406 MemoryManagerSlotError::InvalidMemoryManagerId { id }
407 if id == ic_memory::MEMORY_MANAGER_INVALID_ID
408 );
409 }
410
411 fn validate(stable_key: &str, id: u8) -> Result<(), MemoryRegistryError> {
412 policy().validate_slot(&key(stable_key), &slot(id))
413 }
414
415 fn validate_reserved(stable_key: &str, id: u8) -> Result<(), MemoryRegistryError> {
416 policy().validate_reserved_slot(&key(stable_key), &slot(id))
417 }
418
419 #[test]
420 fn accepts_canic_framework_namespaces_in_owned_ranges() {
421 validate("canic.control_plane.fixture_store.v1", FIXTURE_STORE_ID)
422 .expect("dedicated fixture Store slot");
423 validate("canic.core.runtime.canister_children.v1", CANIC_CORE_MIN_ID)
424 .expect("first core slot");
425 validate("canic.core.future.v1", CANIC_CORE_MAX_ID).expect("last core slot");
426 validate(
427 "canic.control_plane.template.manifests.v1",
428 CANIC_CONTROL_PLANE_MIN_ID,
429 )
430 .expect("first control-plane slot");
431 validate("canic.control_plane.future.v1", CANIC_CONTROL_PLANE_MAX_ID)
432 .expect("last control-plane slot");
433 validate(
434 "canic.control_plane.fleet_coordinator.funding.v1",
435 FLEET_COORDINATOR_FUNDING_ID,
436 )
437 .expect("dedicated Fleet Coordinator funding slot");
438 validate("canic.control_plane.root.funding.v1", ROOT_FUNDING_ID)
439 .expect("dedicated Root funding slot");
440 validate(
441 "canic.control_plane.fleet_admission.v1",
442 FLEET_COORDINATOR_ADMISSION_ID,
443 )
444 .expect("dedicated Fleet Coordinator admission slot");
445 validate("canic.control_plane.root.admission.v1", ROOT_ADMISSION_ID)
446 .expect("dedicated Root admission slot");
447 }
448
449 #[test]
450 fn rejects_canic_framework_namespaces_outside_owned_ranges() {
451 for invalid_key in [
452 "canic.core.future.v1",
453 "app.fixture.v1",
454 "canic.control_plane.future.v1",
455 ] {
456 std::assert_matches!(
457 validate(invalid_key, FIXTURE_STORE_ID),
458 Err(MemoryRegistryError::RangeAuthorityViolation { .. })
459 );
460 }
461 std::assert_matches!(
462 validate(
463 "canic.control_plane.fixture_store.v1",
464 CANIC_CONTROL_PLANE_MIN_ID
465 ),
466 Err(MemoryRegistryError::RangeAuthorityViolation { .. })
467 );
468 let err = validate("canic.core.fleet.state.v1", CANIC_CONTROL_PLANE_MIN_ID)
469 .expect_err("core key cannot claim control-plane range");
470 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
471
472 let err = validate(
473 "canic.control_plane.template.manifests.v1",
474 CANIC_CORE_MIN_ID,
475 )
476 .expect_err("control-plane key cannot claim core range");
477 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
478
479 let err = validate("canic.core.future.v1", FLEET_COORDINATOR_FUNDING_ID)
480 .expect_err("core key cannot claim the dedicated funding slot");
481 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
482 let err = validate("canic.core.future.v1", ROOT_FUNDING_ID)
483 .expect_err("core key cannot claim the dedicated Root funding slot");
484 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
485 let err = validate("canic.core.future.v1", FLEET_COORDINATOR_ADMISSION_ID)
486 .expect_err("core key cannot claim the dedicated Coordinator admission slot");
487 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
488 let err = validate("canic.core.future.v1", ROOT_ADMISSION_ID)
489 .expect_err("core key cannot claim the dedicated Root admission slot");
490 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
491
492 let err = validate("canic.unknown.state.v1", CANIC_CORE_MAX_ID + 1)
493 .expect_err("unknown canic namespace is reserved");
494 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
495 }
496
497 #[test]
498 fn accepts_application_keys_only_outside_reserved_ranges() {
499 validate("app.users.v1", CANIC_CORE_MAX_ID + 1).expect("application slot");
500 validate("app.archive.v1", ic_memory::MEMORY_MANAGER_MAX_ID).expect("last app slot");
501
502 let err = validate("app.users.v1", CANIC_CORE_MIN_ID)
503 .expect_err("application key cannot claim Canic core range");
504 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
505
506 let err = validate("app.users.v1", CANIC_CONTROL_PLANE_MAX_ID)
507 .expect_err("application key cannot claim Canic control-plane reserve");
508 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
509
510 let err = validate("app.users.v1", ic_memory::MEMORY_MANAGER_LEDGER_ID)
511 .expect_err("application key cannot claim ic-memory governance range");
512 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
513 }
514
515 #[test]
516 fn rejects_application_reservations() {
517 let err = validate_reserved("app.users.v1", CANIC_CORE_MAX_ID + 1)
518 .expect_err("Canic does not pre-reserve application keys");
519 std::assert_matches!(err, MemoryRegistryError::RangeAuthorityViolation { .. });
520 }
521}