Skip to main content

native_ipc_core/
layout.rs

1//! Checked construction and validation of independent directional regions.
2
3use alloc::vec::Vec;
4use core::fmt;
5use core::ops::Range;
6
7use crate::codec::{VERSION_MAJOR, VERSION_MINOR};
8use crate::slot::{
9    ACKNOWLEDGEMENT_CELL_SIZE, AcknowledgementReaderBinding, AcknowledgementWriterBinding,
10    ReaderSlotBinding, SLOT_HEADER_SIZE, WriterSlotBinding,
11};
12
13/// Cache-line granularity used for concurrently accessed records.
14pub const CACHE_LINE: u64 = 64;
15/// Region signature stored in every mapping header.
16pub const REGION_MAGIC: [u8; 8] = *b"NIPCREG\0";
17/// Manually encoded region header size.
18pub const REGION_HEADER_SIZE: u64 = 128;
19
20/// A validated, nonzero numeric region role.
21#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
22pub struct RoleId(u32);
23
24impl RoleId {
25    /// Creates a role, rejecting the reserved zero value.
26    pub const fn new(value: u32) -> Option<Self> {
27        if value == 0 { None } else { Some(Self(value)) }
28    }
29
30    /// Returns the fixed-width wire value.
31    pub const fn get(self) -> u32 {
32        self.0
33    }
34}
35
36/// One of the two authenticated endpoints of a connection.
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38#[repr(u32)]
39pub enum Endpoint {
40    /// Endpoint that initiated the connection.
41    Initiator = 1,
42    /// Endpoint accepted or inherited the connection.
43    Responder = 2,
44}
45
46impl Endpoint {
47    const fn from_raw(raw: u32) -> Option<Self> {
48        match raw {
49            1 => Some(Self::Initiator),
50            2 => Some(Self::Responder),
51            _ => None,
52        }
53    }
54}
55
56/// Capacity of one independently permissioned region.
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub struct RegionSpec {
59    /// Numeric role unique within the connection.
60    pub role: RoleId,
61    /// Sole endpoint allowed to write this region.
62    pub writer: Endpoint,
63    /// Number of fixed-capacity ring slots.
64    pub slot_count: u32,
65    /// Maximum opaque payload bytes in each slot.
66    pub payload_bytes: u32,
67    /// Number of independently routed acknowledgement cells.
68    pub acknowledgement_count: u32,
69}
70
71/// Proposed per-slot acknowledgement route validated during region composition.
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73pub struct AcknowledgementRouteSpec {
74    /// Role whose mapping owns the acknowledgement cell.
75    pub owner: RoleId,
76    /// Producer role whose slot is acknowledged.
77    pub target: RoleId,
78    /// Slot index in the target region.
79    pub slot_index: u32,
80    /// Cell index in the owner region.
81    pub cell_index: u32,
82}
83
84/// A composition-validated, exact per-slot acknowledgement route.
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86pub struct AcknowledgementRoute {
87    owner: RoleId,
88    target: RoleId,
89    slot_index: u32,
90    cell_index: u32,
91}
92
93impl AcknowledgementRoute {
94    pub(crate) const fn validated(
95        owner: RoleId,
96        target: RoleId,
97        slot_index: u32,
98        cell_index: u32,
99    ) -> Self {
100        Self {
101            owner,
102            target,
103            slot_index,
104            cell_index,
105        }
106    }
107    /// Returns the role that owns the acknowledgement cell.
108    pub const fn owner(self) -> RoleId {
109        self.owner
110    }
111    /// Returns the producer role being acknowledged.
112    pub const fn target(self) -> RoleId {
113        self.target
114    }
115    /// Returns the exact target slot index.
116    pub const fn slot_index(self) -> u32 {
117        self.slot_index
118    }
119    /// Returns the exact owner cell index.
120    pub const fn cell_index(self) -> u32 {
121        self.cell_index
122    }
123}
124
125/// Bounds applied while calculating or validating a region.
126#[derive(Clone, Copy, Debug, Eq, PartialEq)]
127pub struct LayoutLimits {
128    /// Maximum complete mapping size.
129    pub maximum_mapping_size: u64,
130    /// Maximum slots in one region.
131    pub maximum_slot_count: u32,
132    /// Maximum acknowledgement cells in one region.
133    pub maximum_acknowledgement_count: u32,
134    /// Maximum opaque bytes in one slot.
135    pub maximum_payload_bytes: u32,
136}
137
138/// Checked layouts for caller-configured independent directional regions.
139#[derive(Clone, Debug)]
140pub struct RegionSetLayout {
141    regions: Vec<RegionLayout>,
142    routes: Vec<AcknowledgementRoute>,
143}
144
145impl RegionSetLayout {
146    /// Calculates layouts and rejects empty or duplicate role sets.
147    pub fn calculate(
148        schema_id: [u8; 32],
149        generation: u64,
150        specs: &[RegionSpec],
151        route_specs: &[AcknowledgementRouteSpec],
152        limits: LayoutLimits,
153    ) -> Result<Self, LayoutError> {
154        if generation == 0 {
155            return Err(LayoutError::ZeroGeneration);
156        }
157        if specs.is_empty() {
158            return Err(LayoutError::EmptyRegionSet);
159        }
160        let mut regions = Vec::new();
161        regions
162            .try_reserve_exact(specs.len())
163            .map_err(|_| LayoutError::AllocationFailed)?;
164        for (index, spec) in specs.iter().copied().enumerate() {
165            if specs[..index].iter().any(|prior| prior.role == spec.role) {
166                return Err(LayoutError::DuplicateRole(spec.role));
167            }
168            regions.push(RegionLayout::calculate(
169                schema_id, generation, spec, limits,
170            )?);
171        }
172        let routes = validate_routes(&regions, route_specs)?;
173        Ok(Self { regions, routes })
174    }
175
176    /// Returns all independent layouts.
177    pub fn regions(&self) -> &[RegionLayout] {
178        &self.regions
179    }
180
181    /// Finds a layout by validated numeric role.
182    pub fn region(&self, role: RoleId) -> Option<&RegionLayout> {
183        self.regions.iter().find(|region| region.role() == role)
184    }
185
186    /// Returns the validated route for one producer slot.
187    pub fn acknowledgement_route(
188        &self,
189        target: RoleId,
190        slot_index: u32,
191    ) -> Option<AcknowledgementRoute> {
192        self.routes
193            .iter()
194            .copied()
195            .find(|route| route.target == target && route.slot_index == slot_index)
196    }
197
198    /// Returns all exact acknowledgement routes.
199    pub fn acknowledgement_routes(&self) -> &[AcknowledgementRoute] {
200        &self.routes
201    }
202}
203
204/// Layout of one independent, single-writer mapping.
205#[derive(Clone, Debug)]
206pub struct RegionLayout {
207    header: RegionHeader,
208}
209
210impl RegionLayout {
211    fn calculate(
212        schema_id: [u8; 32],
213        generation: u64,
214        spec: RegionSpec,
215        limits: LayoutLimits,
216    ) -> Result<Self, LayoutError> {
217        validate_counts(spec, limits)?;
218        let acknowledgement_offset = REGION_HEADER_SIZE;
219        let acknowledgement_len = u64::from(spec.acknowledgement_count)
220            .checked_mul(ACKNOWLEDGEMENT_CELL_SIZE)
221            .ok_or(LayoutError::Overflow)?;
222        let slots_offset = align_up(
223            acknowledgement_offset
224                .checked_add(acknowledgement_len)
225                .ok_or(LayoutError::Overflow)?,
226            CACHE_LINE,
227        )?;
228        let slot_stride = align_up(
229            SLOT_HEADER_SIZE
230                .checked_add(u64::from(spec.payload_bytes))
231                .ok_or(LayoutError::Overflow)?,
232            CACHE_LINE,
233        )?;
234        let slots_len = slot_stride
235            .checked_mul(u64::from(spec.slot_count))
236            .ok_or(LayoutError::Overflow)?;
237        let total_size = slots_offset
238            .checked_add(slots_len)
239            .ok_or(LayoutError::Overflow)?;
240        if slot_stride > u64::from(u32::MAX)
241            || total_size > limits.maximum_mapping_size
242            || total_size > usize::MAX as u64
243        {
244            return Err(LayoutError::LimitExceeded);
245        }
246        Ok(Self {
247            header: RegionHeader {
248                total_size,
249                schema_id,
250                generation,
251                role: spec.role.get(),
252                writer: spec.writer as u32,
253                acknowledgement_offset,
254                acknowledgement_count: spec.acknowledgement_count,
255                acknowledgement_stride: ACKNOWLEDGEMENT_CELL_SIZE as u32,
256                slots_offset,
257                slot_count: spec.slot_count,
258                slot_stride: slot_stride as u32,
259                payload_capacity: spec.payload_bytes,
260            },
261        })
262    }
263
264    /// Returns the region role.
265    pub const fn role(&self) -> RoleId {
266        RoleId(self.header.role)
267    }
268
269    /// Returns the sole writer endpoint.
270    pub const fn writer(&self) -> Endpoint {
271        match Endpoint::from_raw(self.header.writer) {
272            Some(writer) => writer,
273            None => unreachable!(),
274        }
275    }
276
277    /// Returns the exact mapping size.
278    pub const fn total_size(&self) -> u64 {
279        self.header.total_size
280    }
281
282    /// Returns the fixed slot count.
283    pub const fn slot_count(&self) -> u32 {
284        self.header.slot_count
285    }
286
287    /// Returns the per-slot payload capacity.
288    pub const fn payload_capacity(&self) -> u32 {
289        self.header.payload_capacity
290    }
291
292    /// Initializes a quiescent mapping with a manually encoded header and zero metadata.
293    pub fn encode_into(&self, destination: &mut [u8]) -> Result<(), LayoutError> {
294        let total = usize::try_from(self.header.total_size).map_err(|_| LayoutError::Overflow)?;
295        if destination.len() < total {
296            return Err(LayoutError::MappingTooSmall {
297                required: self.header.total_size,
298                actual: destination.len() as u64,
299            });
300        }
301        destination[..total].fill(0);
302        encode_header(
303            &self.header,
304            &mut destination[..REGION_HEADER_SIZE as usize],
305        );
306        for slot in 0..self.header.slot_count {
307            let start = self.header.slots_offset as usize
308                + slot as usize * self.header.slot_stride as usize;
309            put_u64(destination, start, self.header.generation);
310        }
311        Ok(())
312    }
313}
314
315/// Expected identity for quiescent mapping validation.
316#[derive(Clone, Copy, Debug, Eq, PartialEq)]
317pub struct ValidationExpectations {
318    /// Exact protocol schema.
319    pub schema_id: [u8; 32],
320    /// Exact nonzero connection generation.
321    pub generation: u64,
322    /// Expected numeric role.
323    pub role: RoleId,
324    /// Expected sole writer endpoint.
325    pub writer: Endpoint,
326    /// Maximum accepted complete mapping size.
327    pub maximum_mapping_size: u64,
328}
329
330/// Owned metadata and checked ranges copied from one validated mapping.
331///
332/// This type never retains or returns a slice into cross-process storage.
333#[derive(Clone, Debug)]
334pub struct ValidatedRegionLayout {
335    header: RegionHeader,
336    acknowledgements: Range<usize>,
337    slots: Range<usize>,
338    mapping_size: usize,
339    routes: Vec<AcknowledgementRoute>,
340}
341
342impl ValidatedRegionLayout {
343    /// Returns whether this validation belongs to an exact composed topology.
344    pub fn matches_topology(&self, topology: &RegionSetLayout) -> bool {
345        topology
346            .region(self.role())
347            .is_some_and(|region| region.header == self.header)
348            && self.routes.len() == topology.routes.len()
349            && self
350                .routes
351                .iter()
352                .all(|route| topology.routes.contains(route))
353    }
354    /// Validates a mapping while it is quiescent and before peer mutation begins.
355    ///
356    /// # Safety
357    ///
358    /// No process may mutate `bytes` for the duration of this call. The caller
359    /// mapping must be the exact future capability range.
360    pub unsafe fn validate(
361        bytes: &[u8],
362        expected: ValidationExpectations,
363        topology: &RegionSetLayout,
364    ) -> Result<Self, LayoutError> {
365        if expected.generation == 0 {
366            return Err(LayoutError::ZeroGeneration);
367        }
368        if bytes.len() < REGION_HEADER_SIZE as usize {
369            return Err(LayoutError::MappingTooSmall {
370                required: REGION_HEADER_SIZE,
371                actual: bytes.len() as u64,
372            });
373        }
374        validate_header_encoding(&bytes[..REGION_HEADER_SIZE as usize])?;
375        let header = decode_header(&bytes[..REGION_HEADER_SIZE as usize]);
376        validate_header(&header, bytes.len(), expected)?;
377        match topology.region(expected.role) {
378            Some(region) if region.header == header => {}
379            _ => return Err(LayoutError::TopologyMismatch),
380        }
381        let acknowledgements_end = header
382            .acknowledgement_offset
383            .checked_add(
384                u64::from(header.acknowledgement_count)
385                    .checked_mul(u64::from(header.acknowledgement_stride))
386                    .ok_or(LayoutError::Overflow)?,
387            )
388            .ok_or(LayoutError::Overflow)?;
389        let acknowledgements = checked_range(
390            header.acknowledgement_offset,
391            acknowledgements_end,
392            header.total_size,
393            bytes.len(),
394        )?;
395        if bytes[acknowledgements.clone()]
396            .iter()
397            .any(|byte| *byte != 0)
398        {
399            return Err(LayoutError::AcknowledgementNotZero);
400        }
401        if bytes[header.total_size as usize..]
402            .iter()
403            .any(|byte| *byte != 0)
404        {
405            return Err(LayoutError::CapabilityPaddingNotZero);
406        }
407        let minimum_stride = align_up(
408            SLOT_HEADER_SIZE
409                .checked_add(u64::from(header.payload_capacity))
410                .ok_or(LayoutError::Overflow)?,
411            CACHE_LINE,
412        )?;
413        if u64::from(header.slot_stride) != minimum_stride {
414            return Err(LayoutError::BadSlotStride);
415        }
416        let slots_end = header
417            .slots_offset
418            .checked_add(
419                u64::from(header.slot_stride)
420                    .checked_mul(u64::from(header.slot_count))
421                    .ok_or(LayoutError::Overflow)?,
422            )
423            .ok_or(LayoutError::Overflow)?;
424        if slots_end != header.total_size {
425            return Err(LayoutError::BadTotalSize);
426        }
427        let slots = checked_range(
428            header.slots_offset,
429            slots_end,
430            header.total_size,
431            bytes.len(),
432        )?;
433        for slot in 0..header.slot_count {
434            let start = slots.start + slot as usize * header.slot_stride as usize;
435            if get_u64(bytes, start) != header.generation
436                || bytes[start + 8..start + SLOT_HEADER_SIZE as usize]
437                    .iter()
438                    .any(|byte| *byte != 0)
439            {
440                return Err(LayoutError::SlotMetadataNotInitialized);
441            }
442        }
443        Ok(Self {
444            header,
445            acknowledgements,
446            slots,
447            mapping_size: bytes.len(),
448            routes: topology.routes.clone(),
449        })
450    }
451
452    /// Returns the validated role.
453    pub const fn role(&self) -> RoleId {
454        RoleId(self.header.role)
455    }
456
457    /// Returns the validated generation.
458    pub const fn generation(&self) -> u64 {
459        self.header.generation
460    }
461
462    /// Returns the exact validated native capability size, including zero padding.
463    pub const fn mapping_size(&self) -> usize {
464        self.mapping_size
465    }
466
467    /// Returns a checked complete slot range without granting memory access.
468    pub fn slot_range(&self, slot: u32) -> Result<Range<usize>, LayoutError> {
469        if slot >= self.header.slot_count {
470            return Err(LayoutError::SlotOutOfBounds {
471                slot,
472                count: self.header.slot_count,
473            });
474        }
475        let start = self
476            .slots
477            .start
478            .checked_add(slot as usize * self.header.slot_stride as usize)
479            .ok_or(LayoutError::Overflow)?;
480        let end = start
481            .checked_add(self.header.slot_stride as usize)
482            .ok_or(LayoutError::Overflow)?;
483        Ok(start..end)
484    }
485
486    pub(crate) fn slot_payload_range(
487        &self,
488        slot: u32,
489        payload_len: u32,
490    ) -> Result<Range<usize>, LayoutError> {
491        if payload_len > self.header.payload_capacity {
492            return Err(LayoutError::PayloadOutOfBounds {
493                length: payload_len,
494                capacity: self.header.payload_capacity,
495            });
496        }
497        let slot = self.slot_range(slot)?;
498        let start = slot
499            .start
500            .checked_add(SLOT_HEADER_SIZE as usize)
501            .ok_or(LayoutError::Overflow)?;
502        let end = start
503            .checked_add(payload_len as usize)
504            .ok_or(LayoutError::Overflow)?;
505        if end > slot.end {
506            return Err(LayoutError::RangeOutOfBounds);
507        }
508        Ok(start..end)
509    }
510
511    /// Binds metadata for a sole writer. Read-only mappings cannot call this successfully.
512    pub(crate) fn writer_slot_binding(
513        &self,
514        route: AcknowledgementRoute,
515    ) -> Result<WriterSlotBinding, LayoutError> {
516        if route.target != self.role() {
517            return Err(LayoutError::RouteRegionMismatch);
518        }
519        self.slot_range(route.slot_index)?;
520        Ok(WriterSlotBinding::validated(
521            self.role(),
522            self.generation(),
523            self.header.payload_capacity,
524            route.slot_index,
525            self.header.slot_count,
526            route.owner,
527            route.cell_index,
528        ))
529    }
530
531    /// Binds metadata for an acquire-only reader. Writable mappings are not treated as readers.
532    pub(crate) fn reader_slot_binding(&self, slot: u32) -> Result<ReaderSlotBinding, LayoutError> {
533        self.slot_range(slot)?;
534        Ok(ReaderSlotBinding::validated(
535            self.role(),
536            self.generation(),
537            self.header.payload_capacity,
538            slot,
539            self.header.slot_count,
540        ))
541    }
542
543    /// Returns a checked acknowledgement cell range without granting access.
544    pub fn acknowledgement_range(&self, index: u32) -> Result<Range<usize>, LayoutError> {
545        if index >= self.header.acknowledgement_count {
546            return Err(LayoutError::AcknowledgementOutOfBounds {
547                index,
548                count: self.header.acknowledgement_count,
549            });
550        }
551        let start = self
552            .acknowledgements
553            .start
554            .checked_add(index as usize * self.header.acknowledgement_stride as usize)
555            .ok_or(LayoutError::Overflow)?;
556        let end = start
557            .checked_add(self.header.acknowledgement_stride as usize)
558            .ok_or(LayoutError::Overflow)?;
559        Ok(start..end)
560    }
561
562    /// Binds a store-capable acknowledgement route only for a writable mapping.
563    pub(crate) fn acknowledgement_writer_binding(
564        &self,
565        route: AcknowledgementRoute,
566    ) -> Result<AcknowledgementWriterBinding, LayoutError> {
567        if route.owner != self.role() {
568            return Err(LayoutError::RouteRegionMismatch);
569        }
570        self.acknowledgement_range(route.cell_index)?;
571        Ok(AcknowledgementWriterBinding::validated(
572            route,
573            self.generation(),
574        ))
575    }
576
577    /// Binds an acquire-only acknowledgement route only for a read-only mapping.
578    pub(crate) fn acknowledgement_reader_binding(
579        &self,
580        route: AcknowledgementRoute,
581    ) -> Result<AcknowledgementReaderBinding, LayoutError> {
582        if route.owner != self.role() {
583            return Err(LayoutError::RouteRegionMismatch);
584        }
585        self.acknowledgement_range(route.cell_index)?;
586        Ok(AcknowledgementReaderBinding::validated(
587            route,
588            self.generation(),
589        ))
590    }
591}
592
593#[derive(Clone, Copy, Debug, Eq, PartialEq)]
594struct RegionHeader {
595    total_size: u64,
596    schema_id: [u8; 32],
597    generation: u64,
598    role: u32,
599    writer: u32,
600    acknowledgement_offset: u64,
601    acknowledgement_count: u32,
602    acknowledgement_stride: u32,
603    slots_offset: u64,
604    slot_count: u32,
605    slot_stride: u32,
606    payload_capacity: u32,
607}
608
609/// Bounded region layout validation failures.
610#[derive(Clone, Copy, Debug, Eq, PartialEq)]
611pub enum LayoutError {
612    /// Checked arithmetic overflowed.
613    Overflow,
614    /// Allocation of owned layout metadata failed.
615    AllocationFailed,
616    /// Generation zero is reserved.
617    ZeroGeneration,
618    /// No independent regions were configured.
619    EmptyRegionSet,
620    /// A numeric role appeared more than once.
621    DuplicateRole(RoleId),
622    /// Slot, payload, acknowledgement, or total-size limit was exceeded.
623    LimitExceeded,
624    /// Region has no slots or zero-capacity slots.
625    EmptySlots,
626    /// Mapping cannot contain the declared region.
627    MappingTooSmall {
628        /// Minimum mapping size required by the encoded layout.
629        required: u64,
630        /// Supplied mapping size.
631        actual: u64,
632    },
633    /// Region signature is invalid.
634    BadMagic,
635    /// Region wire revision is unsupported.
636    BadVersion {
637        /// Received major layout version.
638        major: u16,
639        /// Received minor layout version.
640        minor: u16,
641    },
642    /// Encoded header size is noncanonical.
643    BadHeaderSize(u32),
644    /// Schema identity differs.
645    SchemaMismatch,
646    /// Generation differs.
647    StaleGeneration {
648        /// Negotiated connection generation.
649        expected: u64,
650        /// Generation encoded in the mapping.
651        actual: u64,
652    },
653    /// Role differs.
654    UnexpectedRole {
655        /// Negotiated region role.
656        expected: RoleId,
657        /// Role encoded in the mapping.
658        actual: u32,
659    },
660    /// Writer endpoint differs or is invalid.
661    UnexpectedWriter,
662    /// Reserved bytes or flags are nonzero.
663    ReservedFieldSet,
664    /// Total size is invalid.
665    BadTotalSize,
666    /// Acknowledgement layout is invalid.
667    BadAcknowledgementLayout,
668    /// Acknowledgement storage was not zero before transfer.
669    AcknowledgementNotZero,
670    /// Slot layout is invalid.
671    BadSlotLayout,
672    /// Quiescent slot generation or unpublished metadata is invalid.
673    SlotMetadataNotInitialized,
674    /// Slot stride is invalid.
675    BadSlotStride,
676    /// A checked range escapes the mapping.
677    RangeOutOfBounds,
678    /// Slot index is outside the negotiated count.
679    SlotOutOfBounds {
680        /// Requested slot index.
681        slot: u32,
682        /// Validated slot count.
683        count: u32,
684    },
685    /// Acknowledgement index is outside the negotiated count.
686    AcknowledgementOutOfBounds {
687        /// Requested acknowledgement-cell index.
688        index: u32,
689        /// Validated acknowledgement-cell count.
690        count: u32,
691    },
692    /// Payload length exceeds its validated fixed-capacity slot.
693    PayloadOutOfBounds {
694        /// Requested payload length.
695        length: u32,
696        /// Validated fixed payload capacity.
697        capacity: u32,
698    },
699    /// Page-rounded capability padding was not zero before transfer.
700    CapabilityPaddingNotZero,
701    /// A route names a missing role, same-direction owner, or out-of-range index.
702    InvalidAcknowledgementRoute,
703    /// More than one route names a target slot or owner cell.
704    DuplicateAcknowledgementRoute,
705    /// A producer slot or acknowledgement cell has no exact route.
706    IncompleteAcknowledgementRoutes,
707    /// A validated route was applied to a different region.
708    RouteRegionMismatch,
709    /// Validated bytes do not belong to the supplied composed topology.
710    TopologyMismatch,
711}
712
713impl fmt::Display for LayoutError {
714    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
715        write!(formatter, "region layout failed validation: {self:?}")
716    }
717}
718
719#[cfg(feature = "std")]
720impl std::error::Error for LayoutError {}
721
722fn validate_counts(spec: RegionSpec, limits: LayoutLimits) -> Result<(), LayoutError> {
723    if spec.slot_count == 0 || spec.payload_bytes == 0 {
724        return Err(LayoutError::EmptySlots);
725    }
726    if spec.slot_count > limits.maximum_slot_count
727        || spec.payload_bytes > limits.maximum_payload_bytes
728        || spec.acknowledgement_count > limits.maximum_acknowledgement_count
729    {
730        return Err(LayoutError::LimitExceeded);
731    }
732    Ok(())
733}
734
735fn validate_routes(
736    regions: &[RegionLayout],
737    specs: &[AcknowledgementRouteSpec],
738) -> Result<Vec<AcknowledgementRoute>, LayoutError> {
739    let total_slots = regions.iter().try_fold(0_usize, |total, region| {
740        total
741            .checked_add(region.header.slot_count as usize)
742            .ok_or(LayoutError::Overflow)
743    })?;
744    let total_cells = regions.iter().try_fold(0_usize, |total, region| {
745        total
746            .checked_add(region.header.acknowledgement_count as usize)
747            .ok_or(LayoutError::Overflow)
748    })?;
749    if specs.len() != total_slots || specs.len() != total_cells {
750        return Err(LayoutError::IncompleteAcknowledgementRoutes);
751    }
752    let mut routes = Vec::new();
753    routes
754        .try_reserve_exact(specs.len())
755        .map_err(|_| LayoutError::AllocationFailed)?;
756    for spec in specs {
757        let owner = regions
758            .iter()
759            .find(|region| region.role() == spec.owner)
760            .ok_or(LayoutError::InvalidAcknowledgementRoute)?;
761        let target = regions
762            .iter()
763            .find(|region| region.role() == spec.target)
764            .ok_or(LayoutError::InvalidAcknowledgementRoute)?;
765        if owner.role() == target.role()
766            || owner.writer() == target.writer()
767            || spec.slot_index >= target.header.slot_count
768            || spec.cell_index >= owner.header.acknowledgement_count
769        {
770            return Err(LayoutError::InvalidAcknowledgementRoute);
771        }
772        if routes.iter().any(|route: &AcknowledgementRoute| {
773            (route.target == spec.target && route.slot_index == spec.slot_index)
774                || (route.owner == spec.owner && route.cell_index == spec.cell_index)
775        }) {
776            return Err(LayoutError::DuplicateAcknowledgementRoute);
777        }
778        routes.push(AcknowledgementRoute::validated(
779            spec.owner,
780            spec.target,
781            spec.slot_index,
782            spec.cell_index,
783        ));
784    }
785    Ok(routes)
786}
787
788fn validate_header(
789    header: &RegionHeader,
790    mapped_len: usize,
791    expected: ValidationExpectations,
792) -> Result<(), LayoutError> {
793    if header.schema_id != expected.schema_id {
794        return Err(LayoutError::SchemaMismatch);
795    }
796    if header.generation != expected.generation {
797        return Err(LayoutError::StaleGeneration {
798            expected: expected.generation,
799            actual: header.generation,
800        });
801    }
802    if header.role != expected.role.get() {
803        return Err(LayoutError::UnexpectedRole {
804            expected: expected.role,
805            actual: header.role,
806        });
807    }
808    if Endpoint::from_raw(header.writer) != Some(expected.writer) {
809        return Err(LayoutError::UnexpectedWriter);
810    }
811    if mapped_len as u64 > expected.maximum_mapping_size
812        || header.total_size > mapped_len as u64
813        || header.total_size > expected.maximum_mapping_size
814        || header.total_size > usize::MAX as u64
815    {
816        return Err(LayoutError::BadTotalSize);
817    }
818    if header.acknowledgement_offset != REGION_HEADER_SIZE
819        || header.acknowledgement_stride != ACKNOWLEDGEMENT_CELL_SIZE as u32
820    {
821        return Err(LayoutError::BadAcknowledgementLayout);
822    }
823    let acknowledgement_len = u64::from(header.acknowledgement_count)
824        .checked_mul(u64::from(header.acknowledgement_stride))
825        .ok_or(LayoutError::Overflow)?;
826    let expected_slots_offset = align_up(
827        header
828            .acknowledgement_offset
829            .checked_add(acknowledgement_len)
830            .ok_or(LayoutError::Overflow)?,
831        CACHE_LINE,
832    )?;
833    if header.slots_offset != expected_slots_offset
834        || !header.slots_offset.is_multiple_of(CACHE_LINE)
835    {
836        return Err(LayoutError::BadSlotLayout);
837    }
838    if header.slot_count == 0 || header.payload_capacity == 0 {
839        return Err(LayoutError::EmptySlots);
840    }
841    Ok(())
842}
843
844fn validate_header_encoding(bytes: &[u8]) -> Result<(), LayoutError> {
845    if bytes[0..8] != REGION_MAGIC {
846        return Err(LayoutError::BadMagic);
847    }
848    let major = get_u16(bytes, 8);
849    let minor = get_u16(bytes, 10);
850    if major != VERSION_MAJOR || minor != VERSION_MINOR {
851        return Err(LayoutError::BadVersion { major, minor });
852    }
853    let header_size = get_u32(bytes, 12);
854    if header_size != REGION_HEADER_SIZE as u32 {
855        return Err(LayoutError::BadHeaderSize(header_size));
856    }
857    if get_u32(bytes, 108) != 0 || bytes[112..128].iter().any(|byte| *byte != 0) {
858        return Err(LayoutError::ReservedFieldSet);
859    }
860    Ok(())
861}
862
863fn encode_header(header: &RegionHeader, bytes: &mut [u8]) {
864    bytes[0..8].copy_from_slice(&REGION_MAGIC);
865    put_u16(bytes, 8, VERSION_MAJOR);
866    put_u16(bytes, 10, VERSION_MINOR);
867    put_u32(bytes, 12, REGION_HEADER_SIZE as u32);
868    put_u64(bytes, 16, header.total_size);
869    bytes[24..56].copy_from_slice(&header.schema_id);
870    put_u64(bytes, 56, header.generation);
871    put_u32(bytes, 64, header.role);
872    put_u32(bytes, 68, header.writer);
873    put_u64(bytes, 72, header.acknowledgement_offset);
874    put_u32(bytes, 80, header.acknowledgement_count);
875    put_u32(bytes, 84, header.acknowledgement_stride);
876    put_u64(bytes, 88, header.slots_offset);
877    put_u32(bytes, 96, header.slot_count);
878    put_u32(bytes, 100, header.slot_stride);
879    put_u32(bytes, 104, header.payload_capacity);
880    put_u32(bytes, 108, 0);
881    bytes[112..128].fill(0);
882}
883
884fn decode_header(bytes: &[u8]) -> RegionHeader {
885    let mut schema_id = [0; 32];
886    schema_id.copy_from_slice(&bytes[24..56]);
887    RegionHeader {
888        total_size: get_u64(bytes, 16),
889        schema_id,
890        generation: get_u64(bytes, 56),
891        role: get_u32(bytes, 64),
892        writer: get_u32(bytes, 68),
893        acknowledgement_offset: get_u64(bytes, 72),
894        acknowledgement_count: get_u32(bytes, 80),
895        acknowledgement_stride: get_u32(bytes, 84),
896        slots_offset: get_u64(bytes, 88),
897        slot_count: get_u32(bytes, 96),
898        slot_stride: get_u32(bytes, 100),
899        payload_capacity: get_u32(bytes, 104),
900    }
901}
902
903fn checked_range(
904    start: u64,
905    end: u64,
906    total: u64,
907    mapped_len: usize,
908) -> Result<Range<usize>, LayoutError> {
909    if start > end || end > total || end > mapped_len as u64 || end > usize::MAX as u64 {
910        return Err(LayoutError::RangeOutOfBounds);
911    }
912    Ok(start as usize..end as usize)
913}
914
915fn align_up(value: u64, alignment: u64) -> Result<u64, LayoutError> {
916    value
917        .checked_add(alignment - 1)
918        .map(|sum| sum & !(alignment - 1))
919        .ok_or(LayoutError::Overflow)
920}
921
922fn put_u16(bytes: &mut [u8], offset: usize, value: u16) {
923    bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
924}
925fn put_u32(bytes: &mut [u8], offset: usize, value: u32) {
926    bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
927}
928fn put_u64(bytes: &mut [u8], offset: usize, value: u64) {
929    bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
930}
931fn get_u32(bytes: &[u8], offset: usize) -> u32 {
932    u32::from_le_bytes(
933        bytes[offset..offset + 4]
934            .try_into()
935            .expect("fixed checked range"),
936    )
937}
938fn get_u16(bytes: &[u8], offset: usize) -> u16 {
939    u16::from_le_bytes(
940        bytes[offset..offset + 2]
941            .try_into()
942            .expect("fixed checked range"),
943    )
944}
945fn get_u64(bytes: &[u8], offset: usize) -> u64 {
946    u64::from_le_bytes(
947        bytes[offset..offset + 8]
948            .try_into()
949            .expect("fixed checked range"),
950    )
951}
952
953#[cfg(test)]
954#[path = "layout_test.rs"]
955mod tests;