Skip to main content

zerodds_flatdata/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! FlatStruct-Trait + Slot-Header fuer Zero-Copy Same-Host-Pub/Sub.
4//!
5//! Crate `zerodds-flatdata`. Safety classification: **STANDARD**.
6//!
7//! Spec: `docs/specs/zerodds-flatdata-1.0.md`.
8//!
9//! # Sicherheits-Begruendung
10//!
11//! Dieser Crate implementiert eine `unsafe trait FlatStruct`, dessen
12//! Garantien (Copy + repr(C) + 'static + keine Pointer/Vec/String) der
13//! Implementer per `unsafe impl` zusichert. Die `as_bytes` /
14//! `from_bytes_unchecked`-Helpers sind dann sicher per Layout — wir
15//! lokalisieren das `unsafe`-Island hier statt es in den DataWriter-
16//! Pfad zu streuen.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19#![warn(missing_docs)]
20// Begruendet: FlatStruct-Trait ist by-design unsafe (Layout-Garantien
21// liegen beim Caller). Per-Block-SAFETY-Kommentare sind unten.
22#![allow(unsafe_code)]
23
24#[cfg(feature = "alloc")]
25extern crate alloc;
26
27#[cfg(feature = "std")]
28mod allocator;
29#[cfg(feature = "std")]
30mod backend;
31#[cfg(feature = "iceoryx2-bridge")]
32mod iceoryx;
33#[cfg(feature = "alloc")]
34mod locator;
35#[cfg(feature = "posix-mmap")]
36mod posix;
37#[cfg(feature = "std")]
38mod pubsub;
39mod slot;
40
41#[cfg(feature = "std")]
42pub use allocator::{InMemorySlotAllocator, SlotError, SlotHandle};
43#[cfg(feature = "std")]
44pub use backend::SlotBackend;
45#[cfg(feature = "iceoryx2-bridge")]
46pub use iceoryx::{Iceoryx2Error, Iceoryx2Publisher, Iceoryx2Subscriber};
47#[cfg(feature = "alloc")]
48pub use locator::{LocatorError, ShmLocator, fnv1a_32, is_same_host};
49#[cfg(feature = "posix-mmap")]
50pub use posix::{PosixSlotAllocator, PosixSlotError};
51#[cfg(feature = "std")]
52pub use pubsub::{FlatReader, FlatSampleRef, FlatSlot, FlatWriter};
53pub use slot::{ReaderMask, SLOT_HEADER_SIZE, SlotHeader};
54
55/// Marker-Trait fuer FlatData-faehige Types.
56///
57/// Spec-Zitat (zerodds-flatdata-1.0 §1.1):
58///
59/// > Garantiert:
60/// > - `Self: Copy` (kein Drop-Glue, plain bytes)
61/// > - `Self: 'static` (kein Lifetime-Reference)
62/// > - `#[repr(C)]` mit fest definiertem Alignment
63/// > - `as_bytes()` und `from_bytes_unchecked()` sind safe-by-Layout
64///
65/// # Safety
66///
67/// Implementer MUSS sicherstellen:
68/// - `Self` ist `#[repr(C)]` (oder `#[repr(transparent)]` ueber einen
69///   einzigen `repr(C)`-Type).
70/// - Alle Felder sind `FlatStruct` oder Primitiv-Types ohne padding-
71///   sensitive UB (kein `#[repr(packed)]` mit Pointer-aligned Fields).
72/// - `Self: Copy` (Trait-Bound erzwingt das).
73/// - `TYPE_HASH` ist eindeutig fuer die exakte Wire-Layout-Variante;
74///   bei jeder Schema-Aenderung MUSS der Hash regeneriert werden.
75pub unsafe trait FlatStruct: Copy + 'static + Send + Sync {
76    /// Wire-Size der `repr(C)`-Struktur (= `core::mem::size_of::<Self>()`).
77    const WIRE_SIZE: usize = core::mem::size_of::<Self>();
78
79    /// Eindeutiger Type-Hash (16 byte). Caller-Code generiert via
80    /// SHA-256(`type_name + field_layout_string`) und nimmt die
81    /// ersten 16 byte. Reader prueft diesen Hash gegen den
82    /// Discovery-Hash; Mismatch → Slot-Drop.
83    const TYPE_HASH: [u8; 16];
84
85    /// Liefert das Slot-Layout als Slice. Safe-by-Layout — `Self: Copy`
86    /// + `repr(C)` garantieren dass der byte-cast defined ist.
87    #[must_use]
88    fn as_bytes(&self) -> &[u8] {
89        // SAFETY: FlatStruct-Trait verlangt repr(C) + Copy. Damit ist
90        // `*const Self` ↔ `*const u8` ein wohldefinierter Reinterpret-
91        // Cast (keine Tail-Padding-Lecks weil Caller sich verpflichtet
92        // hat).
93        unsafe {
94            core::slice::from_raw_parts(core::ptr::from_ref(self).cast::<u8>(), Self::WIRE_SIZE)
95        }
96    }
97
98    /// Rekonstruiert aus rohem Slice. Caller MUSS:
99    /// - bytes.len() >= WIRE_SIZE
100    /// - bytes-Provenance valide fuer WIRE_SIZE
101    /// - Type-Hash zuvor verifiziert (sonst UB bei alignement-Mismatch)
102    ///
103    /// # Safety
104    /// Siehe oben.
105    #[must_use]
106    unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self {
107        debug_assert!(bytes.len() >= Self::WIRE_SIZE);
108        // SAFETY: Caller-Kontrakt + WIRE_SIZE-Check.
109        unsafe { core::ptr::read_unaligned(bytes.as_ptr().cast::<Self>()) }
110    }
111}
112
113#[cfg(test)]
114#[allow(clippy::expect_used, clippy::unwrap_used)]
115mod tests {
116    use super::*;
117
118    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
119    #[repr(C)]
120    struct Pose {
121        x: i64,
122        y: i64,
123        z: i64,
124    }
125
126    // SAFETY: Pose ist repr(C), Copy, 'static. Felder sind alle
127    // Primitiv-i64 ohne padding-Issues.
128    unsafe impl FlatStruct for Pose {
129        const TYPE_HASH: [u8; 16] = [0x42; 16];
130    }
131
132    #[test]
133    fn wire_size_matches_size_of() {
134        assert_eq!(Pose::WIRE_SIZE, core::mem::size_of::<Pose>());
135        assert_eq!(Pose::WIRE_SIZE, 24);
136    }
137
138    #[test]
139    fn as_bytes_roundtrip() {
140        let p = Pose { x: 1, y: 2, z: 3 };
141        let bytes = p.as_bytes();
142        assert_eq!(bytes.len(), 24);
143        // SAFETY: bytes stammt aus genau dieser Pose-Instanz, daher
144        // Type-Hash trivial konsistent.
145        let p2: Pose = unsafe { Pose::from_bytes_unchecked(bytes) };
146        assert_eq!(p, p2);
147    }
148
149    #[test]
150    fn type_hash_is_consistent() {
151        assert_eq!(Pose::TYPE_HASH, [0x42; 16]);
152    }
153}