Skip to main content

native_ipc_core/
slot.rs

1//! Generation-bound, role-bound slot and acknowledgement capabilities.
2
3use 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
13/// Bytes occupied by one cache-line-aligned slot metadata record.
14pub const SLOT_HEADER_SIZE: u64 = 64;
15/// Bytes occupied by one cache-line-aligned acknowledgement cell.
16pub const ACKNOWLEDGEMENT_CELL_SIZE: u64 = 64;
17
18/// Writer-owned atomic publication metadata for one fixed-capacity slot.
19///
20/// This type is an in-memory concurrency record, not a serializable Rust wire
21/// layout. A region layout fixes its offsets and requires 64-bit atomics.
22#[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    /// Creates unpublished metadata in quiescent, process-local storage.
34    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    /// Reinitializes metadata before any peer can access the mapping.
45    ///
46    /// # Safety
47    ///
48    /// No peer process or concurrent thread may access this slot until the
49    /// initialization and all associated payload initialization complete.
50    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        // SAFETY: `&mut self` and the function contract exclude all aliases.
57        unsafe { *self.reserved_word.get() = 0 };
58        *self.published_sequence.get_mut() = 0;
59        // SAFETY: `&mut self` and the function contract exclude all aliases.
60        unsafe { *self.reserved.get() = [0; 40] };
61        Ok(())
62    }
63}
64
65// SAFETY: live fields are accessed only through atomics. `UnsafeCell` makes
66// peer mutation of padding explicit; protocol code never reads that padding
67// after quiescent validation. Peers must use compatible aligned atomics for
68// atomic fields.
69unsafe impl Sync for SlotMetadata {}
70
71/// A single writer-owned atomic acknowledgement sequence.
72#[repr(C, align(64))]
73pub struct AcknowledgementCell {
74    sequence: AtomicU64,
75    reserved: UnsafeCell<[u8; 56]>,
76}
77
78impl AcknowledgementCell {
79    /// Creates a zero/unpublished cell in quiescent storage.
80    pub const fn new() -> Self {
81        Self {
82            sequence: AtomicU64::new(0),
83            reserved: UnsafeCell::new([0; 56]),
84        }
85    }
86
87    /// Reinitializes this cell before peer access.
88    ///
89    /// # Safety
90    ///
91    /// No peer process or concurrent thread may access this cell.
92    pub unsafe fn initialize(&mut self) {
93        *self.sequence.get_mut() = 0;
94        // SAFETY: `&mut self` and the function contract exclude all aliases.
95        unsafe { *self.reserved.get() = [0; 56] };
96    }
97}
98
99// SAFETY: `sequence` is atomic and externally mutable padding is never read
100// after quiescent validation.
101unsafe impl Sync for AcknowledgementCell {}
102
103impl Default for AcknowledgementCell {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109/// Immutable facts for a slot in a validated writable mapping.
110#[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/// Immutable facts for a slot in a validated read-only mapping.
136#[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
170/// Sole-writer capability bound to a validated writable mapping.
171pub struct WriterSlot<'a> {
172    header: &'a SlotMetadata,
173    binding: SlotBinding,
174    _not_sync: PhantomData<Cell<()>>,
175}
176
177impl<'a> WriterSlot<'a> {
178    /// Binds atomic metadata reached through the sole writer's mapping.
179    ///
180    /// # Safety
181    ///
182    /// `header` must reside at the checked slot offset represented by
183    /// `binding`. This process must be the only holder of a writable mapping,
184    /// and the mapping must remain live for `'a`. No other writer capability
185    /// may be bound for this slot while this value exists.
186    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    /// Checks sequence/slot/reuse invariants before payload mutation begins.
199    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
264/// Acquire-only capability bound to a validated read-only mapping.
265pub struct ReaderSlot<'a> {
266    header: &'a SlotMetadata,
267    binding: SlotBinding,
268}
269
270impl<'a> ReaderSlot<'a> {
271    /// Binds atomic metadata reached through a read-only native mapping.
272    ///
273    /// # Safety
274    ///
275    /// `header` must reside at the checked slot offset represented by
276    /// `binding`, be readable for `'a`, and must not be reached through a
277    /// writable mapping in this process.
278    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    /// Acquires and validates publication metadata before an owned payload copy.
290    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    /// Rechecks metadata stability after copying hostile bytes to owned storage.
317    ///
318    /// This detects metadata changes, not payload integrity. A malicious writer
319    /// can mutate bytes without changing metadata; callers must parse every
320    /// owned payload copy as hostile input.
321    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        // Keep preceding payload reads before the final metadata loads on
332        // weakly ordered hardware.
333        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/// Permission to publish metadata after the caller has written the payload.
354#[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    /// Stores length, then Release-publishes the nonzero sequence.
365    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/// Owned identity observed before copying a slot payload.
383#[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    /// Returns the producer region role.
394    pub const fn role(self) -> RoleId {
395        self.role
396    }
397    /// Returns the exact observed slot index.
398    pub const fn slot_index(self) -> u32 {
399        self.slot_index
400    }
401    /// Returns the connection generation.
402    pub const fn generation(self) -> u64 {
403        self.generation
404    }
405    /// Returns the published sequence.
406    pub const fn sequence(self) -> u64 {
407        self.sequence
408    }
409    /// Returns the validated payload length.
410    pub const fn payload_len(self) -> u32 {
411        self.payload_len
412    }
413}
414
415/// Immutable route metadata for a writable acknowledgement mapping.
416#[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/// Immutable route metadata for a read-only acknowledgement mapping.
432#[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
456/// Release-store capability for a cell in a writable mapping.
457pub struct AcknowledgementWriter<'a> {
458    cell: &'a AcknowledgementCell,
459    binding: AcknowledgementBinding,
460    _not_sync: PhantomData<Cell<()>>,
461}
462
463impl<'a> AcknowledgementWriter<'a> {
464    /// Binds a cell reached through its owner's writable mapping.
465    ///
466    /// # Safety
467    ///
468    /// `cell` must be the checked route cell described by `binding`; this
469    /// process must be the only writer of its containing mapping. No other
470    /// writer capability may be bound for this route while this value exists.
471    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    /// Acknowledges an exact observed slot identity with Release ordering.
483    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
516/// Acquire-only capability for a cell in a read-only mapping.
517pub struct AcknowledgementReader<'a> {
518    cell: &'a AcknowledgementCell,
519    binding: AcknowledgementBinding,
520}
521
522impl<'a> AcknowledgementReader<'a> {
523    /// Binds a cell reached through a read-only mapping.
524    ///
525    /// # Safety
526    ///
527    /// `cell` must be the checked route cell described by `binding` and must
528    /// remain readable for `'a`; this API must not be bound from a writable
529    /// alias in the current process.
530    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    /// Acquire-observes the exact route identity and current sequence.
541    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/// Owned acknowledgement identity passed to a slot writer.
554#[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    /// Returns the role that owns the acknowledgement mapping.
566    pub const fn owner(self) -> RoleId {
567        self.owner
568    }
569    /// Returns the acknowledged producer role.
570    pub const fn target(self) -> RoleId {
571        self.target
572    }
573    /// Returns the acknowledged generation.
574    pub const fn generation(self) -> u64 {
575        self.generation
576    }
577    /// Returns the acknowledged producer slot.
578    pub const fn slot_index(self) -> u32 {
579        self.slot_index
580    }
581    /// Returns the exact acknowledgement cell.
582    pub const fn cell_index(self) -> u32 {
583        self.cell_index
584    }
585    /// Returns the acknowledged sequence.
586    pub const fn sequence(self) -> u64 {
587        self.sequence
588    }
589}
590
591/// Bounded slot validation failures.
592#[allow(missing_docs)]
593#[derive(Clone, Copy, Debug, Eq, PartialEq)]
594pub enum SlotError {
595    /// Generation zero is reserved.
596    ZeroGeneration,
597    /// Sequence zero is unpublished.
598    UnpublishedSequence,
599    /// Bound generation differs from shared metadata.
600    StaleGeneration { expected: u64, actual: u64 },
601    /// Sequence would wrap.
602    SequenceWrap,
603    /// Sequence points at a different ring slot.
604    WrongSlot { expected: u32, actual: u32 },
605    /// First use of a slot had a noncanonical sequence.
606    UnexpectedFirstSequence { expected: u64, actual: u64 },
607    /// Reuse was not exactly one ring rotation later.
608    UnexpectedNextSequence { expected: u64, actual: u64 },
609    /// Reuse lacked an acknowledgement.
610    MissingAcknowledgement { sequence: u64 },
611    /// Acknowledgement targets another producer role.
612    WrongAcknowledgementTarget,
613    /// Acknowledgement came from another routed owner.
614    WrongAcknowledgementOwner,
615    /// Acknowledgement belongs to another producer slot.
616    WrongAcknowledgementSlot,
617    /// Acknowledgement came from another cell.
618    WrongAcknowledgementCell,
619    /// Acknowledgement came from another generation.
620    StaleAcknowledgementGeneration,
621    /// Acknowledgement lags the exact prior publication.
622    LaggingAcknowledgement { expected: u64, actual: u64 },
623    /// Acknowledgement attempts to pre-authorize future reuse.
624    FutureAcknowledgement { expected: u64, actual: u64 },
625    /// Observed sequence differs from expectation.
626    StaleSequence { expected: u64, actual: u64 },
627    /// Peer-declared payload exceeds fixed capacity.
628    PayloadTooLarge { length: u32, capacity: u32 },
629    /// Payload length changed during the copy window.
630    ChangedPayloadLength { expected: u32, actual: u32 },
631    /// Observation belongs to another role.
632    WrongObservationRole,
633}
634
635/// Bounded acknowledgement failures.
636#[allow(missing_docs)]
637#[derive(Clone, Copy, Debug, Eq, PartialEq)]
638pub enum AcknowledgementError {
639    /// Slot observation targets another route.
640    WrongTarget,
641    /// Slot observation belongs to another generation.
642    StaleGeneration,
643    /// Slot observation belongs to another routed slot.
644    WrongSlot,
645    /// Sequence zero is unpublished.
646    UnpublishedSequence,
647    /// Acknowledgement moved backwards.
648    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            // SAFETY: local test storage has one simulated writer capability.
745            let mut writer = unsafe { WriterSlot::bind(&header, writer_binding(0)) }.unwrap();
746            writer.prepare_publish(1, None).unwrap().publish(4).unwrap();
747        }
748        // SAFETY: writer capability was dropped and test performs no mutation.
749        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        // SAFETY: local test storage has one writer capability and no aliases.
759        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            // SAFETY: local test storage has one simulated acknowledgement writer.
831            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        // SAFETY: simulated writer was dropped; storage is immutable here.
849        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        // SAFETY: local test storage has one writer capability.
969        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        // SAFETY: separate local storage has one writer capability.
986        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}