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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
611pub enum LayoutError {
612 Overflow,
614 AllocationFailed,
616 ZeroGeneration,
618 EmptyRegionSet,
620 DuplicateRole(RoleId),
622 LimitExceeded,
624 EmptySlots,
626 MappingTooSmall {
628 required: u64,
630 actual: u64,
632 },
633 BadMagic,
635 BadVersion {
637 major: u16,
639 minor: u16,
641 },
642 BadHeaderSize(u32),
644 SchemaMismatch,
646 StaleGeneration {
648 expected: u64,
650 actual: u64,
652 },
653 UnexpectedRole {
655 expected: RoleId,
657 actual: u32,
659 },
660 UnexpectedWriter,
662 ReservedFieldSet,
664 BadTotalSize,
666 BadAcknowledgementLayout,
668 AcknowledgementNotZero,
670 BadSlotLayout,
672 SlotMetadataNotInitialized,
674 BadSlotStride,
676 RangeOutOfBounds,
678 SlotOutOfBounds {
680 slot: u32,
682 count: u32,
684 },
685 AcknowledgementOutOfBounds {
687 index: u32,
689 count: u32,
691 },
692 PayloadOutOfBounds {
694 length: u32,
696 capacity: u32,
698 },
699 CapabilityPaddingNotZero,
701 InvalidAcknowledgementRoute,
703 DuplicateAcknowledgementRoute,
705 IncompleteAcknowledgementRoutes,
707 RouteRegionMismatch,
709 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(®ION_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;