use serde::{Deserialize, Serialize};
pub const MEMORY_MANAGER_SUBSTRATE: &str = "ic-stable-structures.memory_manager";
pub const MEMORY_MANAGER_DESCRIPTOR_VERSION: u32 = 1;
pub const MEMORY_MANAGER_MIN_ID: u8 = 0;
pub const MEMORY_MANAGER_MAX_ID: u8 = 254;
pub const MEMORY_MANAGER_INVALID_ID: u8 = u8::MAX;
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub enum AllocationSlot {
MemoryManagerId(u8),
NamedPartition(String),
Custom {
kind: String,
version: u32,
value: Vec<u8>,
},
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct AllocationSlotDescriptor {
pub slot: AllocationSlot,
pub substrate: String,
pub descriptor_version: u32,
}
impl AllocationSlotDescriptor {
#[must_use]
pub fn memory_manager(id: u8) -> Self {
Self {
slot: AllocationSlot::MemoryManagerId(id),
substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
}
}
pub fn memory_manager_checked(id: u8) -> Result<Self, MemoryManagerSlotError> {
validate_memory_manager_id(id)?;
Ok(Self::memory_manager(id))
}
pub fn memory_manager_id(&self) -> Result<u8, MemoryManagerSlotError> {
if self.substrate != MEMORY_MANAGER_SUBSTRATE {
return Err(MemoryManagerSlotError::UnsupportedSubstrate {
substrate: self.substrate.clone(),
});
}
if self.descriptor_version != MEMORY_MANAGER_DESCRIPTOR_VERSION {
return Err(MemoryManagerSlotError::UnsupportedDescriptorVersion {
version: self.descriptor_version,
});
}
let AllocationSlot::MemoryManagerId(id) = self.slot else {
return Err(MemoryManagerSlotError::UnsupportedSlot);
};
validate_memory_manager_id(id)?;
Ok(id)
}
}
#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
pub enum MemoryManagerSlotError {
#[error("allocation slot is not a MemoryManager virtual memory ID")]
UnsupportedSlot,
#[error("allocation slot substrate '{substrate}' is not supported as a MemoryManager slot")]
UnsupportedSubstrate {
substrate: String,
},
#[error("MemoryManager slot descriptor version {version} is unsupported")]
UnsupportedDescriptorVersion {
version: u32,
},
#[error("MemoryManager ID {id} is not a usable allocation slot")]
InvalidMemoryManagerId {
id: u8,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MemoryManagerIdRange {
start: u8,
end: u8,
}
impl MemoryManagerIdRange {
pub const fn new(start: u8, end: u8) -> Result<Self, MemoryManagerRangeError> {
if start > end {
return Err(MemoryManagerRangeError::InvalidRange { start, end });
}
if start == MEMORY_MANAGER_INVALID_ID {
return Err(MemoryManagerRangeError::InvalidMemoryManagerId { id: start });
}
if end == MEMORY_MANAGER_INVALID_ID {
return Err(MemoryManagerRangeError::InvalidMemoryManagerId { id: end });
}
Ok(Self { start, end })
}
#[must_use]
pub const fn contains(&self, id: u8) -> bool {
id >= self.start && id <= self.end
}
#[must_use]
pub const fn start(&self) -> u8 {
self.start
}
#[must_use]
pub const fn end(&self) -> u8 {
self.end
}
}
#[derive(Clone, Copy, Debug, Eq, thiserror::Error, PartialEq)]
pub enum MemoryManagerRangeError {
#[error("MemoryManager ID range is invalid: start={start} end={end}")]
InvalidRange {
start: u8,
end: u8,
},
#[error("MemoryManager ID {id} is not a usable allocation slot")]
InvalidMemoryManagerId {
id: u8,
},
}
pub const fn validate_memory_manager_id(id: u8) -> Result<(), MemoryManagerSlotError> {
if id == MEMORY_MANAGER_INVALID_ID {
return Err(MemoryManagerSlotError::InvalidMemoryManagerId { id });
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn memory_manager_checked_accepts_usable_ids() {
assert!(AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_MIN_ID).is_ok());
assert!(AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_MAX_ID).is_ok());
}
#[test]
fn memory_manager_checked_rejects_sentinel() {
let err = AllocationSlotDescriptor::memory_manager_checked(MEMORY_MANAGER_INVALID_ID)
.expect_err("sentinel must fail");
assert_eq!(
err,
MemoryManagerSlotError::InvalidMemoryManagerId {
id: MEMORY_MANAGER_INVALID_ID
}
);
}
#[test]
fn memory_manager_id_validates_descriptor_shape() {
let slot = AllocationSlotDescriptor::memory_manager(42);
assert_eq!(slot.memory_manager_id().expect("usable ID"), 42);
let err = AllocationSlotDescriptor {
slot: AllocationSlot::NamedPartition("ledger".to_string()),
substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
}
.memory_manager_id()
.expect_err("slot kind should fail");
assert_eq!(err, MemoryManagerSlotError::UnsupportedSlot);
let err = AllocationSlotDescriptor {
slot: AllocationSlot::MemoryManagerId(42),
substrate: "other".to_string(),
descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION,
}
.memory_manager_id()
.expect_err("substrate should fail");
assert_eq!(
err,
MemoryManagerSlotError::UnsupportedSubstrate {
substrate: "other".to_string()
}
);
let err = AllocationSlotDescriptor {
slot: AllocationSlot::MemoryManagerId(42),
substrate: MEMORY_MANAGER_SUBSTRATE.to_string(),
descriptor_version: MEMORY_MANAGER_DESCRIPTOR_VERSION + 1,
}
.memory_manager_id()
.expect_err("version should fail");
assert_eq!(
err,
MemoryManagerSlotError::UnsupportedDescriptorVersion {
version: MEMORY_MANAGER_DESCRIPTOR_VERSION + 1
}
);
}
#[test]
fn memory_manager_range_accepts_usable_ranges() {
let range = MemoryManagerIdRange::new(MEMORY_MANAGER_MIN_ID, MEMORY_MANAGER_MAX_ID)
.expect("usable full range");
assert!(range.contains(MEMORY_MANAGER_MIN_ID));
assert!(range.contains(MEMORY_MANAGER_MAX_ID));
assert!(!range.contains(MEMORY_MANAGER_INVALID_ID));
}
#[test]
fn memory_manager_range_rejects_reversed_bounds() {
let err = MemoryManagerIdRange::new(10, 9).expect_err("reversed range");
assert_eq!(
err,
MemoryManagerRangeError::InvalidRange { start: 10, end: 9 }
);
}
#[test]
fn memory_manager_range_rejects_sentinel_bounds() {
let err =
MemoryManagerIdRange::new(240, MEMORY_MANAGER_INVALID_ID).expect_err("sentinel range");
assert_eq!(
err,
MemoryManagerRangeError::InvalidMemoryManagerId {
id: MEMORY_MANAGER_INVALID_ID
}
);
}
}