extern crate alloc;
use alloc::vec::Vec;
use crate::error::WireError;
use crate::wire_types::GuidPrefix;
pub const ENCAPSULATION_CDR_BE: [u8; 2] = [0x00, 0x00];
pub const ENCAPSULATION_CDR_LE: [u8; 2] = [0x00, 0x01];
pub const ENCAPSULATION_CDR2_BE: [u8; 2] = [0x00, 0x06];
pub const ENCAPSULATION_CDR2_LE: [u8; 2] = [0x00, 0x07];
pub const MAX_DATA_LEN: usize = 4096;
pub const PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE: u32 = 0x0000_0000;
pub const PARTICIPANT_MESSAGE_DATA_KIND_MANUAL_BY_PARTICIPANT_LIVELINESS_UPDATE: u32 = 0x0000_0001;
pub const PARTICIPANT_MESSAGE_DATA_KIND_VENDOR_BASE: u32 = 0x8000_0000;
pub const PARTICIPANT_MESSAGE_DATA_KIND_ZERODDS_MANUAL_BY_TOPIC: u32 = 0x8000_0001;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParticipantMessageData {
pub participant_guid: [u8; 16],
pub kind: u32,
pub data: Vec<u8>,
}
impl ParticipantMessageData {
#[must_use]
pub fn automatic(prefix: GuidPrefix) -> Self {
Self {
participant_guid: full_guid_bytes(prefix),
kind: PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE,
data: Vec::new(),
}
}
#[must_use]
pub fn manual_by_participant(prefix: GuidPrefix) -> Self {
Self {
participant_guid: full_guid_bytes(prefix),
kind: PARTICIPANT_MESSAGE_DATA_KIND_MANUAL_BY_PARTICIPANT_LIVELINESS_UPDATE,
data: Vec::new(),
}
}
#[must_use]
pub fn manual_by_topic(prefix: GuidPrefix, topic_token: Vec<u8>) -> Self {
Self {
participant_guid: full_guid_bytes(prefix),
kind: PARTICIPANT_MESSAGE_DATA_KIND_ZERODDS_MANUAL_BY_TOPIC,
data: topic_token,
}
}
pub fn to_cdr(&self, little_endian: bool) -> Result<Vec<u8>, WireError> {
if self.data.len() > MAX_DATA_LEN {
return Err(WireError::ValueOutOfRange {
message: "ParticipantMessageData.data exceeds MAX_DATA_LEN",
});
}
let data_len_u32 =
u32::try_from(self.data.len()).map_err(|_| WireError::ValueOutOfRange {
message: "ParticipantMessageData.data length exceeds u32",
})?;
let mut out = Vec::with_capacity(4 + 16 + 4 + 4 + self.data.len());
if little_endian {
out.extend_from_slice(&ENCAPSULATION_CDR_LE);
} else {
out.extend_from_slice(&ENCAPSULATION_CDR_BE);
}
out.extend_from_slice(&[0, 0]); out.extend_from_slice(&self.participant_guid);
let kind_bytes = if little_endian {
self.kind.to_le_bytes()
} else {
self.kind.to_be_bytes()
};
out.extend_from_slice(&kind_bytes);
let len_bytes = if little_endian {
data_len_u32.to_le_bytes()
} else {
data_len_u32.to_be_bytes()
};
out.extend_from_slice(&len_bytes);
out.extend_from_slice(&self.data);
Ok(out)
}
pub fn from_cdr(bytes: &[u8]) -> Result<Self, WireError> {
if bytes.len() < 4 {
return Err(WireError::UnexpectedEof {
needed: 4,
offset: 0,
});
}
let little_endian = match (bytes[0], bytes[1]) {
(0x00, 0x00) | (0x00, 0x06) => false,
(0x00, 0x01) | (0x00, 0x07) => true,
(a, b) => {
return Err(WireError::UnsupportedEncapsulation { kind: [a, b] });
}
};
let body = &bytes[4..];
let (guid_bytes, after_guid_offset) = parse_guid(body)?;
if body.len() < after_guid_offset + 4 {
return Err(WireError::UnexpectedEof {
needed: after_guid_offset + 4,
offset: 4,
});
}
let kind_slice = &body[after_guid_offset..after_guid_offset + 4];
let mut kind_arr = [0u8; 4];
kind_arr.copy_from_slice(kind_slice);
let kind = if little_endian {
u32::from_le_bytes(kind_arr)
} else {
u32::from_be_bytes(kind_arr)
};
let len_offset = after_guid_offset + 4;
if body.len() < len_offset + 4 {
return Err(WireError::UnexpectedEof {
needed: len_offset + 4,
offset: 4,
});
}
let mut len_arr = [0u8; 4];
len_arr.copy_from_slice(&body[len_offset..len_offset + 4]);
let data_len = if little_endian {
u32::from_le_bytes(len_arr)
} else {
u32::from_be_bytes(len_arr)
} as usize;
if data_len > MAX_DATA_LEN {
return Err(WireError::ValueOutOfRange {
message: "ParticipantMessageData.data exceeds MAX_DATA_LEN",
});
}
let data_offset = len_offset + 4;
if body.len() < data_offset + data_len {
return Err(WireError::UnexpectedEof {
needed: data_offset + data_len,
offset: 4,
});
}
let data = body[data_offset..data_offset + data_len].to_vec();
Ok(Self {
participant_guid: guid_bytes,
kind,
data,
})
}
#[must_use]
pub fn prefix(&self) -> GuidPrefix {
let mut p = [0u8; 12];
p.copy_from_slice(&self.participant_guid[..12]);
GuidPrefix::from_bytes(p)
}
#[must_use]
pub fn is_vendor_kind(&self) -> bool {
self.kind >= PARTICIPANT_MESSAGE_DATA_KIND_VENDOR_BASE
}
}
fn full_guid_bytes(prefix: GuidPrefix) -> [u8; 16] {
let mut g = [0u8; 16];
g[..12].copy_from_slice(&prefix.to_bytes());
g[12] = 0;
g[13] = 0;
g[14] = 1;
g[15] = 0xC1;
g
}
fn parse_guid(body: &[u8]) -> Result<([u8; 16], usize), WireError> {
if body.len() >= 24 {
let mut g = [0u8; 16];
g.copy_from_slice(&body[..16]);
return Ok((g, 16));
}
if body.len() >= 20 {
let mut g = [0u8; 16];
g[..12].copy_from_slice(&body[..12]);
g[14] = 1;
g[15] = 0xC1;
return Ok((g, 12));
}
Err(WireError::UnexpectedEof {
needed: 24,
offset: 4,
})
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
use super::*;
use alloc::vec;
fn sample_prefix() -> GuidPrefix {
GuidPrefix::from_bytes([0xA, 0xB, 0xC, 0xD, 1, 2, 3, 4, 5, 6, 7, 8])
}
#[test]
fn participant_message_data_automatic_default_data_empty() {
let m = ParticipantMessageData::automatic(sample_prefix());
assert_eq!(
m.kind,
PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE
);
assert!(m.data.is_empty());
assert_eq!(m.prefix(), sample_prefix());
}
#[test]
fn participant_message_data_kind_constants_match_spec() {
assert_eq!(
PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE,
0x0000_0000
);
assert_eq!(
PARTICIPANT_MESSAGE_DATA_KIND_MANUAL_BY_PARTICIPANT_LIVELINESS_UPDATE,
0x0000_0001
);
assert_eq!(PARTICIPANT_MESSAGE_DATA_KIND_VENDOR_BASE, 0x8000_0000);
assert_eq!(
PARTICIPANT_MESSAGE_DATA_KIND_ZERODDS_MANUAL_BY_TOPIC
& PARTICIPANT_MESSAGE_DATA_KIND_VENDOR_BASE,
PARTICIPANT_MESSAGE_DATA_KIND_VENDOR_BASE
);
}
#[test]
fn participant_message_data_roundtrip_le() {
let m = ParticipantMessageData::manual_by_participant(sample_prefix());
let bytes = m.to_cdr(true).unwrap();
assert_eq!(&bytes[..4], &[0x00, 0x01, 0x00, 0x00]);
let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
assert_eq!(decoded, m);
}
#[test]
fn participant_message_data_roundtrip_be() {
let m = ParticipantMessageData::automatic(sample_prefix());
let bytes = m.to_cdr(false).unwrap();
assert_eq!(&bytes[..4], &[0x00, 0x00, 0x00, 0x00]);
let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
assert_eq!(decoded, m);
}
#[test]
fn participant_message_data_roundtrip_with_topic_token() {
let m =
ParticipantMessageData::manual_by_topic(sample_prefix(), vec![0xDE, 0xAD, 0xBE, 0xEF]);
assert!(m.is_vendor_kind());
let bytes = m.to_cdr(true).unwrap();
let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
assert_eq!(decoded.data, vec![0xDE, 0xAD, 0xBE, 0xEF]);
assert_eq!(
decoded.kind,
PARTICIPANT_MESSAGE_DATA_KIND_ZERODDS_MANUAL_BY_TOPIC
);
}
#[test]
fn participant_message_data_accepts_xcdr2_le_encapsulation() {
let m = ParticipantMessageData::automatic(sample_prefix());
let mut bytes = m.to_cdr(true).unwrap();
bytes[0] = 0x00;
bytes[1] = 0x07; let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
assert_eq!(decoded, m);
}
#[test]
fn participant_message_data_accepts_xcdr2_be_encapsulation() {
let m = ParticipantMessageData::automatic(sample_prefix());
let mut bytes = m.to_cdr(false).unwrap();
bytes[0] = 0x00;
bytes[1] = 0x06; let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
assert_eq!(decoded, m);
}
#[test]
fn participant_message_data_rejects_unknown_encapsulation() {
let mut bytes = vec![0x99, 0x99, 0, 0];
bytes.extend_from_slice(&[0u8; 24]);
let res = ParticipantMessageData::from_cdr(&bytes);
assert!(matches!(
res,
Err(WireError::UnsupportedEncapsulation { kind: [0x99, 0x99] })
));
}
#[test]
fn participant_message_data_rejects_overlong_data() {
let mut bytes = vec![0x00, 0x01, 0x00, 0x00];
bytes.extend_from_slice(&[0u8; 16]);
bytes.extend_from_slice(&0u32.to_le_bytes()); let too_big = (MAX_DATA_LEN as u32) + 1;
bytes.extend_from_slice(&too_big.to_le_bytes());
let res = ParticipantMessageData::from_cdr(&bytes);
assert!(matches!(res, Err(WireError::ValueOutOfRange { .. })));
}
#[test]
fn participant_message_data_encoder_caps_data_length() {
let mut m = ParticipantMessageData::automatic(sample_prefix());
m.data = vec![0u8; MAX_DATA_LEN + 1];
let res = m.to_cdr(true);
assert!(matches!(res, Err(WireError::ValueOutOfRange { .. })));
}
#[test]
fn participant_message_data_too_short_encapsulation() {
let bytes = [0x00];
let res = ParticipantMessageData::from_cdr(&bytes);
assert!(matches!(res, Err(WireError::UnexpectedEof { .. })));
}
#[test]
fn participant_message_data_too_short_body() {
let bytes = vec![0x00, 0x01, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0];
let res = ParticipantMessageData::from_cdr(&bytes);
assert!(matches!(res, Err(WireError::UnexpectedEof { .. })));
}
#[test]
fn participant_message_data_truncated_data_section() {
let mut bytes = vec![0x00, 0x01, 0x00, 0x00];
bytes.extend_from_slice(&[0u8; 16]);
bytes.extend_from_slice(&0u32.to_le_bytes());
bytes.extend_from_slice(&8u32.to_le_bytes());
bytes.extend_from_slice(&[1, 2, 3, 4]);
let res = ParticipantMessageData::from_cdr(&bytes);
assert!(matches!(res, Err(WireError::UnexpectedEof { .. })));
}
#[test]
fn participant_message_data_le_be_bytes_differ_for_kind() {
let mut m = ParticipantMessageData::automatic(sample_prefix());
m.kind = 0x0102_0304;
let le = m.to_cdr(true).unwrap();
let be = m.to_cdr(false).unwrap();
assert_ne!(le, be);
assert_eq!(ParticipantMessageData::from_cdr(&le).unwrap(), m);
assert_eq!(ParticipantMessageData::from_cdr(&be).unwrap(), m);
}
#[test]
fn participant_message_data_accepts_12_byte_prefix_only_encoding() {
let mut bytes = vec![0x00, 0x01, 0x00, 0x00];
let prefix = sample_prefix().to_bytes();
bytes.extend_from_slice(&prefix); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); let decoded = ParticipantMessageData::from_cdr(&bytes).unwrap();
assert_eq!(decoded.prefix(), sample_prefix());
assert_eq!(&decoded.participant_guid[12..], &[0, 0, 1, 0xC1]);
}
}