use super::{ledger, policy};
use ic_memory::{AllocationDeclaration, DeclarationSnapshotError, SchemaMetadata};
#[cfg(test)]
use std::cell::RefCell;
use std::sync::Mutex;
use thiserror::Error as ThisError;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PendingRegistration {
crate_name: String,
declaration: AllocationDeclaration,
}
impl PendingRegistration {
fn from_parts(
id: u8,
crate_name: &str,
label: &str,
stable_key: &str,
schema_version: Option<u32>,
schema_fingerprint: Option<&str>,
) -> Result<Self, MemoryRegistryError> {
let schema = SchemaMetadata::new(schema_version, schema_fingerprint.map(str::to_string))
.map_err(|_| MemoryRegistryError::InvalidDeclaration {
stable_key: stable_key.to_string(),
reason: "schema metadata rejected by ic-memory",
})?;
let declaration =
AllocationDeclaration::memory_manager_with_schema(stable_key, id, label, schema)
.map_err(|err| memory_registry_error_from_declaration_error(err, stable_key))?;
policy::validate_stable_key_authority(id, crate_name, stable_key)?;
Ok(Self {
crate_name: crate_name.to_string(),
declaration,
})
}
pub(crate) fn internal_layout_ledger() -> Result<Self, MemoryRegistryError> {
Self::from_parts(
ledger::MEMORY_LAYOUT_LEDGER_ID,
ledger::MEMORY_LAYOUT_LEDGER_OWNER,
ledger::MEMORY_LAYOUT_LEDGER_LABEL,
ledger::MEMORY_LAYOUT_LEDGER_STABLE_KEY,
None,
None,
)
}
pub(crate) fn crate_name(&self) -> &str {
&self.crate_name
}
pub(crate) const fn declaration(&self) -> &AllocationDeclaration {
&self.declaration
}
}
#[derive(Debug, ThisError)]
pub enum MemoryRegistryError {
#[error("memory declaration rejected for stable key '{stable_key}': {reason}")]
InvalidDeclaration {
stable_key: String,
reason: &'static str,
},
#[error(
"memory stable key '{stable_key}' with id {id} violates namespace/range authority: {reason}"
)]
RangeAuthorityViolation {
stable_key: String,
id: u8,
reason: &'static str,
},
#[error(
"memory registration after bootstrap is sealed is not allowed: {registrations} registration(s)"
)]
RegistrationAfterBootstrap {
registrations: usize,
},
#[error("memory registry has not completed bootstrap validation")]
RegistryNotBootstrapped,
#[error("memory layout ledger is corrupt: {reason}")]
LedgerCorrupt {
reason: &'static str,
},
}
#[cfg(test)]
thread_local! {
static PENDING_REGISTRATIONS: RefCell<Vec<PendingRegistration>> = const { RefCell::new(Vec::new()) };
}
static STATIC_DECLARATIONS: Mutex<Vec<PendingRegistration>> = Mutex::new(Vec::new());
#[doc(hidden)]
pub fn declare_memory_slot_with_key(
id: u8,
crate_name: &str,
label: &str,
stable_key: &str,
) -> Result<(), MemoryRegistryError> {
declare_memory_slot_with_key_metadata(id, crate_name, label, stable_key, None, None)
}
#[doc(hidden)]
pub fn declare_memory_slot_with_key_metadata(
id: u8,
crate_name: &str,
label: &str,
stable_key: &str,
schema_version: Option<u32>,
schema_fingerprint: Option<&str>,
) -> Result<(), MemoryRegistryError> {
let registration = PendingRegistration::from_parts(
id,
crate_name,
label,
stable_key,
schema_version,
schema_fingerprint,
)?;
STATIC_DECLARATIONS
.lock()
.expect("static memory declaration queue poisoned")
.push(registration);
Ok(())
}
#[cfg(test)]
pub fn defer_register_with_key(
id: u8,
crate_name: &str,
label: &str,
stable_key: &str,
) -> Result<(), MemoryRegistryError> {
defer_register_with_key_metadata(id, crate_name, label, stable_key, None, None)
}
#[cfg(test)]
pub fn defer_register_with_key_metadata(
id: u8,
crate_name: &str,
label: &str,
stable_key: &str,
schema_version: Option<u32>,
schema_fingerprint: Option<&str>,
) -> Result<(), MemoryRegistryError> {
let registration = PendingRegistration::from_parts(
id,
crate_name,
label,
stable_key,
schema_version,
schema_fingerprint,
)?;
PENDING_REGISTRATIONS.with_borrow_mut(|regs| {
regs.push(registration);
});
Ok(())
}
#[must_use]
pub(crate) fn static_declarations() -> Vec<PendingRegistration> {
STATIC_DECLARATIONS
.lock()
.expect("static memory declaration queue poisoned")
.clone()
}
#[cfg(test)]
pub(crate) fn drain_pending_registrations() -> Vec<PendingRegistration> {
PENDING_REGISTRATIONS.with_borrow_mut(std::mem::take)
}
#[cfg(test)]
pub fn reset_for_tests() {
reset_runtime_for_tests();
ledger::reset_for_tests();
super::runtime::registry::reset_initialized_for_tests();
}
#[cfg(test)]
fn reset_runtime_for_tests() {
PENDING_REGISTRATIONS.with_borrow_mut(Vec::clear);
}
pub(super) fn memory_registry_error_from_declaration_error(
err: DeclarationSnapshotError,
stable_key: &str,
) -> MemoryRegistryError {
let (stable_key, reason) = if let DeclarationSnapshotError::Key(err) = err {
(err.stable_key, err.reason)
} else {
(stable_key.to_string(), "declaration rejected by ic-memory")
};
MemoryRegistryError::InvalidDeclaration { stable_key, reason }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defers_valid_application_declaration() {
reset_for_tests();
defer_register_with_key(100, "crate_a", "slot", "app.crate_a.slot.v1")
.expect("register valid app slot");
let registrations = drain_pending_registrations();
assert_eq!(registrations.len(), 1);
assert_eq!(
registrations[0].declaration().stable_key().as_str(),
"app.crate_a.slot.v1"
);
}
#[test]
fn rejects_ic_memory_key_from_non_ic_memory_owner() {
reset_for_tests();
let err = defer_register_with_key(10, "canic-core", "slot", "ic_memory.future.v1")
.expect_err("only ic-memory may declare ic_memory keys");
assert!(matches!(
err,
MemoryRegistryError::RangeAuthorityViolation { .. }
));
}
}