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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
593pub enum SlotError {
594 ZeroGeneration,
596 UnpublishedSequence,
598 StaleGeneration {
600 expected: u64,
602 actual: u64,
604 },
605 SequenceWrap,
607 WrongSlot {
609 expected: u32,
611 actual: u32,
613 },
614 UnexpectedFirstSequence {
616 expected: u64,
618 actual: u64,
620 },
621 UnexpectedNextSequence {
623 expected: u64,
625 actual: u64,
627 },
628 MissingAcknowledgement {
630 sequence: u64,
632 },
633 WrongAcknowledgementTarget,
635 WrongAcknowledgementOwner,
637 WrongAcknowledgementSlot,
639 WrongAcknowledgementCell,
641 StaleAcknowledgementGeneration,
643 LaggingAcknowledgement {
645 expected: u64,
647 actual: u64,
649 },
650 FutureAcknowledgement {
652 expected: u64,
654 actual: u64,
656 },
657 StaleSequence {
659 expected: u64,
661 actual: u64,
663 },
664 PayloadTooLarge {
666 length: u32,
668 capacity: u32,
670 },
671 ChangedPayloadLength {
673 expected: u32,
675 actual: u32,
677 },
678 WrongObservationRole,
680}
681
682#[derive(Clone, Copy, Debug, Eq, PartialEq)]
684pub enum AcknowledgementError {
685 WrongTarget,
687 StaleGeneration,
689 WrongSlot,
691 UnpublishedSequence,
693 NonMonotonic {
695 current: u64,
697 next: u64,
699 },
700}
701
702impl fmt::Display for SlotError {
703 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
704 write!(formatter, "slot operation failed: {self:?}")
705 }
706}
707impl fmt::Display for AcknowledgementError {
708 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
709 write!(formatter, "acknowledgement failed: {self:?}")
710 }
711}
712
713#[cfg(feature = "std")]
714impl std::error::Error for SlotError {}
715#[cfg(feature = "std")]
716impl std::error::Error for AcknowledgementError {}
717
718fn validate_bound_generation(header: &SlotMetadata, expected: u64) -> Result<(), SlotError> {
719 if expected == 0 {
720 return Err(SlotError::ZeroGeneration);
721 }
722 let actual = header.generation.load(Ordering::Relaxed);
723 if actual == expected {
724 Ok(())
725 } else {
726 Err(SlotError::StaleGeneration { expected, actual })
727 }
728}
729
730fn validate_sequence_slot(binding: SlotBinding, sequence: u64) -> Result<(), SlotError> {
731 if sequence == 0 {
732 return Err(SlotError::UnpublishedSequence);
733 }
734 let expected = ((sequence - 1) % u64::from(binding.slot_count)) as u32;
735 if binding.slot_index == expected {
736 Ok(())
737 } else {
738 Err(SlotError::WrongSlot {
739 expected,
740 actual: binding.slot_index,
741 })
742 }
743}
744
745const _: () = assert!(core::mem::size_of::<SlotMetadata>() == SLOT_HEADER_SIZE as usize);
746const _: () = assert!(core::mem::align_of::<SlotMetadata>() == 64);
747const _: () = assert!(core::mem::offset_of!(SlotMetadata, generation) == 0);
748const _: () = assert!(core::mem::offset_of!(SlotMetadata, payload_len) == 8);
749const _: () = assert!(core::mem::offset_of!(SlotMetadata, reserved_word) == 12);
750const _: () = assert!(core::mem::offset_of!(SlotMetadata, published_sequence) == 16);
751const _: () = assert!(core::mem::offset_of!(SlotMetadata, reserved) == 24);
752const _: () = assert!(core::mem::size_of::<AcknowledgementCell>() == 64);
753const _: () = assert!(core::mem::align_of::<AcknowledgementCell>() == 64);
754const _: () = assert!(core::mem::offset_of!(AcknowledgementCell, sequence) == 0);
755const _: () = assert!(core::mem::offset_of!(AcknowledgementCell, reserved) == 8);
756
757#[cfg(test)]
758mod tests {
759 use super::*;
760
761 const PRODUCER: RoleId = RoleId::new(1).unwrap();
762 const ACK_OWNER: RoleId = RoleId::new(2).unwrap();
763 const GENERATION: u64 = 9;
764
765 fn writer_binding(slot: u32) -> WriterSlotBinding {
766 WriterSlotBinding::validated(PRODUCER, GENERATION, 8, slot, 2, ACK_OWNER, slot)
767 }
768
769 const fn route(slot: u32) -> AcknowledgementRoute {
770 AcknowledgementRoute::validated(ACK_OWNER, PRODUCER, slot, slot)
771 }
772
773 fn reader_binding(slot: u32) -> ReaderSlotBinding {
774 ReaderSlotBinding::validated(PRODUCER, GENERATION, 8, slot, 2)
775 }
776
777 fn observation(sequence: u64) -> SlotObservation {
778 SlotObservation {
779 role: PRODUCER,
780 slot_index: if sequence == 0 {
781 0
782 } else {
783 ((sequence - 1) % 2) as u32
784 },
785 generation: GENERATION,
786 sequence,
787 payload_len: 4,
788 }
789 }
790
791 #[test]
792 fn writer_release_publishes_and_reader_acquires() {
793 let header = SlotMetadata::new(GENERATION);
794 {
795 let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(0)) }.unwrap();
797 writer.prepare_publish(1, None).unwrap().publish(4).unwrap();
798 }
799 let reader = unsafe { ReaderSlot::bind(&header, reader_binding(0)) }.unwrap();
801 let observed = reader.observe(1).unwrap();
802 assert_eq!(observed, observation(1));
803 reader.recheck(observed).unwrap();
804 }
805
806 #[test]
807 fn reuse_requires_exact_target_generation_and_prior_sequence() {
808 let header = SlotMetadata::new(GENERATION);
809 let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(0)) }.unwrap();
811 writer.prepare_publish(1, None).unwrap().publish(4).unwrap();
812 assert_eq!(
813 writer.prepare_publish(3, None).unwrap_err(),
814 SlotError::MissingAcknowledgement { sequence: 1 }
815 );
816
817 let exact = AcknowledgementObservation {
818 owner: ACK_OWNER,
819 target: PRODUCER,
820 generation: GENERATION,
821 slot_index: 0,
822 cell_index: 0,
823 sequence: 1,
824 };
825 writer
826 .prepare_publish(3, Some(exact))
827 .unwrap()
828 .publish(2)
829 .unwrap();
830
831 let lagging = AcknowledgementObservation {
832 sequence: 0,
833 ..exact
834 };
835 assert!(matches!(
836 writer.prepare_publish(5, Some(lagging)),
837 Err(SlotError::LaggingAcknowledgement { .. })
838 ));
839 let future = AcknowledgementObservation {
840 sequence: 4,
841 ..exact
842 };
843 assert!(matches!(
844 writer.prepare_publish(5, Some(future)),
845 Err(SlotError::FutureAcknowledgement { .. })
846 ));
847 let wrong_target = AcknowledgementObservation {
848 target: ACK_OWNER,
849 sequence: 3,
850 ..exact
851 };
852 assert_eq!(
853 writer.prepare_publish(5, Some(wrong_target)).unwrap_err(),
854 SlotError::WrongAcknowledgementTarget
855 );
856 let wrong_owner = AcknowledgementObservation {
857 owner: PRODUCER,
858 sequence: 3,
859 ..exact
860 };
861 assert_eq!(
862 writer.prepare_publish(5, Some(wrong_owner)).unwrap_err(),
863 SlotError::WrongAcknowledgementOwner
864 );
865 let stale = AcknowledgementObservation {
866 generation: GENERATION - 1,
867 sequence: 3,
868 ..exact
869 };
870 assert_eq!(
871 writer.prepare_publish(5, Some(stale)).unwrap_err(),
872 SlotError::StaleAcknowledgementGeneration
873 );
874 }
875
876 #[test]
877 fn acknowledgement_capabilities_are_split_and_monotonic() {
878 let cell = AcknowledgementCell::new();
879 let writer_binding = AcknowledgementWriterBinding::validated(route(0), GENERATION);
880 {
881 let mut writer = unsafe { AcknowledgementWriter::bind(&cell, writer_binding) };
883 writer.acknowledge(observation(1)).unwrap();
884 writer.acknowledge(observation(1)).unwrap();
885 writer.acknowledge(observation(3)).unwrap();
886 assert_eq!(
887 writer.acknowledge(observation(1)).unwrap_err(),
888 AcknowledgementError::NonMonotonic {
889 current: 3,
890 next: 1
891 }
892 );
893 assert!(matches!(
894 writer.acknowledge(observation(0)),
895 Err(AcknowledgementError::UnpublishedSequence)
896 ));
897 }
898 let reader_binding = AcknowledgementReaderBinding::validated(route(0), GENERATION);
899 let reader = unsafe { AcknowledgementReader::bind(&cell, reader_binding) };
901 let observed = reader.observe();
902 assert_eq!(observed.owner(), ACK_OWNER);
903 assert_eq!(observed.target(), PRODUCER);
904 assert_eq!(observed.generation(), GENERATION);
905 assert_eq!(observed.sequence(), 3);
906
907 let mut terminal = AcknowledgementCell::new();
908 *terminal.sequence.get_mut() = u64::MAX;
909 let mut writer = unsafe {
910 AcknowledgementWriter::bind(
911 &terminal,
912 AcknowledgementWriterBinding::validated(route(0), GENERATION),
913 )
914 };
915 let maximum = SlotObservation {
916 sequence: u64::MAX,
917 slot_index: 0,
918 ..observation(1)
919 };
920 writer.acknowledge(maximum).unwrap();
921 }
922
923 #[test]
924 fn two_slot_routes_complete_multiple_rotations() {
925 let headers = [SlotMetadata::new(GENERATION), SlotMetadata::new(GENERATION)];
926 let cells = [AcknowledgementCell::new(), AcknowledgementCell::new()];
927 let mut acknowledgements = [None, None];
928
929 for sequence in 1..=6 {
930 let slot = ((sequence - 1) % 2) as usize;
931 let mut writer =
932 unsafe { WriterSlot::bind(&headers[slot], writer_binding(slot as u32)) }.unwrap();
933 writer
934 .prepare_publish(sequence, acknowledgements[slot])
935 .unwrap()
936 .publish(4)
937 .unwrap();
938 let reader =
939 unsafe { ReaderSlot::bind(&headers[slot], reader_binding(slot as u32)) }.unwrap();
940 let observed = reader.observe(sequence).unwrap();
941 reader.recheck(observed).unwrap();
942 let binding = AcknowledgementWriterBinding::validated(route(slot as u32), GENERATION);
943 let mut acknowledgement_writer =
944 unsafe { AcknowledgementWriter::bind(&cells[slot], binding) };
945 acknowledgement_writer.acknowledge(observed).unwrap();
946 let binding = AcknowledgementReaderBinding::validated(route(slot as u32), GENERATION);
947 let acknowledgement_reader =
948 unsafe { AcknowledgementReader::bind(&cells[slot], binding) };
949 acknowledgements[slot] = Some(acknowledgement_reader.observe());
950 }
951
952 let mut slot_zero = unsafe { WriterSlot::bind(&headers[0], writer_binding(0)) }.unwrap();
953 let wrong_cell = AcknowledgementObservation {
954 cell_index: 1,
955 sequence: 5,
956 ..acknowledgements[0].unwrap()
957 };
958 assert_eq!(
959 slot_zero.prepare_publish(7, Some(wrong_cell)).unwrap_err(),
960 SlotError::WrongAcknowledgementCell
961 );
962 let wrong_slot = AcknowledgementObservation {
963 slot_index: 1,
964 cell_index: 0,
965 sequence: 5,
966 ..acknowledgements[0].unwrap()
967 };
968 assert_eq!(
969 slot_zero.prepare_publish(7, Some(wrong_slot)).unwrap_err(),
970 SlotError::WrongAcknowledgementSlot
971 );
972 }
973
974 #[test]
975 fn production_interleaving_model_accepts_only_exact_prior_ack() {
976 for current in [1_u64, 3, 5] {
977 for acknowledged in 0..=current + 1 {
978 let mut header = SlotMetadata::new(GENERATION);
979 *header.published_sequence.get_mut() = current;
980 let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(0)) }.unwrap();
981 let result = writer.prepare_publish(
982 current + 2,
983 Some(AcknowledgementObservation {
984 owner: ACK_OWNER,
985 target: PRODUCER,
986 generation: GENERATION,
987 slot_index: 0,
988 cell_index: 0,
989 sequence: acknowledged,
990 }),
991 );
992 assert_eq!(result.is_ok(), acknowledged == current);
993 }
994 }
995 }
996
997 #[test]
998 fn recheck_detects_length_change_but_does_not_claim_payload_integrity() {
999 let header = SlotMetadata::new(GENERATION);
1000 {
1001 let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(0)) }.unwrap();
1002 writer.prepare_publish(1, None).unwrap().publish(4).unwrap();
1003 }
1004 let reader = unsafe { ReaderSlot::bind(&header, reader_binding(0)) }.unwrap();
1005 let observed = reader.observe(1).unwrap();
1006 header.payload_len.store(5, Ordering::Relaxed);
1007 assert_eq!(
1008 reader.recheck(observed).unwrap_err(),
1009 SlotError::ChangedPayloadLength {
1010 expected: 4,
1011 actual: 5
1012 }
1013 );
1014 }
1015
1016 #[test]
1017 fn rejects_wrong_slot_zero_sequence_oversize_and_wrap() {
1018 let header = SlotMetadata::new(GENERATION);
1019 let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(1)) }.unwrap();
1021 assert_eq!(
1022 writer.prepare_publish(0, None).unwrap_err(),
1023 SlotError::UnpublishedSequence
1024 );
1025 assert!(matches!(
1026 writer.prepare_publish(1, None),
1027 Err(SlotError::WrongSlot { .. })
1028 ));
1029 assert!(matches!(
1030 writer.prepare_publish(2, None).unwrap().publish(9),
1031 Err(SlotError::PayloadTooLarge { .. })
1032 ));
1033
1034 let mut wrapped = SlotMetadata::new(GENERATION);
1035 *wrapped.published_sequence.get_mut() = u64::MAX;
1036 let mut writer = unsafe { WriterSlot::bind(&wrapped, writer_binding(0)) }.unwrap();
1038 assert_eq!(
1039 writer
1040 .prepare_publish(
1041 u64::MAX,
1042 Some(AcknowledgementObservation {
1043 owner: ACK_OWNER,
1044 target: PRODUCER,
1045 generation: GENERATION,
1046 slot_index: 0,
1047 cell_index: 0,
1048 sequence: u64::MAX,
1049 })
1050 )
1051 .unwrap_err(),
1052 SlotError::SequenceWrap
1053 );
1054 }
1055}