Skip to main content

ic_memory/
requests.rs

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