Skip to main content

ic_memory/slot/
range_authority.rs

1use super::descriptor::AllocationSlotDescriptor;
2use super::memory_manager::{
3    MEMORY_MANAGER_INVALID_ID, MEMORY_MANAGER_MAX_ID, MEMORY_MANAGER_MIN_ID,
4    MemoryManagerSlotError, validate_memory_manager_id,
5};
6use crate::constants::DIAGNOSTIC_STRING_MAX_BYTES;
7use serde::{Deserialize, Serialize};
8
9///
10/// MemoryManagerIdRange
11///
12/// Inclusive range of usable `MemoryManager` virtual memory IDs.
13#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
14#[serde(deny_unknown_fields)]
15pub struct MemoryManagerIdRange {
16    pub(crate) start: u8,
17    pub(crate) end: u8,
18}
19
20impl MemoryManagerIdRange {
21    /// Construct and validate an inclusive `MemoryManager` ID range.
22    pub const fn new(start: u8, end: u8) -> Result<Self, MemoryManagerRangeError> {
23        if start > end {
24            return Err(MemoryManagerRangeError::InvalidRange { start, end });
25        }
26        if start == MEMORY_MANAGER_INVALID_ID {
27            return Err(MemoryManagerRangeError::InvalidMemoryManagerId { id: start });
28        }
29        if end == MEMORY_MANAGER_INVALID_ID {
30            return Err(MemoryManagerRangeError::InvalidMemoryManagerId { id: end });
31        }
32        Ok(Self { start, end })
33    }
34
35    /// Return the full usable `MemoryManager` ID range.
36    #[must_use]
37    pub const fn all_usable() -> Self {
38        Self {
39            start: MEMORY_MANAGER_MIN_ID,
40            end: MEMORY_MANAGER_MAX_ID,
41        }
42    }
43
44    /// Return true when `id` is inside this inclusive range.
45    #[must_use]
46    pub const fn contains(&self, id: u8) -> bool {
47        id >= self.start && id <= self.end
48    }
49
50    /// Validate this range's decoded bounds.
51    pub const fn validate(&self) -> Result<(), MemoryManagerRangeError> {
52        match Self::new(self.start, self.end) {
53            Ok(_) => Ok(()),
54            Err(err) => Err(err),
55        }
56    }
57
58    /// First usable ID in the range.
59    #[must_use]
60    pub const fn start(&self) -> u8 {
61        self.start
62    }
63
64    /// Last usable ID in the range.
65    #[must_use]
66    pub const fn end(&self) -> u8 {
67        self.end
68    }
69}
70
71///
72/// MemoryManagerRangeError
73///
74/// Invalid `MemoryManager` virtual memory ID range.
75#[derive(Clone, Copy, Debug, Eq, thiserror::Error, PartialEq)]
76pub enum MemoryManagerRangeError {
77    /// Range bounds are reversed.
78    #[error("MemoryManager ID range is invalid: start={start} end={end}")]
79    InvalidRange {
80        /// Requested first ID.
81        start: u8,
82        /// Requested last ID.
83        end: u8,
84    },
85    /// ID 255 is the unallocated-bucket sentinel.
86    #[error("MemoryManager ID {id} is not a usable allocation slot")]
87    InvalidMemoryManagerId {
88        /// Invalid MemoryManager ID.
89        id: u8,
90    },
91}
92
93///
94/// MemoryManagerRangeMode
95///
96/// Diagnostic policy mode for a `MemoryManager` authority range.
97///
98/// These modes describe policy authority, not durable allocation state.
99#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
100pub enum MemoryManagerRangeMode {
101    /// Range is reserved for authority-owned framework or infrastructure use.
102    ///
103    /// Reserved does not mean every ID in the range has been allocated.
104    Reserved,
105    /// Range is allowed for authority-governed application allocation use.
106    ///
107    /// Allowed does not allocate any ID in the range.
108    Allowed,
109}
110
111///
112/// MemoryManagerAuthorityRecord
113///
114/// Ordered diagnostic authority record for a `MemoryManager` ID range.
115#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
116#[serde(deny_unknown_fields)]
117pub struct MemoryManagerAuthorityRecord {
118    /// Inclusive range governed by this authority.
119    pub range: MemoryManagerIdRange,
120    /// Stable printable ASCII authority identifier.
121    pub authority: String,
122    /// Policy mode for this authority range.
123    pub mode: MemoryManagerRangeMode,
124    /// Optional stable printable ASCII diagnostic purpose.
125    pub purpose: Option<String>,
126}
127
128impl MemoryManagerAuthorityRecord {
129    /// Build a diagnostic authority record after validating printable metadata.
130    pub fn new(
131        range: MemoryManagerIdRange,
132        authority: impl Into<String>,
133        mode: MemoryManagerRangeMode,
134        purpose: Option<String>,
135    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
136        let record = Self {
137            range,
138            authority: authority.into(),
139            mode,
140            purpose,
141        };
142        validate_authority_record(&record)?;
143        Ok(record)
144    }
145}
146
147///
148/// MemoryManagerRangeAuthority
149///
150/// Substrate-specific range authority policy helper for `MemoryManager` IDs.
151///
152/// This helper records policy and diagnostic authority ranges only. It never
153/// mutates the allocation ledger, and it does not allocate or reserve durable
154/// stable-memory slots. Durable allocation remains the generic ledger mapping
155/// from stable key to allocation slot.
156///
157/// When used through the default runtime registry, registered ranges are
158/// authoritative generic policy and are checked before caller-supplied
159/// [`crate::AllocationPolicy`]. Frameworks that want their own policy to own
160/// application space should avoid registering ranges for that space.
161#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
162#[serde(deny_unknown_fields)]
163pub struct MemoryManagerRangeAuthority {
164    authorities: Vec<MemoryManagerAuthorityRecord>,
165}
166
167impl MemoryManagerRangeAuthority {
168    /// Create an empty `MemoryManager` range authority policy.
169    #[must_use]
170    pub const fn new() -> Self {
171        Self {
172            authorities: Vec::new(),
173        }
174    }
175
176    /// Build a range authority from diagnostic records.
177    ///
178    /// Records are validated with the same rules as the builder methods and
179    /// stored in ascending range order.
180    pub fn from_records(
181        records: Vec<MemoryManagerAuthorityRecord>,
182    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
183        let mut authority = Self::new();
184        for record in records {
185            authority = authority.insert_record(record)?;
186        }
187        Ok(authority)
188    }
189
190    /// Add a reserved authority range.
191    ///
192    /// Reserved is a policy authority mode. It does not allocate every ID in
193    /// the range and does not write to the allocation ledger.
194    pub fn reserve(
195        self,
196        range: MemoryManagerIdRange,
197        authority: impl Into<String>,
198    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
199        self.reserve_with_purpose(range, authority, None)
200    }
201
202    /// Add a reserved authority range from inclusive ID bounds.
203    ///
204    /// Reserved is a policy authority mode. It does not allocate every ID in
205    /// the range and does not write to the allocation ledger.
206    pub fn reserve_ids(
207        self,
208        start: u8,
209        end: u8,
210        authority: impl Into<String>,
211    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
212        self.reserve(MemoryManagerIdRange::new(start, end)?, authority)
213    }
214
215    /// Add a reserved authority range with a diagnostic purpose.
216    ///
217    /// Reserved is a policy authority mode. It does not allocate every ID in
218    /// the range and does not write to the allocation ledger.
219    pub fn reserve_with_purpose(
220        self,
221        range: MemoryManagerIdRange,
222        authority: impl Into<String>,
223        purpose: Option<String>,
224    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
225        self.insert(range, authority, MemoryManagerRangeMode::Reserved, purpose)
226    }
227
228    /// Add a reserved authority range from inclusive ID bounds with a diagnostic purpose.
229    ///
230    /// Reserved is a policy authority mode. It does not allocate every ID in
231    /// the range and does not write to the allocation ledger.
232    pub fn reserve_ids_with_purpose(
233        self,
234        start: u8,
235        end: u8,
236        authority: impl Into<String>,
237        purpose: Option<String>,
238    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
239        self.reserve_with_purpose(MemoryManagerIdRange::new(start, end)?, authority, purpose)
240    }
241
242    /// Add an allowed authority range.
243    ///
244    /// Allowed is a policy authority mode. It does not allocate any ID in the
245    /// range and does not write to the allocation ledger.
246    pub fn allow(
247        self,
248        range: MemoryManagerIdRange,
249        authority: impl Into<String>,
250    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
251        self.allow_with_purpose(range, authority, None)
252    }
253
254    /// Add an allowed authority range from inclusive ID bounds.
255    ///
256    /// Allowed is a policy authority mode. It does not allocate any ID in the
257    /// range and does not write to the allocation ledger.
258    pub fn allow_ids(
259        self,
260        start: u8,
261        end: u8,
262        authority: impl Into<String>,
263    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
264        self.allow(MemoryManagerIdRange::new(start, end)?, authority)
265    }
266
267    /// Add an allowed authority range with a diagnostic purpose.
268    ///
269    /// Allowed is a policy authority mode. It does not allocate any ID in the
270    /// range and does not write to the allocation ledger.
271    pub fn allow_with_purpose(
272        self,
273        range: MemoryManagerIdRange,
274        authority: impl Into<String>,
275        purpose: Option<String>,
276    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
277        self.insert(range, authority, MemoryManagerRangeMode::Allowed, purpose)
278    }
279
280    /// Add an allowed authority range from inclusive ID bounds with a diagnostic purpose.
281    ///
282    /// Allowed is a policy authority mode. It does not allocate any ID in the
283    /// range and does not write to the allocation ledger.
284    pub fn allow_ids_with_purpose(
285        self,
286        start: u8,
287        end: u8,
288        authority: impl Into<String>,
289        purpose: Option<String>,
290    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
291        self.allow_with_purpose(MemoryManagerIdRange::new(start, end)?, authority, purpose)
292    }
293
294    /// Validate that `slot` belongs to `expected_authority`.
295    pub fn validate_slot_authority(
296        &self,
297        slot: &AllocationSlotDescriptor,
298        expected_authority: &str,
299    ) -> Result<&MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError> {
300        let id = slot
301            .memory_manager_id()
302            .map_err(MemoryManagerRangeAuthorityError::Slot)?;
303        self.validate_id_authority(id, expected_authority)
304    }
305
306    /// Validate that `slot` belongs to `expected_authority` with `expected_mode`.
307    pub fn validate_slot_authority_mode(
308        &self,
309        slot: &AllocationSlotDescriptor,
310        expected_authority: &str,
311        expected_mode: MemoryManagerRangeMode,
312    ) -> Result<&MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError> {
313        let id = slot
314            .memory_manager_id()
315            .map_err(MemoryManagerRangeAuthorityError::Slot)?;
316        self.validate_id_authority_mode(id, expected_authority, expected_mode)
317    }
318
319    /// Validate that `id` belongs to `expected_authority`.
320    pub fn validate_id_authority(
321        &self,
322        id: u8,
323        expected_authority: &str,
324    ) -> Result<&MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError> {
325        validate_diagnostic_string("expected_authority", expected_authority)?;
326        let record = self.covering_record(id)?;
327
328        if record.authority != expected_authority {
329            return Err(MemoryManagerRangeAuthorityError::AuthorityMismatch {
330                id,
331                expected_authority: expected_authority.to_string(),
332                actual_authority: record.authority.clone(),
333            });
334        }
335
336        Ok(record)
337    }
338
339    /// Validate that `id` belongs to `expected_authority` with `expected_mode`.
340    pub fn validate_id_authority_mode(
341        &self,
342        id: u8,
343        expected_authority: &str,
344        expected_mode: MemoryManagerRangeMode,
345    ) -> Result<&MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError> {
346        let record = self.validate_id_authority(id, expected_authority)?;
347        if record.mode != expected_mode {
348            return Err(MemoryManagerRangeAuthorityError::ModeMismatch {
349                id,
350                authority: record.authority.clone(),
351                expected_mode,
352                actual_mode: record.mode,
353            });
354        }
355        Ok(record)
356    }
357
358    /// Return the authority record that governs `id`, if any.
359    pub fn authority_for_id(
360        &self,
361        id: u8,
362    ) -> Result<Option<&MemoryManagerAuthorityRecord>, MemoryManagerRangeAuthorityError> {
363        validate_memory_manager_id(id).map_err(MemoryManagerRangeAuthorityError::Slot)?;
364        Ok(self
365            .authorities
366            .iter()
367            .find(|record| record.range.contains(id)))
368    }
369
370    /// Ordered non-overlapping authority records.
371    ///
372    /// This is the stable diagnostic/export surface for the authority table.
373    /// Records are returned in ascending range order and do not imply ledger
374    /// allocation state.
375    #[must_use]
376    pub fn authorities(&self) -> &[MemoryManagerAuthorityRecord] {
377        &self.authorities
378    }
379
380    /// Validate that authority records exactly and contiguously cover `target`.
381    ///
382    /// All records must be inside `target`, and together they must form a
383    /// gap-free partition. This checks policy table coverage only and never
384    /// changes allocation ledger state.
385    pub fn validate_complete_coverage(
386        &self,
387        target: MemoryManagerIdRange,
388    ) -> Result<(), MemoryManagerRangeAuthorityError> {
389        if self.authorities.is_empty() {
390            return Err(MemoryManagerRangeAuthorityError::MissingCoverage {
391                start: target.start(),
392                end: target.end(),
393            });
394        }
395
396        for record in &self.authorities {
397            if record.range.start() < target.start() || record.range.end() > target.end() {
398                return Err(
399                    MemoryManagerRangeAuthorityError::RangeOutsideCoverageTarget {
400                        start: record.range.start(),
401                        end: record.range.end(),
402                        target_start: target.start(),
403                        target_end: target.end(),
404                    },
405                );
406            }
407        }
408
409        let mut next_uncovered = u16::from(target.start());
410        let target_end = u16::from(target.end());
411        for record in &self.authorities {
412            let record_start = u16::from(record.range.start());
413            let record_end = u16::from(record.range.end());
414
415            if record_start > next_uncovered {
416                return Err(MemoryManagerRangeAuthorityError::MissingCoverage {
417                    start: u8::try_from(next_uncovered).expect("valid MemoryManager ID"),
418                    end: record.range.start() - 1,
419                });
420            }
421
422            if record_end >= next_uncovered {
423                next_uncovered = record_end + 1;
424            }
425        }
426
427        if next_uncovered <= target_end {
428            return Err(MemoryManagerRangeAuthorityError::MissingCoverage {
429                start: u8::try_from(next_uncovered).expect("valid MemoryManager ID"),
430                end: target.end(),
431            });
432        }
433
434        Ok(())
435    }
436
437    fn insert(
438        self,
439        range: MemoryManagerIdRange,
440        authority: impl Into<String>,
441        mode: MemoryManagerRangeMode,
442        purpose: Option<String>,
443    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
444        let record = MemoryManagerAuthorityRecord {
445            range,
446            authority: authority.into(),
447            mode,
448            purpose,
449        };
450        self.insert_record(record)
451    }
452
453    fn insert_record(
454        mut self,
455        record: MemoryManagerAuthorityRecord,
456    ) -> Result<Self, MemoryManagerRangeAuthorityError> {
457        validate_authority_record(&record)?;
458
459        for existing in &self.authorities {
460            if ranges_overlap(existing.range, record.range) {
461                return Err(MemoryManagerRangeAuthorityError::OverlappingRanges {
462                    existing_start: existing.range.start(),
463                    existing_end: existing.range.end(),
464                    candidate_start: record.range.start(),
465                    candidate_end: record.range.end(),
466                });
467            }
468        }
469
470        self.authorities.push(record);
471        self.authorities.sort_by_key(|record| record.range.start());
472        Ok(self)
473    }
474
475    fn covering_record(
476        &self,
477        id: u8,
478    ) -> Result<&MemoryManagerAuthorityRecord, MemoryManagerRangeAuthorityError> {
479        let Some(record) = self.authority_for_id(id)? else {
480            return Err(MemoryManagerRangeAuthorityError::UnclaimedId { id });
481        };
482        Ok(record)
483    }
484}
485
486fn validate_authority_record(
487    record: &MemoryManagerAuthorityRecord,
488) -> Result<(), MemoryManagerRangeAuthorityError> {
489    record.range.validate()?;
490    validate_diagnostic_string("authority", &record.authority)?;
491    if let Some(purpose) = &record.purpose {
492        validate_diagnostic_string("purpose", purpose)?;
493    }
494    Ok(())
495}
496
497///
498/// MemoryManagerRangeAuthorityError
499///
500/// Invalid `MemoryManager` range authority policy.
501#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
502pub enum MemoryManagerRangeAuthorityError {
503    /// Authority range bounds are invalid.
504    #[error(transparent)]
505    Range(#[from] MemoryManagerRangeError),
506    /// Slot descriptor is not a usable `MemoryManager` ID slot.
507    #[error("{0}")]
508    Slot(#[from] MemoryManagerSlotError),
509    /// Authority range overlaps an existing range.
510    #[error(
511        "MemoryManager authority range {candidate_start}-{candidate_end} overlaps existing range {existing_start}-{existing_end}"
512    )]
513    OverlappingRanges {
514        /// Existing range start.
515        existing_start: u8,
516        /// Existing range end.
517        existing_end: u8,
518        /// Candidate range start.
519        candidate_start: u8,
520        /// Candidate range end.
521        candidate_end: u8,
522    },
523    /// Authority or purpose text failed diagnostic string validation.
524    #[error("{field} {reason}")]
525    InvalidDiagnosticString {
526        /// Diagnostic field name.
527        field: &'static str,
528        /// Validation failure.
529        reason: &'static str,
530    },
531    /// No authority range covers the requested ID.
532    #[error("MemoryManager ID {id} is not covered by an authority range")]
533    UnclaimedId {
534        /// Unclaimed MemoryManager ID.
535        id: u8,
536    },
537    /// Slot is governed by a different authority.
538    #[error(
539        "MemoryManager ID {id} belongs to authority '{actual_authority}', not '{expected_authority}'"
540    )]
541    AuthorityMismatch {
542        /// MemoryManager ID.
543        id: u8,
544        /// Expected authority identifier.
545        expected_authority: String,
546        /// Actual authority identifier.
547        actual_authority: String,
548    },
549    /// Slot is governed by the expected authority with a different mode.
550    #[error(
551        "MemoryManager ID {id} belongs to authority '{authority}' with mode {actual_mode:?}, not {expected_mode:?}"
552    )]
553    ModeMismatch {
554        /// MemoryManager ID.
555        id: u8,
556        /// Authority identifier.
557        authority: String,
558        /// Expected authority mode.
559        expected_mode: MemoryManagerRangeMode,
560        /// Actual authority mode.
561        actual_mode: MemoryManagerRangeMode,
562    },
563    /// A complete coverage target has no authority records for part of it.
564    #[error("MemoryManager authority coverage is missing range {start}-{end}")]
565    MissingCoverage {
566        /// First missing ID.
567        start: u8,
568        /// Last missing ID.
569        end: u8,
570    },
571    /// An authority record lies outside the complete coverage target.
572    #[error(
573        "MemoryManager authority range {start}-{end} is outside coverage target {target_start}-{target_end}"
574    )]
575    RangeOutsideCoverageTarget {
576        /// Authority range start.
577        start: u8,
578        /// Authority range end.
579        end: u8,
580        /// Coverage target start.
581        target_start: u8,
582        /// Coverage target end.
583        target_end: u8,
584    },
585}
586
587const fn ranges_overlap(left: MemoryManagerIdRange, right: MemoryManagerIdRange) -> bool {
588    left.start() <= right.end() && right.start() <= left.end()
589}
590
591fn validate_diagnostic_string(
592    field: &'static str,
593    value: &str,
594) -> Result<(), MemoryManagerRangeAuthorityError> {
595    if value.is_empty() {
596        return Err(MemoryManagerRangeAuthorityError::InvalidDiagnosticString {
597            field,
598            reason: "must not be empty",
599        });
600    }
601    if value.len() > DIAGNOSTIC_STRING_MAX_BYTES {
602        return Err(MemoryManagerRangeAuthorityError::InvalidDiagnosticString {
603            field,
604            reason: "must be at most 256 bytes",
605        });
606    }
607    if !value.is_ascii() {
608        return Err(MemoryManagerRangeAuthorityError::InvalidDiagnosticString {
609            field,
610            reason: "must be ASCII",
611        });
612    }
613    if value.bytes().any(|byte| byte.is_ascii_control()) {
614        return Err(MemoryManagerRangeAuthorityError::InvalidDiagnosticString {
615            field,
616            reason: "must not contain ASCII control characters",
617        });
618    }
619    Ok(())
620}