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