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 `MemoryManager` virtual memory ID.
55    #[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    /// Construct a descriptor for a usable `MemoryManager` virtual memory ID.
65    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    /// Return the usable `MemoryManager` virtual memory ID represented by this descriptor.
71    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///
93/// MemoryManagerSlotError
94///
95/// Invalid or unsupported `MemoryManager` allocation slot descriptor.
96#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
97pub enum MemoryManagerSlotError {
98    /// Descriptor is not a `MemoryManagerId` slot.
99    #[error("allocation slot is not a MemoryManager virtual memory ID")]
100    UnsupportedSlot,
101    /// Descriptor is attached to another substrate.
102    #[error("allocation slot substrate '{substrate}' is not supported as a MemoryManager slot")]
103    UnsupportedSubstrate {
104        /// Unsupported substrate identifier.
105        substrate: String,
106    },
107    /// Descriptor uses an unsupported encoding version.
108    #[error("MemoryManager slot descriptor version {version} is unsupported")]
109    UnsupportedDescriptorVersion {
110        /// Unsupported descriptor version.
111        version: u32,
112    },
113    /// ID 255 is the unallocated-bucket sentinel.
114    #[error("MemoryManager ID {id} is not a usable allocation slot")]
115    InvalidMemoryManagerId {
116        /// Invalid MemoryManager ID.
117        id: u8,
118    },
119}
120
121///
122/// MemoryManagerIdRange
123///
124/// Inclusive range of usable `MemoryManager` virtual memory IDs.
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub struct MemoryManagerIdRange {
127    start: u8,
128    end: u8,
129}
130
131impl MemoryManagerIdRange {
132    /// Construct and validate an inclusive `MemoryManager` ID range.
133    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    /// Return true when `id` is inside this inclusive range.
147    #[must_use]
148    pub const fn contains(&self, id: u8) -> bool {
149        id >= self.start && id <= self.end
150    }
151
152    /// First usable ID in the range.
153    #[must_use]
154    pub const fn start(&self) -> u8 {
155        self.start
156    }
157
158    /// Last usable ID in the range.
159    #[must_use]
160    pub const fn end(&self) -> u8 {
161        self.end
162    }
163}
164
165///
166/// MemoryManagerRangeError
167///
168/// Invalid `MemoryManager` virtual memory ID range.
169#[derive(Clone, Copy, Debug, Eq, thiserror::Error, PartialEq)]
170pub enum MemoryManagerRangeError {
171    /// Range bounds are reversed.
172    #[error("MemoryManager ID range is invalid: start={start} end={end}")]
173    InvalidRange {
174        /// Requested first ID.
175        start: u8,
176        /// Requested last ID.
177        end: u8,
178    },
179    /// ID 255 is the unallocated-bucket sentinel.
180    #[error("MemoryManager ID {id} is not a usable allocation slot")]
181    InvalidMemoryManagerId {
182        /// Invalid MemoryManager ID.
183        id: u8,
184    },
185}
186
187/// Validate that a `MemoryManager` ID is usable as an allocation slot.
188pub 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}