use alloc::string::String;
use alloc::vec::Vec;
use zerodds_cdr::Endianness;
use crate::error::{IorError, IorResult};
use crate::ior::Ior;
pub const STRINGIFIED_IOR_PREFIX: &str = "IOR:";
pub fn to_stringified(ior: &Ior, endianness: Endianness) -> IorResult<String> {
let bytes = ior.encode_encapsulation(endianness)?;
let mut out = String::with_capacity(STRINGIFIED_IOR_PREFIX.len() + bytes.len() * 2);
out.push_str(STRINGIFIED_IOR_PREFIX);
for b in bytes {
out.push(hex_nibble(b >> 4));
out.push(hex_nibble(b & 0x0f));
}
Ok(out)
}
pub fn from_stringified(s: &str) -> IorResult<Ior> {
let s = s.trim();
let payload = s
.strip_prefix(STRINGIFIED_IOR_PREFIX)
.ok_or(IorError::MissingIorPrefix)?;
if payload.len() % 2 != 0 {
return Err(IorError::OddHexLength);
}
let mut bytes = Vec::with_capacity(payload.len() / 2);
let mut chars = payload.chars();
while let (Some(hi), Some(lo)) = (chars.next(), chars.next()) {
let h = hex_value(hi)?;
let l = hex_value(lo)?;
bytes.push((h << 4) | l);
}
Ok(Ior::decode_encapsulation(&bytes)?)
}
const fn hex_nibble(n: u8) -> char {
match n {
0..=9 => (b'0' + n) as char,
10..=15 => (b'a' + n - 10) as char,
_ => '0', }
}
fn hex_value(c: char) -> IorResult<u8> {
match c {
'0'..='9' => Ok(c as u8 - b'0'),
'a'..='f' => Ok(c as u8 - b'a' + 10),
'A'..='F' => Ok(c as u8 - b'A' + 10),
other => Err(IorError::InvalidHexChar(other)),
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use crate::tagged_profile::TaggedProfile;
use zerodds_corba_iiop::{IiopProfileBody, IiopVersion};
fn sample_ior() -> Ior {
let body = IiopProfileBody::new(
IiopVersion::V1_2,
"host.example".into(),
7777,
alloc::vec![0xde, 0xad, 0xbe, 0xef],
);
let profile = TaggedProfile::iiop(&body, Endianness::Big).unwrap();
Ior::new("IDL:demo/Echo:1.0".into(), alloc::vec![profile])
}
#[test]
fn round_trip_be() {
let ior = sample_ior();
let s = to_stringified(&ior, Endianness::Big).unwrap();
assert!(s.starts_with("IOR:"));
let decoded = from_stringified(&s).unwrap();
assert_eq!(decoded, ior);
}
#[test]
fn round_trip_le() {
let ior = sample_ior();
let s = to_stringified(&ior, Endianness::Little).unwrap();
let decoded = from_stringified(&s).unwrap();
assert_eq!(decoded, ior);
}
#[test]
fn upper_and_lower_hex_both_decoded() {
let ior = sample_ior();
let s = to_stringified(&ior, Endianness::Big).unwrap();
let mut canonical_prefix_then_upper = String::from("IOR:");
canonical_prefix_then_upper.push_str(&s["IOR:".len()..].to_uppercase());
let decoded = from_stringified(&canonical_prefix_then_upper).unwrap();
assert_eq!(decoded, ior);
}
#[test]
fn missing_prefix_is_diagnostic() {
let err = from_stringified("00000000abcdef").unwrap_err();
assert!(matches!(err, IorError::MissingIorPrefix));
}
#[test]
fn odd_hex_length_is_diagnostic() {
let err = from_stringified("IOR:abc").unwrap_err();
assert!(matches!(err, IorError::OddHexLength));
}
#[test]
fn non_hex_char_is_diagnostic() {
let err = from_stringified("IOR:zz").unwrap_err();
assert!(matches!(err, IorError::InvalidHexChar('z')));
}
#[test]
fn nil_ior_round_trip() {
let nil = Ior::default();
let s = to_stringified(&nil, Endianness::Big).unwrap();
let decoded = from_stringified(&s).unwrap();
assert_eq!(decoded, nil);
assert!(decoded.is_nil());
}
}