1use serde::{Deserialize, Serialize};
2
3pub const MEMORY_MANAGER_SUBSTRATE: &str = "ic-stable-structures.memory_manager";
5
6pub const MEMORY_MANAGER_DESCRIPTOR_VERSION: u32 = 1;
8
9pub const MEMORY_MANAGER_MIN_ID: u8 = 0;
11
12pub const MEMORY_MANAGER_MAX_ID: u8 = 254;
14
15pub const MEMORY_MANAGER_INVALID_ID: u8 = u8::MAX;
17
18pub const IC_MEMORY_STABLE_KEY_PREFIX: &str = "ic_memory.";
20
21pub const IC_MEMORY_AUTHORITY_OWNER: &str = "ic-memory";
23
24pub const IC_MEMORY_AUTHORITY_PURPOSE: &str = "ic-memory allocation-governance authority";
26
27pub const IC_MEMORY_LEDGER_STABLE_KEY: &str = "ic_memory.ledger.v1";
29
30pub const IC_MEMORY_LEDGER_LABEL: &str = "MemoryLayoutLedger";
32
33pub const MEMORY_MANAGER_LEDGER_ID: u8 = MEMORY_MANAGER_MIN_ID;
35
36pub const MEMORY_MANAGER_GOVERNANCE_MAX_ID: u8 = 9;
38
39#[must_use]
41pub fn is_ic_memory_stable_key(stable_key: &str) -> bool {
42 stable_key.starts_with(IC_MEMORY_STABLE_KEY_PREFIX)
43}
44
45#[must_use]
47pub const fn memory_manager_governance_range() -> MemoryManagerIdRange {
48 MemoryManagerIdRange {
49 start: MEMORY_MANAGER_MIN_ID,
50 end: MEMORY_MANAGER_GOVERNANCE_MAX_ID,
51 }
52}
53
54#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
59pub enum AllocationSlot {
60 MemoryManagerId(u8),
62 NamedPartition(String),
64 Custom {
66 kind: String,
68 version: u32,
70 value: Vec<u8>,
72 },
73}
74
75#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
80pub struct AllocationSlotDescriptor {
81 pub slot: AllocationSlot,
83 pub substrate: String,
85 pub descriptor_version: u32,
87}
88
89impl AllocationSlotDescriptor {
90 pub fn memory_manager(id: u8) -> Result<Self, MemoryManagerSlotError> {
92 validate_memory_manager_id(id)?;
93 Ok(Self::memory_manager_unchecked(id))
94 }
95
96 #[must_use]
102 pub fn memory_manager_unchecked(id: u8) -> Self {
103 Self {
104 slot: AllocationSlot::MemoryManagerId(id),
105 substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
106 descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
107 }
108 }
109
110 pub fn memory_manager_checked(id: u8) -> Result<Self, MemoryManagerSlotError> {
112 Self::memory_manager(id)
113 }
114
115 pub fn memory_manager_id(&self) -> Result<u8, MemoryManagerSlotError> {
117 if self.substrate != MEMORY_MANAGER_SUBSTRATE {
118 return Err(MemoryManagerSlotError::UnsupportedSubstrate {
119 substrate: self.substrate.clone(),
120 });
121 }
122 if self.descriptor_version != MEMORY_MANAGER_DESCRIPTOR_VERSION {
123 return Err(MemoryManagerSlotError::UnsupportedDescriptorVersion {
124 version: self.descriptor_version,
125 });
126 }
127
128 let AllocationSlot::MemoryManagerId(id) = self.slot else {
129 return Err(MemoryManagerSlotError::UnsupportedSlot);
130 };
131
132 validate_memory_manager_id(id)?;
133 Ok(id)
134 }
135}
136
137#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
142pub enum MemoryManagerSlotError {
143 #[error("allocation slot is not a MemoryManager virtual memory ID")]
145 UnsupportedSlot,
146 #[error("allocation slot substrate '{substrate}' is not supported as a MemoryManager slot")]
148 UnsupportedSubstrate {
149 substrate: String,
151 },
152 #[error("MemoryManager slot descriptor version {version} is unsupported")]
154 UnsupportedDescriptorVersion {
155 version: u32,
157 },
158 #[error("MemoryManager ID {id} is not a usable allocation slot")]
160 InvalidMemoryManagerId {
161 id: u8,
163 },
164}
165
166#[derive(Clone, Copy, Debug, Eq, PartialEq)]
171pub struct MemoryManagerIdRange {
172 start: u8,
173 end: u8,
174}
175
176impl MemoryManagerIdRange {
177 pub const fn new(start: u8, end: u8) -> Result<Self, MemoryManagerRangeError> {
179 if start > end {
180 return Err(MemoryManagerRangeError::InvalidRange { start, end });
181 }
182 if start == MEMORY_MANAGER_INVALID_ID {
183 return Err(MemoryManagerRangeError::InvalidMemoryManagerId { id: start });
184 }
185 if end == MEMORY_MANAGER_INVALID_ID {
186 return Err(MemoryManagerRangeError::InvalidMemoryManagerId { id: end });
187 }
188 Ok(Self { start, end })
189 }
190
191 #[must_use]
193 pub const fn contains(&self, id: u8) -> bool {
194 id >= self.start && id <= self.end
195 }
196
197 #[must_use]
199 pub const fn start(&self) -> u8 {
200 self.start
201 }
202
203 #[must_use]
205 pub const fn end(&self) -> u8 {
206 self.end
207 }
208}
209
210#[derive(Clone, Copy, Debug, Eq, thiserror::Error, PartialEq)]
215pub enum MemoryManagerRangeError {
216 #[error("MemoryManager ID range is invalid: start={start} end={end}")]
218 InvalidRange {
219 start: u8,
221 end: u8,
223 },
224 #[error("MemoryManager ID {id} is not a usable allocation slot")]
226 InvalidMemoryManagerId {
227 id: u8,
229 },
230}
231
232pub const fn validate_memory_manager_id(id: u8) -> Result<(), MemoryManagerSlotError> {
234 if id == MEMORY_MANAGER_INVALID_ID {
235 return Err(MemoryManagerSlotError::InvalidMemoryManagerId { id });
236 }
237 Ok(())
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn memory_manager_checked_accepts_usable_ids() {
246 assert!(AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_MIN_ID).is_ok());
247 assert!(AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_MAX_ID).is_ok());
248 }
249
250 #[test]
251 fn memory_manager_checked_rejects_sentinel() {
252 let err = AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_INVALID_ID)
253 .expect_err("sentinel must fail");
254
255 assert_eq!(
256 err,
257 MemoryManagerSlotError::InvalidMemoryManagerId {
258 id: MEMORY_MANAGER_INVALID_ID
259 }
260 );
261 }
262
263 #[test]
264 fn memory_manager_default_constructor_rejects_sentinel() {
265 let err = AllocationSlotDescriptor::memory_manager(MEMORY_MANAGER_INVALID_ID)
266 .expect_err("sentinel must fail");
267
268 assert_eq!(
269 err,
270 MemoryManagerSlotError::InvalidMemoryManagerId {
271 id: MEMORY_MANAGER_INVALID_ID
272 }
273 );
274 }
275
276 #[test]
277 fn memory_manager_id_validates_descriptor_shape() {
278 let slot = AllocationSlotDescriptor::memory_manager(42).expect("usable slot");
279 assert_eq!(slot.memory_manager_id().expect("usable ID"), 42);
280
281 let err = AllocationSlotDescriptor {
282 slot: AllocationSlot::NamedPartition("ledger".to_string()),
283 substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
284 descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
285 }
286 .memory_manager_id()
287 .expect_err("slot kind should fail");
288 assert_eq!(err, MemoryManagerSlotError::UnsupportedSlot);
289
290 let err = AllocationSlotDescriptor {
291 slot: AllocationSlot::MemoryManagerId(42),
292 substrate: "other".to_string(),
293 descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
294 }
295 .memory_manager_id()
296 .expect_err("substrate should fail");
297 assert_eq!(
298 err,
299 MemoryManagerSlotError::UnsupportedSubstrate {
300 substrate: "other".to_string()
301 }
302 );
303
304 let err = AllocationSlotDescriptor {
305 slot: AllocationSlot::MemoryManagerId(42),
306 substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
307 descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION + 1,
308 }
309 .memory_manager_id()
310 .expect_err("version should fail");
311 assert_eq!(
312 err,
313 MemoryManagerSlotError::UnsupportedDescriptorVersion {
314 version: MEMORY_MANAGER_DESCRIPTOR_VERSION + 1
315 }
316 );
317 }
318
319 #[test]
320 fn memory_manager_range_accepts_usable_ranges() {
321 let range = MemoryManagerIdRange::new(MEMORY_MANAGER_MIN_ID, MEMORY_MANAGER_MAX_ID)
322 .expect("usable full range");
323
324 assert!(range.contains(MEMORY_MANAGER_MIN_ID));
325 assert!(range.contains(MEMORY_MANAGER_MAX_ID));
326 assert!(!range.contains(MEMORY_MANAGER_INVALID_ID));
327 }
328
329 #[test]
330 fn memory_manager_governance_range_is_owned_by_ic_memory() {
331 let range = memory_manager_governance_range();
332
333 assert_eq!(range.start(), MEMORY_MANAGER_MIN_ID);
334 assert_eq!(MEMORY_MANAGER_LEDGER_ID, range.start());
335 assert!(range.contains(MEMORY_MANAGER_LEDGER_ID));
336 assert!(is_ic_memory_stable_key(IC_MEMORY_LEDGER_STABLE_KEY));
337 assert_eq!(IC_MEMORY_AUTHORITY_OWNER, "ic-memory");
338 }
339
340 #[test]
341 fn memory_manager_range_rejects_reversed_bounds() {
342 let err = MemoryManagerIdRange::new(10, 9).expect_err("reversed range");
343
344 assert_eq!(
345 err,
346 MemoryManagerRangeError::InvalidRange { start: 10, end: 9 }
347 );
348 }
349
350 #[test]
351 fn memory_manager_range_rejects_sentinel_bounds() {
352 let err =
353 MemoryManagerIdRange::new(240, MEMORY_MANAGER_INVALID_ID).expect_err("sentinel range");
354
355 assert_eq!(
356 err,
357 MemoryManagerRangeError::InvalidMemoryManagerId {
358 id: MEMORY_MANAGER_INVALID_ID
359 }
360 );
361 }
362}