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_LABEL, IC_MEMORY_LEDGER_STABLE_KEY,
6        MEMORY_MANAGER_LEDGER_ID, is_ic_memory_stable_key,
7    },
8    text::validate_diagnostic_text,
9};
10use serde::{Deserialize, Serialize};
11use std::{borrow::Cow, 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        Self::new(
39            request.authority,
40            request.stable_key.as_str(),
41            request.schema,
42        )
43        .map_err(D::Error::custom)
44    }
45}
46
47impl MemoryRequest {
48    /// Build a checked logical request before sealing.
49    pub fn new(
50        authority: impl Into<String>,
51        stable_key: &str,
52        schema: SchemaMetadata,
53    ) -> Result<Self, MemoryDeclarationError> {
54        let authority = authority.into();
55        validate_external_authority(&authority)?;
56        let stable_key =
57            crate::StableKey::parse(stable_key).map_err(crate::DeclarationSnapshotError::Key)?;
58        if is_ic_memory_stable_key(stable_key.as_str()) {
59            return Err(MemoryDeclarationError::ReservedStableKey {
60                stable_key: stable_key.as_str().to_string(),
61            });
62        }
63        Ok(Self {
64            authority,
65            stable_key,
66            schema,
67        })
68    }
69
70    /// Attach schema metadata from the immutable, integrity-checked recovered ledger.
71    pub(crate) const fn with_schema(mut self, schema: SchemaMetadata) -> Self {
72        self.schema = schema;
73        self
74    }
75
76    /// Borrow the requested durable key.
77    #[must_use]
78    pub const fn stable_key(&self) -> &crate::StableKey {
79        &self.stable_key
80    }
81
82    /// Borrow the requested diagnostic schema metadata.
83    #[must_use]
84    pub const fn schema(&self) -> &SchemaMetadata {
85        &self.schema
86    }
87
88    /// Borrow the declaring authority.
89    #[must_use]
90    pub fn authority(&self) -> &str {
91        &self.authority
92    }
93}
94
95///
96/// MemoryDeclarationError
97///
98/// Failure to construct a complete allocation declaration snapshot.
99///
100
101#[non_exhaustive]
102#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
103pub enum MemoryDeclarationError {
104    #[error("at most 254 external requests are supported")]
105    TooManyDeclarations,
106    #[error("duplicate requested stable key {stable_key}")]
107    DuplicateRequest { stable_key: crate::StableKey },
108    /// Declaration validation failed.
109    #[error(transparent)]
110    Declaration(#[from] crate::DeclarationSnapshotError),
111    /// A request supplied an invalid authority identifier.
112    #[error("authority {reason}")]
113    InvalidAuthority {
114        /// Validation failure.
115        reason: &'static str,
116    },
117    /// A request attempted to impersonate the internal authority.
118    #[error("authority '{authority}' is reserved for ic-memory runtime internals")]
119    ReservedAuthority {
120        /// Reserved authority identifier.
121        authority: String,
122    },
123    /// A request attempted to claim the internal stable-key namespace.
124    #[error("stable key '{stable_key}' is reserved for ic-memory runtime internals")]
125    ReservedStableKey {
126        /// Reserved stable key.
127        stable_key: String,
128    },
129}
130
131///
132/// SealedDeclarationSnapshot
133///
134/// Immutable, canonical host-composed allocation requests supplied to each concrete [`crate::MemoryRuntime`].
135///
136/// Construction takes ownership of the complete request list and validates its
137/// canonical meaning. Clones share the same immutable snapshot. This value contains
138/// declaration authority only; it contains no memory handles, recovery state,
139/// bootstrap lifecycle, or committed allocation capability.
140///
141
142#[derive(Clone, Debug, Eq, PartialEq)]
143pub struct SealedDeclarationSnapshot {
144    inner: Arc<SealedDeclarationSnapshotInner>,
145}
146
147///
148/// SealedDeclarationFingerprint
149///
150/// Deterministic non-cryptographic fingerprint of one canonical sealed
151/// declaration snapshot.
152///
153/// The fingerprint covers canonical source keys, owner labels and schema metadata. It is diagnostic
154/// metadata for comparing in-memory bootstrap bindings, not persisted
155/// allocation authority or an adversarial integrity proof.
156///
157
158#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
159#[serde(deny_unknown_fields)]
160pub struct SealedDeclarationFingerprint {
161    algorithm_version: u8,
162    value: u64,
163}
164
165impl SealedDeclarationFingerprint {
166    /// Return the diagnostic fingerprint algorithm version.
167    #[must_use]
168    pub const fn algorithm_version(&self) -> u8 {
169        self.algorithm_version
170    }
171
172    /// Return the non-cryptographic fingerprint value.
173    #[must_use]
174    pub const fn value(&self) -> u64 {
175        self.value
176    }
177}
178
179#[derive(Debug, Eq, PartialEq)]
180struct SealedDeclarationSnapshotInner {
181    requests: Vec<MemoryRequest>,
182    fingerprint: SealedDeclarationFingerprint,
183}
184
185impl SealedDeclarationSnapshot {
186    /// Take ownership of the host's complete requests, checking the bound and
187    /// rejecting duplicate keys before publishing their canonical fingerprint.
188    pub fn new(mut requests: Vec<MemoryRequest>) -> Result<Self, MemoryDeclarationError> {
189        if requests.len() > 254 {
190            return Err(MemoryDeclarationError::TooManyDeclarations);
191        }
192        crate::sort::sort_unstable_by(&mut requests, |a, b| a.stable_key.cmp(&b.stable_key));
193        for pair in requests.windows(2) {
194            if pair[0].stable_key == pair[1].stable_key {
195                return Err(MemoryDeclarationError::DuplicateRequest {
196                    stable_key: pair[1].stable_key.clone(),
197                });
198            }
199        }
200        let fingerprint = sealed_declaration_fingerprint(&requests);
201        Ok(Self {
202            inner: Arc::new(SealedDeclarationSnapshotInner {
203                requests,
204                fingerprint,
205            }),
206        })
207    }
208
209    /// Borrow canonical unresolved key-only requests.
210    #[must_use]
211    pub fn requests(&self) -> &[MemoryRequest] {
212        &self.inner.requests
213    }
214
215    pub(crate) fn resolve(
216        &self,
217        ledger: &crate::AllocationLedger,
218        historical: Vec<MemoryRequest>,
219        pool: &crate::MemoryAllocationPool,
220    ) -> Result<DeclarationSnapshot, crate::MemoryResolutionError> {
221        if self.requests().len() + historical.len() > 254 {
222            return Err(MemoryDeclarationError::TooManyDeclarations.into());
223        }
224        let mut declarations = Vec::with_capacity(self.requests().len() + historical.len() + 1);
225        declarations.push(internal_ledger_declaration());
226        let mut occupied = [false; 255];
227        for record in ledger.records() {
228            occupied[usize::from(record.slot().id())] = true;
229        }
230        // Only the original requests can allocate new slots and they are already
231        // canonical. Admission selections are known-only: all their slots are
232        // occupied above regardless of selection order. Final declarations are
233        // canonicalized and checked together below.
234        for request in self
235            .requests()
236            .iter()
237            .map(Cow::Borrowed)
238            .chain(historical.into_iter().map(Cow::Owned))
239        {
240            let historical = ledger
241                .records()
242                .iter()
243                .find(|record| record.stable_key() == &request.stable_key);
244            pool.validate_authority(&request.stable_key, &request.authority)?;
245            let id = if let Some(record) = historical {
246                // Durable identity selects placement; the host separately grants
247                // current key ownership and retains explicit physical exclusions.
248                pool.validate_id(record.slot().id())?;
249                record.slot().id()
250            } else {
251                (0..crate::MEMORY_MANAGER_INVALID_ID)
252                    .find(|id| pool.contains(*id) && !occupied[usize::from(*id)])
253                    .ok_or_else(|| crate::MemoryResolutionError::Exhausted {
254                        stable_key: request.stable_key.clone(),
255                        authority: request.authority.clone(),
256                    })?
257            };
258            let slot = crate::MemoryManagerSlot::new(id).expect("usable id");
259            occupied[usize::from(id)] = true;
260            // Request construction checked authority/key/schema, and recovery
261            // checked historical schemas. The final declaration needs only
262            // key/schema: do not copy the already-checked authority string.
263            // Admission selections transfer those owned fields without copying.
264            let declaration = match request {
265                Cow::Borrowed(request) => AllocationDeclaration {
266                    stable_key: request.stable_key.clone(),
267                    slot,
268                    label: None,
269                    schema: request.schema.clone(),
270                },
271                Cow::Owned(request) => AllocationDeclaration {
272                    stable_key: request.stable_key,
273                    slot,
274                    label: None,
275                    schema: request.schema,
276                },
277            };
278            declarations.push(declaration);
279        }
280        crate::sort::sort_unstable_by(&mut declarations, |a, b| a.stable_key().cmp(b.stable_key()));
281        Ok(DeclarationSnapshot::new(declarations).map_err(MemoryDeclarationError::from)?)
282    }
283
284    /// Return the deterministic fingerprint of this sealed declaration meaning.
285    #[must_use]
286    pub fn fingerprint(&self) -> SealedDeclarationFingerprint {
287        self.inner.fingerprint
288    }
289
290    #[cfg(test)]
291    pub(crate) fn shares_storage_with(&self, other: &Self) -> bool {
292        Arc::ptr_eq(&self.inner, &other.inner)
293    }
294}
295
296fn validate_external_authority(value: &str) -> Result<(), MemoryDeclarationError> {
297    reject_internal_authority(value)?;
298    validate_diagnostic_text(value).map_err(|error| MemoryDeclarationError::InvalidAuthority {
299        reason: error.reason(),
300    })
301}
302
303fn reject_internal_authority(value: &str) -> Result<(), MemoryDeclarationError> {
304    if value == IC_MEMORY_AUTHORITY_OWNER {
305        return Err(MemoryDeclarationError::ReservedAuthority {
306            authority: value.to_string(),
307        });
308    }
309    Ok(())
310}
311
312#[derive(Serialize)]
313struct SealedDeclarationFingerprintMaterial<'a> {
314    format: &'static str,
315    requests: &'a [MemoryRequest],
316}
317
318// Fingerprints need the canonical encoded bytes only as input to the hash;
319// keep no payload buffer after serialization.
320struct FingerprintWriter(u64);
321
322impl std::io::Write for FingerprintWriter {
323    fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
324        self.0 = crate::hash::fnv64(self.0, bytes);
325        Ok(bytes.len())
326    }
327
328    fn flush(&mut self) -> std::io::Result<()> {
329        Ok(())
330    }
331}
332
333fn sealed_declaration_fingerprint(requests: &[MemoryRequest]) -> SealedDeclarationFingerprint {
334    let material = SealedDeclarationFingerprintMaterial {
335        format: "ic-memory.sealed-declaration-fingerprint.v1",
336        requests,
337    };
338    let mut writer = FingerprintWriter(crate::hash::FNV_OFFSET);
339    // Concrete derived serializers and this hash writer have no recoverable failures.
340    ciborium::into_writer(&material, &mut writer)
341        .unwrap_or_else(|_| panic!("sealed declaration fingerprint encodes into hash"));
342
343    SealedDeclarationFingerprint {
344        algorithm_version: SEALED_DECLARATION_FINGERPRINT_VERSION,
345        value: writer.0,
346    }
347}
348
349const SEALED_DECLARATION_FINGERPRINT_VERSION: u8 = 1;
350
351fn internal_ledger_declaration() -> AllocationDeclaration {
352    AllocationDeclaration::memory_manager(
353        IC_MEMORY_LEDGER_STABLE_KEY,
354        MEMORY_MANAGER_LEDGER_ID,
355        IC_MEMORY_LEDGER_LABEL,
356    )
357    .unwrap_or_else(|_| unreachable!("built-in ledger declaration constants are valid"))
358}
359
360#[cfg(test)]
361mod tests;