Skip to main content

zerodds_corba_giop/
header.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! GIOP Message-Header — 12 Bytes, Spec §15.4.1.
5//!
6//! ```text
7//! struct MessageHeader {
8//!     char           magic[4];        // "GIOP" = 47 49 4F 50
9//!     octet          GIOP_version_major;
10//!     octet          GIOP_version_minor;
11//!     octet          flags;           // GIOP 1.0: byte_order
12//!     octet          message_type;
13//!     unsigned long  message_size;    // body size, in bytes
14//! };
15//! ```
16//!
17//! Total: 12 Bytes. `message_size` ist mit der Header-Endianness
18//! kodiert (Bit 0 von `flags`).
19
20use zerodds_cdr::{BufferReader, BufferWriter, Endianness};
21
22use crate::error::{GiopError, GiopResult};
23use crate::flags::Flags;
24use crate::message_type::MessageType;
25use crate::version::Version;
26
27/// `MAGIC` — 4-Byte ASCII `"GIOP"` (Spec §15.4.1).
28pub const MAGIC_BYTES: [u8; 4] = *b"GIOP";
29
30/// `MAGIC` als Konstante fuer Tests.
31pub const MAGIC: u32 = u32::from_be_bytes(MAGIC_BYTES);
32
33/// GIOP-Header-Groesse in Bytes (immer 12).
34pub const HEADER_SIZE: usize = 12;
35
36/// GIOP Message-Header (Spec §15.4.1).
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct MessageHeader {
39    /// GIOP-Version (Spec §15.4.1).
40    pub version: Version,
41    /// Flags-Octet (in GIOP 1.0 nur `byte_order`).
42    pub flags: Flags,
43    /// Message-Type-Discriminant.
44    pub message_type: MessageType,
45    /// `message_size` — Body-Groesse in Bytes (ohne Header).
46    pub message_size: u32,
47}
48
49impl MessageHeader {
50    /// Konstruktor.
51    #[must_use]
52    pub const fn new(
53        version: Version,
54        flags: Flags,
55        message_type: MessageType,
56        message_size: u32,
57    ) -> Self {
58        Self {
59            version,
60            flags,
61            message_type,
62            message_size,
63        }
64    }
65
66    /// Liefert die Endianness aus dem `flags`-Octet (Bit 0).
67    #[must_use]
68    pub const fn endianness(&self) -> Endianness {
69        self.flags.endianness()
70    }
71
72    /// Encodiert den 12-Byte-Header.
73    ///
74    /// # Errors
75    /// Buffer-Schreibfehler aus dem CDR-Layer.
76    pub fn encode(&self, w: &mut BufferWriter) -> GiopResult<()> {
77        // Spec §15.4.1: erste vier Bytes sind das `magic` als Sequence
78        // of `char` — also kein CDR-Length-Prefix, sondern Roh-Bytes.
79        // Wir schreiben hier ohne Alignment, weil der Header am
80        // Stream-Anfang liegt (pos=0, alignment irrelevant).
81        w.write_bytes(&MAGIC_BYTES)?;
82        w.write_u8(self.version.major)?;
83        w.write_u8(self.version.minor)?;
84        w.write_u8(self.flags.0)?;
85        w.write_u8(self.message_type.as_u8())?;
86        // `message_size` ist `unsigned long` mit Header-Endianness.
87        // Der Buffer wurde mit dieser Endianness konstruiert.
88        w.write_u32(self.message_size)?;
89        Ok(())
90    }
91
92    /// Decodiert den 12-Byte-Header.
93    ///
94    /// **Wichtig:** Caller muss den `BufferReader` mit
95    /// [`Endianness::Big`] erzeugen — die ersten 8 Bytes
96    /// (Magic+Version+Flags+MsgType) sind endian-frei, aber das
97    /// `message_size`-`u32` muss anhand des `flags`-Bytes
98    /// re-interpretiert werden. Dieser Helper liest das `flags`-Byte
99    /// und kehrt die Endianness um, falls noetig.
100    ///
101    /// # Errors
102    /// * `InvalidMagic` wenn Bytes 0..4 nicht `"GIOP"` sind.
103    /// * `UnsupportedVersion` wenn major/minor weder 1.0/1.1/1.2.
104    /// * `UnknownMessageType` wenn der Octet nicht in 0..=7 liegt.
105    /// * Buffer-Lesefehler.
106    pub fn decode(bytes: &[u8]) -> GiopResult<(Self, &[u8])> {
107        if bytes.len() < HEADER_SIZE {
108            return Err(GiopError::Malformed(alloc::format!(
109                "header needs {HEADER_SIZE} bytes, got {}",
110                bytes.len()
111            )));
112        }
113        // Magic + Version + Flags + MessageType sind endian-frei
114        // (Bytes 0..8). Wir starten mit BigEndian, justieren dann.
115        let mut tmp = BufferReader::new(&bytes[..8], Endianness::Big);
116        let magic = tmp.read_bytes(4)?;
117        if magic != MAGIC_BYTES {
118            let mut m = [0u8; 4];
119            m.copy_from_slice(magic);
120            return Err(GiopError::InvalidMagic(m));
121        }
122        let major = tmp.read_u8()?;
123        let minor = tmp.read_u8()?;
124        let version = Version::new(major, minor);
125        // Spec §15.4.1: nur 1.0, 1.1, 1.2 sind aktuell standardisiert.
126        if !matches!((major, minor), (1, 0) | (1, 1) | (1, 2)) {
127            return Err(GiopError::UnsupportedVersion { major, minor });
128        }
129        let flags_byte = tmp.read_u8()?;
130        let flags = Flags(flags_byte);
131        // Fragment-Bit-Constraint (Spec §15.4.9).
132        if flags.has_more_fragments() && !version.supports_fragments() {
133            return Err(GiopError::FragmentNotSupported { major, minor });
134        }
135        let mt_byte = tmp.read_u8()?;
136        let message_type = MessageType::from_u8(mt_byte)?;
137
138        // Jetzt mit der ermittelten Endianness das `message_size`-u32 lesen.
139        let mut size_reader = BufferReader::new(&bytes[8..12], flags.endianness());
140        let message_size = size_reader.read_u32()?;
141        Ok((
142            Self {
143                version,
144                flags,
145                message_type,
146                message_size,
147            },
148            &bytes[HEADER_SIZE..],
149        ))
150    }
151}
152
153#[cfg(test)]
154#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn magic_bytes_match_spec() {
160        // Spec §15.4.1: "GIOP" = 0x47 0x49 0x4F 0x50.
161        assert_eq!(MAGIC_BYTES, [0x47, 0x49, 0x4F, 0x50]);
162    }
163
164    #[test]
165    fn header_size_is_12_bytes() {
166        // Spec §15.4.1: Header ist immer 12 Bytes.
167        assert_eq!(HEADER_SIZE, 12);
168    }
169
170    #[test]
171    fn round_trip_big_endian_request_header() {
172        let h = MessageHeader::new(
173            Version::V1_2,
174            Flags::from_endianness(Endianness::Big),
175            MessageType::Request,
176            42,
177        );
178        let mut w = BufferWriter::new(Endianness::Big);
179        h.encode(&mut w).unwrap();
180        let bytes = w.into_bytes();
181        assert_eq!(bytes.len(), HEADER_SIZE);
182        // Pruefe Magic + Version + Flags + MsgType + Size (BE).
183        assert_eq!(&bytes[0..4], b"GIOP");
184        assert_eq!(bytes[4], 1); // major
185        assert_eq!(bytes[5], 2); // minor
186        assert_eq!(bytes[6], 0); // flags=BE
187        assert_eq!(bytes[7], MessageType::Request.as_u8());
188        assert_eq!(&bytes[8..12], &[0, 0, 0, 42]); // BE u32
189
190        let (decoded, rest) = MessageHeader::decode(&bytes).unwrap();
191        assert_eq!(decoded, h);
192        assert!(rest.is_empty());
193    }
194
195    #[test]
196    fn round_trip_little_endian_reply_header() {
197        let h = MessageHeader::new(
198            Version::V1_1,
199            Flags::from_endianness(Endianness::Little),
200            MessageType::Reply,
201            0x1234_5678,
202        );
203        let mut w = BufferWriter::new(Endianness::Little);
204        h.encode(&mut w).unwrap();
205        let bytes = w.into_bytes();
206        assert_eq!(&bytes[0..4], b"GIOP");
207        assert_eq!(bytes[4], 1);
208        assert_eq!(bytes[5], 1);
209        assert_eq!(bytes[6], Flags::BYTE_ORDER_BIT);
210        assert_eq!(bytes[7], MessageType::Reply.as_u8());
211        // LE-u32-Bytes von 0x12345678 = 78 56 34 12.
212        assert_eq!(&bytes[8..12], &[0x78, 0x56, 0x34, 0x12]);
213        let (decoded, _) = MessageHeader::decode(&bytes).unwrap();
214        assert_eq!(decoded, h);
215    }
216
217    #[test]
218    fn invalid_magic_yields_diagnostic() {
219        let bytes = [
220            b'X', b'X', b'X', b'X', // bad magic
221            1, 2, 0, 0, 0, 0, 0, 42,
222        ];
223        let err = MessageHeader::decode(&bytes).unwrap_err();
224        assert!(matches!(
225            err,
226            GiopError::InvalidMagic([b'X', b'X', b'X', b'X'])
227        ));
228    }
229
230    #[test]
231    fn unsupported_version_is_diagnostic() {
232        let bytes = [
233            b'G', b'I', b'O', b'P', 2, 0, // 2.0 not standardized
234            0, 0, 0, 0, 0, 0,
235        ];
236        let err = MessageHeader::decode(&bytes).unwrap_err();
237        assert!(matches!(
238            err,
239            GiopError::UnsupportedVersion { major: 2, minor: 0 }
240        ));
241    }
242
243    #[test]
244    fn fragment_bit_in_giop_1_0_is_rejected() {
245        // Spec §15.4.9: Fragment-Bit gibts erst ab GIOP 1.1.
246        let bytes = [
247            b'G',
248            b'I',
249            b'O',
250            b'P',
251            1,
252            0,                   // GIOP 1.0
253            Flags::FRAGMENT_BIT, // fragment bit set — illegal
254            MessageType::Request.as_u8(),
255            0,
256            0,
257            0,
258            0,
259        ];
260        let err = MessageHeader::decode(&bytes).unwrap_err();
261        assert!(matches!(
262            err,
263            GiopError::FragmentNotSupported { major: 1, minor: 0 }
264        ));
265    }
266
267    #[test]
268    fn truncated_input_is_malformed() {
269        let bytes = [b'G', b'I', b'O', b'P'];
270        let err = MessageHeader::decode(&bytes).unwrap_err();
271        assert!(matches!(err, GiopError::Malformed(_)));
272    }
273
274    #[test]
275    fn unknown_message_type_propagates() {
276        let bytes = [
277            b'G', b'I', b'O', b'P', 1, 2, 0, 99, // out of range
278            0, 0, 0, 0,
279        ];
280        let err = MessageHeader::decode(&bytes).unwrap_err();
281        assert!(matches!(err, GiopError::UnknownMessageType(99)));
282    }
283}