use zerodds_cdr::{BufferReader, BufferWriter, Endianness};
use crate::error::{GiopError, GiopResult};
use crate::flags::Flags;
use crate::message_type::MessageType;
use crate::version::Version;
pub const MAGIC_BYTES: [u8; 4] = *b"GIOP";
pub const MAGIC: u32 = u32::from_be_bytes(MAGIC_BYTES);
pub const HEADER_SIZE: usize = 12;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MessageHeader {
pub version: Version,
pub flags: Flags,
pub message_type: MessageType,
pub message_size: u32,
}
impl MessageHeader {
#[must_use]
pub const fn new(
version: Version,
flags: Flags,
message_type: MessageType,
message_size: u32,
) -> Self {
Self {
version,
flags,
message_type,
message_size,
}
}
#[must_use]
pub const fn endianness(&self) -> Endianness {
self.flags.endianness()
}
pub fn encode(&self, w: &mut BufferWriter) -> GiopResult<()> {
w.write_bytes(&MAGIC_BYTES)?;
w.write_u8(self.version.major)?;
w.write_u8(self.version.minor)?;
w.write_u8(self.flags.0)?;
w.write_u8(self.message_type.as_u8())?;
w.write_u32(self.message_size)?;
Ok(())
}
pub fn decode(bytes: &[u8]) -> GiopResult<(Self, &[u8])> {
if bytes.len() < HEADER_SIZE {
return Err(GiopError::Malformed(alloc::format!(
"header needs {HEADER_SIZE} bytes, got {}",
bytes.len()
)));
}
let mut tmp = BufferReader::new(&bytes[..8], Endianness::Big);
let magic = tmp.read_bytes(4)?;
if magic != MAGIC_BYTES {
let mut m = [0u8; 4];
m.copy_from_slice(magic);
return Err(GiopError::InvalidMagic(m));
}
let major = tmp.read_u8()?;
let minor = tmp.read_u8()?;
let version = Version::new(major, minor);
if !matches!((major, minor), (1, 0) | (1, 1) | (1, 2)) {
return Err(GiopError::UnsupportedVersion { major, minor });
}
let flags_byte = tmp.read_u8()?;
let flags = Flags(flags_byte);
if flags.has_more_fragments() && !version.supports_fragments() {
return Err(GiopError::FragmentNotSupported { major, minor });
}
let mt_byte = tmp.read_u8()?;
let message_type = MessageType::from_u8(mt_byte)?;
let mut size_reader = BufferReader::new(&bytes[8..12], flags.endianness());
let message_size = size_reader.read_u32()?;
Ok((
Self {
version,
flags,
message_type,
message_size,
},
&bytes[HEADER_SIZE..],
))
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn magic_bytes_match_spec() {
assert_eq!(MAGIC_BYTES, [0x47, 0x49, 0x4F, 0x50]);
}
#[test]
fn header_size_is_12_bytes() {
assert_eq!(HEADER_SIZE, 12);
}
#[test]
fn round_trip_big_endian_request_header() {
let h = MessageHeader::new(
Version::V1_2,
Flags::from_endianness(Endianness::Big),
MessageType::Request,
42,
);
let mut w = BufferWriter::new(Endianness::Big);
h.encode(&mut w).unwrap();
let bytes = w.into_bytes();
assert_eq!(bytes.len(), HEADER_SIZE);
assert_eq!(&bytes[0..4], b"GIOP");
assert_eq!(bytes[4], 1); assert_eq!(bytes[5], 2); assert_eq!(bytes[6], 0); assert_eq!(bytes[7], MessageType::Request.as_u8());
assert_eq!(&bytes[8..12], &[0, 0, 0, 42]);
let (decoded, rest) = MessageHeader::decode(&bytes).unwrap();
assert_eq!(decoded, h);
assert!(rest.is_empty());
}
#[test]
fn round_trip_little_endian_reply_header() {
let h = MessageHeader::new(
Version::V1_1,
Flags::from_endianness(Endianness::Little),
MessageType::Reply,
0x1234_5678,
);
let mut w = BufferWriter::new(Endianness::Little);
h.encode(&mut w).unwrap();
let bytes = w.into_bytes();
assert_eq!(&bytes[0..4], b"GIOP");
assert_eq!(bytes[4], 1);
assert_eq!(bytes[5], 1);
assert_eq!(bytes[6], Flags::BYTE_ORDER_BIT);
assert_eq!(bytes[7], MessageType::Reply.as_u8());
assert_eq!(&bytes[8..12], &[0x78, 0x56, 0x34, 0x12]);
let (decoded, _) = MessageHeader::decode(&bytes).unwrap();
assert_eq!(decoded, h);
}
#[test]
fn invalid_magic_yields_diagnostic() {
let bytes = [
b'X', b'X', b'X', b'X', 1, 2, 0, 0, 0, 0, 0, 42,
];
let err = MessageHeader::decode(&bytes).unwrap_err();
assert!(matches!(
err,
GiopError::InvalidMagic([b'X', b'X', b'X', b'X'])
));
}
#[test]
fn unsupported_version_is_diagnostic() {
let bytes = [
b'G', b'I', b'O', b'P', 2, 0, 0, 0, 0, 0, 0, 0,
];
let err = MessageHeader::decode(&bytes).unwrap_err();
assert!(matches!(
err,
GiopError::UnsupportedVersion { major: 2, minor: 0 }
));
}
#[test]
fn fragment_bit_in_giop_1_0_is_rejected() {
let bytes = [
b'G',
b'I',
b'O',
b'P',
1,
0, Flags::FRAGMENT_BIT, MessageType::Request.as_u8(),
0,
0,
0,
0,
];
let err = MessageHeader::decode(&bytes).unwrap_err();
assert!(matches!(
err,
GiopError::FragmentNotSupported { major: 1, minor: 0 }
));
}
#[test]
fn truncated_input_is_malformed() {
let bytes = [b'G', b'I', b'O', b'P'];
let err = MessageHeader::decode(&bytes).unwrap_err();
assert!(matches!(err, GiopError::Malformed(_)));
}
#[test]
fn unknown_message_type_propagates() {
let bytes = [
b'G', b'I', b'O', b'P', 1, 2, 0, 99, 0, 0, 0, 0,
];
let err = MessageHeader::decode(&bytes).unwrap_err();
assert!(matches!(err, GiopError::UnknownMessageType(99)));
}
}