Skip to main content

ic_memory/
requests.rs

1use crate::{
2    declaration::AllocationDeclaration,
3    slot::{
4        IC_MEMORY_AUTHORITY_OWNER, IC_MEMORY_LEDGER_STABLE_KEY, MEMORY_MANAGER_LEDGER_ID,
5        is_ic_memory_stable_key,
6    },
7    text::validate_diagnostic_text,
8};
9use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11
12///
13/// MemoryRequest
14///
15/// Key-only request resolved after ledger recovery. All admitted owners share the
16/// host's free application pool; known keys retain their durable slot.
17///
18
19#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
20pub struct MemoryRequest {
21    authority: String,
22    stable_key: crate::StableKey,
23}
24
25impl<'de> Deserialize<'de> for MemoryRequest {
26    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
27        use serde::de::Error as _;
28        #[derive(Deserialize)]
29        #[serde(deny_unknown_fields)]
30        struct Request {
31            authority: String,
32            stable_key: crate::StableKey,
33        }
34        let request = Request::deserialize(deserializer)?;
35        validate_external_authority(&request.authority).map_err(D::Error::custom)?;
36        validate_external_key(&request.stable_key).map_err(D::Error::custom)?;
37        Ok(Self {
38            authority: request.authority,
39            stable_key: request.stable_key,
40        })
41    }
42}
43
44impl MemoryRequest {
45    /// Build a checked logical request before sealing.
46    pub fn new(
47        authority: impl Into<String>,
48        stable_key: &str,
49    ) -> Result<Self, MemoryDeclarationError> {
50        let authority = authority.into();
51        let stable_key = checked_request_key(&authority, stable_key)?;
52        Ok(Self {
53            authority,
54            stable_key,
55        })
56    }
57
58    /// Borrow the requested durable key.
59    #[must_use]
60    pub const fn stable_key(&self) -> &crate::StableKey {
61        &self.stable_key
62    }
63
64    /// Borrow the declaring authority.
65    #[must_use]
66    pub fn authority(&self) -> &str {
67        &self.authority
68    }
69}
70
71///
72/// MemoryDeclarationError
73///
74/// Failure to construct a complete allocation declaration snapshot.
75///
76
77#[non_exhaustive]
78#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
79pub enum MemoryDeclarationError {
80    #[error("at most 254 external requests are supported")]
81    TooManyDeclarations,
82    #[error("duplicate requested stable key {stable_key}")]
83    DuplicateRequest { stable_key: crate::StableKey },
84    /// Declaration validation failed.
85    #[error(transparent)]
86    Declaration(#[from] crate::DeclarationSnapshotError),
87    /// A request supplied an invalid authority identifier.
88    #[error("authority {reason}")]
89    InvalidAuthority {
90        /// Validation failure.
91        reason: &'static str,
92    },
93    /// A request attempted to impersonate the internal authority.
94    #[error("authority '{authority}' is reserved for ic-memory runtime internals")]
95    ReservedAuthority {
96        /// Reserved authority identifier.
97        authority: String,
98    },
99    /// A request attempted to claim the internal stable-key namespace.
100    #[error("stable key '{stable_key}' is reserved for ic-memory runtime internals")]
101    ReservedStableKey {
102        /// Reserved stable key.
103        stable_key: String,
104    },
105}
106
107///
108/// SealedDeclarationSnapshot
109///
110/// Immutable, canonical host-composed allocation requests supplied to each concrete [`crate::MemoryRuntime`].
111///
112/// Construction takes ownership of the complete request list and validates its
113/// canonical meaning. Clones share the same immutable snapshot. This value contains
114/// declaration authority only; it contains no memory handles, recovery state,
115/// bootstrap lifecycle, or committed allocation capability.
116///
117
118#[derive(Clone, Debug, Eq, PartialEq)]
119pub struct SealedDeclarationSnapshot {
120    inner: Arc<Vec<MemoryRequest>>,
121}
122
123///
124/// DeclarationSnapshot
125///
126/// Internal canonical output of resolution, without open authority.
127/// Sealing, recovered-ledger admission and the resolver establish its bounds
128/// and unique keys/slots. Only this module constructs production snapshots.
129///
130
131#[derive(Debug, Eq, PartialEq)]
132pub struct DeclarationSnapshot {
133    declarations: Vec<AllocationDeclaration>,
134}
135
136impl DeclarationSnapshot {
137    pub(crate) fn declarations(&self) -> &[AllocationDeclaration] {
138        &self.declarations
139    }
140
141    pub(crate) fn into_declarations(self) -> Vec<AllocationDeclaration> {
142        self.declarations
143    }
144
145    // Explicit synthetic staging inputs remain confined to unit fixtures.
146    #[cfg(test)]
147    pub(crate) fn for_test(mut declarations: Vec<AllocationDeclaration>) -> Self {
148        crate::sort::sort_unstable_by(&mut declarations, |a, b| a.stable_key.cmp(&b.stable_key));
149        Self { declarations }
150    }
151}
152
153impl SealedDeclarationSnapshot {
154    /// Take ownership of the host's complete requests, checking the bound and
155    /// rejecting duplicate keys before sharing their canonical contents.
156    pub fn new(mut requests: Vec<MemoryRequest>) -> Result<Self, MemoryDeclarationError> {
157        if requests.len() > 254 {
158            return Err(MemoryDeclarationError::TooManyDeclarations);
159        }
160        crate::sort::sort_unstable_by(&mut requests, |a, b| a.stable_key.cmp(&b.stable_key));
161        for pair in requests.windows(2) {
162            if pair[0].stable_key == pair[1].stable_key {
163                return Err(MemoryDeclarationError::DuplicateRequest {
164                    stable_key: pair[1].stable_key.clone(),
165                });
166            }
167        }
168        Ok(Self {
169            inner: Arc::new(requests),
170        })
171    }
172
173    /// Borrow canonical unresolved key-only requests.
174    #[must_use]
175    pub fn requests(&self) -> &[MemoryRequest] {
176        &self.inner
177    }
178
179    pub(crate) fn resolve(
180        &self,
181        recovered: &crate::RecoveredLedger,
182        occupied: &[bool; crate::constants::MAX_ALLOCATIONS],
183        historical: Vec<AllocationDeclaration>,
184        pool: &crate::MemoryAllocationPool,
185    ) -> Result<DeclarationSnapshot, crate::MemoryResolutionError> {
186        let ledger = recovered.ledger();
187        if self.requests().len() + historical.len() > 254 {
188            return Err(MemoryDeclarationError::TooManyDeclarations.into());
189        }
190        let mut declarations = Vec::with_capacity(self.requests().len() + historical.len() + 1);
191        declarations.push(internal_ledger_declaration());
192        // Retained slots are already occupied. The iterator consumes each new
193        // ID once, so no request rescans or updates that recovered occupancy.
194        let mut available_ids = 0..crate::MEMORY_MANAGER_INVALID_ID;
195        // Admission already resolved historical selections against this ledger
196        // and pool. Only the original requests can allocate new slots.
197        for request in self.requests() {
198            let historical = ledger
199                .records()
200                .iter()
201                .find(|record| record.stable_key() == &request.stable_key);
202            pool.validate_authority(&request.stable_key, &request.authority)?;
203            let id = if let Some(record) = historical {
204                // Durable identity selects placement; the host separately grants
205                // current key ownership and retains explicit physical exclusions.
206                pool.validate_id(record.slot().id())?;
207                record.slot().id()
208            } else {
209                available_ids
210                    .find(|id| pool.contains(*id) && !occupied[usize::from(*id)])
211                    .ok_or_else(|| crate::MemoryResolutionError::Exhausted {
212                        stable_key: request.stable_key.clone(),
213                        authority: request.authority.clone(),
214                    })?
215            };
216            let slot = crate::MemoryManagerSlot::new(id).expect("usable id");
217            // Resolution retains checked allocation identity without copying the owner.
218            declarations.push(AllocationDeclaration {
219                stable_key: request.stable_key.clone(),
220                slot,
221            });
222        }
223        declarations.extend(historical);
224        crate::sort::sort_unstable_by(&mut declarations, |a, b| a.stable_key.cmp(&b.stable_key));
225        Ok(DeclarationSnapshot { declarations })
226    }
227
228    #[cfg(test)]
229    pub(crate) fn shares_storage_with(&self, other: &Self) -> bool {
230        Arc::ptr_eq(&self.inner, &other.inner)
231    }
232}
233
234// Admission needs a checked key, without allocating a discarded request owner.
235pub fn checked_request_key(
236    authority: &str,
237    stable_key: &str,
238) -> Result<crate::StableKey, MemoryDeclarationError> {
239    validate_external_authority(authority)?;
240    let stable_key =
241        crate::StableKey::parse(stable_key).map_err(crate::DeclarationSnapshotError::Key)?;
242    validate_external_key(&stable_key)?;
243    Ok(stable_key)
244}
245
246fn validate_external_key(stable_key: &crate::StableKey) -> Result<(), MemoryDeclarationError> {
247    if is_ic_memory_stable_key(stable_key.as_str()) {
248        return Err(MemoryDeclarationError::ReservedStableKey {
249            stable_key: stable_key.as_str().to_string(),
250        });
251    }
252    Ok(())
253}
254
255fn validate_external_authority(value: &str) -> Result<(), MemoryDeclarationError> {
256    reject_internal_authority(value)?;
257    validate_diagnostic_text(value).map_err(|error| MemoryDeclarationError::InvalidAuthority {
258        reason: error.reason(),
259    })
260}
261
262fn reject_internal_authority(value: &str) -> Result<(), MemoryDeclarationError> {
263    if value == IC_MEMORY_AUTHORITY_OWNER {
264        return Err(MemoryDeclarationError::ReservedAuthority {
265            authority: value.to_string(),
266        });
267    }
268    Ok(())
269}
270
271fn internal_ledger_declaration() -> AllocationDeclaration {
272    AllocationDeclaration::memory_manager(IC_MEMORY_LEDGER_STABLE_KEY, MEMORY_MANAGER_LEDGER_ID)
273        .unwrap_or_else(|_| unreachable!("built-in ledger declaration constants are valid"))
274}
275
276#[cfg(test)]
277mod tests;