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///
19/// AllocationSlot
20///
21/// Durable physical allocation identity interpreted by a storage substrate.
22#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
23pub enum AllocationSlot {
24    /// `ic-stable-structures::MemoryManager` virtual memory ID.
25    MemoryManagerId(u8),
26    /// Named substrate partition.
27    NamedPartition(String),
28    /// Forward-compatible custom slot descriptor.
29    Custom {
30        /// Substrate-defined slot kind.
31        kind: String,
32        /// Slot descriptor version.
33        version: u32,
34        /// Canonical substrate-defined value.
35        value: Vec<u8>,
36    },
37}
38
39///
40/// AllocationSlotDescriptor
41///
42/// Encoded allocation slot persisted in the ledger.
43#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
44pub struct AllocationSlotDescriptor {
45    /// Durable allocation slot.
46    pub slot: AllocationSlot,
47    /// Substrate identifier that interprets the slot.
48    pub substrate: String,
49    /// Descriptor encoding version.
50    pub descriptor_version: u32,
51}
52
53impl AllocationSlotDescriptor {
54    /// Construct a descriptor for a usable `MemoryManager` virtual memory ID.
55    pub fn memory_manager(id: u8) -> Result<Self, MemoryManagerSlotError> {
56        validate_memory_manager_id(id)?;
57        Ok(Self::memory_manager_unchecked(id))
58    }
59
60    /// Construct a descriptor for a `MemoryManager` virtual memory ID without validating it.
61    ///
62    /// This exists for tests, diagnostics, and decoding untrusted durable DTOs.
63    /// Use [`Self::memory_manager`] when constructing new allocation
64    /// declarations.
65    #[must_use]
66    pub fn memory_manager_unchecked(id: u8) -> Self {
67        Self {
68            slot: AllocationSlot::MemoryManagerId(id),
69            substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
70            descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
71        }
72    }
73
74    /// Construct a descriptor for a usable `MemoryManager` virtual memory ID.
75    pub fn memory_manager_checked(id: u8) -> Result<Self, MemoryManagerSlotError> {
76        Self::memory_manager(id)
77    }
78
79    /// Return the usable `MemoryManager` virtual memory ID represented by this descriptor.
80    pub fn memory_manager_id(&self) -> Result<u8, MemoryManagerSlotError> {
81        if self.substrate != MEMORY_MANAGER_SUBSTRATE {
82            return Err(MemoryManagerSlotError::UnsupportedSubstrate {
83                substrate: self.substrate.clone(),
84            });
85        }
86        if self.descriptor_version != MEMORY_MANAGER_DESCRIPTOR_VERSION {
87            return Err(MemoryManagerSlotError::UnsupportedDescriptorVersion {
88                version: self.descriptor_version,
89            });
90        }
91
92        let AllocationSlot::MemoryManagerId(id) = self.slot else {
93            return Err(MemoryManagerSlotError::UnsupportedSlot);
94        };
95
96        validate_memory_manager_id(id)?;
97        Ok(id)
98    }
99}
100
101///
102/// MemoryManagerSlotError
103///
104/// Invalid or unsupported `MemoryManager` allocation slot descriptor.
105#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
106pub enum MemoryManagerSlotError {
107    /// Descriptor is not a `MemoryManagerId` slot.
108    #[error("allocation slot is not a MemoryManager virtual memory ID")]
109    UnsupportedSlot,
110    /// Descriptor is attached to another substrate.
111    #[error("allocation slot substrate '{substrate}' is not supported as a MemoryManager slot")]
112    UnsupportedSubstrate {
113        /// Unsupported substrate identifier.
114        substrate: String,
115    },
116    /// Descriptor uses an unsupported encoding version.
117    #[error("MemoryManager slot descriptor version {version} is unsupported")]
118    UnsupportedDescriptorVersion {
119        /// Unsupported descriptor version.
120        version: u32,
121    },
122    /// ID 255 is the unallocated-bucket sentinel.
123    #[error("MemoryManager ID {id} is not a usable allocation slot")]
124    InvalidMemoryManagerId {
125        /// Invalid MemoryManager ID.
126        id: u8,
127    },
128}
129
130///
131/// MemoryManagerIdRange
132///
133/// Inclusive range of usable `MemoryManager` virtual memory IDs.
134#[derive(Clone, Copy, Debug, Eq, PartialEq)]
135pub struct MemoryManagerIdRange {
136    start: u8,
137    end: u8,
138}
139
140impl MemoryManagerIdRange {
141    /// Construct and validate an inclusive `MemoryManager` ID range.
142    pub const fn new(start: u8, end: u8) -> Result<Self, MemoryManagerRangeError> {
143        if start > end {
144            return Err(MemoryManagerRangeError::InvalidRange { start, end });
145        }
146        if start == MEMORY_MANAGER_INVALID_ID {
147            return Err(MemoryManagerRangeError::InvalidMemoryManagerId { id: start });
148        }
149        if end == MEMORY_MANAGER_INVALID_ID {
150            return Err(MemoryManagerRangeError::InvalidMemoryManagerId { id: end });
151        }
152        Ok(Self { start, end })
153    }
154
155    /// Return true when `id` is inside this inclusive range.
156    #[must_use]
157    pub const fn contains(&self, id: u8) -> bool {
158        id >= self.start && id <= self.end
159    }
160
161    /// First usable ID in the range.
162    #[must_use]
163    pub const fn start(&self) -> u8 {
164        self.start
165    }
166
167    /// Last usable ID in the range.
168    #[must_use]
169    pub const fn end(&self) -> u8 {
170        self.end
171    }
172}
173
174///
175/// MemoryManagerRangeError
176///
177/// Invalid `MemoryManager` virtual memory ID range.
178#[derive(Clone, Copy, Debug, Eq, thiserror::Error, PartialEq)]
179pub enum MemoryManagerRangeError {
180    /// Range bounds are reversed.
181    #[error("MemoryManager ID range is invalid: start={start} end={end}")]
182    InvalidRange {
183        /// Requested first ID.
184        start: u8,
185        /// Requested last ID.
186        end: u8,
187    },
188    /// ID 255 is the unallocated-bucket sentinel.
189    #[error("MemoryManager ID {id} is not a usable allocation slot")]
190    InvalidMemoryManagerId {
191        /// Invalid MemoryManager ID.
192        id: u8,
193    },
194}
195
196/// Validate that a `MemoryManager` ID is usable as an allocation slot.
197pub const fn validate_memory_manager_id(id: u8) -> Result<(), MemoryManagerSlotError> {
198    if id == MEMORY_MANAGER_INVALID_ID {
199        return Err(MemoryManagerSlotError::InvalidMemoryManagerId { id });
200    }
201    Ok(())
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn memory_manager_checked_accepts_usable_ids() {
210        assert!(AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_MIN_ID).is_ok());
211        assert!(AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_MAX_ID).is_ok());
212    }
213
214    #[test]
215    fn memory_manager_checked_rejects_sentinel() {
216        let err = AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_INVALID_ID)
217            .expect_err("sentinel must fail");
218
219        assert_eq!(
220            err,
221            MemoryManagerSlotError::InvalidMemoryManagerId {
222                id: MEMORY_MANAGER_INVALID_ID
223            }
224        );
225    }
226
227    #[test]
228    fn memory_manager_default_constructor_rejects_sentinel() {
229        let err = AllocationSlotDescriptor::memory_manager(MEMORY_MANAGER_INVALID_ID)
230            .expect_err("sentinel must fail");
231
232        assert_eq!(
233            err,
234            MemoryManagerSlotError::InvalidMemoryManagerId {
235                id: MEMORY_MANAGER_INVALID_ID
236            }
237        );
238    }
239
240    #[test]
241    fn memory_manager_id_validates_descriptor_shape() {
242        let slot = AllocationSlotDescriptor::memory_manager(42).expect("usable slot");
243        assert_eq!(slot.memory_manager_id().expect("usable ID"), 42);
244
245        let err = AllocationSlotDescriptor {
246            slot: AllocationSlot::NamedPartition("ledger".to_string()),
247            substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
248            descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
249        }
250        .memory_manager_id()
251        .expect_err("slot kind should fail");
252        assert_eq!(err, MemoryManagerSlotError::UnsupportedSlot);
253
254        let err = AllocationSlotDescriptor {
255            slot: AllocationSlot::MemoryManagerId(42),
256            substrate: "other".to_string(),
257            descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
258        }
259        .memory_manager_id()
260        .expect_err("substrate should fail");
261        assert_eq!(
262            err,
263            MemoryManagerSlotError::UnsupportedSubstrate {
264                substrate: "other".to_string()
265            }
266        );
267
268        let err = AllocationSlotDescriptor {
269            slot: AllocationSlot::MemoryManagerId(42),
270            substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
271            descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION + 1,
272        }
273        .memory_manager_id()
274        .expect_err("version should fail");
275        assert_eq!(
276            err,
277            MemoryManagerSlotError::UnsupportedDescriptorVersion {
278                version: MEMORY_MANAGER_DESCRIPTOR_VERSION + 1
279            }
280        );
281    }
282
283    #[test]
284    fn memory_manager_range_accepts_usable_ranges() {
285        let range = MemoryManagerIdRange::new(MEMORY_MANAGER_MIN_ID, MEMORY_MANAGER_MAX_ID)
286            .expect("usable full range");
287
288        assert!(range.contains(MEMORY_MANAGER_MIN_ID));
289        assert!(range.contains(MEMORY_MANAGER_MAX_ID));
290        assert!(!range.contains(MEMORY_MANAGER_INVALID_ID));
291    }
292
293    #[test]
294    fn memory_manager_range_rejects_reversed_bounds() {
295        let err = MemoryManagerIdRange::new(10, 9).expect_err("reversed range");
296
297        assert_eq!(
298            err,
299            MemoryManagerRangeError::InvalidRange { start: 10, end: 9 }
300        );
301    }
302
303    #[test]
304    fn memory_manager_range_rejects_sentinel_bounds() {
305        let err =
306            MemoryManagerIdRange::new(240, MEMORY_MANAGER_INVALID_ID).expect_err("sentinel range");
307
308        assert_eq!(
309            err,
310            MemoryManagerRangeError::InvalidMemoryManagerId {
311                id: MEMORY_MANAGER_INVALID_ID
312            }
313        );
314    }
315}