Skip to main content

zerodds_rtps/
participant_message_data.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! `ParticipantMessageData` Wire-Encoding (DDSI-RTPS 2.5 §9.6.3.1).
4//!
5//! Payload-Struktur des Writer-Liveliness-Protocols (WLP, §8.4.13).
6//! Wird vom `BUILTIN_PARTICIPANT_MESSAGE_WRITER` als DATA-Submessage
7//! im Topic `DCPSParticipantMessage` periodisch publiziert. Reader
8//! nutzen den Empfang als implicit `assert_liveliness()` und treiben
9//! damit das Lease-Tracking pro Peer-Participant.
10//!
11//! # Wire-Layout (§9.6.3.1)
12//!
13//! ```text
14//! struct ParticipantMessageData {
15//!     GUID_t   participantGuidPrefix; // 12 byte (GuidPrefix only!)
16//!     octet    kind[4];               // 4 byte u32 (BE/LE per CDR)
17//!     sequence<octet> data;           // 4 byte length + N byte
18//! };
19//! ```
20//!
21//! Spec-Pitfall: trotz Feldname `participantGuidPrefix` wird in der
22//! Praxis von Cyclone DDS und Fast-DDS ein voller 16-Byte-Guid
23//! geschickt (Prefix + ENTITYID_PARTICIPANT). Wir schreiben deshalb
24//! 16 Byte und parsen tolerant: 16 Byte → Voll-Guid, 12 Byte →
25//! Prefix-Only.
26//!
27//! Die `data`-Sequenz ist semantisch eine `vec<octet>` mit voran-
28//! gestellter 32-Bit-Laenge. Spec §9.6.3.1 definiert sie als opaque
29//! Token; ZeroDDS nutzt sie fuer MANUAL_BY_TOPIC, um den Topic-Token
30//! zu transportieren (Topic-Hash-Fingerprint).
31//!
32//! # CDR-Encoding
33//!
34//! Encoded als XCDR1 Plain (Encapsulation 0x0000 BE / 0x0001 LE)
35//! bzw. XCDR2 Plain (0x0006 BE / 0x0007 LE). Cyclone schickt das
36//! Topic per Default als XCDR1 Plain. Wir akzeptieren alle vier
37//! Encapsulation-Kinds und schreiben per Default LE.
38//!
39//! # DoS-Caps
40//!
41//! `data.len()` ist auf [`MAX_DATA_LEN`] = 4096 Byte gedeckelt.
42//! Encoder kuerzt nicht — Caller muss vor Aufruf cappen — Decoder
43//! lehnt ueberlange Daten mit [`WireError::ValueOutOfRange`] ab.
44
45extern crate alloc;
46use alloc::vec::Vec;
47
48use crate::error::WireError;
49use crate::wire_types::GuidPrefix;
50
51/// CDR-Encapsulation-Header: XCDR1 Plain Big-Endian (`0x0000`).
52pub const ENCAPSULATION_CDR_BE: [u8; 2] = [0x00, 0x00];
53/// CDR-Encapsulation-Header: XCDR1 Plain Little-Endian (`0x0001`).
54pub const ENCAPSULATION_CDR_LE: [u8; 2] = [0x00, 0x01];
55/// CDR-Encapsulation-Header: XCDR2 Plain Big-Endian (`0x0006`).
56pub const ENCAPSULATION_CDR2_BE: [u8; 2] = [0x00, 0x06];
57/// CDR-Encapsulation-Header: XCDR2 Plain Little-Endian (`0x0007`).
58pub const ENCAPSULATION_CDR2_LE: [u8; 2] = [0x00, 0x07];
59
60/// DoS-Cap fuer `data`-Sequenz (Topic-Token / vendor opaque).
61/// Bewusst klein gewaehlt — WLP-Heartbeats sollen leicht sein, ein
62/// Peer der mehr schickt ist entweder buggy oder bösartig.
63pub const MAX_DATA_LEN: usize = 4096;
64
65/// Spec-defined `kind` Code: AUTOMATIC_LIVELINESS_UPDATE (§9.6.3.1).
66/// Wird vom Builtin-WLP-Writer geschickt, wenn LIVELINESS=AUTOMATIC.
67pub const PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE: u32 = 0x0000_0000;
68
69/// Spec-defined `kind` Code: MANUAL_BY_PARTICIPANT_LIVELINESS_UPDATE
70/// (§9.6.3.1). Getrigger durch `assert_liveliness()` auf
71/// `DomainParticipant`.
72pub const PARTICIPANT_MESSAGE_DATA_KIND_MANUAL_BY_PARTICIPANT_LIVELINESS_UPDATE: u32 = 0x0000_0001;
73
74/// Vendor-Specific-Kind-Range: oberstes Bit gesetzt
75/// (`0x80000000..=0xFFFFFFFF`). Spec §9.6.3.1 reserviert dies fuer
76/// Vendor-eigene Kinds (z.B. ZeroDDS-MANUAL_BY_TOPIC mit Topic-Token
77/// in der `data`-Sequenz).
78pub const PARTICIPANT_MESSAGE_DATA_KIND_VENDOR_BASE: u32 = 0x8000_0000;
79
80/// ZeroDDS-Vendor-Kind: MANUAL_BY_TOPIC. Wird durch
81/// `DataWriter::assert_liveliness()` getriggert; die `data`-Sequenz
82/// traegt einen Topic-Token (typisch 4-Byte-Hash). Cyclone-Peers
83/// ignorieren das Vendor-Kind (Spec §9.6.3.1: "If kind has its
84/// MSB set, implementations not understanding the kind shall ignore
85/// the message").
86pub const PARTICIPANT_MESSAGE_DATA_KIND_ZERODDS_MANUAL_BY_TOPIC: u32 = 0x8000_0001;
87
88/// `ParticipantMessageData` (DDSI-RTPS 2.5 §9.6.3.1) — Payload des
89/// `DCPSParticipantMessage`-Topics.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct ParticipantMessageData {
92    /// 16-Byte GUID des Senders (Prefix + EntityId::PARTICIPANT).
93    /// Auch wenn die Spec "Prefix" sagt, schreiben Cyclone und Fast-
94    /// DDS hier den vollen Guid; wir folgen.
95    pub participant_guid: [u8; 16],
96    /// Liveliness-Kind (siehe `PARTICIPANT_MESSAGE_DATA_KIND_*`).
97    pub kind: u32,
98    /// Opaque Token. Bei MANUAL_BY_TOPIC Topic-Hash, sonst leer.
99    pub data: Vec<u8>,
100}
101
102impl ParticipantMessageData {
103    /// Konstruktor fuer AUTOMATIC-Heartbeat (data leer).
104    #[must_use]
105    pub fn automatic(prefix: GuidPrefix) -> Self {
106        Self {
107            participant_guid: full_guid_bytes(prefix),
108            kind: PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE,
109            data: Vec::new(),
110        }
111    }
112
113    /// Konstruktor fuer MANUAL_BY_PARTICIPANT-Heartbeat (data leer).
114    #[must_use]
115    pub fn manual_by_participant(prefix: GuidPrefix) -> Self {
116        Self {
117            participant_guid: full_guid_bytes(prefix),
118            kind: PARTICIPANT_MESSAGE_DATA_KIND_MANUAL_BY_PARTICIPANT_LIVELINESS_UPDATE,
119            data: Vec::new(),
120        }
121    }
122
123    /// Konstruktor fuer ZeroDDS-MANUAL_BY_TOPIC (data = Topic-Token).
124    #[must_use]
125    pub fn manual_by_topic(prefix: GuidPrefix, topic_token: Vec<u8>) -> Self {
126        Self {
127            participant_guid: full_guid_bytes(prefix),
128            kind: PARTICIPANT_MESSAGE_DATA_KIND_ZERODDS_MANUAL_BY_TOPIC,
129            data: topic_token,
130        }
131    }
132
133    /// Encoded zu CDR-Bytes (mit 4-byte Encapsulation-Header).
134    /// `little_endian = true` → `ENCAPSULATION_CDR_LE`, sonst BE.
135    ///
136    /// # Errors
137    /// `WireError::ValueOutOfRange` wenn `data.len() > MAX_DATA_LEN`.
138    pub fn to_cdr(&self, little_endian: bool) -> Result<Vec<u8>, WireError> {
139        if self.data.len() > MAX_DATA_LEN {
140            return Err(WireError::ValueOutOfRange {
141                message: "ParticipantMessageData.data exceeds MAX_DATA_LEN",
142            });
143        }
144        let data_len_u32 =
145            u32::try_from(self.data.len()).map_err(|_| WireError::ValueOutOfRange {
146                message: "ParticipantMessageData.data length exceeds u32",
147            })?;
148        let mut out = Vec::with_capacity(4 + 16 + 4 + 4 + self.data.len());
149        // Encapsulation-Header
150        if little_endian {
151            out.extend_from_slice(&ENCAPSULATION_CDR_LE);
152        } else {
153            out.extend_from_slice(&ENCAPSULATION_CDR_BE);
154        }
155        out.extend_from_slice(&[0, 0]); // options
156        // Body Start (CDR-Offset 0)
157        // GUID — 16 byte raw, kein Endian-Swap (Bytes sind opaque).
158        out.extend_from_slice(&self.participant_guid);
159        // kind — 4 byte u32 BE/LE
160        let kind_bytes = if little_endian {
161            self.kind.to_le_bytes()
162        } else {
163            self.kind.to_be_bytes()
164        };
165        out.extend_from_slice(&kind_bytes);
166        // data: u32 length + N byte
167        let len_bytes = if little_endian {
168            data_len_u32.to_le_bytes()
169        } else {
170            data_len_u32.to_be_bytes()
171        };
172        out.extend_from_slice(&len_bytes);
173        out.extend_from_slice(&self.data);
174        Ok(out)
175    }
176
177    /// Decoded aus CDR-Bytes (mit Encapsulation-Header).
178    ///
179    /// Akzeptiert `0x0000`/`0x0001` (XCDR1 Plain) und `0x0006`/`0x0007`
180    /// (XCDR2 Plain). Andere Encapsulation-Kinds → Fehler.
181    ///
182    /// Tolerant gegenueber 12-Byte-Prefix-Only-Kodierung (fuellt mit
183    /// 0 auf 16 Byte auf).
184    ///
185    /// # Errors
186    /// - `UnsupportedEncapsulation` bei nicht-CDR-Encapsulation
187    /// - `UnexpectedEof` wenn Bytes zu kurz fuer Header / Body
188    /// - `ValueOutOfRange` wenn `data.len > MAX_DATA_LEN`
189    pub fn from_cdr(bytes: &[u8]) -> Result<Self, WireError> {
190        if bytes.len() < 4 {
191            return Err(WireError::UnexpectedEof {
192                needed: 4,
193                offset: 0,
194            });
195        }
196        let little_endian = match (bytes[0], bytes[1]) {
197            (0x00, 0x00) | (0x00, 0x06) => false,
198            (0x00, 0x01) | (0x00, 0x07) => true,
199            (a, b) => {
200                return Err(WireError::UnsupportedEncapsulation { kind: [a, b] });
201            }
202        };
203        // Body startet bei Offset 4 (nach options).
204        let body = &bytes[4..];
205        // Wir akzeptieren 16 Byte Guid (Spec-de-facto) ODER 12 Byte
206        // Prefix-Only (alternative Lesart der Spec-Schreibweise
207        // "GUID_t guidPrefix" — strenge 12-Byte-Encoder existieren).
208        // Heuristik: 16 Byte ist Default, 12 Byte ist nur dann gueltig,
209        // wenn die Folge-Felder (kind + data-len) sich mit 12 Byte
210        // korrekt parsen.
211        let (guid_bytes, after_guid_offset) = parse_guid(body)?;
212        if body.len() < after_guid_offset + 4 {
213            return Err(WireError::UnexpectedEof {
214                needed: after_guid_offset + 4,
215                offset: 4,
216            });
217        }
218        let kind_slice = &body[after_guid_offset..after_guid_offset + 4];
219        let mut kind_arr = [0u8; 4];
220        kind_arr.copy_from_slice(kind_slice);
221        let kind = if little_endian {
222            u32::from_le_bytes(kind_arr)
223        } else {
224            u32::from_be_bytes(kind_arr)
225        };
226        let len_offset = after_guid_offset + 4;
227        if body.len() < len_offset + 4 {
228            return Err(WireError::UnexpectedEof {
229                needed: len_offset + 4,
230                offset: 4,
231            });
232        }
233        let mut len_arr = [0u8; 4];
234        len_arr.copy_from_slice(&body[len_offset..len_offset + 4]);
235        let data_len = if little_endian {
236            u32::from_le_bytes(len_arr)
237        } else {
238            u32::from_be_bytes(len_arr)
239        } as usize;
240        if data_len > MAX_DATA_LEN {
241            return Err(WireError::ValueOutOfRange {
242                message: "ParticipantMessageData.data exceeds MAX_DATA_LEN",
243            });
244        }
245        let data_offset = len_offset + 4;
246        if body.len() < data_offset + data_len {
247            return Err(WireError::UnexpectedEof {
248                needed: data_offset + data_len,
249                offset: 4,
250            });
251        }
252        let data = body[data_offset..data_offset + data_len].to_vec();
253        Ok(Self {
254            participant_guid: guid_bytes,
255            kind,
256            data,
257        })
258    }
259
260    /// Liefert den GuidPrefix (erste 12 Byte).
261    #[must_use]
262    pub fn prefix(&self) -> GuidPrefix {
263        let mut p = [0u8; 12];
264        p.copy_from_slice(&self.participant_guid[..12]);
265        GuidPrefix::from_bytes(p)
266    }
267
268    /// `true` wenn der `kind`-Wert vendor-spezifisch ist (MSB gesetzt).
269    #[must_use]
270    pub fn is_vendor_kind(&self) -> bool {
271        self.kind >= PARTICIPANT_MESSAGE_DATA_KIND_VENDOR_BASE
272    }
273}
274
275fn full_guid_bytes(prefix: GuidPrefix) -> [u8; 16] {
276    let mut g = [0u8; 16];
277    g[..12].copy_from_slice(&prefix.to_bytes());
278    // EntityId::PARTICIPANT = [0, 0, 1, 0xC1]
279    g[12] = 0;
280    g[13] = 0;
281    g[14] = 1;
282    g[15] = 0xC1;
283    g
284}
285
286/// Parsed den GUID-Body. Versucht zuerst 16 Byte (Cyclone-/Fast-DDS-
287/// Default), fallt zurueck auf 12 Byte (strenge Spec-Lesart).
288fn parse_guid(body: &[u8]) -> Result<([u8; 16], usize), WireError> {
289    // 16-Byte-Variante: braucht mindestens 16 + 4 (kind) + 4 (data-len).
290    if body.len() >= 24 {
291        let mut g = [0u8; 16];
292        g.copy_from_slice(&body[..16]);
293        return Ok((g, 16));
294    }
295    // 12-Byte-Variante: braucht mindestens 12 + 4 + 4.
296    if body.len() >= 20 {
297        let mut g = [0u8; 16];
298        g[..12].copy_from_slice(&body[..12]);
299        // EntityId-Default: PARTICIPANT
300        g[14] = 1;
301        g[15] = 0xC1;
302        return Ok((g, 12));
303    }
304    Err(WireError::UnexpectedEof {
305        needed: 24,
306        offset: 4,
307    })
308}
309
310#[cfg(test)]
311mod tests {
312    #![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
313    use super::*;
314    use alloc::vec;
315
316    fn sample_prefix() -> GuidPrefix {
317        GuidPrefix::from_bytes([0xA, 0xB, 0xC, 0xD, 1, 2, 3, 4, 5, 6, 7, 8])
318    }
319
320    #[test]
321    fn participant_message_data_automatic_default_data_empty() {
322        let m = ParticipantMessageData::automatic(sample_prefix());
323        assert_eq!(
324            m.kind,
325            PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE
326        );
327        assert!(m.data.is_empty());
328        assert_eq!(m.prefix(), sample_prefix());
329    }
330
331    #[test]
332    fn participant_message_data_kind_constants_match_spec() {
333        // §9.6.3.1: AUTOMATIC = 0x00000000, MANUAL_BY_PARTICIPANT = 0x00000001.
334        // Vendor-Range: MSB gesetzt, also >= 0x80000000.
335        assert_eq!(
336            PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE,
337            0x0000_0000
338        );
339        assert_eq!(
340            PARTICIPANT_MESSAGE_DATA_KIND_MANUAL_BY_PARTICIPANT_LIVELINESS_UPDATE,
341            0x0000_0001
342        );
343        assert_eq!(PARTICIPANT_MESSAGE_DATA_KIND_VENDOR_BASE, 0x8000_0000);
344        // ZeroDDS-Vendor-Kind muss im Vendor-Range liegen (MSB gesetzt).
345        // `assert_eq!` statt `assert!` weil clippy `assertions_on_constants`
346        // sonst meckert; hier vergleichen wir mit konstantem Erwartwert.
347        assert_eq!(
348            PARTICIPANT_MESSAGE_DATA_KIND_ZERODDS_MANUAL_BY_TOPIC
349                & PARTICIPANT_MESSAGE_DATA_KIND_VENDOR_BASE,
350            PARTICIPANT_MESSAGE_DATA_KIND_VENDOR_BASE
351        );
352    }
353
354    #[test]
355    fn participant_message_data_roundtrip_le() {
356        let m = ParticipantMessageData::manual_by_participant(sample_prefix());
357        let bytes = m.to_cdr(true).unwrap();
358        // Encapsulation-Header LE
359        assert_eq!(&bytes[..4], &[0x00, 0x01, 0x00, 0x00]);
360        let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
361        assert_eq!(decoded, m);
362    }
363
364    #[test]
365    fn participant_message_data_roundtrip_be() {
366        let m = ParticipantMessageData::automatic(sample_prefix());
367        let bytes = m.to_cdr(false).unwrap();
368        assert_eq!(&bytes[..4], &[0x00, 0x00, 0x00, 0x00]);
369        let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
370        assert_eq!(decoded, m);
371    }
372
373    #[test]
374    fn participant_message_data_roundtrip_with_topic_token() {
375        let m =
376            ParticipantMessageData::manual_by_topic(sample_prefix(), vec![0xDE, 0xAD, 0xBE, 0xEF]);
377        assert!(m.is_vendor_kind());
378        let bytes = m.to_cdr(true).unwrap();
379        let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
380        assert_eq!(decoded.data, vec![0xDE, 0xAD, 0xBE, 0xEF]);
381        assert_eq!(
382            decoded.kind,
383            PARTICIPANT_MESSAGE_DATA_KIND_ZERODDS_MANUAL_BY_TOPIC
384        );
385    }
386
387    #[test]
388    fn participant_message_data_accepts_xcdr2_le_encapsulation() {
389        // ZeroDDS-Default fuer User-Topics ist XCDR2-LE (0x0007). Wenn
390        // ein Peer das WLP-Topic mit XCDR2 schickt, muessen wir das
391        // dekodieren koennen (Cyclone tut das mit Spec-2.5-default-rep).
392        let m = ParticipantMessageData::automatic(sample_prefix());
393        let mut bytes = m.to_cdr(true).unwrap();
394        bytes[0] = 0x00;
395        bytes[1] = 0x07; // XCDR2 LE
396        let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
397        assert_eq!(decoded, m);
398    }
399
400    #[test]
401    fn participant_message_data_accepts_xcdr2_be_encapsulation() {
402        let m = ParticipantMessageData::automatic(sample_prefix());
403        let mut bytes = m.to_cdr(false).unwrap();
404        bytes[0] = 0x00;
405        bytes[1] = 0x06; // XCDR2 BE
406        let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
407        assert_eq!(decoded, m);
408    }
409
410    #[test]
411    fn participant_message_data_rejects_unknown_encapsulation() {
412        let mut bytes = vec![0x99, 0x99, 0, 0];
413        bytes.extend_from_slice(&[0u8; 24]);
414        let res = ParticipantMessageData::from_cdr(&bytes);
415        assert!(matches!(
416            res,
417            Err(WireError::UnsupportedEncapsulation { kind: [0x99, 0x99] })
418        ));
419    }
420
421    #[test]
422    fn participant_message_data_rejects_overlong_data() {
423        // Build manually: encapsulation + 16 byte guid + 4 byte kind +
424        // 4 byte length=MAX+1.
425        let mut bytes = vec![0x00, 0x01, 0x00, 0x00];
426        bytes.extend_from_slice(&[0u8; 16]);
427        bytes.extend_from_slice(&0u32.to_le_bytes()); // kind
428        let too_big = (MAX_DATA_LEN as u32) + 1;
429        bytes.extend_from_slice(&too_big.to_le_bytes());
430        // data missing — that's fine, the cap check fires first.
431        let res = ParticipantMessageData::from_cdr(&bytes);
432        assert!(matches!(res, Err(WireError::ValueOutOfRange { .. })));
433    }
434
435    #[test]
436    fn participant_message_data_encoder_caps_data_length() {
437        let mut m = ParticipantMessageData::automatic(sample_prefix());
438        m.data = vec![0u8; MAX_DATA_LEN + 1];
439        let res = m.to_cdr(true);
440        assert!(matches!(res, Err(WireError::ValueOutOfRange { .. })));
441    }
442
443    #[test]
444    fn participant_message_data_too_short_encapsulation() {
445        let bytes = [0x00];
446        let res = ParticipantMessageData::from_cdr(&bytes);
447        assert!(matches!(res, Err(WireError::UnexpectedEof { .. })));
448    }
449
450    #[test]
451    fn participant_message_data_too_short_body() {
452        // Encapsulation valid, body only 8 byte (less than 12-byte prefix variant).
453        let bytes = vec![0x00, 0x01, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0];
454        let res = ParticipantMessageData::from_cdr(&bytes);
455        assert!(matches!(res, Err(WireError::UnexpectedEof { .. })));
456    }
457
458    #[test]
459    fn participant_message_data_truncated_data_section() {
460        // length=8 but only 4 byte follow.
461        let mut bytes = vec![0x00, 0x01, 0x00, 0x00];
462        bytes.extend_from_slice(&[0u8; 16]);
463        bytes.extend_from_slice(&0u32.to_le_bytes());
464        bytes.extend_from_slice(&8u32.to_le_bytes());
465        bytes.extend_from_slice(&[1, 2, 3, 4]);
466        let res = ParticipantMessageData::from_cdr(&bytes);
467        assert!(matches!(res, Err(WireError::UnexpectedEof { .. })));
468    }
469
470    #[test]
471    fn participant_message_data_le_be_bytes_differ_for_kind() {
472        // Sanity: BE und LE Encoding unterscheiden sich ueber kind.
473        let mut m = ParticipantMessageData::automatic(sample_prefix());
474        m.kind = 0x0102_0304;
475        let le = m.to_cdr(true).unwrap();
476        let be = m.to_cdr(false).unwrap();
477        assert_ne!(le, be);
478        // Beide muessen wieder denselben Wert ergeben.
479        assert_eq!(ParticipantMessageData::from_cdr(&le).unwrap(), m);
480        assert_eq!(ParticipantMessageData::from_cdr(&be).unwrap(), m);
481    }
482
483    #[test]
484    fn participant_message_data_accepts_12_byte_prefix_only_encoding() {
485        // Legacy/strict Encoder schreibt nur 12 Byte (Prefix). Wir
486        // muessen das dekodieren koennen + die EntityId-Default
487        // (PARTICIPANT = 0xC1) auffuellen.
488        let mut bytes = vec![0x00, 0x01, 0x00, 0x00];
489        let prefix = sample_prefix().to_bytes();
490        bytes.extend_from_slice(&prefix); // 12 byte
491        bytes.extend_from_slice(&0u32.to_le_bytes()); // kind
492        bytes.extend_from_slice(&0u32.to_le_bytes()); // data len = 0
493        let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
494        assert_eq!(decoded.prefix(), sample_prefix());
495        // EntityId-Default-Auffuellung
496        assert_eq!(&decoded.participant_guid[12..], &[0, 0, 1, 0xC1]);
497    }
498}