1use 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
13pub const CACHE_LINE: u64 = 64;
15pub const REGION_MAGIC: [u8; 8] = *b"NIPCREG\0";
17pub const REGION_HEADER_SIZE: u64 = 128;
19
20#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
22pub struct RoleId(u32);
23
24impl RoleId {
25 pub const fn new(value: u32) -> Option<Self> {
27 if value == 0 { None } else { Some(Self(value)) }
28 }
29
30 pub const fn get(self) -> u32 {
32 self.0
33 }
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38#[repr(u32)]
39pub enum Endpoint {
40 Initiator = 1,
42 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub struct RegionSpec {
59 pub role: RoleId,
61 pub writer: Endpoint,
63 pub slot_count: u32,
65 pub payload_bytes: u32,
67 pub acknowledgement_count: u32,
69}
70
71#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73pub struct AcknowledgementRouteSpec {
74 pub owner: RoleId,
76 pub target: RoleId,
78 pub slot_index: u32,
80 pub cell_index: u32,
82}
83
84#[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 pub const fn owner(self) -> RoleId {
109 self.owner
110 }
111 pub const fn target(self) -> RoleId {
113 self.target
114 }
115 pub const fn slot_index(self) -> u32 {
117 self.slot_index
118 }
119 pub const fn cell_index(self) -> u32 {
121 self.cell_index
122 }
123}
124
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
127pub struct LayoutLimits {
128 pub maximum_mapping_size: u64,
130 pub maximum_slot_count: u32,
132 pub maximum_acknowledgement_count: u32,
134 pub maximum_payload_bytes: u32,
136}
137
138#[derive(Clone, Debug)]
140pub struct RegionSetLayout {
141 regions: Vec<RegionLayout>,
142 routes: Vec<AcknowledgementRoute>,
143}
144
145impl RegionSetLayout {
146 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(®ions, route_specs)?;
173 Ok(Self { regions, routes })
174 }
175
176 pub fn regions(&self) -> &[RegionLayout] {
178 &self.regions
179 }
180
181 pub fn region(&self, role: RoleId) -> Option<&RegionLayout> {
183 self.regions.iter().find(|region| region.role() == role)
184 }
185
186 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 pub fn acknowledgement_routes(&self) -> &[AcknowledgementRoute] {
200 &self.routes
201 }
202}
203
204#[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 pub const fn role(&self) -> RoleId {
266 RoleId(self.header.role)
267 }
268
269 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 pub const fn total_size(&self) -> u64 {
279 self.header.total_size
280 }
281
282 pub const fn slot_count(&self) -> u32 {
284 self.header.slot_count
285 }
286
287 pub const fn payload_capacity(&self) -> u32 {
289 self.header.payload_capacity
290 }
291
292 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
317pub struct ValidationExpectations {
318 pub schema_id: [u8; 32],
320 pub generation: u64,
322 pub role: RoleId,
324 pub writer: Endpoint,
326 pub maximum_mapping_size: u64,
328}
329
330#[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 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 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 pub const fn role(&self) -> RoleId {
454 RoleId(self.header.role)
455 }
456
457 pub const fn generation(&self) -> u64 {
459 self.header.generation
460 }
461
462 pub const fn mapping_size(&self) -> usize {
464 self.mapping_size
465 }
466
467 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 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 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 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 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 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#[allow(missing_docs)]
611#[derive(Clone, Copy, Debug, Eq, PartialEq)]
612pub enum LayoutError {
613 Overflow,
615 AllocationFailed,
617 ZeroGeneration,
619 EmptyRegionSet,
621 DuplicateRole(RoleId),
623 LimitExceeded,
625 EmptySlots,
627 MappingTooSmall { required: u64, actual: u64 },
629 BadMagic,
631 BadVersion { major: u16, minor: u16 },
633 BadHeaderSize(u32),
635 SchemaMismatch,
637 StaleGeneration { expected: u64, actual: u64 },
639 UnexpectedRole { expected: RoleId, actual: u32 },
641 UnexpectedWriter,
643 ReservedFieldSet,
645 BadTotalSize,
647 BadAcknowledgementLayout,
649 AcknowledgementNotZero,
651 BadSlotLayout,
653 SlotMetadataNotInitialized,
655 BadSlotStride,
657 RangeOutOfBounds,
659 SlotOutOfBounds { slot: u32, count: u32 },
661 AcknowledgementOutOfBounds { index: u32, count: u32 },
663 PayloadOutOfBounds { length: u32, capacity: u32 },
665 CapabilityPaddingNotZero,
667 InvalidAcknowledgementRoute,
669 DuplicateAcknowledgementRoute,
671 IncompleteAcknowledgementRoutes,
673 RouteRegionMismatch,
675 TopologyMismatch,
677}
678
679impl fmt::Display for LayoutError {
680 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
681 write!(formatter, "region layout failed validation: {self:?}")
682 }
683}
684
685#[cfg(feature = "std")]
686impl std::error::Error for LayoutError {}
687
688fn validate_counts(spec: RegionSpec, limits: LayoutLimits) -> Result<(), LayoutError> {
689 if spec.slot_count == 0 || spec.payload_bytes == 0 {
690 return Err(LayoutError::EmptySlots);
691 }
692 if spec.slot_count > limits.maximum_slot_count
693 || spec.payload_bytes > limits.maximum_payload_bytes
694 || spec.acknowledgement_count > limits.maximum_acknowledgement_count
695 {
696 return Err(LayoutError::LimitExceeded);
697 }
698 Ok(())
699}
700
701fn validate_routes(
702 regions: &[RegionLayout],
703 specs: &[AcknowledgementRouteSpec],
704) -> Result<Vec<AcknowledgementRoute>, LayoutError> {
705 let total_slots = regions.iter().try_fold(0_usize, |total, region| {
706 total
707 .checked_add(region.header.slot_count as usize)
708 .ok_or(LayoutError::Overflow)
709 })?;
710 let total_cells = regions.iter().try_fold(0_usize, |total, region| {
711 total
712 .checked_add(region.header.acknowledgement_count as usize)
713 .ok_or(LayoutError::Overflow)
714 })?;
715 if specs.len() != total_slots || specs.len() != total_cells {
716 return Err(LayoutError::IncompleteAcknowledgementRoutes);
717 }
718 let mut routes = Vec::new();
719 routes
720 .try_reserve_exact(specs.len())
721 .map_err(|_| LayoutError::AllocationFailed)?;
722 for spec in specs {
723 let owner = regions
724 .iter()
725 .find(|region| region.role() == spec.owner)
726 .ok_or(LayoutError::InvalidAcknowledgementRoute)?;
727 let target = regions
728 .iter()
729 .find(|region| region.role() == spec.target)
730 .ok_or(LayoutError::InvalidAcknowledgementRoute)?;
731 if owner.role() == target.role()
732 || owner.writer() == target.writer()
733 || spec.slot_index >= target.header.slot_count
734 || spec.cell_index >= owner.header.acknowledgement_count
735 {
736 return Err(LayoutError::InvalidAcknowledgementRoute);
737 }
738 if routes.iter().any(|route: &AcknowledgementRoute| {
739 (route.target == spec.target && route.slot_index == spec.slot_index)
740 || (route.owner == spec.owner && route.cell_index == spec.cell_index)
741 }) {
742 return Err(LayoutError::DuplicateAcknowledgementRoute);
743 }
744 routes.push(AcknowledgementRoute::validated(
745 spec.owner,
746 spec.target,
747 spec.slot_index,
748 spec.cell_index,
749 ));
750 }
751 Ok(routes)
752}
753
754fn validate_header(
755 header: &RegionHeader,
756 mapped_len: usize,
757 expected: ValidationExpectations,
758) -> Result<(), LayoutError> {
759 if header.schema_id != expected.schema_id {
760 return Err(LayoutError::SchemaMismatch);
761 }
762 if header.generation != expected.generation {
763 return Err(LayoutError::StaleGeneration {
764 expected: expected.generation,
765 actual: header.generation,
766 });
767 }
768 if header.role != expected.role.get() {
769 return Err(LayoutError::UnexpectedRole {
770 expected: expected.role,
771 actual: header.role,
772 });
773 }
774 if Endpoint::from_raw(header.writer) != Some(expected.writer) {
775 return Err(LayoutError::UnexpectedWriter);
776 }
777 if mapped_len as u64 > expected.maximum_mapping_size
778 || header.total_size > mapped_len as u64
779 || header.total_size > expected.maximum_mapping_size
780 || header.total_size > usize::MAX as u64
781 {
782 return Err(LayoutError::BadTotalSize);
783 }
784 if header.acknowledgement_offset != REGION_HEADER_SIZE
785 || header.acknowledgement_stride != ACKNOWLEDGEMENT_CELL_SIZE as u32
786 {
787 return Err(LayoutError::BadAcknowledgementLayout);
788 }
789 let acknowledgement_len = u64::from(header.acknowledgement_count)
790 .checked_mul(u64::from(header.acknowledgement_stride))
791 .ok_or(LayoutError::Overflow)?;
792 let expected_slots_offset = align_up(
793 header
794 .acknowledgement_offset
795 .checked_add(acknowledgement_len)
796 .ok_or(LayoutError::Overflow)?,
797 CACHE_LINE,
798 )?;
799 if header.slots_offset != expected_slots_offset
800 || !header.slots_offset.is_multiple_of(CACHE_LINE)
801 {
802 return Err(LayoutError::BadSlotLayout);
803 }
804 if header.slot_count == 0 || header.payload_capacity == 0 {
805 return Err(LayoutError::EmptySlots);
806 }
807 Ok(())
808}
809
810fn validate_header_encoding(bytes: &[u8]) -> Result<(), LayoutError> {
811 if bytes[0..8] != REGION_MAGIC {
812 return Err(LayoutError::BadMagic);
813 }
814 let major = get_u16(bytes, 8);
815 let minor = get_u16(bytes, 10);
816 if major != VERSION_MAJOR || minor != VERSION_MINOR {
817 return Err(LayoutError::BadVersion { major, minor });
818 }
819 let header_size = get_u32(bytes, 12);
820 if header_size != REGION_HEADER_SIZE as u32 {
821 return Err(LayoutError::BadHeaderSize(header_size));
822 }
823 if get_u32(bytes, 108) != 0 || bytes[112..128].iter().any(|byte| *byte != 0) {
824 return Err(LayoutError::ReservedFieldSet);
825 }
826 Ok(())
827}
828
829fn encode_header(header: &RegionHeader, bytes: &mut [u8]) {
830 bytes[0..8].copy_from_slice(®ION_MAGIC);
831 put_u16(bytes, 8, VERSION_MAJOR);
832 put_u16(bytes, 10, VERSION_MINOR);
833 put_u32(bytes, 12, REGION_HEADER_SIZE as u32);
834 put_u64(bytes, 16, header.total_size);
835 bytes[24..56].copy_from_slice(&header.schema_id);
836 put_u64(bytes, 56, header.generation);
837 put_u32(bytes, 64, header.role);
838 put_u32(bytes, 68, header.writer);
839 put_u64(bytes, 72, header.acknowledgement_offset);
840 put_u32(bytes, 80, header.acknowledgement_count);
841 put_u32(bytes, 84, header.acknowledgement_stride);
842 put_u64(bytes, 88, header.slots_offset);
843 put_u32(bytes, 96, header.slot_count);
844 put_u32(bytes, 100, header.slot_stride);
845 put_u32(bytes, 104, header.payload_capacity);
846 put_u32(bytes, 108, 0);
847 bytes[112..128].fill(0);
848}
849
850fn decode_header(bytes: &[u8]) -> RegionHeader {
851 let mut schema_id = [0; 32];
852 schema_id.copy_from_slice(&bytes[24..56]);
853 RegionHeader {
854 total_size: get_u64(bytes, 16),
855 schema_id,
856 generation: get_u64(bytes, 56),
857 role: get_u32(bytes, 64),
858 writer: get_u32(bytes, 68),
859 acknowledgement_offset: get_u64(bytes, 72),
860 acknowledgement_count: get_u32(bytes, 80),
861 acknowledgement_stride: get_u32(bytes, 84),
862 slots_offset: get_u64(bytes, 88),
863 slot_count: get_u32(bytes, 96),
864 slot_stride: get_u32(bytes, 100),
865 payload_capacity: get_u32(bytes, 104),
866 }
867}
868
869fn checked_range(
870 start: u64,
871 end: u64,
872 total: u64,
873 mapped_len: usize,
874) -> Result<Range<usize>, LayoutError> {
875 if start > end || end > total || end > mapped_len as u64 || end > usize::MAX as u64 {
876 return Err(LayoutError::RangeOutOfBounds);
877 }
878 Ok(start as usize..end as usize)
879}
880
881fn align_up(value: u64, alignment: u64) -> Result<u64, LayoutError> {
882 value
883 .checked_add(alignment - 1)
884 .map(|sum| sum & !(alignment - 1))
885 .ok_or(LayoutError::Overflow)
886}
887
888fn put_u16(bytes: &mut [u8], offset: usize, value: u16) {
889 bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
890}
891fn put_u32(bytes: &mut [u8], offset: usize, value: u32) {
892 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
893}
894fn put_u64(bytes: &mut [u8], offset: usize, value: u64) {
895 bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
896}
897fn get_u32(bytes: &[u8], offset: usize) -> u32 {
898 u32::from_le_bytes(
899 bytes[offset..offset + 4]
900 .try_into()
901 .expect("fixed checked range"),
902 )
903}
904fn get_u16(bytes: &[u8], offset: usize) -> u16 {
905 u16::from_le_bytes(
906 bytes[offset..offset + 2]
907 .try_into()
908 .expect("fixed checked range"),
909 )
910}
911fn get_u64(bytes: &[u8], offset: usize) -> u64 {
912 u64::from_le_bytes(
913 bytes[offset..offset + 8]
914 .try_into()
915 .expect("fixed checked range"),
916 )
917}
918
919#[cfg(test)]
920mod tests {
921 use super::*;
922
923 const SCHEMA: [u8; 32] = [0x5a; 32];
924 const GENERATION: u64 = 11;
925 const ROLE_A: RoleId = RoleId::new(1).unwrap();
926 const ROLE_B: RoleId = RoleId::new(2).unwrap();
927
928 fn limits() -> LayoutLimits {
929 LayoutLimits {
930 maximum_mapping_size: 1 << 20,
931 maximum_slot_count: 32,
932 maximum_acknowledgement_count: 8,
933 maximum_payload_bytes: 4096,
934 }
935 }
936
937 fn specs() -> [RegionSpec; 2] {
938 [
939 RegionSpec {
940 role: ROLE_A,
941 writer: Endpoint::Initiator,
942 slot_count: 2,
943 payload_bytes: 128,
944 acknowledgement_count: 4,
945 },
946 RegionSpec {
947 role: ROLE_B,
948 writer: Endpoint::Responder,
949 slot_count: 4,
950 payload_bytes: 64,
951 acknowledgement_count: 2,
952 },
953 ]
954 }
955
956 fn routes() -> [AcknowledgementRouteSpec; 6] {
957 [
958 AcknowledgementRouteSpec {
959 owner: ROLE_B,
960 target: ROLE_A,
961 slot_index: 0,
962 cell_index: 0,
963 },
964 AcknowledgementRouteSpec {
965 owner: ROLE_B,
966 target: ROLE_A,
967 slot_index: 1,
968 cell_index: 1,
969 },
970 AcknowledgementRouteSpec {
971 owner: ROLE_A,
972 target: ROLE_B,
973 slot_index: 0,
974 cell_index: 0,
975 },
976 AcknowledgementRouteSpec {
977 owner: ROLE_A,
978 target: ROLE_B,
979 slot_index: 1,
980 cell_index: 1,
981 },
982 AcknowledgementRouteSpec {
983 owner: ROLE_A,
984 target: ROLE_B,
985 slot_index: 2,
986 cell_index: 2,
987 },
988 AcknowledgementRouteSpec {
989 owner: ROLE_A,
990 target: ROLE_B,
991 slot_index: 3,
992 cell_index: 3,
993 },
994 ]
995 }
996
997 fn encoded(role: RoleId) -> (RegionLayout, Vec<u8>) {
998 let set = topology();
999 let layout = set.region(role).unwrap().clone();
1000 let mut bytes = vec![0; layout.total_size() as usize];
1001 layout.encode_into(&mut bytes).unwrap();
1002 (layout, bytes)
1003 }
1004
1005 fn topology() -> RegionSetLayout {
1006 RegionSetLayout::calculate(SCHEMA, GENERATION, &specs(), &routes(), limits()).unwrap()
1007 }
1008
1009 fn expected(role: RoleId, writer: Endpoint, size: u64) -> ValidationExpectations {
1010 ValidationExpectations {
1011 schema_id: SCHEMA,
1012 generation: GENERATION,
1013 role,
1014 writer,
1015 maximum_mapping_size: size,
1016 }
1017 }
1018
1019 #[test]
1020 fn configurable_regions_have_checked_independent_layouts() {
1021 let set =
1022 RegionSetLayout::calculate(SCHEMA, GENERATION, &specs(), &routes(), limits()).unwrap();
1023 assert_eq!(set.regions().len(), 2);
1024 assert_eq!(set.acknowledgement_routes().len(), 6);
1025 let (layout, bytes) = encoded(ROLE_B);
1026 let validated = unsafe {
1028 ValidatedRegionLayout::validate(
1029 &bytes,
1030 expected(ROLE_B, Endpoint::Responder, layout.total_size()),
1031 &set,
1032 )
1033 }
1034 .unwrap();
1035 assert_eq!(validated.role(), ROLE_B);
1036 assert_eq!(validated.slot_range(3).unwrap().len(), 128);
1037 assert_eq!(validated.acknowledgement_range(1).unwrap().len(), 64);
1038 assert!(validated.reader_slot_binding(0).is_ok());
1039 assert!(
1040 validated
1041 .writer_slot_binding(set.acknowledgement_route(ROLE_B, 0).unwrap())
1042 .is_ok()
1043 );
1044 assert!(
1045 validated
1046 .acknowledgement_reader_binding(set.acknowledgement_route(ROLE_A, 0).unwrap())
1047 .is_ok()
1048 );
1049 }
1050
1051 #[test]
1052 fn writable_layout_cannot_mint_reader_or_ack_reader_capabilities() {
1053 let set = topology();
1054 let (layout, bytes) = encoded(ROLE_B);
1055 let validated = unsafe {
1057 ValidatedRegionLayout::validate(
1058 &bytes,
1059 expected(ROLE_B, Endpoint::Responder, layout.total_size()),
1060 &set,
1061 )
1062 }
1063 .unwrap();
1064 assert!(
1065 validated
1066 .writer_slot_binding(set.acknowledgement_route(ROLE_B, 0).unwrap())
1067 .is_ok()
1068 );
1069 assert!(validated.reader_slot_binding(0).is_ok());
1070 assert!(
1071 validated
1072 .acknowledgement_writer_binding(set.acknowledgement_route(ROLE_A, 0).unwrap())
1073 .is_ok()
1074 );
1075 assert!(
1076 validated
1077 .acknowledgement_reader_binding(set.acknowledgement_route(ROLE_A, 0).unwrap())
1078 .is_ok()
1079 );
1080 }
1081
1082 #[test]
1083 fn malformed_headers_offsets_slots_and_limits_fail_closed() {
1084 let topology = topology();
1085 let (layout, original) = encoded(ROLE_B);
1086 let expectation = expected(ROLE_B, Endpoint::Responder, layout.total_size());
1087 for offset in [
1088 0_usize, 8, 12, 16, 24, 56, 64, 68, 72, 84, 88, 100, 108, 112,
1089 ] {
1090 let mut bytes = original.clone();
1091 bytes[offset] ^= 1;
1092 assert!(
1094 unsafe { ValidatedRegionLayout::validate(&bytes, expectation, &topology) }.is_err()
1095 );
1096 }
1097 let slot_start = get_u64(&original, 88) as usize;
1098 let mut bytes = original;
1099 bytes[slot_start + 16] = 1;
1100 assert_eq!(
1102 unsafe { ValidatedRegionLayout::validate(&bytes, expectation, &topology) }.unwrap_err(),
1103 LayoutError::SlotMetadataNotInitialized
1104 );
1105 }
1106
1107 #[test]
1108 fn rejects_zero_generation_duplicate_roles_and_excess_capacity() {
1109 let topology = topology();
1110 assert_eq!(
1111 RegionSetLayout::calculate(SCHEMA, 0, &specs(), &routes(), limits()).unwrap_err(),
1112 LayoutError::ZeroGeneration
1113 );
1114 let duplicate = [specs()[0], specs()[0]];
1115 assert_eq!(
1116 RegionSetLayout::calculate(SCHEMA, 1, &duplicate, &routes(), limits()).unwrap_err(),
1117 LayoutError::DuplicateRole(ROLE_A)
1118 );
1119 let mut too_large = specs();
1120 too_large[0].slot_count = 33;
1121 assert_eq!(
1122 RegionSetLayout::calculate(SCHEMA, 1, &too_large, &routes(), limits()).unwrap_err(),
1123 LayoutError::LimitExceeded
1124 );
1125
1126 let (layout, bytes) = encoded(ROLE_A);
1127 let zero_generation = ValidationExpectations {
1128 generation: 0,
1129 ..expected(ROLE_A, Endpoint::Initiator, layout.total_size())
1130 };
1131 assert_eq!(
1133 unsafe { ValidatedRegionLayout::validate(&bytes, zero_generation, &topology) }
1134 .unwrap_err(),
1135 LayoutError::ZeroGeneration
1136 );
1137 }
1138
1139 #[test]
1140 fn composition_rejects_ambiguous_and_directionally_invalid_routes() {
1141 let mut shared_cell = routes();
1142 shared_cell[1].cell_index = shared_cell[0].cell_index;
1143 assert_eq!(
1144 RegionSetLayout::calculate(SCHEMA, GENERATION, &specs(), &shared_cell, limits())
1145 .unwrap_err(),
1146 LayoutError::DuplicateAcknowledgementRoute
1147 );
1148
1149 let mut duplicate_slot = routes();
1150 duplicate_slot[1].slot_index = 0;
1151 assert_eq!(
1152 RegionSetLayout::calculate(SCHEMA, GENERATION, &specs(), &duplicate_slot, limits())
1153 .unwrap_err(),
1154 LayoutError::DuplicateAcknowledgementRoute
1155 );
1156
1157 for hostile in [
1158 AcknowledgementRouteSpec {
1159 owner: RoleId::new(99).unwrap(),
1160 ..routes()[0]
1161 },
1162 AcknowledgementRouteSpec {
1163 target: RoleId::new(99).unwrap(),
1164 ..routes()[0]
1165 },
1166 AcknowledgementRouteSpec {
1167 slot_index: 99,
1168 ..routes()[0]
1169 },
1170 AcknowledgementRouteSpec {
1171 cell_index: 99,
1172 ..routes()[0]
1173 },
1174 ] {
1175 let mut hostile_routes = routes();
1176 hostile_routes[0] = hostile;
1177 assert_eq!(
1178 RegionSetLayout::calculate(SCHEMA, GENERATION, &specs(), &hostile_routes, limits())
1179 .unwrap_err(),
1180 LayoutError::InvalidAcknowledgementRoute
1181 );
1182 }
1183
1184 let mut wrong_direction = routes();
1185 wrong_direction[0].owner = ROLE_A;
1186 assert_eq!(
1187 RegionSetLayout::calculate(SCHEMA, GENERATION, &specs(), &wrong_direction, limits())
1188 .unwrap_err(),
1189 LayoutError::InvalidAcknowledgementRoute
1190 );
1191
1192 let mut same_endpoint = specs();
1193 same_endpoint[1].writer = Endpoint::Initiator;
1194 assert_eq!(
1195 RegionSetLayout::calculate(SCHEMA, GENERATION, &same_endpoint, &routes(), limits())
1196 .unwrap_err(),
1197 LayoutError::InvalidAcknowledgementRoute
1198 );
1199
1200 assert_eq!(
1201 RegionSetLayout::calculate(SCHEMA, GENERATION, &specs(), &routes()[..5], limits())
1202 .unwrap_err(),
1203 LayoutError::IncompleteAcknowledgementRoutes
1204 );
1205 }
1206}