extern crate alloc;
use alloc::vec::Vec;
use crate::error::XrceError;
use crate::object_id::ObjectId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ResultStatusCode {
Ok = 0x00,
OkMatched = 0x01,
DdsError = 0x80,
Mismatch = 0x81,
AlreadyExists = 0x82,
Denied = 0x83,
UnknownReference = 0x84,
InvalidData = 0x85,
Incompatible = 0x86,
Resources = 0x87,
Unsupported = 0x88,
}
impl ResultStatusCode {
#[must_use]
pub fn as_u8(self) -> u8 {
self as u8
}
pub fn from_u8(byte: u8) -> Result<Self, XrceError> {
match byte {
0x00 => Ok(Self::Ok),
0x01 => Ok(Self::OkMatched),
0x80 => Ok(Self::DdsError),
0x81 => Ok(Self::Mismatch),
0x82 => Ok(Self::AlreadyExists),
0x83 => Ok(Self::Denied),
0x84 => Ok(Self::UnknownReference),
0x85 => Ok(Self::InvalidData),
0x86 => Ok(Self::Incompatible),
0x87 => Ok(Self::Resources),
0x88 => Ok(Self::Unsupported),
_ => Err(XrceError::ValueOutOfRange {
message: "ResultStatusCode: unknown wire value",
}),
}
}
#[must_use]
pub fn is_success(self) -> bool {
matches!(self, Self::Ok | Self::OkMatched)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResultStatus {
pub status_code: ResultStatusCode,
pub implementation_status: u8,
}
impl ResultStatus {
pub const WIRE_SIZE: usize = 2;
#[must_use]
pub fn ok() -> Self {
Self {
status_code: ResultStatusCode::Ok,
implementation_status: 0,
}
}
#[must_use]
pub fn encode(&self) -> [u8; 2] {
[self.status_code.as_u8(), self.implementation_status]
}
pub fn decode(bytes: &[u8]) -> Result<(Self, usize), XrceError> {
if bytes.len() < Self::WIRE_SIZE {
return Err(XrceError::UnexpectedEof {
needed: Self::WIRE_SIZE,
offset: 0,
});
}
let status_code = ResultStatusCode::from_u8(bytes[0])?;
let implementation_status = bytes[1];
Ok((
Self {
status_code,
implementation_status,
},
Self::WIRE_SIZE,
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BaseObjectRequest {
pub request_id: [u8; 2],
pub object_id: ObjectId,
}
impl BaseObjectRequest {
pub const WIRE_SIZE: usize = 4;
#[must_use]
pub fn encode(&self) -> [u8; 4] {
let oid = self.object_id.to_bytes();
[self.request_id[0], self.request_id[1], oid[0], oid[1]]
}
pub fn decode(bytes: &[u8]) -> Result<(Self, usize), XrceError> {
if bytes.len() < Self::WIRE_SIZE {
return Err(XrceError::UnexpectedEof {
needed: Self::WIRE_SIZE,
offset: 0,
});
}
let request_id = [bytes[0], bytes[1]];
let object_id = ObjectId::from_bytes(&bytes[2..4])?;
Ok((
Self {
request_id,
object_id,
},
Self::WIRE_SIZE,
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BaseObjectReply {
pub related_request: BaseObjectRequest,
pub result: ResultStatus,
}
impl BaseObjectReply {
pub const WIRE_SIZE: usize = BaseObjectRequest::WIRE_SIZE + ResultStatus::WIRE_SIZE;
#[must_use]
pub fn encode(&self) -> [u8; 6] {
let req = self.related_request.encode();
let res = self.result.encode();
[req[0], req[1], req[2], req[3], res[0], res[1]]
}
pub fn decode(bytes: &[u8]) -> Result<(Self, usize), XrceError> {
let (related_request, n1) = BaseObjectRequest::decode(bytes)?;
let (result, n2) = ResultStatus::decode(&bytes[n1..])?;
Ok((
Self {
related_request,
result,
},
n1 + n2,
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RelatedObjectRequest {
pub base: BaseObjectRequest,
pub related_object: ObjectId,
}
impl RelatedObjectRequest {
pub const WIRE_SIZE: usize = BaseObjectRequest::WIRE_SIZE + 2;
#[must_use]
pub fn encode(&self) -> [u8; 6] {
let b = self.base.encode();
let r = self.related_object.to_bytes();
[b[0], b[1], b[2], b[3], r[0], r[1]]
}
pub fn decode(bytes: &[u8]) -> Result<(Self, usize), XrceError> {
let (base, n1) = BaseObjectRequest::decode(bytes)?;
if bytes.len() < n1 + 2 {
return Err(XrceError::UnexpectedEof {
needed: n1 + 2,
offset: n1,
});
}
let related_object = ObjectId::from_bytes(&bytes[n1..n1 + 2])?;
Ok((
Self {
base,
related_object,
},
n1 + 2,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ActivityInfoVariant {
Agent {
availability: u8,
address_seq: Vec<u8>,
},
DataReader {
highest_acked_num: i16,
unread_sample_count: u32,
},
DataWriter {
unacked_sample_count: u32,
sample_seq_num: i64,
},
}
impl ActivityInfoVariant {
#[must_use]
pub fn discriminator(&self) -> u8 {
match self {
Self::Agent { .. } => 0x0D, Self::DataReader { .. } => 0x06, Self::DataWriter { .. } => 0x05, }
}
#[must_use]
pub fn encode(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(16);
out.push(self.discriminator());
match self {
Self::Agent {
availability,
address_seq,
} => {
out.push(*availability);
let len = u16::try_from(address_seq.len()).unwrap_or(u16::MAX);
out.extend_from_slice(&len.to_le_bytes());
out.extend_from_slice(address_seq);
}
Self::DataReader {
highest_acked_num,
unread_sample_count,
} => {
out.extend_from_slice(&highest_acked_num.to_le_bytes());
out.extend_from_slice(&unread_sample_count.to_le_bytes());
}
Self::DataWriter {
unacked_sample_count,
sample_seq_num,
} => {
out.extend_from_slice(&unacked_sample_count.to_le_bytes());
out.extend_from_slice(&sample_seq_num.to_le_bytes());
}
}
out
}
pub fn decode(bytes: &[u8]) -> Result<(Self, usize), XrceError> {
if bytes.is_empty() {
return Err(XrceError::UnexpectedEof {
needed: 1,
offset: 0,
});
}
let disc = bytes[0];
let rest = &bytes[1..];
match disc {
0x0D => {
if rest.len() < 3 {
return Err(XrceError::UnexpectedEof {
needed: 3,
offset: 1,
});
}
let availability = rest[0];
let len = u16::from_le_bytes([rest[1], rest[2]]) as usize;
if rest.len() < 3 + len {
return Err(XrceError::UnexpectedEof {
needed: 3 + len,
offset: 1,
});
}
let address_seq = rest[3..3 + len].to_vec();
Ok((
Self::Agent {
availability,
address_seq,
},
1 + 3 + len,
))
}
0x06 => {
if rest.len() < 6 {
return Err(XrceError::UnexpectedEof {
needed: 6,
offset: 1,
});
}
let highest_acked_num = i16::from_le_bytes([rest[0], rest[1]]);
let unread_sample_count = u32::from_le_bytes([rest[2], rest[3], rest[4], rest[5]]);
Ok((
Self::DataReader {
highest_acked_num,
unread_sample_count,
},
7,
))
}
0x05 => {
if rest.len() < 12 {
return Err(XrceError::UnexpectedEof {
needed: 12,
offset: 1,
});
}
let unacked_sample_count = u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]);
let mut snn = [0u8; 8];
snn.copy_from_slice(&rest[4..12]);
let sample_seq_num = i64::from_le_bytes(snn);
Ok((
Self::DataWriter {
unacked_sample_count,
sample_seq_num,
},
13,
))
}
_ => Err(XrceError::ValueOutOfRange {
message: "ActivityInfoVariant: unknown discriminator",
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ObjectInfo {
pub config: Option<Vec<u8>>,
pub activity: Option<ActivityInfoVariant>,
}
impl ObjectInfo {
#[must_use]
pub fn encode(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(16);
if let Some(cfg) = &self.config {
out.push(1);
let len = u16::try_from(cfg.len()).unwrap_or(u16::MAX);
out.extend_from_slice(&len.to_le_bytes());
out.extend_from_slice(cfg);
} else {
out.push(0);
}
if let Some(act) = &self.activity {
out.push(1);
out.extend_from_slice(&act.encode());
} else {
out.push(0);
}
out
}
pub fn decode(bytes: &[u8]) -> Result<(Self, usize), XrceError> {
if bytes.is_empty() {
return Err(XrceError::UnexpectedEof {
needed: 1,
offset: 0,
});
}
let mut cursor = 0usize;
let cfg_present = bytes[cursor];
cursor += 1;
let config = if cfg_present == 1 {
if bytes.len() < cursor + 2 {
return Err(XrceError::UnexpectedEof {
needed: cursor + 2,
offset: cursor,
});
}
let len = u16::from_le_bytes([bytes[cursor], bytes[cursor + 1]]) as usize;
cursor += 2;
if bytes.len() < cursor + len {
return Err(XrceError::UnexpectedEof {
needed: cursor + len,
offset: cursor,
});
}
let v = bytes[cursor..cursor + len].to_vec();
cursor += len;
Some(v)
} else {
None
};
if bytes.len() < cursor + 1 {
return Err(XrceError::UnexpectedEof {
needed: cursor + 1,
offset: cursor,
});
}
let act_present = bytes[cursor];
cursor += 1;
let activity = if act_present == 1 {
let (a, n) = ActivityInfoVariant::decode(&bytes[cursor..])?;
cursor += n;
Some(a)
} else {
None
};
Ok((Self { config, activity }, cursor))
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use crate::object_kind::OBJK_PARTICIPANT;
#[test]
fn result_status_code_all_11_spec_values_roundtrip() {
for code in [
ResultStatusCode::Ok,
ResultStatusCode::OkMatched,
ResultStatusCode::DdsError,
ResultStatusCode::Mismatch,
ResultStatusCode::AlreadyExists,
ResultStatusCode::Denied,
ResultStatusCode::UnknownReference,
ResultStatusCode::InvalidData,
ResultStatusCode::Incompatible,
ResultStatusCode::Resources,
ResultStatusCode::Unsupported,
] {
let byte = code.as_u8();
let decoded = ResultStatusCode::from_u8(byte).expect("roundtrip");
assert_eq!(decoded, code);
}
}
#[test]
fn result_status_code_unknown_byte_rejected() {
assert!(ResultStatusCode::from_u8(0x02).is_err());
assert!(ResultStatusCode::from_u8(0x7F).is_err());
assert!(ResultStatusCode::from_u8(0x89).is_err());
assert!(ResultStatusCode::from_u8(0xFF).is_err());
}
#[test]
fn result_status_code_is_success() {
assert!(ResultStatusCode::Ok.is_success());
assert!(ResultStatusCode::OkMatched.is_success());
assert!(!ResultStatusCode::DdsError.is_success());
assert!(!ResultStatusCode::Denied.is_success());
}
#[test]
fn result_status_roundtrip() {
let r = ResultStatus {
status_code: ResultStatusCode::AlreadyExists,
implementation_status: 0x42,
};
let bytes = r.encode();
assert_eq!(bytes, [0x82, 0x42]);
let (r2, n) = ResultStatus::decode(&bytes).expect("decode");
assert_eq!(r2, r);
assert_eq!(n, 2);
}
#[test]
fn result_status_decode_short_returns_eof() {
assert!(ResultStatus::decode(&[]).is_err());
assert!(ResultStatus::decode(&[0x00]).is_err());
}
#[test]
fn base_object_request_roundtrip() {
let oid = ObjectId::new(
0x010,
crate::object_kind::ObjectKind::from_u8(OBJK_PARTICIPANT).unwrap(),
)
.unwrap();
let r = BaseObjectRequest {
request_id: [0xAB, 0xCD],
object_id: oid,
};
let bytes = r.encode();
let (r2, n) = BaseObjectRequest::decode(&bytes).expect("decode");
assert_eq!(r2, r);
assert_eq!(n, BaseObjectRequest::WIRE_SIZE);
}
#[test]
fn base_object_request_decode_short_returns_eof() {
assert!(BaseObjectRequest::decode(&[0x01, 0x02, 0x03]).is_err());
}
#[test]
fn base_object_reply_roundtrip() {
let oid = ObjectId::new(
0x123,
crate::object_kind::ObjectKind::from_u8(OBJK_PARTICIPANT).unwrap(),
)
.unwrap();
let r = BaseObjectReply {
related_request: BaseObjectRequest {
request_id: [0x11, 0x22],
object_id: oid,
},
result: ResultStatus {
status_code: ResultStatusCode::OkMatched,
implementation_status: 0,
},
};
let bytes = r.encode();
let (r2, n) = BaseObjectReply::decode(&bytes).expect("decode");
assert_eq!(r2, r);
assert_eq!(n, BaseObjectReply::WIRE_SIZE);
}
#[test]
fn related_object_request_roundtrip() {
let oid_a = ObjectId::new(
0x010,
crate::object_kind::ObjectKind::from_u8(OBJK_PARTICIPANT).unwrap(),
)
.unwrap();
let oid_b = ObjectId::new(
0x020,
crate::object_kind::ObjectKind::from_u8(OBJK_PARTICIPANT).unwrap(),
)
.unwrap();
let r = RelatedObjectRequest {
base: BaseObjectRequest {
request_id: [0xAA, 0xBB],
object_id: oid_a,
},
related_object: oid_b,
};
let bytes = r.encode();
let (r2, n) = RelatedObjectRequest::decode(&bytes).expect("decode");
assert_eq!(r2, r);
assert_eq!(n, RelatedObjectRequest::WIRE_SIZE);
}
#[test]
fn activity_info_agent_roundtrip() {
let a = ActivityInfoVariant::Agent {
availability: 1,
address_seq: alloc::vec![10, 0, 0, 1, 0xE8, 0x1C],
};
let bytes = a.encode();
let (a2, _) = ActivityInfoVariant::decode(&bytes).expect("roundtrip");
assert_eq!(a2, a);
}
#[test]
fn activity_info_data_reader_roundtrip() {
let a = ActivityInfoVariant::DataReader {
highest_acked_num: -3,
unread_sample_count: 42,
};
let bytes = a.encode();
let (a2, _) = ActivityInfoVariant::decode(&bytes).expect("roundtrip");
assert_eq!(a2, a);
}
#[test]
fn activity_info_data_writer_roundtrip() {
let a = ActivityInfoVariant::DataWriter {
unacked_sample_count: 7,
sample_seq_num: 123_456_789_012i64,
};
let bytes = a.encode();
let (a2, _) = ActivityInfoVariant::decode(&bytes).expect("roundtrip");
assert_eq!(a2, a);
}
#[test]
fn activity_info_decode_unknown_discriminator_rejected() {
let bytes = [0xFF, 0x00, 0x01, 0x02];
assert!(ActivityInfoVariant::decode(&bytes).is_err());
}
#[test]
fn activity_info_decode_truncated_rejected() {
assert!(ActivityInfoVariant::decode(&[0x06, 0x01]).is_err());
}
#[test]
fn object_info_with_both_present_roundtrip() {
let info = ObjectInfo {
config: Some(alloc::vec![0xDE, 0xAD, 0xBE, 0xEF]),
activity: Some(ActivityInfoVariant::DataReader {
highest_acked_num: 100,
unread_sample_count: 5,
}),
};
let bytes = info.encode();
let (i2, _) = ObjectInfo::decode(&bytes).expect("roundtrip");
assert_eq!(i2, info);
}
#[test]
fn object_info_only_config_roundtrip() {
let info = ObjectInfo {
config: Some(alloc::vec![0x01, 0x02, 0x03]),
activity: None,
};
let bytes = info.encode();
let (i2, _) = ObjectInfo::decode(&bytes).expect("roundtrip");
assert_eq!(i2, info);
}
#[test]
fn object_info_only_activity_roundtrip() {
let info = ObjectInfo {
config: None,
activity: Some(ActivityInfoVariant::Agent {
availability: 1,
address_seq: alloc::vec![],
}),
};
let bytes = info.encode();
let (i2, _) = ObjectInfo::decode(&bytes).expect("roundtrip");
assert_eq!(i2, info);
}
#[test]
fn object_info_empty_roundtrip() {
let info = ObjectInfo::default();
let bytes = info.encode();
assert_eq!(bytes, [0, 0]);
let (i2, _) = ObjectInfo::decode(&bytes).expect("roundtrip");
assert_eq!(i2, info);
}
#[test]
fn object_info_truncated_returns_eof() {
assert!(ObjectInfo::decode(&[1u8]).is_err());
}
}