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