use crate::error::{IgtlError, Result};
use bytes::{Buf, BufMut, BytesMut};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeName([u8; 12]);
impl TypeName {
pub fn new(name: &str) -> Result<Self> {
if name.len() > 12 {
return Err(IgtlError::InvalidHeader(format!(
"Type name too long: {} bytes (max: 12)",
name.len()
)));
}
let mut bytes = [0u8; 12];
bytes[..name.len()].copy_from_slice(name.as_bytes());
Ok(TypeName(bytes))
}
pub fn as_str(&self) -> Result<&str> {
let len = self.0.iter().position(|&b| b == 0).unwrap_or(12);
std::str::from_utf8(&self.0[..len])
.map_err(|_| IgtlError::InvalidHeader("Invalid UTF-8 in type name".to_string()))
}
}
impl From<[u8; 12]> for TypeName {
fn from(bytes: [u8; 12]) -> Self {
TypeName(bytes)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeviceName([u8; 20]);
impl DeviceName {
pub fn new(name: &str) -> Result<Self> {
if name.len() > 20 {
return Err(IgtlError::InvalidHeader(format!(
"Device name too long: {} bytes (max: 20)",
name.len()
)));
}
let mut bytes = [0u8; 20];
bytes[..name.len()].copy_from_slice(name.as_bytes());
Ok(DeviceName(bytes))
}
pub fn as_str(&self) -> Result<&str> {
let len = self.0.iter().position(|&b| b == 0).unwrap_or(20);
std::str::from_utf8(&self.0[..len])
.map_err(|_| IgtlError::InvalidHeader("Invalid UTF-8 in device name".to_string()))
}
}
impl From<[u8; 20]> for DeviceName {
fn from(bytes: [u8; 20]) -> Self {
DeviceName(bytes)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Timestamp {
pub seconds: u32,
pub fraction: u32,
}
impl Timestamp {
pub fn new(seconds: u32, fraction: u32) -> Self {
Timestamp { seconds, fraction }
}
pub fn now() -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap();
let seconds = now.as_secs() as u32;
let nanos = now.subsec_nanos();
let fraction = ((nanos as u64) * 0x1_0000_0000 / 1_000_000_000) as u32;
Timestamp { seconds, fraction }
}
pub fn zero() -> Self {
Timestamp {
seconds: 0,
fraction: 0,
}
}
pub fn to_u64(self) -> u64 {
((self.seconds as u64) << 32) | (self.fraction as u64)
}
pub fn from_u64(value: u64) -> Self {
Timestamp {
seconds: (value >> 32) as u32,
fraction: (value & 0xFFFFFFFF) as u32,
}
}
pub fn to_nanos(self) -> u64 {
let sec_nanos = (self.seconds as u64) * 1_000_000_000;
let frac_nanos = ((self.fraction as u64) * 1_000_000_000) / 0x1_0000_0000;
sec_nanos + frac_nanos
}
pub fn from_nanos(nanos: u64) -> Self {
let seconds = (nanos / 1_000_000_000) as u32;
let remaining_nanos = (nanos % 1_000_000_000) as u32;
let fraction = ((remaining_nanos as u64) * 0x1_0000_0000 / 1_000_000_000) as u32;
Timestamp { seconds, fraction }
}
pub fn to_f64(self) -> f64 {
let frac_f64 = (self.fraction as f64) / (u32::MAX as f64 + 1.0);
(self.seconds as f64) + frac_f64
}
}
#[derive(Debug, Clone)]
pub struct Header {
pub version: u16,
pub type_name: TypeName,
pub device_name: DeviceName,
pub timestamp: Timestamp,
pub body_size: u64,
pub crc: u64,
}
impl Header {
pub const SIZE: usize = 58;
pub fn decode(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::SIZE {
return Err(IgtlError::InvalidSize {
expected: Self::SIZE,
actual: buf.len(),
});
}
let mut cursor = std::io::Cursor::new(buf);
let version = cursor.get_u16();
let mut type_bytes = [0u8; 12];
cursor.copy_to_slice(&mut type_bytes);
let type_name = TypeName::from(type_bytes);
let mut device_bytes = [0u8; 20];
cursor.copy_to_slice(&mut device_bytes);
let device_name = DeviceName::from(device_bytes);
let timestamp_u64 = cursor.get_u64();
let timestamp = Timestamp::from_u64(timestamp_u64);
let body_size = cursor.get_u64();
let crc = cursor.get_u64();
Ok(Header {
version,
type_name,
device_name,
timestamp,
body_size,
crc,
})
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = BytesMut::with_capacity(Self::SIZE);
buf.put_u16(self.version);
buf.put_slice(&self.type_name.0);
buf.put_slice(&self.device_name.0);
buf.put_u64(self.timestamp.to_u64());
buf.put_u64(self.body_size);
buf.put_u64(self.crc);
buf.to_vec()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_type_name_creation() {
let name = TypeName::new("TRANSFORM").unwrap();
assert_eq!(name.as_str().unwrap(), "TRANSFORM");
}
#[test]
fn test_type_name_too_long() {
let result = TypeName::new("VERY_LONG_TYPE_NAME");
assert!(result.is_err());
}
#[test]
fn test_device_name_creation() {
let name = DeviceName::new("TestDevice").unwrap();
assert_eq!(name.as_str().unwrap(), "TestDevice");
}
#[test]
fn test_header_size() {
assert_eq!(Header::SIZE, 58);
}
#[test]
fn test_timestamp_now() {
let ts = Timestamp::now();
assert!(ts.seconds > 0);
assert!(ts.to_f64() > 0.0);
}
#[test]
fn test_timestamp_zero() {
let ts = Timestamp::zero();
assert_eq!(ts.seconds, 0);
assert_eq!(ts.fraction, 0);
assert_eq!(ts.to_u64(), 0);
}
#[test]
fn test_timestamp_conversion() {
let ts = Timestamp::new(1000, 0x80000000); assert_eq!(ts.seconds, 1000);
assert_eq!(ts.fraction, 0x80000000);
let nanos = ts.to_nanos();
assert_eq!(nanos, 1_000_500_000_000);
let ts2 = Timestamp::from_nanos(nanos);
assert_eq!(ts2.seconds, ts.seconds);
assert!((ts2.fraction as i64 - ts.fraction as i64).abs() < 100);
let f = ts.to_f64();
assert!((f - 1000.5).abs() < 0.0001);
}
#[test]
fn test_timestamp_u64_roundtrip() {
let original = Timestamp::new(1234567890, 0xABCDEF12);
let u64_val = original.to_u64();
let restored = Timestamp::from_u64(u64_val);
assert_eq!(restored.seconds, original.seconds);
assert_eq!(restored.fraction, original.fraction);
}
#[test]
fn test_header_roundtrip() {
let original = Header {
version: 2,
type_name: TypeName::new("TRANSFORM").unwrap(),
device_name: DeviceName::new("TestDevice").unwrap(),
timestamp: Timestamp::new(1234567890, 0x12345678),
body_size: 48,
crc: 0xDEADBEEFCAFEBABE,
};
let encoded = original.encode();
assert_eq!(encoded.len(), Header::SIZE);
let decoded = Header::decode(&encoded).unwrap();
assert_eq!(decoded.version, original.version);
assert_eq!(decoded.type_name, original.type_name);
assert_eq!(decoded.device_name, original.device_name);
assert_eq!(decoded.timestamp, original.timestamp);
assert_eq!(decoded.body_size, original.body_size);
assert_eq!(decoded.crc, original.crc);
}
#[test]
fn test_header_decode_short_buffer() {
let short_buf = vec![0u8; 30];
let result = Header::decode(&short_buf);
assert!(matches!(result, Err(IgtlError::InvalidSize { .. })));
}
#[test]
fn test_big_endian_encoding() {
let header = Header {
version: 0x0102,
type_name: TypeName::new("TEST").unwrap(),
device_name: DeviceName::new("DEV").unwrap(),
timestamp: Timestamp::from_u64(0x0102030405060708),
body_size: 0x090A0B0C0D0E0F10,
crc: 0x1112131415161718,
};
let encoded = header.encode();
assert_eq!(encoded[0], 0x01);
assert_eq!(encoded[1], 0x02);
assert_eq!(encoded[34], 0x01);
assert_eq!(encoded[35], 0x02);
assert_eq!(encoded[36], 0x03);
assert_eq!(encoded[37], 0x04);
}
}