1use super::{ledger, policy};
2use ic_memory::{AllocationDeclaration, DeclarationSnapshotError, SchemaMetadata};
3#[cfg(test)]
4use std::cell::RefCell;
5use std::sync::Mutex;
6use thiserror::Error as ThisError;
7
8#[derive(Clone, Debug, Eq, PartialEq)]
15pub(crate) struct PendingRegistration {
16 crate_name: String,
17 declaration: AllocationDeclaration,
18}
19
20impl PendingRegistration {
21 fn from_parts(
22 id: u8,
23 crate_name: &str,
24 label: &str,
25 stable_key: &str,
26 schema_version: Option<u32>,
27 schema_fingerprint: Option<&str>,
28 ) -> Result<Self, MemoryRegistryError> {
29 let schema = SchemaMetadata::new(schema_version, schema_fingerprint.map(str::to_string))
30 .map_err(|_| MemoryRegistryError::InvalidDeclaration {
31 stable_key: stable_key.to_string(),
32 reason: "schema metadata rejected by ic-memory",
33 })?;
34 let declaration =
35 AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)
36 .map_err(|err| memory_registry_error_from_declaration_error(err, stable_key))?;
37 policy::validate_stable_key_authority(id, crate_name, stable_key)?;
38
39 Ok(Self {
40 crate_name: crate_name.to_string(),
41 declaration,
42 })
43 }
44
45 pub(crate) fn internal_layout_ledger() -> Result<Self, MemoryRegistryError> {
46 Self::from_parts(
47 ledger::MEMORY_LAYOUT_LEDGER_ID,
48 ledger::MEMORY_LAYOUT_LEDGER_OWNER,
49 ledger::MEMORY_LAYOUT_LEDGER_LABEL,
50 ledger::MEMORY_LAYOUT_LEDGER_STABLE_KEY,
51 None,
52 None,
53 )
54 }
55
56 pub(crate) fn crate_name(&self) -> &str {
57 &self.crate_name
58 }
59
60 pub(crate) const fn declaration(&self) -> &AllocationDeclaration {
61 &self.declaration
62 }
63}
64
65#[derive(Debug, ThisError)]
71pub enum MemoryRegistryError {
72 #[error("memory declaration rejected for stable key '{stable_key}': {reason}")]
74 InvalidDeclaration {
75 stable_key: String,
76 reason: &'static str,
77 },
78
79 #[error(
81 "memory stable key '{stable_key}' with id {id} violates namespace/range authority: {reason}"
82 )]
83 RangeAuthorityViolation {
84 stable_key: String,
86 id: u8,
88 reason: &'static str,
90 },
91
92 #[error(
94 "memory registration after bootstrap is sealed is not allowed: {registrations} registration(s)"
95 )]
96 RegistrationAfterBootstrap {
97 registrations: usize,
99 },
100
101 #[error("memory registry has not completed bootstrap validation")]
103 RegistryNotBootstrapped,
104
105 #[error("memory layout ledger is corrupt: {reason}")]
107 LedgerCorrupt {
108 reason: &'static str,
110 },
111}
112
113#[cfg(test)]
114thread_local! {
115 static PENDING_REGISTRATIONS: RefCell<Vec<PendingRegistration>> = const { RefCell::new(Vec::new()) };
116}
117
118static STATIC_DECLARATIONS: Mutex<Vec<PendingRegistration>> = Mutex::new(Vec::new());
119
120#[doc(hidden)]
122pub fn declare_memory_slot_with_key(
123 id: u8,
124 crate_name: &str,
125 label: &str,
126 stable_key: &str,
127) -> Result<(), MemoryRegistryError> {
128 declare_memory_slot_with_key_metadata(id, crate_name, label, stable_key, None, None)
129}
130
131#[doc(hidden)]
133pub fn declare_memory_slot_with_key_metadata(
134 id: u8,
135 crate_name: &str,
136 label: &str,
137 stable_key: &str,
138 schema_version: Option<u32>,
139 schema_fingerprint: Option<&str>,
140) -> Result<(), MemoryRegistryError> {
141 let registration = PendingRegistration::from_parts(
142 id,
143 crate_name,
144 label,
145 stable_key,
146 schema_version,
147 schema_fingerprint,
148 )?;
149
150 STATIC_DECLARATIONS
151 .lock()
152 .expect("static memory declaration queue poisoned")
153 .push(registration);
154
155 Ok(())
156}
157
158#[cfg(test)]
159pub fn defer_register_with_key(
160 id: u8,
161 crate_name: &str,
162 label: &str,
163 stable_key: &str,
164) -> Result<(), MemoryRegistryError> {
165 defer_register_with_key_metadata(id, crate_name, label, stable_key, None, None)
166}
167
168#[cfg(test)]
169pub fn defer_register_with_key_metadata(
170 id: u8,
171 crate_name: &str,
172 label: &str,
173 stable_key: &str,
174 schema_version: Option<u32>,
175 schema_fingerprint: Option<&str>,
176) -> Result<(), MemoryRegistryError> {
177 let registration = PendingRegistration::from_parts(
178 id,
179 crate_name,
180 label,
181 stable_key,
182 schema_version,
183 schema_fingerprint,
184 )?;
185
186 PENDING_REGISTRATIONS.with_borrow_mut(|regs| {
187 regs.push(registration);
188 });
189
190 Ok(())
191}
192
193#[must_use]
195pub(crate) fn static_declarations() -> Vec<PendingRegistration> {
196 STATIC_DECLARATIONS
197 .lock()
198 .expect("static memory declaration queue poisoned")
199 .clone()
200}
201
202#[cfg(test)]
203pub(crate) fn drain_pending_registrations() -> Vec<PendingRegistration> {
204 PENDING_REGISTRATIONS.with_borrow_mut(std::mem::take)
205}
206
207#[cfg(test)]
208pub fn reset_for_tests() {
210 reset_runtime_for_tests();
211 ledger::reset_for_tests();
212 super::runtime::registry::reset_initialized_for_tests();
213}
214
215#[cfg(test)]
216fn reset_runtime_for_tests() {
217 PENDING_REGISTRATIONS.with_borrow_mut(Vec::clear);
218}
219
220pub(super) fn memory_registry_error_from_declaration_error(
221 err: DeclarationSnapshotError,
222 stable_key: &str,
223) -> MemoryRegistryError {
224 let (stable_key, reason) = if let DeclarationSnapshotError::Key(err) = err {
225 (err.stable_key, err.reason)
226 } else {
227 (stable_key.to_string(), "declaration rejected by ic-memory")
228 };
229
230 MemoryRegistryError::InvalidDeclaration { stable_key, reason }
231}
232
233#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn defers_valid_application_declaration() {
243 reset_for_tests();
244
245 defer_register_with_key(100, "crate_a", "slot", "app.crate_a.slot.v1")
246 .expect("register valid app slot");
247
248 let registrations = drain_pending_registrations();
249 assert_eq!(registrations.len(), 1);
250 assert_eq!(
251 registrations[0].declaration().stable_key().as_str(),
252 "app.crate_a.slot.v1"
253 );
254 }
255
256 #[test]
257 fn rejects_ic_memory_key_from_non_ic_memory_owner() {
258 reset_for_tests();
259
260 let err = defer_register_with_key(10, "canic-core", "slot", "ic_memory.future.v1")
261 .expect_err("only ic-memory may declare ic_memory keys");
262 assert!(matches!(
263 err,
264 MemoryRegistryError::RangeAuthorityViolation { .. }
265 ));
266 }
267}