1use core::cell::{Cell, UnsafeCell};
4use core::fmt;
5use core::marker::PhantomData;
6use core::sync::atomic::{AtomicU32, AtomicU64, Ordering, fence};
7
8use crate::layout::{AcknowledgementRoute, RoleId};
9
10#[cfg(not(target_has_atomic = "64"))]
11compile_error!("native-ipc-core requires lock-free 64-bit atomic support");
12
13pub const SLOT_HEADER_SIZE: u64 = 64;
15pub const ACKNOWLEDGEMENT_CELL_SIZE: u64 = 64;
17
18#[repr(C, align(64))]
23#[derive(Debug)]
24pub struct SlotMetadata {
25 generation: AtomicU64,
26 payload_len: AtomicU32,
27 reserved_word: UnsafeCell<u32>,
28 published_sequence: AtomicU64,
29 reserved: UnsafeCell<[u8; 40]>,
30}
31
32impl SlotMetadata {
33 pub const fn new(generation: u64) -> Self {
35 Self {
36 generation: AtomicU64::new(generation),
37 payload_len: AtomicU32::new(0),
38 reserved_word: UnsafeCell::new(0),
39 published_sequence: AtomicU64::new(0),
40 reserved: UnsafeCell::new([0; 40]),
41 }
42 }
43
44 pub unsafe fn initialize(&mut self, generation: u64) -> Result<(), SlotError> {
51 if generation == 0 {
52 return Err(SlotError::ZeroGeneration);
53 }
54 *self.generation.get_mut() = generation;
55 *self.payload_len.get_mut() = 0;
56 unsafe { *self.reserved_word.get() = 0 };
58 *self.published_sequence.get_mut() = 0;
59 unsafe { *self.reserved.get() = [0; 40] };
61 Ok(())
62 }
63}
64
65unsafe impl Sync for SlotMetadata {}
70
71#[repr(C, align(64))]
73pub struct AcknowledgementCell {
74 sequence: AtomicU64,
75 reserved: UnsafeCell<[u8; 56]>,
76}
77
78impl AcknowledgementCell {
79 pub const fn new() -> Self {
81 Self {
82 sequence: AtomicU64::new(0),
83 reserved: UnsafeCell::new([0; 56]),
84 }
85 }
86
87 pub unsafe fn initialize(&mut self) {
93 *self.sequence.get_mut() = 0;
94 unsafe { *self.reserved.get() = [0; 56] };
96 }
97}
98
99unsafe impl Sync for AcknowledgementCell {}
102
103impl Default for AcknowledgementCell {
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109#[derive(Clone, Copy, Debug, Eq, PartialEq)]
111pub struct WriterSlotBinding(SlotBinding);
112
113impl WriterSlotBinding {
114 pub(crate) const fn validated(
115 role: RoleId,
116 generation: u64,
117 payload_capacity: u32,
118 slot_index: u32,
119 slot_count: u32,
120 acknowledgement_owner: RoleId,
121 acknowledgement_cell_index: u32,
122 ) -> Self {
123 Self(SlotBinding {
124 role,
125 generation,
126 payload_capacity,
127 slot_index,
128 slot_count,
129 acknowledgement_owner: Some(acknowledgement_owner),
130 acknowledgement_cell_index: Some(acknowledgement_cell_index),
131 })
132 }
133}
134
135#[derive(Clone, Copy, Debug, Eq, PartialEq)]
137pub struct ReaderSlotBinding(SlotBinding);
138
139impl ReaderSlotBinding {
140 pub(crate) const fn validated(
141 role: RoleId,
142 generation: u64,
143 payload_capacity: u32,
144 slot_index: u32,
145 slot_count: u32,
146 ) -> Self {
147 Self(SlotBinding {
148 role,
149 generation,
150 payload_capacity,
151 slot_index,
152 slot_count,
153 acknowledgement_owner: None,
154 acknowledgement_cell_index: None,
155 })
156 }
157}
158
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160struct SlotBinding {
161 role: RoleId,
162 generation: u64,
163 payload_capacity: u32,
164 slot_index: u32,
165 slot_count: u32,
166 acknowledgement_owner: Option<RoleId>,
167 acknowledgement_cell_index: Option<u32>,
168}
169
170pub struct WriterSlot<'a> {
172 header: &'a SlotMetadata,
173 binding: SlotBinding,
174 _not_sync: PhantomData<Cell<()>>,
175}
176
177impl<'a> WriterSlot<'a> {
178 pub unsafe fn bind(
187 header: &'a SlotMetadata,
188 binding: WriterSlotBinding,
189 ) -> Result<Self, SlotError> {
190 validate_bound_generation(header, binding.0.generation)?;
191 Ok(Self {
192 header,
193 binding: binding.0,
194 _not_sync: PhantomData,
195 })
196 }
197
198 pub fn prepare_publish(
200 &mut self,
201 sequence: u64,
202 acknowledgement: Option<AcknowledgementObservation>,
203 ) -> Result<PublishReservation<'_>, SlotError> {
204 validate_bound_generation(self.header, self.binding.generation)?;
205 validate_sequence_slot(self.binding, sequence)?;
206 let current = self.header.published_sequence.load(Ordering::Relaxed);
207 if current == 0 {
208 let expected = u64::from(self.binding.slot_index) + 1;
209 if sequence != expected {
210 return Err(SlotError::UnexpectedFirstSequence {
211 expected,
212 actual: sequence,
213 });
214 }
215 } else {
216 let expected = current
217 .checked_add(u64::from(self.binding.slot_count))
218 .ok_or(SlotError::SequenceWrap)?;
219 if sequence != expected {
220 return Err(SlotError::UnexpectedNextSequence {
221 expected,
222 actual: sequence,
223 });
224 }
225 let acknowledgement =
226 acknowledgement.ok_or(SlotError::MissingAcknowledgement { sequence: current })?;
227 if acknowledgement.target != self.binding.role {
228 return Err(SlotError::WrongAcknowledgementTarget);
229 }
230 if acknowledgement.owner != self.binding.acknowledgement_owner.unwrap() {
231 return Err(SlotError::WrongAcknowledgementOwner);
232 }
233 if acknowledgement.slot_index != self.binding.slot_index {
234 return Err(SlotError::WrongAcknowledgementSlot);
235 }
236 if acknowledgement.cell_index != self.binding.acknowledgement_cell_index.unwrap() {
237 return Err(SlotError::WrongAcknowledgementCell);
238 }
239 if acknowledgement.generation != self.binding.generation {
240 return Err(SlotError::StaleAcknowledgementGeneration);
241 }
242 if acknowledgement.sequence < current {
243 return Err(SlotError::LaggingAcknowledgement {
244 expected: current,
245 actual: acknowledgement.sequence,
246 });
247 }
248 if acknowledgement.sequence > current {
249 return Err(SlotError::FutureAcknowledgement {
250 expected: current,
251 actual: acknowledgement.sequence,
252 });
253 }
254 }
255 Ok(PublishReservation {
256 header: self.header,
257 sequence,
258 capacity: self.binding.payload_capacity,
259 _exclusive: PhantomData,
260 })
261 }
262}
263
264pub struct ReaderSlot<'a> {
266 header: &'a SlotMetadata,
267 binding: SlotBinding,
268}
269
270impl<'a> ReaderSlot<'a> {
271 pub unsafe fn bind(
279 header: &'a SlotMetadata,
280 binding: ReaderSlotBinding,
281 ) -> Result<Self, SlotError> {
282 validate_bound_generation(header, binding.0.generation)?;
283 Ok(Self {
284 header,
285 binding: binding.0,
286 })
287 }
288
289 pub fn observe(&self, expected_sequence: u64) -> Result<SlotObservation, SlotError> {
291 validate_sequence_slot(self.binding, expected_sequence)?;
292 let sequence = self.header.published_sequence.load(Ordering::Acquire);
293 if sequence != expected_sequence {
294 return Err(SlotError::StaleSequence {
295 expected: expected_sequence,
296 actual: sequence,
297 });
298 }
299 validate_bound_generation(self.header, self.binding.generation)?;
300 let payload_len = self.header.payload_len.load(Ordering::Relaxed);
301 if payload_len > self.binding.payload_capacity {
302 return Err(SlotError::PayloadTooLarge {
303 length: payload_len,
304 capacity: self.binding.payload_capacity,
305 });
306 }
307 Ok(SlotObservation {
308 role: self.binding.role,
309 slot_index: self.binding.slot_index,
310 generation: self.binding.generation,
311 sequence,
312 payload_len,
313 })
314 }
315
316 pub fn recheck(&self, observation: SlotObservation) -> Result<(), SlotError> {
322 if observation.role != self.binding.role {
323 return Err(SlotError::WrongObservationRole);
324 }
325 if observation.generation != self.binding.generation {
326 return Err(SlotError::StaleGeneration {
327 expected: self.binding.generation,
328 actual: observation.generation,
329 });
330 }
331 fence(Ordering::SeqCst);
334 let sequence = self.header.published_sequence.load(Ordering::Acquire);
335 if sequence != observation.sequence {
336 return Err(SlotError::StaleSequence {
337 expected: observation.sequence,
338 actual: sequence,
339 });
340 }
341 validate_bound_generation(self.header, observation.generation)?;
342 let payload_len = self.header.payload_len.load(Ordering::Relaxed);
343 if payload_len != observation.payload_len {
344 return Err(SlotError::ChangedPayloadLength {
345 expected: observation.payload_len,
346 actual: payload_len,
347 });
348 }
349 Ok(())
350 }
351}
352
353#[must_use = "payload bytes remain unpublished until publish is called"]
355#[derive(Debug)]
356pub struct PublishReservation<'a> {
357 header: &'a SlotMetadata,
358 sequence: u64,
359 capacity: u32,
360 _exclusive: PhantomData<&'a mut ()>,
361}
362
363impl PublishReservation<'_> {
364 pub fn publish(self, payload_len: u32) -> Result<(), SlotError> {
366 if payload_len > self.capacity {
367 return Err(SlotError::PayloadTooLarge {
368 length: payload_len,
369 capacity: self.capacity,
370 });
371 }
372 self.header
373 .payload_len
374 .store(payload_len, Ordering::Relaxed);
375 self.header
376 .published_sequence
377 .store(self.sequence, Ordering::Release);
378 Ok(())
379 }
380}
381
382#[derive(Clone, Copy, Debug, Eq, PartialEq)]
384pub struct SlotObservation {
385 role: RoleId,
386 slot_index: u32,
387 generation: u64,
388 sequence: u64,
389 payload_len: u32,
390}
391
392impl SlotObservation {
393 pub const fn role(self) -> RoleId {
395 self.role
396 }
397 pub const fn slot_index(self) -> u32 {
399 self.slot_index
400 }
401 pub const fn generation(self) -> u64 {
403 self.generation
404 }
405 pub const fn sequence(self) -> u64 {
407 self.sequence
408 }
409 pub const fn payload_len(self) -> u32 {
411 self.payload_len
412 }
413}
414
415#[derive(Clone, Copy, Debug, Eq, PartialEq)]
417pub struct AcknowledgementWriterBinding(AcknowledgementBinding);
418
419impl AcknowledgementWriterBinding {
420 pub(crate) const fn validated(route: AcknowledgementRoute, generation: u64) -> Self {
421 Self(AcknowledgementBinding {
422 owner: route.owner(),
423 target: route.target(),
424 slot_index: route.slot_index(),
425 cell_index: route.cell_index(),
426 generation,
427 })
428 }
429}
430
431#[derive(Clone, Copy, Debug, Eq, PartialEq)]
433pub struct AcknowledgementReaderBinding(AcknowledgementBinding);
434
435impl AcknowledgementReaderBinding {
436 pub(crate) const fn validated(route: AcknowledgementRoute, generation: u64) -> Self {
437 Self(AcknowledgementBinding {
438 owner: route.owner(),
439 target: route.target(),
440 slot_index: route.slot_index(),
441 cell_index: route.cell_index(),
442 generation,
443 })
444 }
445}
446
447#[derive(Clone, Copy, Debug, Eq, PartialEq)]
448struct AcknowledgementBinding {
449 owner: RoleId,
450 target: RoleId,
451 slot_index: u32,
452 cell_index: u32,
453 generation: u64,
454}
455
456pub struct AcknowledgementWriter<'a> {
458 cell: &'a AcknowledgementCell,
459 binding: AcknowledgementBinding,
460 _not_sync: PhantomData<Cell<()>>,
461}
462
463impl<'a> AcknowledgementWriter<'a> {
464 pub unsafe fn bind(
472 cell: &'a AcknowledgementCell,
473 binding: AcknowledgementWriterBinding,
474 ) -> Self {
475 Self {
476 cell,
477 binding: binding.0,
478 _not_sync: PhantomData,
479 }
480 }
481
482 pub fn acknowledge(
484 &mut self,
485 observation: SlotObservation,
486 ) -> Result<(), AcknowledgementError> {
487 if observation.role != self.binding.target {
488 return Err(AcknowledgementError::WrongTarget);
489 }
490 if observation.generation != self.binding.generation {
491 return Err(AcknowledgementError::StaleGeneration);
492 }
493 if observation.slot_index != self.binding.slot_index {
494 return Err(AcknowledgementError::WrongSlot);
495 }
496 if observation.sequence == 0 {
497 return Err(AcknowledgementError::UnpublishedSequence);
498 }
499 let current = self.cell.sequence.load(Ordering::Relaxed);
500 if observation.sequence < current {
501 return Err(AcknowledgementError::NonMonotonic {
502 current,
503 next: observation.sequence,
504 });
505 }
506 if observation.sequence == current {
507 return Ok(());
508 }
509 self.cell
510 .sequence
511 .store(observation.sequence, Ordering::Release);
512 Ok(())
513 }
514}
515
516pub struct AcknowledgementReader<'a> {
518 cell: &'a AcknowledgementCell,
519 binding: AcknowledgementBinding,
520}
521
522impl<'a> AcknowledgementReader<'a> {
523 pub unsafe fn bind(
531 cell: &'a AcknowledgementCell,
532 binding: AcknowledgementReaderBinding,
533 ) -> Self {
534 Self {
535 cell,
536 binding: binding.0,
537 }
538 }
539
540 pub fn observe(&self) -> AcknowledgementObservation {
542 AcknowledgementObservation {
543 owner: self.binding.owner,
544 target: self.binding.target,
545 generation: self.binding.generation,
546 slot_index: self.binding.slot_index,
547 cell_index: self.binding.cell_index,
548 sequence: self.cell.sequence.load(Ordering::Acquire),
549 }
550 }
551}
552
553#[derive(Clone, Copy, Debug, Eq, PartialEq)]
555pub struct AcknowledgementObservation {
556 owner: RoleId,
557 target: RoleId,
558 generation: u64,
559 slot_index: u32,
560 cell_index: u32,
561 sequence: u64,
562}
563
564impl AcknowledgementObservation {
565 pub const fn owner(self) -> RoleId {
567 self.owner
568 }
569 pub const fn target(self) -> RoleId {
571 self.target
572 }
573 pub const fn generation(self) -> u64 {
575 self.generation
576 }
577 pub const fn slot_index(self) -> u32 {
579 self.slot_index
580 }
581 pub const fn cell_index(self) -> u32 {
583 self.cell_index
584 }
585 pub const fn sequence(self) -> u64 {
587 self.sequence
588 }
589}
590
591#[allow(missing_docs)]
593#[derive(Clone, Copy, Debug, Eq, PartialEq)]
594pub enum SlotError {
595 ZeroGeneration,
597 UnpublishedSequence,
599 StaleGeneration { expected: u64, actual: u64 },
601 SequenceWrap,
603 WrongSlot { expected: u32, actual: u32 },
605 UnexpectedFirstSequence { expected: u64, actual: u64 },
607 UnexpectedNextSequence { expected: u64, actual: u64 },
609 MissingAcknowledgement { sequence: u64 },
611 WrongAcknowledgementTarget,
613 WrongAcknowledgementOwner,
615 WrongAcknowledgementSlot,
617 WrongAcknowledgementCell,
619 StaleAcknowledgementGeneration,
621 LaggingAcknowledgement { expected: u64, actual: u64 },
623 FutureAcknowledgement { expected: u64, actual: u64 },
625 StaleSequence { expected: u64, actual: u64 },
627 PayloadTooLarge { length: u32, capacity: u32 },
629 ChangedPayloadLength { expected: u32, actual: u32 },
631 WrongObservationRole,
633}
634
635#[allow(missing_docs)]
637#[derive(Clone, Copy, Debug, Eq, PartialEq)]
638pub enum AcknowledgementError {
639 WrongTarget,
641 StaleGeneration,
643 WrongSlot,
645 UnpublishedSequence,
647 NonMonotonic { current: u64, next: u64 },
649}
650
651impl fmt::Display for SlotError {
652 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
653 write!(formatter, "slot operation failed: {self:?}")
654 }
655}
656impl fmt::Display for AcknowledgementError {
657 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
658 write!(formatter, "acknowledgement failed: {self:?}")
659 }
660}
661
662#[cfg(feature = "std")]
663impl std::error::Error for SlotError {}
664#[cfg(feature = "std")]
665impl std::error::Error for AcknowledgementError {}
666
667fn validate_bound_generation(header: &SlotMetadata, expected: u64) -> Result<(), SlotError> {
668 if expected == 0 {
669 return Err(SlotError::ZeroGeneration);
670 }
671 let actual = header.generation.load(Ordering::Relaxed);
672 if actual == expected {
673 Ok(())
674 } else {
675 Err(SlotError::StaleGeneration { expected, actual })
676 }
677}
678
679fn validate_sequence_slot(binding: SlotBinding, sequence: u64) -> Result<(), SlotError> {
680 if sequence == 0 {
681 return Err(SlotError::UnpublishedSequence);
682 }
683 let expected = ((sequence - 1) % u64::from(binding.slot_count)) as u32;
684 if binding.slot_index == expected {
685 Ok(())
686 } else {
687 Err(SlotError::WrongSlot {
688 expected,
689 actual: binding.slot_index,
690 })
691 }
692}
693
694const _: () = assert!(core::mem::size_of::<SlotMetadata>() == SLOT_HEADER_SIZE as usize);
695const _: () = assert!(core::mem::align_of::<SlotMetadata>() == 64);
696const _: () = assert!(core::mem::offset_of!(SlotMetadata, generation) == 0);
697const _: () = assert!(core::mem::offset_of!(SlotMetadata, payload_len) == 8);
698const _: () = assert!(core::mem::offset_of!(SlotMetadata, reserved_word) == 12);
699const _: () = assert!(core::mem::offset_of!(SlotMetadata, published_sequence) == 16);
700const _: () = assert!(core::mem::offset_of!(SlotMetadata, reserved) == 24);
701const _: () = assert!(core::mem::size_of::<AcknowledgementCell>() == 64);
702const _: () = assert!(core::mem::align_of::<AcknowledgementCell>() == 64);
703const _: () = assert!(core::mem::offset_of!(AcknowledgementCell, sequence) == 0);
704const _: () = assert!(core::mem::offset_of!(AcknowledgementCell, reserved) == 8);
705
706#[cfg(test)]
707mod tests {
708 use super::*;
709
710 const PRODUCER: RoleId = RoleId::new(1).unwrap();
711 const ACK_OWNER: RoleId = RoleId::new(2).unwrap();
712 const GENERATION: u64 = 9;
713
714 fn writer_binding(slot: u32) -> WriterSlotBinding {
715 WriterSlotBinding::validated(PRODUCER, GENERATION, 8, slot, 2, ACK_OWNER, slot)
716 }
717
718 const fn route(slot: u32) -> AcknowledgementRoute {
719 AcknowledgementRoute::validated(ACK_OWNER, PRODUCER, slot, slot)
720 }
721
722 fn reader_binding(slot: u32) -> ReaderSlotBinding {
723 ReaderSlotBinding::validated(PRODUCER, GENERATION, 8, slot, 2)
724 }
725
726 fn observation(sequence: u64) -> SlotObservation {
727 SlotObservation {
728 role: PRODUCER,
729 slot_index: if sequence == 0 {
730 0
731 } else {
732 ((sequence - 1) % 2) as u32
733 },
734 generation: GENERATION,
735 sequence,
736 payload_len: 4,
737 }
738 }
739
740 #[test]
741 fn writer_release_publishes_and_reader_acquires() {
742 let header = SlotMetadata::new(GENERATION);
743 {
744 let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(0)) }.unwrap();
746 writer.prepare_publish(1, None).unwrap().publish(4).unwrap();
747 }
748 let reader = unsafe { ReaderSlot::bind(&header, reader_binding(0)) }.unwrap();
750 let observed = reader.observe(1).unwrap();
751 assert_eq!(observed, observation(1));
752 reader.recheck(observed).unwrap();
753 }
754
755 #[test]
756 fn reuse_requires_exact_target_generation_and_prior_sequence() {
757 let header = SlotMetadata::new(GENERATION);
758 let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(0)) }.unwrap();
760 writer.prepare_publish(1, None).unwrap().publish(4).unwrap();
761 assert_eq!(
762 writer.prepare_publish(3, None).unwrap_err(),
763 SlotError::MissingAcknowledgement { sequence: 1 }
764 );
765
766 let exact = AcknowledgementObservation {
767 owner: ACK_OWNER,
768 target: PRODUCER,
769 generation: GENERATION,
770 slot_index: 0,
771 cell_index: 0,
772 sequence: 1,
773 };
774 writer
775 .prepare_publish(3, Some(exact))
776 .unwrap()
777 .publish(2)
778 .unwrap();
779
780 let lagging = AcknowledgementObservation {
781 sequence: 0,
782 ..exact
783 };
784 assert!(matches!(
785 writer.prepare_publish(5, Some(lagging)),
786 Err(SlotError::LaggingAcknowledgement { .. })
787 ));
788 let future = AcknowledgementObservation {
789 sequence: 4,
790 ..exact
791 };
792 assert!(matches!(
793 writer.prepare_publish(5, Some(future)),
794 Err(SlotError::FutureAcknowledgement { .. })
795 ));
796 let wrong_target = AcknowledgementObservation {
797 target: ACK_OWNER,
798 sequence: 3,
799 ..exact
800 };
801 assert_eq!(
802 writer.prepare_publish(5, Some(wrong_target)).unwrap_err(),
803 SlotError::WrongAcknowledgementTarget
804 );
805 let wrong_owner = AcknowledgementObservation {
806 owner: PRODUCER,
807 sequence: 3,
808 ..exact
809 };
810 assert_eq!(
811 writer.prepare_publish(5, Some(wrong_owner)).unwrap_err(),
812 SlotError::WrongAcknowledgementOwner
813 );
814 let stale = AcknowledgementObservation {
815 generation: GENERATION - 1,
816 sequence: 3,
817 ..exact
818 };
819 assert_eq!(
820 writer.prepare_publish(5, Some(stale)).unwrap_err(),
821 SlotError::StaleAcknowledgementGeneration
822 );
823 }
824
825 #[test]
826 fn acknowledgement_capabilities_are_split_and_monotonic() {
827 let cell = AcknowledgementCell::new();
828 let writer_binding = AcknowledgementWriterBinding::validated(route(0), GENERATION);
829 {
830 let mut writer = unsafe { AcknowledgementWriter::bind(&cell, writer_binding) };
832 writer.acknowledge(observation(1)).unwrap();
833 writer.acknowledge(observation(1)).unwrap();
834 writer.acknowledge(observation(3)).unwrap();
835 assert_eq!(
836 writer.acknowledge(observation(1)).unwrap_err(),
837 AcknowledgementError::NonMonotonic {
838 current: 3,
839 next: 1
840 }
841 );
842 assert!(matches!(
843 writer.acknowledge(observation(0)),
844 Err(AcknowledgementError::UnpublishedSequence)
845 ));
846 }
847 let reader_binding = AcknowledgementReaderBinding::validated(route(0), GENERATION);
848 let reader = unsafe { AcknowledgementReader::bind(&cell, reader_binding) };
850 let observed = reader.observe();
851 assert_eq!(observed.owner(), ACK_OWNER);
852 assert_eq!(observed.target(), PRODUCER);
853 assert_eq!(observed.generation(), GENERATION);
854 assert_eq!(observed.sequence(), 3);
855
856 let mut terminal = AcknowledgementCell::new();
857 *terminal.sequence.get_mut() = u64::MAX;
858 let mut writer = unsafe {
859 AcknowledgementWriter::bind(
860 &terminal,
861 AcknowledgementWriterBinding::validated(route(0), GENERATION),
862 )
863 };
864 let maximum = SlotObservation {
865 sequence: u64::MAX,
866 slot_index: 0,
867 ..observation(1)
868 };
869 writer.acknowledge(maximum).unwrap();
870 }
871
872 #[test]
873 fn two_slot_routes_complete_multiple_rotations() {
874 let headers = [SlotMetadata::new(GENERATION), SlotMetadata::new(GENERATION)];
875 let cells = [AcknowledgementCell::new(), AcknowledgementCell::new()];
876 let mut acknowledgements = [None, None];
877
878 for sequence in 1..=6 {
879 let slot = ((sequence - 1) % 2) as usize;
880 let mut writer =
881 unsafe { WriterSlot::bind(&headers[slot], writer_binding(slot as u32)) }.unwrap();
882 writer
883 .prepare_publish(sequence, acknowledgements[slot])
884 .unwrap()
885 .publish(4)
886 .unwrap();
887 let reader =
888 unsafe { ReaderSlot::bind(&headers[slot], reader_binding(slot as u32)) }.unwrap();
889 let observed = reader.observe(sequence).unwrap();
890 reader.recheck(observed).unwrap();
891 let binding = AcknowledgementWriterBinding::validated(route(slot as u32), GENERATION);
892 let mut acknowledgement_writer =
893 unsafe { AcknowledgementWriter::bind(&cells[slot], binding) };
894 acknowledgement_writer.acknowledge(observed).unwrap();
895 let binding = AcknowledgementReaderBinding::validated(route(slot as u32), GENERATION);
896 let acknowledgement_reader =
897 unsafe { AcknowledgementReader::bind(&cells[slot], binding) };
898 acknowledgements[slot] = Some(acknowledgement_reader.observe());
899 }
900
901 let mut slot_zero = unsafe { WriterSlot::bind(&headers[0], writer_binding(0)) }.unwrap();
902 let wrong_cell = AcknowledgementObservation {
903 cell_index: 1,
904 sequence: 5,
905 ..acknowledgements[0].unwrap()
906 };
907 assert_eq!(
908 slot_zero.prepare_publish(7, Some(wrong_cell)).unwrap_err(),
909 SlotError::WrongAcknowledgementCell
910 );
911 let wrong_slot = AcknowledgementObservation {
912 slot_index: 1,
913 cell_index: 0,
914 sequence: 5,
915 ..acknowledgements[0].unwrap()
916 };
917 assert_eq!(
918 slot_zero.prepare_publish(7, Some(wrong_slot)).unwrap_err(),
919 SlotError::WrongAcknowledgementSlot
920 );
921 }
922
923 #[test]
924 fn production_interleaving_model_accepts_only_exact_prior_ack() {
925 for current in [1_u64, 3, 5] {
926 for acknowledged in 0..=current + 1 {
927 let mut header = SlotMetadata::new(GENERATION);
928 *header.published_sequence.get_mut() = current;
929 let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(0)) }.unwrap();
930 let result = writer.prepare_publish(
931 current + 2,
932 Some(AcknowledgementObservation {
933 owner: ACK_OWNER,
934 target: PRODUCER,
935 generation: GENERATION,
936 slot_index: 0,
937 cell_index: 0,
938 sequence: acknowledged,
939 }),
940 );
941 assert_eq!(result.is_ok(), acknowledged == current);
942 }
943 }
944 }
945
946 #[test]
947 fn recheck_detects_length_change_but_does_not_claim_payload_integrity() {
948 let header = SlotMetadata::new(GENERATION);
949 {
950 let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(0)) }.unwrap();
951 writer.prepare_publish(1, None).unwrap().publish(4).unwrap();
952 }
953 let reader = unsafe { ReaderSlot::bind(&header, reader_binding(0)) }.unwrap();
954 let observed = reader.observe(1).unwrap();
955 header.payload_len.store(5, Ordering::Relaxed);
956 assert_eq!(
957 reader.recheck(observed).unwrap_err(),
958 SlotError::ChangedPayloadLength {
959 expected: 4,
960 actual: 5
961 }
962 );
963 }
964
965 #[test]
966 fn rejects_wrong_slot_zero_sequence_oversize_and_wrap() {
967 let header = SlotMetadata::new(GENERATION);
968 let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(1)) }.unwrap();
970 assert_eq!(
971 writer.prepare_publish(0, None).unwrap_err(),
972 SlotError::UnpublishedSequence
973 );
974 assert!(matches!(
975 writer.prepare_publish(1, None),
976 Err(SlotError::WrongSlot { .. })
977 ));
978 assert!(matches!(
979 writer.prepare_publish(2, None).unwrap().publish(9),
980 Err(SlotError::PayloadTooLarge { .. })
981 ));
982
983 let mut wrapped = SlotMetadata::new(GENERATION);
984 *wrapped.published_sequence.get_mut() = u64::MAX;
985 let mut writer = unsafe { WriterSlot::bind(&wrapped, writer_binding(0)) }.unwrap();
987 assert_eq!(
988 writer
989 .prepare_publish(
990 u64::MAX,
991 Some(AcknowledgementObservation {
992 owner: ACK_OWNER,
993 target: PRODUCER,
994 generation: GENERATION,
995 slot_index: 0,
996 cell_index: 0,
997 sequence: u64::MAX,
998 })
999 )
1000 .unwrap_err(),
1001 SlotError::SequenceWrap
1002 );
1003 }
1004}