1use 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
27pub const MAGIC_BYTES: [u8; 4] = *b"GIOP";
29
30pub const MAGIC: u32 = u32::from_be_bytes(MAGIC_BYTES);
32
33pub const HEADER_SIZE: usize = 12;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct MessageHeader {
39 pub version: Version,
41 pub flags: Flags,
43 pub message_type: MessageType,
45 pub message_size: u32,
47}
48
49impl MessageHeader {
50 #[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 #[must_use]
68 pub const fn endianness(&self) -> Endianness {
69 self.flags.endianness()
70 }
71
72 pub fn encode(&self, w: &mut BufferWriter) -> GiopResult<()> {
77 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 w.write_u32(self.message_size)?;
89 Ok(())
90 }
91
92 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 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 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 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 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 assert_eq!(MAGIC_BYTES, [0x47, 0x49, 0x4F, 0x50]);
162 }
163
164 #[test]
165 fn header_size_is_12_bytes() {
166 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 assert_eq!(&bytes[0..4], b"GIOP");
184 assert_eq!(bytes[4], 1); assert_eq!(bytes[5], 2); assert_eq!(bytes[6], 0); assert_eq!(bytes[7], MessageType::Request.as_u8());
188 assert_eq!(&bytes[8..12], &[0, 0, 0, 42]); 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 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', 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, 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 let bytes = [
247 b'G',
248 b'I',
249 b'O',
250 b'P',
251 1,
252 0, Flags::FRAGMENT_BIT, 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, 0, 0, 0, 0,
279 ];
280 let err = MessageHeader::decode(&bytes).unwrap_err();
281 assert!(matches!(err, GiopError::UnknownMessageType(99)));
282 }
283}