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
18#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
23pub enum AllocationSlot {
24 MemoryManagerId(u8),
26 NamedPartition(String),
28 Custom {
30 kind: String,
32 version: u32,
34 value: Vec<u8>,
36 },
37}
38
39#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
44pub struct AllocationSlotDescriptor {
45 pub slot: AllocationSlot,
47 pub substrate: String,
49 pub descriptor_version: u32,
51}
52
53impl AllocationSlotDescriptor {
54 #[must_use]
56 pub fn memory_manager(id: u8) -> Self {
57 Self {
58 slot: AllocationSlot::MemoryManagerId(id),
59 substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
60 descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
61 }
62 }
63
64 pub fn memory_manager_checked(id: u8) -> Result<Self, MemoryManagerSlotError> {
66 validate_memory_manager_id(id)?;
67 Ok(Self::memory_manager(id))
68 }
69
70 pub fn memory_manager_id(&self) -> Result<u8, MemoryManagerSlotError> {
72 if self.substrate != MEMORY_MANAGER_SUBSTRATE {
73 return Err(MemoryManagerSlotError::UnsupportedSubstrate {
74 substrate: self.substrate.clone(),
75 });
76 }
77 if self.descriptor_version != MEMORY_MANAGER_DESCRIPTOR_VERSION {
78 return Err(MemoryManagerSlotError::UnsupportedDescriptorVersion {
79 version: self.descriptor_version,
80 });
81 }
82
83 let AllocationSlot::MemoryManagerId(id) = self.slot else {
84 return Err(MemoryManagerSlotError::UnsupportedSlot);
85 };
86
87 validate_memory_manager_id(id)?;
88 Ok(id)
89 }
90}
91
92#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
97pub enum MemoryManagerSlotError {
98 #[error("allocation slot is not a MemoryManager virtual memory ID")]
100 UnsupportedSlot,
101 #[error("allocation slot substrate '{substrate}' is not supported as a MemoryManager slot")]
103 UnsupportedSubstrate {
104 substrate: String,
106 },
107 #[error("MemoryManager slot descriptor version {version} is unsupported")]
109 UnsupportedDescriptorVersion {
110 version: u32,
112 },
113 #[error("MemoryManager ID {id} is not a usable allocation slot")]
115 InvalidMemoryManagerId {
116 id: u8,
118 },
119}
120
121#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub struct MemoryManagerIdRange {
127 start: u8,
128 end: u8,
129}
130
131impl MemoryManagerIdRange {
132 pub const fn new(start: u8, end: u8) -> Result<Self, MemoryManagerRangeError> {
134 if start > end {
135 return Err(MemoryManagerRangeError::InvalidRange { start, end });
136 }
137 if start == MEMORY_MANAGER_INVALID_ID {
138 return Err(MemoryManagerRangeError::InvalidMemoryManagerId { id: start });
139 }
140 if end == MEMORY_MANAGER_INVALID_ID {
141 return Err(MemoryManagerRangeError::InvalidMemoryManagerId { id: end });
142 }
143 Ok(Self { start, end })
144 }
145
146 #[must_use]
148 pub const fn contains(&self, id: u8) -> bool {
149 id >= self.start && id <= self.end
150 }
151
152 #[must_use]
154 pub const fn start(&self) -> u8 {
155 self.start
156 }
157
158 #[must_use]
160 pub const fn end(&self) -> u8 {
161 self.end
162 }
163}
164
165#[derive(Clone, Copy, Debug, Eq, thiserror::Error, PartialEq)]
170pub enum MemoryManagerRangeError {
171 #[error("MemoryManager ID range is invalid: start={start} end={end}")]
173 InvalidRange {
174 start: u8,
176 end: u8,
178 },
179 #[error("MemoryManager ID {id} is not a usable allocation slot")]
181 InvalidMemoryManagerId {
182 id: u8,
184 },
185}
186
187pub const fn validate_memory_manager_id(id: u8) -> Result<(), MemoryManagerSlotError> {
189 if id == MEMORY_MANAGER_INVALID_ID {
190 return Err(MemoryManagerSlotError::InvalidMemoryManagerId { id });
191 }
192 Ok(())
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn memory_manager_checked_accepts_usable_ids() {
201 assert!(AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_MIN_ID).is_ok());
202 assert!(AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_MAX_ID).is_ok());
203 }
204
205 #[test]
206 fn memory_manager_checked_rejects_sentinel() {
207 let err = AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_INVALID_ID)
208 .expect_err("sentinel must fail");
209
210 assert_eq!(
211 err,
212 MemoryManagerSlotError::InvalidMemoryManagerId {
213 id: MEMORY_MANAGER_INVALID_ID
214 }
215 );
216 }
217
218 #[test]
219 fn memory_manager_id_validates_descriptor_shape() {
220 let slot = AllocationSlotDescriptor::memory_manager(42);
221 assert_eq!(slot.memory_manager_id().expect("usable ID"), 42);
222
223 let err = AllocationSlotDescriptor {
224 slot: AllocationSlot::NamedPartition("ledger".to_string()),
225 substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
226 descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
227 }
228 .memory_manager_id()
229 .expect_err("slot kind should fail");
230 assert_eq!(err, MemoryManagerSlotError::UnsupportedSlot);
231
232 let err = AllocationSlotDescriptor {
233 slot: AllocationSlot::MemoryManagerId(42),
234 substrate: "other".to_string(),
235 descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
236 }
237 .memory_manager_id()
238 .expect_err("substrate should fail");
239 assert_eq!(
240 err,
241 MemoryManagerSlotError::UnsupportedSubstrate {
242 substrate: "other".to_string()
243 }
244 );
245
246 let err = AllocationSlotDescriptor {
247 slot: AllocationSlot::MemoryManagerId(42),
248 substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
249 descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION + 1,
250 }
251 .memory_manager_id()
252 .expect_err("version should fail");
253 assert_eq!(
254 err,
255 MemoryManagerSlotError::UnsupportedDescriptorVersion {
256 version: MEMORY_MANAGER_DESCRIPTOR_VERSION + 1
257 }
258 );
259 }
260
261 #[test]
262 fn memory_manager_range_accepts_usable_ranges() {
263 let range = MemoryManagerIdRange::new(MEMORY_MANAGER_MIN_ID, MEMORY_MANAGER_MAX_ID)
264 .expect("usable full range");
265
266 assert!(range.contains(MEMORY_MANAGER_MIN_ID));
267 assert!(range.contains(MEMORY_MANAGER_MAX_ID));
268 assert!(!range.contains(MEMORY_MANAGER_INVALID_ID));
269 }
270
271 #[test]
272 fn memory_manager_range_rejects_reversed_bounds() {
273 let err = MemoryManagerIdRange::new(10, 9).expect_err("reversed range");
274
275 assert_eq!(
276 err,
277 MemoryManagerRangeError::InvalidRange { start: 10, end: 9 }
278 );
279 }
280
281 #[test]
282 fn memory_manager_range_rejects_sentinel_bounds() {
283 let err =
284 MemoryManagerIdRange::new(240, MEMORY_MANAGER_INVALID_ID).expect_err("sentinel range");
285
286 assert_eq!(
287 err,
288 MemoryManagerRangeError::InvalidMemoryManagerId {
289 id: MEMORY_MANAGER_INVALID_ID
290 }
291 );
292 }
293}