mod hash;
mod type_description;
mod type_id;
pub use hash::{calculate_hash, to_ros2_json};
pub use type_description::{
FieldDescription, FieldTypeDescription, TypeDescription, TypeDescriptionMsg, to_hash_version,
};
pub use type_id::TypeId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TypeHash(pub [u8; 32]);
impl TypeHash {
pub fn to_rihs_string(&self) -> String {
format!("RIHS01_{}", hex::encode(self.0))
}
pub fn from_rihs_string(s: &str) -> Result<Self, String> {
if !s.starts_with("RIHS01_") {
return Err("Invalid RIHS01 format: must start with 'RIHS01_'".to_string());
}
let hex_part = &s[7..];
let bytes = hex::decode(hex_part).map_err(|e| format!("Invalid hex encoding: {}", e))?;
if bytes.len() != 32 {
return Err(format!("Hash must be 32 bytes, got {}", bytes.len()));
}
let mut hash = [0u8; 32];
hash.copy_from_slice(&bytes);
Ok(TypeHash(hash))
}
pub fn zero() -> Self {
TypeHash([0u8; 32])
}
}
impl std::fmt::Display for TypeHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_rihs_string())
}
}
impl Default for TypeHash {
fn default() -> Self {
Self::zero()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_type_hash_roundtrip() {
let hash = TypeHash([
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
0x66, 0x77, 0x88, 0x99,
]);
let s = hash.to_rihs_string();
assert!(s.starts_with("RIHS01_"));
assert_eq!(s.len(), 7 + 64);
let decoded = TypeHash::from_rihs_string(&s).unwrap();
assert_eq!(hash, decoded);
}
#[test]
fn test_type_hash_invalid_prefix() {
let result = TypeHash::from_rihs_string("INVALID_1234");
assert!(result.is_err());
}
#[test]
fn test_type_hash_invalid_length() {
let result = TypeHash::from_rihs_string("RIHS01_1234");
assert!(result.is_err());
}
}