Skip to main content

zerodds_rtps/
wire_types.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! RTPS-Wire-Basistypen (DDSI-RTPS 2.5 §8.3.5, §8.3.5.1).
4//!
5//! Diese Typen sind die Atome des RTPS-Wire-Formats: GUID-Komponenten,
6//! Sequence-Numbers, Locators. Sie sind alle reine Byte-Strukturen mit
7//! festem Layout (kein XCDR-Alignment, kein Endianness-Tagging am Typ —
8//! die Endianness eines Submessage-Stream-Slices kommt aus dem
9//! Submessage-Header E-Flag).
10//!
11//! # Konvention
12//!
13//! - `read_from_le` / `read_from_be`: Decoder mit expliziter Endianness.
14//! - `write_to_le` / `write_to_be`: Encoder symmetrisch.
15//! - `WIRE_SIZE`: Konstante mit der festen Bytezahl auf der Wire.
16
17use crate::error::WireError;
18
19// ============================================================================
20// ProtocolVersion (§8.3.5.5)
21// ============================================================================
22
23/// `ProtocolVersion`: Major + Minor des RTPS-Protokolls. Aktuell 2.5.
24///
25/// `PartialOrd`/`Ord` vergleichen lexikographisch — `(major, minor)`-
26/// Tupel-Reihenfolge — was der Spec-Versions-Ordnung entspricht
27/// (2.4 < 2.5).
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
29pub struct ProtocolVersion {
30    /// Major version.
31    pub major: u8,
32    /// Minor version.
33    pub minor: u8,
34}
35
36impl ProtocolVersion {
37    /// Wire-Size: 2 Bytes.
38    pub const WIRE_SIZE: usize = 2;
39
40    /// RTPS 1.0 — historisch (Spec §8.3.5.5).
41    pub const V1_0: Self = Self { major: 1, minor: 0 };
42    /// RTPS 1.1 — historisch.
43    pub const V1_1: Self = Self { major: 1, minor: 1 };
44    /// RTPS 2.0 — historisch.
45    pub const V2_0: Self = Self { major: 2, minor: 0 };
46    /// RTPS 2.1 — historisch.
47    pub const V2_1: Self = Self { major: 2, minor: 1 };
48    /// RTPS 2.2 — historisch.
49    pub const V2_2: Self = Self { major: 2, minor: 2 };
50    /// RTPS 2.3 — historisch.
51    pub const V2_3: Self = Self { major: 2, minor: 3 };
52    /// RTPS 2.4 — Cyclone DDS Default vor Update auf 2.5.
53    pub const V2_4: Self = Self { major: 2, minor: 4 };
54    /// RTPS 2.5 (Default fuer ZeroDDS).
55    pub const V2_5: Self = Self { major: 2, minor: 5 };
56
57    /// `PROTOCOLVERSION` — Spec-Alias fuer den aktuellsten unterstuetzten
58    /// Wert (RTPS 2.5).
59    pub const CURRENT: Self = Self::V2_5;
60
61    /// Bytes [major, minor].
62    #[must_use]
63    pub fn to_bytes(self) -> [u8; 2] {
64        [self.major, self.minor]
65    }
66
67    /// Liest 2 Bytes.
68    #[must_use]
69    pub fn from_bytes(bytes: [u8; 2]) -> Self {
70        Self {
71            major: bytes[0],
72            minor: bytes[1],
73        }
74    }
75}
76
77impl Default for ProtocolVersion {
78    fn default() -> Self {
79        Self::V2_5
80    }
81}
82
83// ============================================================================
84// VendorId (§8.3.5.6)
85// ============================================================================
86
87/// `VendorId`: 2-byte Vendor-Identifier. ZeroDDS nutzt `0x01F0` als
88/// Interim-Wert aus dem OMG-Entwickler-Range, bis ein offizieller
89/// VendorId beantragt wird.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
91pub struct VendorId(pub [u8; 2]);
92
93impl VendorId {
94    /// Wire-Size: 2 Bytes.
95    pub const WIRE_SIZE: usize = 2;
96
97    /// Sentinel "unknown" (0x00, 0x00) — nur fuer Tests/Stub.
98    pub const UNKNOWN: Self = Self([0, 0]);
99
100    /// ZeroDDS Interim-VendorId aus OMG-Entwickler-Range.
101    pub const ZERODDS: Self = Self([0x01, 0xF0]);
102
103    /// Bytes ungeaendert.
104    #[must_use]
105    pub fn to_bytes(self) -> [u8; 2] {
106        self.0
107    }
108
109    /// Bytes ungeaendert.
110    #[must_use]
111    pub fn from_bytes(bytes: [u8; 2]) -> Self {
112        Self(bytes)
113    }
114}
115
116// ============================================================================
117// GuidPrefix (§8.3.5.1)
118// ============================================================================
119
120/// `GuidPrefix`: 12-byte-Prefix einer GUID. Identifiziert einen
121/// Participant; bleibt fuer alle Endpoints des Participants gleich.
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
123pub struct GuidPrefix(pub [u8; 12]);
124
125impl GuidPrefix {
126    /// Wire-Size: 12 Bytes.
127    pub const WIRE_SIZE: usize = 12;
128
129    /// Sentinel "unknown".
130    pub const UNKNOWN: Self = Self([0; 12]);
131
132    /// Bytes ungeaendert.
133    #[must_use]
134    pub fn to_bytes(self) -> [u8; 12] {
135        self.0
136    }
137
138    /// Bytes ungeaendert.
139    #[must_use]
140    pub fn from_bytes(bytes: [u8; 12]) -> Self {
141        Self(bytes)
142    }
143}
144
145// ============================================================================
146// EntityId (§8.3.5.2 + Tabelle 9.1)
147// ============================================================================
148
149/// `EntityKind`: Klassifikation eines Endpunkts. Spec-Tabelle 9.1.
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
151#[repr(u8)]
152#[allow(missing_docs)]
153pub enum EntityKind {
154    Unknown = 0x00,
155    UserWriterNoKey = 0x03,
156    UserWriterWithKey = 0x02,
157    UserReaderNoKey = 0x04,
158    UserReaderWithKey = 0x07,
159    BuiltinWriterNoKey = 0xC3,
160    BuiltinWriterWithKey = 0xC2,
161    BuiltinReaderNoKey = 0xC4,
162    BuiltinReaderWithKey = 0xC7,
163    Participant = 0xC1,
164}
165
166impl EntityKind {
167    /// Konvertiert ein Byte in einen `EntityKind`. Unbekannte Bytes
168    /// werden zu `Unknown` gemappt — das spiegelt Spec-Toleranz-Verhalten.
169    #[must_use]
170    pub fn from_byte(b: u8) -> Self {
171        match b {
172            0x03 => Self::UserWriterNoKey,
173            0x02 => Self::UserWriterWithKey,
174            0x04 => Self::UserReaderNoKey,
175            0x07 => Self::UserReaderWithKey,
176            0xC3 => Self::BuiltinWriterNoKey,
177            0xC2 => Self::BuiltinWriterWithKey,
178            0xC4 => Self::BuiltinReaderNoKey,
179            0xC7 => Self::BuiltinReaderWithKey,
180            0xC1 => Self::Participant,
181            _ => Self::Unknown,
182        }
183    }
184}
185
186/// `EntityId`: 4-byte Endpoint-Identifier innerhalb eines Participants.
187/// Layout: 3 Byte `entity_key` + 1 Byte `entity_kind`.
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
189pub struct EntityId {
190    /// Erste 3 Bytes (key).
191    pub entity_key: [u8; 3],
192    /// Letztes Byte (kind).
193    pub entity_kind: EntityKind,
194}
195
196impl EntityId {
197    /// Wire-Size: 4 Bytes.
198    pub const WIRE_SIZE: usize = 4;
199
200    /// Sentinel.
201    pub const UNKNOWN: Self = Self {
202        entity_key: [0; 3],
203        entity_kind: EntityKind::Unknown,
204    };
205
206    /// Reservierter Participant-EntityId (Spec §9.3.1.2).
207    pub const PARTICIPANT: Self = Self {
208        entity_key: [0, 0, 1],
209        entity_kind: EntityKind::Participant,
210    };
211
212    /// Konstruiert einen User-Writer-Endpoint mit Key.
213    #[must_use]
214    pub const fn user_writer_with_key(key: [u8; 3]) -> Self {
215        Self {
216            entity_key: key,
217            entity_kind: EntityKind::UserWriterWithKey,
218        }
219    }
220
221    /// Konstruiert einen User-Reader-Endpoint mit Key.
222    #[must_use]
223    pub const fn user_reader_with_key(key: [u8; 3]) -> Self {
224        Self {
225            entity_key: key,
226            entity_kind: EntityKind::UserReaderWithKey,
227        }
228    }
229
230    /// SPDP Builtin Participant Writer (Spec §9.3.1.5 Tabelle 9.4).
231    /// Multicast-Beacon-Sender im Discovery-Pfad.
232    pub const SPDP_BUILTIN_PARTICIPANT_WRITER: Self = Self {
233        entity_key: [0, 0x01, 0x00],
234        entity_kind: EntityKind::BuiltinWriterWithKey,
235    };
236
237    /// SPDP Builtin Participant Reader.
238    pub const SPDP_BUILTIN_PARTICIPANT_READER: Self = Self {
239        entity_key: [0, 0x01, 0x00],
240        entity_kind: EntityKind::BuiltinReaderWithKey,
241    };
242
243    /// SEDP Subscriptions Writer .
244    pub const SEDP_BUILTIN_SUBSCRIPTIONS_WRITER: Self = Self {
245        entity_key: [0, 0x00, 0x04],
246        entity_kind: EntityKind::BuiltinWriterWithKey,
247    };
248
249    /// SEDP Subscriptions Reader .
250    pub const SEDP_BUILTIN_SUBSCRIPTIONS_READER: Self = Self {
251        entity_key: [0, 0x00, 0x04],
252        entity_kind: EntityKind::BuiltinReaderWithKey,
253    };
254
255    /// SEDP Publications Writer .
256    pub const SEDP_BUILTIN_PUBLICATIONS_WRITER: Self = Self {
257        entity_key: [0, 0x00, 0x03],
258        entity_kind: EntityKind::BuiltinWriterWithKey,
259    };
260
261    /// SEDP Publications Reader .
262    pub const SEDP_BUILTIN_PUBLICATIONS_READER: Self = Self {
263        entity_key: [0, 0x00, 0x03],
264        entity_kind: EntityKind::BuiltinReaderWithKey,
265    };
266
267    /// `BUILTIN_PARTICIPANT_MESSAGE_WRITER` — Writer-Liveliness-
268    /// Protocol (WLP). Sendet `ParticipantMessageData` ueber das
269    /// Topic `DCPSParticipantMessage` (DDSI-RTPS 2.5 §8.4.13,
270    /// §9.3.1.5 Tab. 9.4 — EntityKey `[00, 02, 00]`,
271    /// EntityKind `BuiltinWriterWithKey = 0xC2`).
272    pub const BUILTIN_PARTICIPANT_MESSAGE_WRITER: Self = Self {
273        entity_key: [0, 0x02, 0x00],
274        entity_kind: EntityKind::BuiltinWriterWithKey,
275    };
276
277    /// `BUILTIN_PARTICIPANT_MESSAGE_READER` — Counterpart zum
278    /// WLP-Writer (DDSI-RTPS 2.5 §8.4.13, §9.3.1.5 Tab. 9.4).
279    pub const BUILTIN_PARTICIPANT_MESSAGE_READER: Self = Self {
280        entity_key: [0, 0x02, 0x00],
281        entity_kind: EntityKind::BuiltinReaderWithKey,
282    };
283
284    // TypeLookup Service (XTypes §7.6.3.3.4): RPC, kein Key.
285    // ENTITYKIND_BUILTIN_WRITER_NO_KEY = 0xC3, _READER_NO_KEY = 0xC4.
286    /// TypeLookup Service Request Writer.
287    pub const TL_SVC_REQ_WRITER: Self = Self {
288        entity_key: [0, 0x03, 0x00],
289        entity_kind: EntityKind::BuiltinWriterNoKey,
290    };
291    /// TypeLookup Service Request Reader.
292    pub const TL_SVC_REQ_READER: Self = Self {
293        entity_key: [0, 0x03, 0x00],
294        entity_kind: EntityKind::BuiltinReaderNoKey,
295    };
296    /// TypeLookup Service Reply Writer.
297    pub const TL_SVC_REPLY_WRITER: Self = Self {
298        entity_key: [0, 0x03, 0x01],
299        entity_kind: EntityKind::BuiltinWriterNoKey,
300    };
301    /// TypeLookup Service Reply Reader.
302    pub const TL_SVC_REPLY_READER: Self = Self {
303        entity_key: [0, 0x03, 0x01],
304        entity_kind: EntityKind::BuiltinReaderNoKey,
305    };
306
307    // ----------------------------------------------------------------
308    // DDS-Security 1.2 §7.4.7.1 Tab.7 — 12 Secure-Builtin-EntityIds
309    // (C3.8). EntityKey-Layout per Spec; Kind = WithKey ausser bei den
310    // Stateless-Topics (NoKey, da diese Topics keyless sind).
311    // ----------------------------------------------------------------
312
313    /// `SEDP_BUILTIN_PUBLICATIONS_SECURE_WRITER` — Secure-SEDP
314    /// Publications-Writer (Bit 16, §9.3.1.6 Tab.13).
315    pub const SEDP_BUILTIN_PUBLICATIONS_SECURE_WRITER: Self = Self {
316        entity_key: [0xff, 0x00, 0x03],
317        entity_kind: EntityKind::BuiltinWriterWithKey,
318    };
319    /// `SEDP_BUILTIN_PUBLICATIONS_SECURE_READER` — Bit 17.
320    pub const SEDP_BUILTIN_PUBLICATIONS_SECURE_READER: Self = Self {
321        entity_key: [0xff, 0x00, 0x03],
322        entity_kind: EntityKind::BuiltinReaderWithKey,
323    };
324    /// `SEDP_BUILTIN_SUBSCRIPTIONS_SECURE_WRITER` — Bit 18.
325    pub const SEDP_BUILTIN_SUBSCRIPTIONS_SECURE_WRITER: Self = Self {
326        entity_key: [0xff, 0x00, 0x04],
327        entity_kind: EntityKind::BuiltinWriterWithKey,
328    };
329    /// `SEDP_BUILTIN_SUBSCRIPTIONS_SECURE_READER` — Bit 19.
330    pub const SEDP_BUILTIN_SUBSCRIPTIONS_SECURE_READER: Self = Self {
331        entity_key: [0xff, 0x00, 0x04],
332        entity_kind: EntityKind::BuiltinReaderWithKey,
333    };
334    /// `BUILTIN_PARTICIPANT_MESSAGE_SECURE_WRITER` — Secure WLP-Writer
335    /// (Bit 20, §7.4.7.1).
336    pub const BUILTIN_PARTICIPANT_MESSAGE_SECURE_WRITER: Self = Self {
337        entity_key: [0xff, 0x02, 0x00],
338        entity_kind: EntityKind::BuiltinWriterWithKey,
339    };
340    /// `BUILTIN_PARTICIPANT_MESSAGE_SECURE_READER` — Bit 21.
341    pub const BUILTIN_PARTICIPANT_MESSAGE_SECURE_READER: Self = Self {
342        entity_key: [0xff, 0x02, 0x00],
343        entity_kind: EntityKind::BuiltinReaderWithKey,
344    };
345    /// `BUILTIN_PARTICIPANT_STATELESS_MESSAGE_WRITER` — Auth-Handshake-
346    /// Topic-Writer (Bit 22, §7.4.7.1, §10.3.4 Auth-Stateless-Wire).
347    /// NoKey, da Stateless-Topic keyless ist.
348    pub const BUILTIN_PARTICIPANT_STATELESS_MESSAGE_WRITER: Self = Self {
349        entity_key: [0x00, 0x02, 0x01],
350        entity_kind: EntityKind::BuiltinWriterNoKey,
351    };
352    /// `BUILTIN_PARTICIPANT_STATELESS_MESSAGE_READER` — Bit 23.
353    pub const BUILTIN_PARTICIPANT_STATELESS_MESSAGE_READER: Self = Self {
354        entity_key: [0x00, 0x02, 0x01],
355        entity_kind: EntityKind::BuiltinReaderNoKey,
356    };
357    /// `BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_WRITER` — Crypto-
358    /// KeyExchange-Topic-Writer (Bit 24, §7.4.7.1, §10.5.4
359    /// VolatileMessageSecure-Wire).
360    pub const BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_WRITER: Self = Self {
361        entity_key: [0xff, 0x02, 0x02],
362        entity_kind: EntityKind::BuiltinWriterWithKey,
363    };
364    /// `BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_READER` — Bit 25.
365    pub const BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_READER: Self = Self {
366        entity_key: [0xff, 0x02, 0x02],
367        entity_kind: EntityKind::BuiltinReaderWithKey,
368    };
369    /// `SPDP_RELIABLE_BUILTIN_PARTICIPANTS_SECURE_WRITER` — Secure-
370    /// SPDP-Writer fuer DCPSParticipantsSecure-Topic (Bit 26).
371    pub const SPDP_RELIABLE_BUILTIN_PARTICIPANTS_SECURE_WRITER: Self = Self {
372        entity_key: [0xff, 0x01, 0x01],
373        entity_kind: EntityKind::BuiltinWriterWithKey,
374    };
375    /// `SPDP_RELIABLE_BUILTIN_PARTICIPANTS_SECURE_READER` — Bit 27.
376    pub const SPDP_RELIABLE_BUILTIN_PARTICIPANTS_SECURE_READER: Self = Self {
377        entity_key: [0xff, 0x01, 0x01],
378        entity_kind: EntityKind::BuiltinReaderWithKey,
379    };
380
381    /// True wenn dies einer der 12 Secure-Builtin-EntityIds aus
382    /// DDS-Security 1.2 §7.4.7.1 Tab.7 ist.
383    #[must_use]
384    pub const fn is_secure_builtin(self) -> bool {
385        matches!(
386            self,
387            Self::SEDP_BUILTIN_PUBLICATIONS_SECURE_WRITER
388                | Self::SEDP_BUILTIN_PUBLICATIONS_SECURE_READER
389                | Self::SEDP_BUILTIN_SUBSCRIPTIONS_SECURE_WRITER
390                | Self::SEDP_BUILTIN_SUBSCRIPTIONS_SECURE_READER
391                | Self::BUILTIN_PARTICIPANT_MESSAGE_SECURE_WRITER
392                | Self::BUILTIN_PARTICIPANT_MESSAGE_SECURE_READER
393                | Self::BUILTIN_PARTICIPANT_STATELESS_MESSAGE_WRITER
394                | Self::BUILTIN_PARTICIPANT_STATELESS_MESSAGE_READER
395                | Self::BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_WRITER
396                | Self::BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_READER
397                | Self::SPDP_RELIABLE_BUILTIN_PARTICIPANTS_SECURE_WRITER
398                | Self::SPDP_RELIABLE_BUILTIN_PARTICIPANTS_SECURE_READER
399        )
400    }
401
402    /// Bytes [key0, key1, key2, kind].
403    #[must_use]
404    pub fn to_bytes(self) -> [u8; 4] {
405        [
406            self.entity_key[0],
407            self.entity_key[1],
408            self.entity_key[2],
409            self.entity_kind as u8,
410        ]
411    }
412
413    /// Liest 4 Bytes.
414    #[must_use]
415    pub fn from_bytes(bytes: [u8; 4]) -> Self {
416        Self {
417            entity_key: [bytes[0], bytes[1], bytes[2]],
418            entity_kind: EntityKind::from_byte(bytes[3]),
419        }
420    }
421}
422
423// ============================================================================
424// Guid (§8.3.5.3)
425// ============================================================================
426
427/// `Guid`: GuidPrefix + EntityId = 16 Bytes. Eindeutiger Identifier
428/// eines Endpunkts global.
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
430pub struct Guid {
431    /// Participant-Prefix.
432    pub prefix: GuidPrefix,
433    /// Endpoint-Identifier innerhalb des Participants.
434    pub entity_id: EntityId,
435}
436
437impl Guid {
438    /// Wire-Size: 16 Bytes.
439    pub const WIRE_SIZE: usize = 16;
440
441    /// Sentinel.
442    pub const UNKNOWN: Self = Self {
443        prefix: GuidPrefix::UNKNOWN,
444        entity_id: EntityId::UNKNOWN,
445    };
446
447    /// Konstruiert eine Guid aus Prefix + EntityId.
448    #[must_use]
449    pub const fn new(prefix: GuidPrefix, entity_id: EntityId) -> Self {
450        Self { prefix, entity_id }
451    }
452
453    /// Bytes (Prefix + EntityId).
454    #[must_use]
455    pub fn to_bytes(self) -> [u8; 16] {
456        let mut out = [0u8; 16];
457        out[..12].copy_from_slice(&self.prefix.to_bytes());
458        out[12..].copy_from_slice(&self.entity_id.to_bytes());
459        out
460    }
461
462    /// Liest 16 Bytes.
463    #[must_use]
464    pub fn from_bytes(bytes: [u8; 16]) -> Self {
465        let mut prefix_bytes = [0u8; 12];
466        prefix_bytes.copy_from_slice(&bytes[..12]);
467        let mut entity_bytes = [0u8; 4];
468        entity_bytes.copy_from_slice(&bytes[12..]);
469        Self {
470            prefix: GuidPrefix::from_bytes(prefix_bytes),
471            entity_id: EntityId::from_bytes(entity_bytes),
472        }
473    }
474}
475
476// ============================================================================
477// SequenceNumber (§8.3.5.4)
478// ============================================================================
479
480/// `SequenceNumber`: 64-bit signed, encoded als (high: i32, low: u32).
481/// Beide Felder werden mit der aktiven Submessage-Endianness geschrieben.
482#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
483pub struct SequenceNumber(pub i64);
484
485impl SequenceNumber {
486    /// Wire-Size: 8 Bytes.
487    pub const WIRE_SIZE: usize = 8;
488
489    /// Sentinel "unknown" (high=-1, low=0) → -2^32.
490    pub const UNKNOWN: Self = Self(-(1_i64 << 32));
491
492    /// Splittet den i64 in (high, low) gemaess Spec.
493    #[must_use]
494    pub fn split(self) -> (i32, u32) {
495        let value = self.0;
496        let high = (value >> 32) as i32;
497        let low = (value & 0xFFFF_FFFF) as u32;
498        (high, low)
499    }
500
501    /// Setzt aus (high, low) zusammen.
502    #[must_use]
503    pub fn from_high_low(high: i32, low: u32) -> Self {
504        let value = (i64::from(high) << 32) | i64::from(low);
505        Self(value)
506    }
507
508    /// Schreibt in 8 Bytes mit gegebener Endianness (LE oder BE).
509    #[must_use]
510    pub fn to_bytes_le(self) -> [u8; 8] {
511        let (high, low) = self.split();
512        let mut out = [0u8; 8];
513        out[..4].copy_from_slice(&high.to_le_bytes());
514        out[4..].copy_from_slice(&low.to_le_bytes());
515        out
516    }
517
518    /// BE-Variante.
519    #[must_use]
520    pub fn to_bytes_be(self) -> [u8; 8] {
521        let (high, low) = self.split();
522        let mut out = [0u8; 8];
523        out[..4].copy_from_slice(&high.to_be_bytes());
524        out[4..].copy_from_slice(&low.to_be_bytes());
525        out
526    }
527
528    /// LE-Decoder.
529    #[must_use]
530    pub fn from_bytes_le(bytes: [u8; 8]) -> Self {
531        let mut hi = [0u8; 4];
532        hi.copy_from_slice(&bytes[..4]);
533        let mut lo = [0u8; 4];
534        lo.copy_from_slice(&bytes[4..]);
535        Self::from_high_low(i32::from_le_bytes(hi), u32::from_le_bytes(lo))
536    }
537
538    /// BE-Decoder.
539    #[must_use]
540    pub fn from_bytes_be(bytes: [u8; 8]) -> Self {
541        let mut hi = [0u8; 4];
542        hi.copy_from_slice(&bytes[..4]);
543        let mut lo = [0u8; 4];
544        lo.copy_from_slice(&bytes[4..]);
545        Self::from_high_low(i32::from_be_bytes(hi), u32::from_be_bytes(lo))
546    }
547}
548
549// ============================================================================
550// Vendor-Extension-Slots (§8.3.2 UExtension4_t / WExtension8_t)
551// ============================================================================
552
553/// `UExtension4_t` — 4-byte vendor-spezifischer Extension-Slot.
554/// Spec §8.3.2: opaker 32-bit-Wert, Vendor entscheidet ueber Bedeutung;
555/// Receiver propagiert den Wert per `extensions`-Feld in den
556/// Receiver-State.
557#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
558pub struct UExtension4(pub [u8; 4]);
559
560impl UExtension4 {
561    /// Wire-Size: 4 Bytes.
562    pub const WIRE_SIZE: usize = 4;
563
564    /// Konstruktor aus u32 (Big-Endian).
565    #[must_use]
566    pub fn from_u32_be(v: u32) -> Self {
567        Self(v.to_be_bytes())
568    }
569
570    /// Liefert den Wert als u32 (Big-Endian-Interpretation).
571    #[must_use]
572    pub fn to_u32_be(self) -> u32 {
573        u32::from_be_bytes(self.0)
574    }
575
576    /// Roundtrip-Identitaet.
577    #[must_use]
578    pub fn to_bytes(self) -> [u8; 4] {
579        self.0
580    }
581
582    /// Roundtrip-Identitaet.
583    #[must_use]
584    pub fn from_bytes(bytes: [u8; 4]) -> Self {
585        Self(bytes)
586    }
587}
588
589/// `WExtension8_t` — 8-byte vendor-spezifischer Extension-Slot.
590/// Spec §8.3.2: opaker 64-bit-Wert (analog UExtension4_t fuer
591/// Felder die 8 Byte brauchen, z.B. fuer 64-bit-Counter).
592#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
593pub struct WExtension8(pub [u8; 8]);
594
595impl WExtension8 {
596    /// Wire-Size: 8 Bytes.
597    pub const WIRE_SIZE: usize = 8;
598
599    /// Konstruktor aus u64 (Big-Endian).
600    #[must_use]
601    pub fn from_u64_be(v: u64) -> Self {
602        Self(v.to_be_bytes())
603    }
604
605    /// Liefert den Wert als u64 (Big-Endian-Interpretation).
606    #[must_use]
607    pub fn to_u64_be(self) -> u64 {
608        u64::from_be_bytes(self.0)
609    }
610
611    /// Roundtrip-Identitaet.
612    #[must_use]
613    pub fn to_bytes(self) -> [u8; 8] {
614        self.0
615    }
616
617    /// Roundtrip-Identitaet.
618    #[must_use]
619    pub fn from_bytes(bytes: [u8; 8]) -> Self {
620        Self(bytes)
621    }
622}
623
624// ============================================================================
625// FragmentNumber (§8.3.5.7)
626// ============================================================================
627
628/// `FragmentNumber`: 32-bit unsigned, 1-basiert (Fragment #1 ist das
629/// erste Fragment eines Samples). `UNKNOWN` = 0 wird als Sentinel
630/// verwendet, ist aber kein gueltiges Fragment.
631#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
632pub struct FragmentNumber(pub u32);
633
634impl FragmentNumber {
635    /// Wire-Size: 4 Bytes.
636    pub const WIRE_SIZE: usize = 4;
637
638    /// Sentinel "unknown" (= 0). Nie ein gueltiges Fragment.
639    pub const UNKNOWN: Self = Self(0);
640
641    /// LE-Bytes.
642    #[must_use]
643    pub fn to_bytes_le(self) -> [u8; 4] {
644        self.0.to_le_bytes()
645    }
646
647    /// BE-Bytes.
648    #[must_use]
649    pub fn to_bytes_be(self) -> [u8; 4] {
650        self.0.to_be_bytes()
651    }
652
653    /// LE-Decoder.
654    #[must_use]
655    pub fn from_bytes_le(bytes: [u8; 4]) -> Self {
656        Self(u32::from_le_bytes(bytes))
657    }
658
659    /// BE-Decoder.
660    #[must_use]
661    pub fn from_bytes_be(bytes: [u8; 4]) -> Self {
662        Self(u32::from_be_bytes(bytes))
663    }
664}
665
666// ============================================================================
667// Locator (§8.3.5.7)
668// ============================================================================
669
670/// `LocatorKind`: Adress-Familie eines Locators.
671#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
672#[repr(i32)]
673#[allow(missing_docs)]
674pub enum LocatorKind {
675    Invalid = -1,
676    Reserved = 0,
677    UdpV4 = 1,
678    UdpV6 = 2,
679    /// TCPv4 (DDS-TCP-PSM §4 `LOCATOR_KIND_TCPV4`).
680    Tcpv4 = 4,
681    /// TCPv6 (DDS-TCP-PSM §4 `LOCATOR_KIND_TCPV6`).
682    Tcpv6 = 8,
683    /// Shared-Memory (Vendor-Range, MSB=1 laut DDSI-RTPS §9.3.1.2 —
684    /// negative `i32`-Werte sind vendor-spezifisch). `0x8100_0000` als
685    /// ZeroDDS-Vendor-Token; Cyclone + Fast-DDS ignorieren unbekannte
686    /// Kinds.
687    Shm = -2_130_706_432, // 0x8100_0000 als i32
688    /// Unix-Domain-Socket (Vendor-Range, ZeroDDS-Extension fuer
689    /// Containerized-IPC wenn Multicast gesperrt oder POSIX-SHM
690    /// cross-container nicht funktioniert). `0x8100_0001`. 16-byte
691    /// Address-Feld traegt einen Identifier, der in einen Socket-Pfad
692    /// unter einem konfigurierbaren Base-Directory aufgeloest wird
693    /// (`/tmp/zerodds/uds/<hex>.sock`).
694    Uds = -2_130_706_431, // 0x8100_0001 als i32
695}
696
697impl LocatorKind {
698    /// Liefert den i32-Wire-Wert.
699    #[must_use]
700    pub fn as_i32(self) -> i32 {
701        self as i32
702    }
703
704    /// Konvertiert i32 in LocatorKind.
705    ///
706    /// # Errors
707    /// `WireError::InvalidLocatorKind` bei unbekanntem Wert.
708    pub fn from_i32(v: i32) -> Result<Self, WireError> {
709        match v {
710            -1 => Ok(Self::Invalid),
711            0 => Ok(Self::Reserved),
712            1 => Ok(Self::UdpV4),
713            2 => Ok(Self::UdpV6),
714            4 => Ok(Self::Tcpv4),
715            8 => Ok(Self::Tcpv6),
716            -2_130_706_432 => Ok(Self::Shm),
717            -2_130_706_431 => Ok(Self::Uds),
718            other => Err(WireError::InvalidLocatorKind { kind: other }),
719        }
720    }
721}
722
723/// SPDP-Default-Multicast-Adresse (Spec §9.6.1.4.1): `239.255.0.1`.
724pub const SPDP_DEFAULT_MULTICAST_ADDRESS: [u8; 4] = [239, 255, 0, 1];
725
726/// SPDP-Discovery-Port-Base (Spec §9.6.1.4.1, PB).
727pub const SPDP_PORT_BASE: u32 = 7400;
728
729/// Domain-spezifischer Port-Offset (Spec §9.6.1.4.1, DG).
730pub const SPDP_DOMAIN_GAIN: u32 = 250;
731
732/// Multicast-Discovery-Port-Offset (Spec §9.6.1.4.1, d0).
733pub const SPDP_DISCOVERY_MULTICAST_OFFSET: u32 = 0;
734
735/// Berechnet den SPDP-Multicast-Discovery-Port fuer eine Domain.
736/// Formel (Spec §9.6.1.4.1):
737///   port = PB + DG * domain_id + d0
738///        = 7400 + 250 * domain + 0
739#[must_use]
740pub fn spdp_multicast_port(domain_id: u32) -> u32 {
741    SPDP_PORT_BASE + SPDP_DOMAIN_GAIN * domain_id + SPDP_DISCOVERY_MULTICAST_OFFSET
742}
743
744/// `Locator`: 24-byte Adresse (kind + port + 16-byte address).
745#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
746pub struct Locator {
747    /// Adress-Familie.
748    pub kind: LocatorKind,
749    /// UDP-Port.
750    pub port: u32,
751    /// 16-byte address. UDPv4 nutzt die letzten 4 Byte, davor 0.
752    pub address: [u8; 16],
753}
754
755impl Locator {
756    /// Wire-Size: 24 Bytes.
757    pub const WIRE_SIZE: usize = 24;
758
759    /// Spec §8.3.5.7 `LOCATOR_INVALID`.
760    pub const INVALID: Self = Self {
761        kind: LocatorKind::Invalid,
762        port: 0,
763        address: [0; 16],
764    };
765
766    /// Spec §8.3.5.7 `LOCATOR_KIND_RESERVED` Template (kind=0,
767    /// port=0, address=0).
768    pub const RESERVED: Self = Self {
769        kind: LocatorKind::Reserved,
770        port: 0,
771        address: [0; 16],
772    };
773
774    /// Spec §8.3.5.7 — Default-UDPv4-Locator (kind=1, port=0, addr=0).
775    pub const UDP_V4_ANY: Self = Self {
776        kind: LocatorKind::UdpV4,
777        port: 0,
778        address: [0; 16],
779    };
780
781    /// Spec §8.3.5.7 — Default-UDPv6-Locator (kind=2, port=0, addr=0).
782    pub const UDP_V6_ANY: Self = Self {
783        kind: LocatorKind::UdpV6,
784        port: 0,
785        address: [0; 16],
786    };
787
788    /// ZeroDDS-Vendor-Extension SHM-Locator-Template.
789    pub const SHM_ANY: Self = Self {
790        kind: LocatorKind::Shm,
791        port: 0,
792        address: [0; 16],
793    };
794
795    /// Spec §8.3.5.7 — `LOCATOR_PORT_INVALID` Sentinel.
796    pub const PORT_INVALID: u32 = 0;
797
798    /// Spec §8.3.5.7 — `LOCATOR_ADDRESS_INVALID` Sentinel.
799    pub const ADDRESS_INVALID: [u8; 16] = [0; 16];
800
801    /// Konstruktor fuer UDPv6 (16-byte address + port).
802    #[must_use]
803    pub fn udp_v6(addr: [u8; 16], port: u32) -> Self {
804        Self {
805            kind: LocatorKind::UdpV6,
806            port,
807            address: addr,
808        }
809    }
810
811    /// Konstruktor fuer UDPv4 (a.b.c.d:port).
812    #[must_use]
813    pub fn udp_v4(addr: [u8; 4], port: u32) -> Self {
814        Self::with_address(LocatorKind::UdpV4, addr, port)
815    }
816
817    /// Konstruktor fuer TCPv4 (DDS-TCP-PSM §4).
818    #[must_use]
819    pub fn tcp_v4(addr: [u8; 4], port: u32) -> Self {
820        Self::with_address(LocatorKind::Tcpv4, addr, port)
821    }
822
823    /// Legacy-Alias fuer [`Self::tcp_v4`]. **Deprecated**: Benenne
824    /// Callsites auf `tcp_v4` um (konsistent mit `udp_v4`).
825    #[must_use]
826    #[deprecated(note = "use Locator::tcp_v4 instead (naming consistency)")]
827    pub fn new_tcp_v4(addr: [u8; 4], port: u32) -> Self {
828        Self::tcp_v4(addr, port)
829    }
830
831    /// Konstruktor fuer Unix-Domain-Socket-Endpoint mit einer 16-byte-
832    /// ID. Port bleibt 0 (nicht sinnvoll fuer UDS). Der Transport
833    /// resolved die ID zu `/<base_dir>/<hex>.sock`.
834    #[must_use]
835    pub fn uds(id: [u8; 16]) -> Self {
836        Self {
837            kind: LocatorKind::Uds,
838            port: 0,
839            address: id,
840        }
841    }
842
843    /// Konstruktor fuer Shared-Memory-Segment mit einer ID (16-byte
844    /// Token). Port-Feld bleibt 0 (nicht sinnvoll fuer SHM).
845    #[must_use]
846    pub fn shm(id: [u8; 16]) -> Self {
847        Self {
848            kind: LocatorKind::Shm,
849            port: 0,
850            address: id,
851        }
852    }
853
854    /// Gemeinsamer IPv4-Konstruktor. Legt die IPv4-Bytes in die letzten
855    /// 4 Byte des 16-byte-`address`-Felds (mapped IPv4-in-IPv6-Layout).
856    #[must_use]
857    fn with_address(kind: LocatorKind, addr: [u8; 4], port: u32) -> Self {
858        let mut address = [0u8; 16];
859        address[12..].copy_from_slice(&addr);
860        Self {
861            kind,
862            port,
863            address,
864        }
865    }
866
867    /// IPv4-Adresse extrahieren (nur fuer Kind == UdpV4 sinnvoll).
868    #[must_use]
869    pub fn ipv4(self) -> [u8; 4] {
870        let mut out = [0u8; 4];
871        out.copy_from_slice(&self.address[12..]);
872        out
873    }
874
875    /// LE-Encoder.
876    #[must_use]
877    pub fn to_bytes_le(self) -> [u8; 24] {
878        let mut out = [0u8; 24];
879        out[..4].copy_from_slice(&self.kind.as_i32().to_le_bytes());
880        out[4..8].copy_from_slice(&self.port.to_le_bytes());
881        out[8..].copy_from_slice(&self.address);
882        out
883    }
884
885    /// LE-Decoder.
886    ///
887    /// # Errors
888    /// `WireError::InvalidLocatorKind` bei unbekanntem Kind.
889    pub fn from_bytes_le(bytes: [u8; 24]) -> Result<Self, WireError> {
890        let mut kind_bytes = [0u8; 4];
891        kind_bytes.copy_from_slice(&bytes[..4]);
892        let kind = LocatorKind::from_i32(i32::from_le_bytes(kind_bytes))?;
893        let mut port_bytes = [0u8; 4];
894        port_bytes.copy_from_slice(&bytes[4..8]);
895        let port = u32::from_le_bytes(port_bytes);
896        let mut address = [0u8; 16];
897        address.copy_from_slice(&bytes[8..]);
898        Ok(Self {
899            kind,
900            port,
901            address,
902        })
903    }
904}
905
906#[cfg(test)]
907mod tests {
908    #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
909    use super::*;
910
911    // ---- ProtocolVersion ----
912    #[test]
913    fn protocol_version_default_is_2_5() {
914        assert_eq!(ProtocolVersion::default(), ProtocolVersion::V2_5);
915    }
916
917    #[test]
918    fn protocol_version_roundtrip() {
919        let v = ProtocolVersion::V2_5;
920        assert_eq!(ProtocolVersion::from_bytes(v.to_bytes()), v);
921    }
922
923    // ---- VendorId ----
924    #[test]
925    fn vendor_id_zerodds_constant() {
926        assert_eq!(VendorId::ZERODDS.0, [0x01, 0xF0]);
927    }
928
929    #[test]
930    fn vendor_id_roundtrip() {
931        let v = VendorId([0xAB, 0xCD]);
932        assert_eq!(VendorId::from_bytes(v.to_bytes()), v);
933    }
934
935    // ---- GuidPrefix ----
936    #[test]
937    fn guid_prefix_unknown_is_zero() {
938        assert_eq!(GuidPrefix::UNKNOWN.0, [0u8; 12]);
939    }
940
941    #[test]
942    fn guid_prefix_roundtrip() {
943        let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
944        let p = GuidPrefix::from_bytes(bytes);
945        assert_eq!(p.to_bytes(), bytes);
946    }
947
948    // ---- EntityId ----
949    #[test]
950    fn entity_id_user_writer_with_key_layout() {
951        let id = EntityId::user_writer_with_key([0xAA, 0xBB, 0xCC]);
952        assert_eq!(id.to_bytes(), [0xAA, 0xBB, 0xCC, 0x02]);
953    }
954
955    #[test]
956    fn entity_id_user_reader_with_key_layout() {
957        let id = EntityId::user_reader_with_key([0x11, 0x22, 0x33]);
958        assert_eq!(id.to_bytes(), [0x11, 0x22, 0x33, 0x07]);
959    }
960
961    #[test]
962    fn entity_id_participant_constant() {
963        assert_eq!(EntityId::PARTICIPANT.to_bytes(), [0, 0, 1, 0xC1]);
964    }
965
966    #[test]
967    fn entity_id_unknown_kind_byte_maps_to_unknown() {
968        let id = EntityId::from_bytes([1, 2, 3, 0xEE]);
969        assert_eq!(id.entity_kind, EntityKind::Unknown);
970    }
971
972    #[test]
973    fn entity_id_roundtrip() {
974        let id = EntityId::user_writer_with_key([1, 2, 3]);
975        assert_eq!(EntityId::from_bytes(id.to_bytes()), id);
976    }
977
978    // ---- Secure-Builtin-EntityIds (DDS-Security 1.2 §7.4.7.1, C3.8) ----
979
980    #[test]
981    fn secure_publications_writer_layout() {
982        // Spec: key=[0xff, 0x00, 0x03], kind=BuiltinWriterWithKey=0xC2.
983        let id = EntityId::SEDP_BUILTIN_PUBLICATIONS_SECURE_WRITER;
984        assert_eq!(id.to_bytes(), [0xff, 0x00, 0x03, 0xC2]);
985    }
986
987    #[test]
988    fn stateless_writer_is_no_key_kind() {
989        // Stateless-Topic ist keyless → BuiltinWriterNoKey=0xC3.
990        let id = EntityId::BUILTIN_PARTICIPANT_STATELESS_MESSAGE_WRITER;
991        assert_eq!(id.entity_kind, EntityKind::BuiltinWriterNoKey);
992        assert_eq!(id.to_bytes(), [0x00, 0x02, 0x01, 0xC3]);
993    }
994
995    #[test]
996    fn volatile_secure_writer_layout() {
997        let id = EntityId::BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_WRITER;
998        assert_eq!(id.to_bytes(), [0xff, 0x02, 0x02, 0xC2]);
999    }
1000
1001    #[test]
1002    fn spdp_secure_reader_layout() {
1003        let id = EntityId::SPDP_RELIABLE_BUILTIN_PARTICIPANTS_SECURE_READER;
1004        assert_eq!(id.to_bytes(), [0xff, 0x01, 0x01, 0xC7]);
1005    }
1006
1007    #[test]
1008    fn all_12_secure_entityids_roundtrip() {
1009        let ids = [
1010            EntityId::SEDP_BUILTIN_PUBLICATIONS_SECURE_WRITER,
1011            EntityId::SEDP_BUILTIN_PUBLICATIONS_SECURE_READER,
1012            EntityId::SEDP_BUILTIN_SUBSCRIPTIONS_SECURE_WRITER,
1013            EntityId::SEDP_BUILTIN_SUBSCRIPTIONS_SECURE_READER,
1014            EntityId::BUILTIN_PARTICIPANT_MESSAGE_SECURE_WRITER,
1015            EntityId::BUILTIN_PARTICIPANT_MESSAGE_SECURE_READER,
1016            EntityId::BUILTIN_PARTICIPANT_STATELESS_MESSAGE_WRITER,
1017            EntityId::BUILTIN_PARTICIPANT_STATELESS_MESSAGE_READER,
1018            EntityId::BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_WRITER,
1019            EntityId::BUILTIN_PARTICIPANT_VOLATILE_MESSAGE_SECURE_READER,
1020            EntityId::SPDP_RELIABLE_BUILTIN_PARTICIPANTS_SECURE_WRITER,
1021            EntityId::SPDP_RELIABLE_BUILTIN_PARTICIPANTS_SECURE_READER,
1022        ];
1023        assert_eq!(ids.len(), 12);
1024        for id in ids {
1025            assert!(id.is_secure_builtin(), "{id:?}");
1026            assert_eq!(EntityId::from_bytes(id.to_bytes()), id);
1027        }
1028    }
1029
1030    #[test]
1031    fn standard_builtin_is_not_secure_builtin() {
1032        for id in [
1033            EntityId::SPDP_BUILTIN_PARTICIPANT_WRITER,
1034            EntityId::SEDP_BUILTIN_PUBLICATIONS_WRITER,
1035            EntityId::BUILTIN_PARTICIPANT_MESSAGE_WRITER,
1036            EntityId::PARTICIPANT,
1037            EntityId::user_writer_with_key([1, 2, 3]),
1038        ] {
1039            assert!(!id.is_secure_builtin(), "{id:?} should not be secure");
1040        }
1041    }
1042
1043    // ---- Guid ----
1044    #[test]
1045    fn guid_layout_is_prefix_then_entity_id() {
1046        let g = Guid::new(
1047            GuidPrefix::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]),
1048            EntityId::user_writer_with_key([0xAA, 0xBB, 0xCC]),
1049        );
1050        let bytes = g.to_bytes();
1051        assert_eq!(&bytes[..12], &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
1052        assert_eq!(&bytes[12..], &[0xAA, 0xBB, 0xCC, 0x02]);
1053    }
1054
1055    #[test]
1056    fn guid_roundtrip() {
1057        let g = Guid::new(GuidPrefix::from_bytes([42; 12]), EntityId::PARTICIPANT);
1058        assert_eq!(Guid::from_bytes(g.to_bytes()), g);
1059    }
1060
1061    // ---- SequenceNumber ----
1062    #[test]
1063    fn sequence_number_split_zero() {
1064        let (h, l) = SequenceNumber(0).split();
1065        assert_eq!((h, l), (0, 0));
1066    }
1067
1068    #[test]
1069    fn sequence_number_split_one() {
1070        let (h, l) = SequenceNumber(1).split();
1071        assert_eq!((h, l), (0, 1));
1072    }
1073
1074    #[test]
1075    fn sequence_number_split_high_low() {
1076        let sn = SequenceNumber::from_high_low(2, 5);
1077        assert_eq!(sn.0, (2_i64 << 32) | 5);
1078    }
1079
1080    #[test]
1081    fn sequence_number_roundtrip_le() {
1082        let sn = SequenceNumber(0x0102_0304_0506_0708);
1083        assert_eq!(SequenceNumber::from_bytes_le(sn.to_bytes_le()), sn);
1084    }
1085
1086    #[test]
1087    fn sequence_number_roundtrip_be() {
1088        let sn = SequenceNumber(0x0102_0304_0506_0708);
1089        assert_eq!(SequenceNumber::from_bytes_be(sn.to_bytes_be()), sn);
1090    }
1091
1092    #[test]
1093    fn sequence_number_unknown_high_minus_one_low_zero() {
1094        let (h, l) = SequenceNumber::UNKNOWN.split();
1095        assert_eq!((h, l), (-1, 0));
1096    }
1097
1098    // ---- FragmentNumber ----
1099    #[test]
1100    fn fragment_number_roundtrip_le_be() {
1101        let fns = [
1102            FragmentNumber(0),
1103            FragmentNumber(1),
1104            FragmentNumber(0x1234_5678),
1105            FragmentNumber(u32::MAX),
1106        ];
1107        for fnum in fns {
1108            assert_eq!(FragmentNumber::from_bytes_le(fnum.to_bytes_le()), fnum);
1109            assert_eq!(FragmentNumber::from_bytes_be(fnum.to_bytes_be()), fnum);
1110        }
1111    }
1112
1113    #[test]
1114    fn fragment_number_unknown_is_zero() {
1115        assert_eq!(FragmentNumber::UNKNOWN.0, 0);
1116    }
1117
1118    #[test]
1119    fn fragment_number_wire_size_is_four() {
1120        assert_eq!(FragmentNumber::WIRE_SIZE, 4);
1121    }
1122
1123    // ---- Locator ----
1124    #[test]
1125    fn locator_kind_roundtrip() {
1126        for kind in [
1127            LocatorKind::Invalid,
1128            LocatorKind::Reserved,
1129            LocatorKind::UdpV4,
1130            LocatorKind::UdpV6,
1131            LocatorKind::Tcpv4,
1132            LocatorKind::Tcpv6,
1133            LocatorKind::Shm,
1134            LocatorKind::Uds,
1135        ] {
1136            assert_eq!(LocatorKind::from_i32(kind.as_i32()).unwrap(), kind);
1137        }
1138    }
1139
1140    #[test]
1141    fn locator_uds_layout() {
1142        let id = [
1143            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
1144            0x0F, 0x10,
1145        ];
1146        let l = Locator::uds(id);
1147        assert_eq!(l.kind, LocatorKind::Uds);
1148        assert_eq!(l.port, 0);
1149        assert_eq!(l.address, id);
1150    }
1151
1152    #[test]
1153    fn locator_kind_rejects_unknown() {
1154        let res = LocatorKind::from_i32(99);
1155        assert!(matches!(
1156            res,
1157            Err(WireError::InvalidLocatorKind { kind: 99 })
1158        ));
1159    }
1160
1161    #[test]
1162    fn locator_udp_v4_layout() {
1163        let l = Locator::udp_v4([192, 168, 1, 100], 7400);
1164        assert_eq!(l.kind, LocatorKind::UdpV4);
1165        assert_eq!(l.port, 7400);
1166        assert_eq!(&l.address[..12], &[0u8; 12]);
1167        assert_eq!(&l.address[12..], &[192, 168, 1, 100]);
1168        assert_eq!(l.ipv4(), [192, 168, 1, 100]);
1169    }
1170
1171    #[test]
1172    fn locator_roundtrip_le() {
1173        let l = Locator::udp_v4([10, 0, 0, 1], 7400);
1174        let bytes = l.to_bytes_le();
1175        assert_eq!(Locator::from_bytes_le(bytes).unwrap(), l);
1176    }
1177
1178    #[test]
1179    fn locator_invalid_kind_decoded() {
1180        let mut bytes = [0u8; 24];
1181        bytes[..4].copy_from_slice(&99_i32.to_le_bytes());
1182        let res = Locator::from_bytes_le(bytes);
1183        assert!(matches!(
1184            res,
1185            Err(WireError::InvalidLocatorKind { kind: 99 })
1186        ));
1187    }
1188
1189    // ---- §8.3.5.7 Locator-Constants ----
1190
1191    #[test]
1192    fn locator_invalid_constant_matches_spec() {
1193        assert_eq!(Locator::INVALID.kind, LocatorKind::Invalid);
1194        assert_eq!(Locator::INVALID.port, Locator::PORT_INVALID);
1195        assert_eq!(Locator::INVALID.address, Locator::ADDRESS_INVALID);
1196    }
1197
1198    #[test]
1199    fn locator_reserved_constant_kind_is_zero() {
1200        assert_eq!(Locator::RESERVED.kind.as_i32(), 0);
1201    }
1202
1203    #[test]
1204    fn locator_udp_v4_any_kind_is_one() {
1205        assert_eq!(Locator::UDP_V4_ANY.kind.as_i32(), 1);
1206        assert_eq!(Locator::UDP_V4_ANY.port, 0);
1207    }
1208
1209    #[test]
1210    fn locator_udp_v6_any_kind_is_two() {
1211        assert_eq!(Locator::UDP_V6_ANY.kind.as_i32(), 2);
1212    }
1213
1214    #[test]
1215    fn locator_shm_any_uses_vendor_kind() {
1216        assert!(Locator::SHM_ANY.kind.as_i32() < 0); // vendor-Range
1217    }
1218
1219    #[test]
1220    fn locator_udp_v6_constructor_keeps_full_address() {
1221        let addr: [u8; 16] = [
1222            0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01,
1223        ];
1224        let l = Locator::udp_v6(addr, 1234);
1225        assert_eq!(l.kind, LocatorKind::UdpV6);
1226        assert_eq!(l.address, addr);
1227        assert_eq!(l.port, 1234);
1228    }
1229
1230    #[test]
1231    fn locator_udp_v6_roundtrip_le() {
1232        let l = Locator::udp_v6([1; 16], 9000);
1233        assert_eq!(Locator::from_bytes_le(l.to_bytes_le()).unwrap(), l);
1234    }
1235
1236    // ---- §8.3.5.5 ProtocolVersion-Aliases ----
1237
1238    #[test]
1239    fn protocol_version_aliases_match_spec() {
1240        assert_eq!(
1241            ProtocolVersion::V1_0,
1242            ProtocolVersion { major: 1, minor: 0 }
1243        );
1244        assert_eq!(
1245            ProtocolVersion::V1_1,
1246            ProtocolVersion { major: 1, minor: 1 }
1247        );
1248        assert_eq!(
1249            ProtocolVersion::V2_0,
1250            ProtocolVersion { major: 2, minor: 0 }
1251        );
1252        assert_eq!(
1253            ProtocolVersion::V2_1,
1254            ProtocolVersion { major: 2, minor: 1 }
1255        );
1256        assert_eq!(
1257            ProtocolVersion::V2_2,
1258            ProtocolVersion { major: 2, minor: 2 }
1259        );
1260        assert_eq!(
1261            ProtocolVersion::V2_3,
1262            ProtocolVersion { major: 2, minor: 3 }
1263        );
1264        assert_eq!(
1265            ProtocolVersion::V2_4,
1266            ProtocolVersion { major: 2, minor: 4 }
1267        );
1268        assert_eq!(
1269            ProtocolVersion::V2_5,
1270            ProtocolVersion { major: 2, minor: 5 }
1271        );
1272        assert_eq!(ProtocolVersion::CURRENT, ProtocolVersion::V2_5);
1273    }
1274
1275    #[test]
1276    fn protocol_version_all_aliases_roundtrip() {
1277        for v in [
1278            ProtocolVersion::V1_0,
1279            ProtocolVersion::V1_1,
1280            ProtocolVersion::V2_0,
1281            ProtocolVersion::V2_1,
1282            ProtocolVersion::V2_2,
1283            ProtocolVersion::V2_3,
1284            ProtocolVersion::V2_4,
1285            ProtocolVersion::V2_5,
1286        ] {
1287            assert_eq!(ProtocolVersion::from_bytes(v.to_bytes()), v);
1288        }
1289    }
1290
1291    // ---- §8.3.2 UExtension4_t ----
1292
1293    #[test]
1294    fn uextension4_roundtrip() {
1295        let u = UExtension4([0xDE, 0xAD, 0xBE, 0xEF]);
1296        assert_eq!(UExtension4::from_bytes(u.to_bytes()), u);
1297    }
1298
1299    #[test]
1300    fn uextension4_u32_be_roundtrip() {
1301        let u = UExtension4::from_u32_be(0x1122_3344);
1302        assert_eq!(u.to_u32_be(), 0x1122_3344);
1303        assert_eq!(u.0, [0x11, 0x22, 0x33, 0x44]);
1304    }
1305
1306    #[test]
1307    fn uextension4_default_is_zero() {
1308        assert_eq!(UExtension4::default().0, [0u8; 4]);
1309        assert_eq!(UExtension4::WIRE_SIZE, 4);
1310    }
1311
1312    // ---- §8.3.2 WExtension8_t ----
1313
1314    #[test]
1315    fn wextension8_roundtrip() {
1316        let w = WExtension8([1, 2, 3, 4, 5, 6, 7, 8]);
1317        assert_eq!(WExtension8::from_bytes(w.to_bytes()), w);
1318    }
1319
1320    #[test]
1321    fn wextension8_u64_be_roundtrip() {
1322        let w = WExtension8::from_u64_be(0x1122_3344_5566_7788);
1323        assert_eq!(w.to_u64_be(), 0x1122_3344_5566_7788);
1324        assert_eq!(w.0, [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]);
1325    }
1326
1327    #[test]
1328    fn wextension8_default_is_zero() {
1329        assert_eq!(WExtension8::default().0, [0u8; 8]);
1330        assert_eq!(WExtension8::WIRE_SIZE, 8);
1331    }
1332
1333    // ---- SPDP Builtin EntityIds (Spec §9.3.1.5 Tabelle 9.4) ----
1334
1335    #[test]
1336    fn spdp_builtin_participant_writer_layout() {
1337        // Spec: 0x000100C2
1338        let bytes = EntityId::SPDP_BUILTIN_PARTICIPANT_WRITER.to_bytes();
1339        assert_eq!(bytes, [0x00, 0x01, 0x00, 0xC2]);
1340    }
1341
1342    #[test]
1343    fn spdp_builtin_participant_reader_layout() {
1344        // Spec: 0x000100C7
1345        let bytes = EntityId::SPDP_BUILTIN_PARTICIPANT_READER.to_bytes();
1346        assert_eq!(bytes, [0x00, 0x01, 0x00, 0xC7]);
1347    }
1348
1349    #[test]
1350    fn sedp_publications_writer_layout() {
1351        // Spec: 0x000003C2
1352        let bytes = EntityId::SEDP_BUILTIN_PUBLICATIONS_WRITER.to_bytes();
1353        assert_eq!(bytes, [0x00, 0x00, 0x03, 0xC2]);
1354    }
1355
1356    #[test]
1357    fn sedp_subscriptions_reader_layout() {
1358        // Spec: 0x000004C7
1359        let bytes = EntityId::SEDP_BUILTIN_SUBSCRIPTIONS_READER.to_bytes();
1360        assert_eq!(bytes, [0x00, 0x00, 0x04, 0xC7]);
1361    }
1362
1363    // ---- SPDP-Multicast-Adresse + Port-Berechnung ----
1364
1365    #[test]
1366    fn spdp_default_multicast_is_239_255_0_1() {
1367        assert_eq!(SPDP_DEFAULT_MULTICAST_ADDRESS, [239, 255, 0, 1]);
1368    }
1369
1370    #[test]
1371    fn spdp_multicast_port_domain_0_is_7400() {
1372        assert_eq!(spdp_multicast_port(0), 7400);
1373    }
1374
1375    #[test]
1376    fn spdp_multicast_port_domain_1_is_7650() {
1377        assert_eq!(spdp_multicast_port(1), 7650);
1378    }
1379
1380    #[test]
1381    fn spdp_multicast_port_domain_5_is_8650() {
1382        assert_eq!(spdp_multicast_port(5), 8650);
1383    }
1384}