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