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