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}