#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MessageHeader {
pub object_id: u32,
pub opcode: u16,
pub size: u16,
}
pub fn encode_u32(out: &mut Vec<u8>, v: u32) {
out.extend_from_slice(&v.to_le_bytes());
}
pub fn encode_string(out: &mut Vec<u8>, s: &str) {
let len_with_nul = s.len() + 1; encode_u32(out, len_with_nul as u32);
out.extend_from_slice(s.as_bytes());
out.push(0); let pad = pad4(len_with_nul);
out.extend(std::iter::repeat_n(0u8, pad));
}
pub fn encode_message(object_id: u32, opcode: u16, args: &[u8]) -> Vec<u8> {
let total: u16 = (8 + args.len()) as u16; let size_opcode: u32 = (u32::from(total) << 16) | u32::from(opcode);
let mut out = Vec::with_capacity(8 + args.len());
encode_u32(&mut out, object_id);
encode_u32(&mut out, size_opcode);
out.extend_from_slice(args);
out
}
pub fn parse_message_header(buf: &[u8]) -> Option<(MessageHeader, &[u8])> {
if buf.len() < 8 {
return None;
}
let object_id = u32::from_le_bytes(buf[0..4].try_into().ok()?);
let size_opcode = u32::from_le_bytes(buf[4..8].try_into().ok()?);
let opcode = (size_opcode & 0xffff) as u16;
let size = (size_opcode >> 16) as u16;
let hdr = MessageHeader {
object_id,
opcode,
size,
};
Some((hdr, &buf[8..]))
}
pub fn parse_string(args: &[u8]) -> Option<(&str, &[u8])> {
if args.len() < 4 {
return None;
}
let len_with_nul = u32::from_le_bytes(args[0..4].try_into().ok()?) as usize;
if len_with_nul == 0 {
let total_consumed = 4; return Some(("", &args[total_consumed..]));
}
let payload_start = 4usize;
let payload_end = payload_start.checked_add(len_with_nul)?;
if args.len() < payload_end {
return None;
}
let str_bytes = &args[payload_start..payload_end - 1];
let s = std::str::from_utf8(str_bytes).ok()?;
let padded_len = len_with_nul.checked_next_multiple_of(4)?;
let total_consumed = payload_start.checked_add(padded_len)?;
if args.len() < total_consumed {
return None;
}
Some((s, &args[total_consumed..]))
}
pub fn parse_u32(args: &[u8]) -> Option<(u32, &[u8])> {
if args.len() < 4 {
return None;
}
let v = u32::from_le_bytes(args[0..4].try_into().ok()?);
Some((v, &args[4..]))
}
fn pad4(len: usize) -> usize {
let rem = len % 4;
if rem == 0 { 0 } else { 4 - rem }
}
#[cfg_attr(not(test), allow(dead_code))]
fn pad4_up(len: usize) -> usize {
len.div_ceil(4) * 4
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_decode_u32_round_trip() {
let cases: &[u32] = &[0, 1, 0x0000_FFFF, 0xDEAD_BEEF, u32::MAX];
for &v in cases {
let mut buf = Vec::new();
encode_u32(&mut buf, v);
assert_eq!(buf.len(), 4, "u32 must encode to exactly 4 bytes");
let (decoded, rest) = parse_u32(&buf).expect("parse_u32 failed");
assert_eq!(decoded, v, "round-trip mismatch for {v:#010x}");
assert!(rest.is_empty());
}
}
#[test]
fn encode_decode_string_round_trip() {
let cases: &[&str] = &[
"", "a", "ab", "abc", "abcd", "wl_registry",
"ext_data_control_manager_v1",
];
for &s in cases {
let mut buf = Vec::new();
encode_string(&mut buf, s);
assert_eq!(buf.len() % 4, 0, "encoded string not 4-byte aligned: {s:?}");
let (decoded, rest) = parse_string(&buf).expect("parse_string failed");
assert_eq!(decoded, s, "round-trip mismatch for {s:?}");
assert!(rest.is_empty(), "unexpected trailing bytes for {s:?}");
}
}
#[test]
fn encode_message_header_layout() {
let mut args = Vec::new();
encode_u32(&mut args, 2u32);
let msg = encode_message(1, 1, &args);
assert_eq!(msg.len(), 12);
assert_eq!(&msg[0..4], &[0x01, 0x00, 0x00, 0x00]);
assert_eq!(&msg[4..8], &[0x01, 0x00, 0x0C, 0x00]);
assert_eq!(&msg[8..12], &[0x02, 0x00, 0x00, 0x00]);
let (hdr, rest) = parse_message_header(&msg).expect("header parse failed");
assert_eq!(hdr.object_id, 1);
assert_eq!(hdr.opcode, 1);
assert_eq!(hdr.size, 12);
assert_eq!(rest, &[0x02u8, 0x00, 0x00, 0x00]);
}
#[test]
fn parse_string_padding() {
let mut buf = Vec::new();
encode_string(&mut buf, "ab");
encode_u32(&mut buf, 99);
let (s, rest) = parse_string(&buf).expect("parse failed");
assert_eq!(s, "ab");
let (sentinel, rest2) = parse_u32(rest).expect("sentinel parse failed");
assert_eq!(sentinel, 99);
assert!(rest2.is_empty());
}
#[test]
fn parse_message_header_partial() {
assert!(parse_message_header(&[]).is_none());
assert!(parse_message_header(&[1, 0, 0, 0]).is_none());
assert!(parse_message_header(&[1, 0, 0, 0, 0, 0, 0]).is_none());
let full = encode_message(5, 3, &[]);
assert_eq!(full.len(), 8);
let r = parse_message_header(&full);
assert!(r.is_some());
let (hdr, _) = r.unwrap();
assert_eq!(hdr.object_id, 5);
assert_eq!(hdr.opcode, 3);
assert_eq!(hdr.size, 8);
}
#[test]
fn padding_zero_filled() {
let mut buf = Vec::new();
encode_string(&mut buf, "a");
assert_eq!(buf.len(), 8);
assert_eq!(buf[6], 0x00, "first pad byte must be zero");
assert_eq!(buf[7], 0x00, "second pad byte must be zero");
}
#[test]
fn pad4_helper() {
assert_eq!(pad4(0), 0);
assert_eq!(pad4(1), 3);
assert_eq!(pad4(2), 2);
assert_eq!(pad4(3), 1);
assert_eq!(pad4(4), 0);
assert_eq!(pad4(5), 3);
assert_eq!(pad4_up(0), 0);
assert_eq!(pad4_up(1), 4);
assert_eq!(pad4_up(4), 4);
assert_eq!(pad4_up(5), 8);
}
}