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 route_lookup: Vec<usize>,
144}
145
146impl RegionSetLayout {
147 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(®ions, route_specs)?;
174 let route_lookup = build_route_lookup(&routes)?;
175 Ok(Self {
176 regions,
177 routes,
178 route_lookup,
179 })
180 }
181
182 pub fn regions(&self) -> &[RegionLayout] {
184 &self.regions
185 }
186
187 pub fn region(&self, role: RoleId) -> Option<&RegionLayout> {
189 self.regions.iter().find(|region| region.role() == role)
190 }
191
192 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 pub fn acknowledgement_routes(&self) -> &[AcknowledgementRoute] {
210 &self.routes
211 }
212}
213
214#[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 pub const fn role(&self) -> RoleId {
276 RoleId(self.header.role)
277 }
278
279 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 pub const fn total_size(&self) -> u64 {
289 self.header.total_size
290 }
291
292 pub const fn slot_count(&self) -> u32 {
294 self.header.slot_count
295 }
296
297 pub const fn payload_capacity(&self) -> u32 {
299 self.header.payload_capacity
300 }
301
302 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
327pub struct ValidationExpectations {
328 pub schema_id: [u8; 32],
330 pub generation: u64,
332 pub role: RoleId,
334 pub writer: Endpoint,
336 pub maximum_mapping_size: u64,
338}
339
340#[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 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 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 pub const fn role(&self) -> RoleId {
464 RoleId(self.header.role)
465 }
466
467 pub const fn generation(&self) -> u64 {
469 self.header.generation
470 }
471
472 pub const fn mapping_size(&self) -> usize {
474 self.mapping_size
475 }
476
477 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 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 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 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 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 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
621pub enum LayoutError {
622 Overflow,
624 AllocationFailed,
626 ZeroGeneration,
628 EmptyRegionSet,
630 DuplicateRole(RoleId),
632 LimitExceeded,
634 EmptySlots,
636 MappingTooSmall {
638 required: u64,
640 actual: u64,
642 },
643 BadMagic,
645 BadVersion {
647 major: u16,
649 minor: u16,
651 },
652 BadHeaderSize(u32),
654 SchemaMismatch,
656 StaleGeneration {
658 expected: u64,
660 actual: u64,
662 },
663 UnexpectedRole {
665 expected: RoleId,
667 actual: u32,
669 },
670 UnexpectedWriter,
672 ReservedFieldSet,
674 BadTotalSize,
676 BadAcknowledgementLayout,
678 AcknowledgementNotZero,
680 BadSlotLayout,
682 SlotMetadataNotInitialized,
684 BadSlotStride,
686 RangeOutOfBounds,
688 SlotOutOfBounds {
690 slot: u32,
692 count: u32,
694 },
695 AcknowledgementOutOfBounds {
697 index: u32,
699 count: u32,
701 },
702 PayloadOutOfBounds {
704 length: u32,
706 capacity: u32,
708 },
709 CapabilityPaddingNotZero,
711 InvalidAcknowledgementRoute,
713 DuplicateAcknowledgementRoute,
715 IncompleteAcknowledgementRoutes,
717 RouteRegionMismatch,
719 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(®ION_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;