Skip to main content

ic_memory/
slot.rs

1use serde::{Deserialize, Serialize};
2
3/// Substrate identifier for `ic-stable-structures::MemoryManager` slots.
4pub const MEMORY_MANAGER_SUBSTRATE: &str = "ic-stable-structures.memory_manager";
5
6/// Descriptor version for current `MemoryManagerId` slots.
7pub const MEMORY_MANAGER_DESCRIPTOR_VERSION: u32 = 1;
8
9/// First usable `MemoryManager` virtual memory ID.
10pub const MEMORY_MANAGER_MIN_ID: u8 = 0;
11
12/// Last usable `MemoryManager` virtual memory ID.
13pub const MEMORY_MANAGER_MAX_ID: u8 = 254;
14
15/// `MemoryManager` unallocated-bucket sentinel. This is not a usable slot.
16pub const MEMORY_MANAGER_INVALID_ID: u8 = u8::MAX;
17
18/// Stable-key namespace prefix reserved for `ic-memory` allocation-governance infrastructure.
19pub const IC_MEMORY_STABLE_KEY_PREFIX: &str = "ic_memory.";
20
21/// Diagnostic owner label for `ic-memory` allocation-governance infrastructure.
22pub const IC_MEMORY_AUTHORITY_OWNER: &str = "ic-memory";
23
24/// Diagnostic purpose for the `ic-memory` allocation-governance authority range.
25pub const IC_MEMORY_AUTHORITY_PURPOSE: &str = "ic-memory allocation-governance authority";
26
27/// Stable key of the allocation ledger when backed by the current MemoryManager substrate.
28pub const IC_MEMORY_LEDGER_STABLE_KEY: &str = "ic_memory.ledger.v1";
29
30/// Diagnostic label of the allocation ledger when backed by the current MemoryManager substrate.
31pub const IC_MEMORY_LEDGER_LABEL: &str = "MemoryLayoutLedger";
32
33/// MemoryManager ID used by the allocation ledger in the current MemoryManager substrate.
34pub const MEMORY_MANAGER_LEDGER_ID: u8 = MEMORY_MANAGER_MIN_ID;
35
36/// Last MemoryManager ID reserved for `ic-memory` governance in the current substrate.
37pub const MEMORY_MANAGER_GOVERNANCE_MAX_ID: u8 = 9;
38
39/// Return true when `stable_key` belongs to the `ic-memory` namespace.
40#[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/// MemoryManager range reserved for `ic-memory` governance in the current substrate.
46#[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///
55/// AllocationSlot
56///
57/// Durable physical allocation identity interpreted by a storage substrate.
58#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
59pub enum AllocationSlot {
60    /// `ic-stable-structures::MemoryManager` virtual memory ID.
61    MemoryManagerId(u8),
62    /// Named substrate partition.
63    NamedPartition(String),
64    /// Forward-compatible custom slot descriptor.
65    Custom {
66        /// Substrate-defined slot kind.
67        kind: String,
68        /// Slot descriptor version.
69        version: u32,
70        /// Canonical substrate-defined value.
71        value: Vec<u8>,
72    },
73}
74
75///
76/// AllocationSlotDescriptor
77///
78/// Encoded allocation slot persisted in the ledger.
79#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
80pub struct AllocationSlotDescriptor {
81    /// Durable allocation slot.
82    pub slot: AllocationSlot,
83    /// Substrate identifier that interprets the slot.
84    pub substrate: String,
85    /// Descriptor encoding version.
86    pub descriptor_version: u32,
87}
88
89impl AllocationSlotDescriptor {
90    /// Construct a descriptor for a usable `MemoryManager` virtual memory ID.
91    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    /// Construct a descriptor for a `MemoryManager` virtual memory ID without validating it.
97    ///
98    /// This exists for tests, diagnostics, and decoding untrusted durable DTOs.
99    /// Use [`Self::memory_manager`] when constructing new allocation
100    /// declarations.
101    #[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    /// Construct a descriptor for a usable `MemoryManager` virtual memory ID.
111    pub fn memory_manager_checked(id: u8) -> Result<Self, MemoryManagerSlotError> {
112        Self::memory_manager(id)
113    }
114
115    /// Return the usable `MemoryManager` virtual memory ID represented by this descriptor.
116    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///
138/// MemoryManagerSlotError
139///
140/// Invalid or unsupported `MemoryManager` allocation slot descriptor.
141#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
142pub enum MemoryManagerSlotError {
143    /// Descriptor is not a `MemoryManagerId` slot.
144    #[error("allocation slot is not a MemoryManager virtual memory ID")]
145    UnsupportedSlot,
146    /// Descriptor is attached to another substrate.
147    #[error("allocation slot substrate '{substrate}' is not supported as a MemoryManager slot")]
148    UnsupportedSubstrate {
149        /// Unsupported substrate identifier.
150        substrate: String,
151    },
152    /// Descriptor uses an unsupported encoding version.
153    #[error("MemoryManager slot descriptor version {version} is unsupported")]
154    UnsupportedDescriptorVersion {
155        /// Unsupported descriptor version.
156        version: u32,
157    },
158    /// ID 255 is the unallocated-bucket sentinel.
159    #[error("MemoryManager ID {id} is not a usable allocation slot")]
160    InvalidMemoryManagerId {
161        /// Invalid MemoryManager ID.
162        id: u8,
163    },
164}
165
166///
167/// MemoryManagerIdRange
168///
169/// Inclusive range of usable `MemoryManager` virtual memory IDs.
170#[derive(Clone, Copy, Debug, Eq, PartialEq)]
171pub struct MemoryManagerIdRange {
172    start: u8,
173    end: u8,
174}
175
176impl MemoryManagerIdRange {
177    /// Construct and validate an inclusive `MemoryManager` ID range.
178    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    /// Return true when `id` is inside this inclusive range.
192    #[must_use]
193    pub const fn contains(&self, id: u8) -> bool {
194        id >= self.start && id <= self.end
195    }
196
197    /// First usable ID in the range.
198    #[must_use]
199    pub const fn start(&self) -> u8 {
200        self.start
201    }
202
203    /// Last usable ID in the range.
204    #[must_use]
205    pub const fn end(&self) -> u8 {
206        self.end
207    }
208}
209
210///
211/// MemoryManagerRangeError
212///
213/// Invalid `MemoryManager` virtual memory ID range.
214#[derive(Clone, Copy, Debug, Eq, thiserror::Error, PartialEq)]
215pub enum MemoryManagerRangeError {
216    /// Range bounds are reversed.
217    #[error("MemoryManager ID range is invalid: start={start} end={end}")]
218    InvalidRange {
219        /// Requested first ID.
220        start: u8,
221        /// Requested last ID.
222        end: u8,
223    },
224    /// ID 255 is the unallocated-bucket sentinel.
225    #[error("MemoryManager ID {id} is not a usable allocation slot")]
226    InvalidMemoryManagerId {
227        /// Invalid MemoryManager ID.
228        id: u8,
229    },
230}
231
232/// Validate that a `MemoryManager` ID is usable as an allocation slot.
233pub 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}