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