use crate::error::XrceError;
use crate::object_kind::ObjectKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct ObjectId(pub u16);
pub const OBJECTID_INVALID: ObjectId = ObjectId(0xFFFF);
pub const OBJECTID_AGENT: ObjectId = ObjectId(0xFFFD);
pub const OBJECTID_CLIENT: ObjectId = ObjectId(0xFFFE);
pub const KIND_MASK_BIT: u16 = 15;
pub const RAW_ID_MAX: u16 = 0x0FFF;
impl ObjectId {
#[must_use]
pub const fn from_raw(value: u16) -> Self {
Self(value)
}
pub fn new(raw_id: u16, kind: ObjectKind) -> Result<Self, XrceError> {
if raw_id > RAW_ID_MAX {
return Err(XrceError::ValueOutOfRange {
message: "ObjectId raw_id exceeds 12 bits",
});
}
Ok(Self((raw_id << 4) | u16::from(kind.to_u8())))
}
pub fn new_with_mask(
raw_id: u16,
kind: ObjectKind,
client_owned: bool,
) -> Result<Self, XrceError> {
if raw_id > 0x07FF {
return Err(XrceError::ValueOutOfRange {
message: "ObjectId raw_id with kind_mask exceeds 11 bits",
});
}
let mut word = (raw_id << 4) | u16::from(kind.to_u8());
if client_owned {
word |= 1u16 << KIND_MASK_BIT;
}
Ok(Self(word))
}
#[must_use]
pub const fn raw(self) -> u16 {
self.0
}
#[must_use]
pub fn is_invalid(self) -> bool {
self == OBJECTID_INVALID
}
pub fn kind(self) -> Result<ObjectKind, XrceError> {
ObjectKind::from_u8((self.0 & 0x000F) as u8)
}
#[must_use]
pub fn raw_id_12(self) -> u16 {
(self.0 >> 4) & 0x0FFF
}
#[must_use]
pub fn raw_id_11(self) -> u16 {
(self.0 >> 4) & 0x07FF
}
#[must_use]
pub fn kind_mask(self) -> bool {
(self.0 & (1u16 << KIND_MASK_BIT)) != 0
}
#[must_use]
pub fn to_bytes(self) -> [u8; 2] {
self.0.to_be_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, XrceError> {
if bytes.len() < 2 {
return Err(XrceError::UnexpectedEof {
needed: 2,
offset: bytes.len(),
});
}
let mut buf = [0u8; 2];
buf.copy_from_slice(&bytes[..2]);
Ok(Self(u16::from_be_bytes(buf)))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::unwrap_used)]
use super::*;
#[test]
fn new_packs_kind_into_lower_4_bits() {
let id = ObjectId::new(0x123, ObjectKind::DataWriter).unwrap();
assert_eq!(id.raw(), (0x123 << 4) | 0x05);
assert_eq!(id.kind().unwrap(), ObjectKind::DataWriter);
assert_eq!(id.raw_id_12(), 0x123);
}
#[test]
fn new_rejects_raw_id_overflow() {
let res = ObjectId::new(0x1000, ObjectKind::Topic);
assert!(res.is_err());
}
#[test]
fn agent_singleton_has_kind_agent() {
assert_eq!(OBJECTID_AGENT.kind().unwrap(), ObjectKind::Agent);
}
#[test]
fn client_singleton_has_kind_client() {
assert_eq!(OBJECTID_CLIENT.kind().unwrap(), ObjectKind::Client);
}
#[test]
fn invalid_object_id_is_all_ones() {
assert!(OBJECTID_INVALID.is_invalid());
assert_eq!(OBJECTID_INVALID.raw(), 0xFFFF);
}
#[test]
fn invalid_kind_lookup_fails() {
let id = ObjectId::from_raw(0xABC7);
assert!(id.kind().is_err());
}
#[test]
fn bytes_are_big_endian() {
let id = ObjectId::from_raw(0x1234);
let b = id.to_bytes();
assert_eq!(b, [0x12, 0x34]);
let id2 = ObjectId::from_bytes(&b).unwrap();
assert_eq!(id2, id);
}
#[test]
fn from_bytes_truncated_returns_eof() {
let res = ObjectId::from_bytes(&[0xAB]);
assert!(matches!(
res,
Err(XrceError::UnexpectedEof { needed: 2, .. })
));
}
#[test]
fn kind_mask_top_bit_distinguishes_client_vs_builtin() {
let builtin = ObjectId::new_with_mask(0x100, ObjectKind::DataWriter, false).unwrap();
let client = ObjectId::new_with_mask(0x100, ObjectKind::DataWriter, true).unwrap();
assert!(!builtin.kind_mask());
assert!(client.kind_mask());
assert_eq!(builtin.raw_id_11(), 0x100);
assert_eq!(client.raw_id_11(), 0x100);
}
#[test]
fn kind_mask_overflow_rejected() {
let res = ObjectId::new_with_mask(0x800, ObjectKind::Topic, false);
assert!(res.is_err());
}
#[test]
fn ordering_is_lexicographic_on_raw() {
let a = ObjectId::from_raw(0x0010);
let b = ObjectId::from_raw(0x0011);
let c = ObjectId::from_raw(0x1000);
assert!(a < b);
assert!(b < c);
}
#[test]
fn default_is_zero() {
assert_eq!(ObjectId::default().raw(), 0);
}
}