use alloc::string::String;
use alloc::vec::Vec;
use zerodds_amqp_bridge::extended_types::AmqpExtValue;
use crate::annex_a::DescriptorForm;
#[must_use]
pub fn narrow_long_double_to_double(value_binary128_as_f64: f64) -> f64 {
value_binary128_as_f64
}
pub fn validate_char_ascii(byte: u8) -> Result<u8, u8> {
if byte <= 0x7F { Ok(byte) } else { Err(byte) }
}
#[must_use]
pub fn encode_16byte_identifier(bytes: [u8; 16], is_rfc4122_uuid: bool) -> AmqpExtValue {
if is_rfc4122_uuid {
AmqpExtValue::Uuid(bytes)
} else {
AmqpExtValue::Binary(bytes.to_vec())
}
}
#[must_use]
pub fn is_rfc4122_uuid(bytes: &[u8; 16]) -> bool {
let variant_ok = (bytes[8] & 0xC0) == 0x80;
let version = (bytes[6] >> 4) & 0x0F;
let version_ok = (1..=5).contains(&version);
variant_ok && version_ok
}
pub fn compute_truncated_descriptor(hash_bytes: &[u8]) -> Result<u64, &'static str> {
if hash_bytes.len() < 8 {
return Err("hash_bytes shorter than 8 octets");
}
let mut buf = [0u8; 8];
buf.copy_from_slice(&hash_bytes[..8]);
Ok(u64::from_be_bytes(buf))
}
#[must_use]
pub fn make_full_descriptor_symbol(type_identifier_bytes: &[u8]) -> String {
let mut s = String::with_capacity(9 + type_identifier_bytes.len() * 2);
s.push_str("dds:type:");
for b in type_identifier_bytes {
let _ = core::fmt::Write::write_fmt(&mut s, core::format_args!("{b:02x}"));
}
s
}
pub fn route_descriptor(
form: DescriptorForm,
hash_bytes: &[u8],
) -> Result<(Option<u64>, Option<String>), &'static str> {
match form {
DescriptorForm::DescTruncated => {
let n = compute_truncated_descriptor(hash_bytes)?;
Ok((Some(n), None))
}
DescriptorForm::DescFull => Ok((None, Some(make_full_descriptor_symbol(hash_bytes)))),
}
}
#[must_use]
pub fn make_union_body(
discriminator: AmqpExtValue,
active_value: Option<AmqpExtValue>,
) -> AmqpExtValue {
let mut items: Vec<AmqpExtValue> = Vec::with_capacity(2);
items.push(discriminator);
if let Some(v) = active_value {
items.push(v);
}
AmqpExtValue::List(items)
}
#[must_use]
pub fn empty_sequence() -> AmqpExtValue {
AmqpExtValue::List(Vec::new())
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn ascii_validates() {
for b in 0x00..=0x7F {
assert_eq!(validate_char_ascii(b), Ok(b));
}
}
#[test]
fn non_ascii_rejected() {
for b in 0x80..=0xFFu8 {
assert_eq!(validate_char_ascii(b), Err(b));
}
}
#[test]
fn rfc4122_v4_uuid_recognised() {
let bytes = [
0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x4A, 0xBC, 0x82, 0x34, 0x56, 0x78, 0x9A, 0xBC,
0xDE, 0xF0,
];
assert!(is_rfc4122_uuid(&bytes));
}
#[test]
fn arbitrary_16_bytes_not_recognised_as_uuid() {
let bytes = [0x00; 16];
assert!(!is_rfc4122_uuid(&bytes));
}
#[test]
fn encode_16byte_routes_binary_for_non_uuid() {
let bytes = [0xDE, 0xAD, 0xBE, 0xEF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let v = encode_16byte_identifier(bytes, false);
match v {
AmqpExtValue::Binary(b) => assert_eq!(b.len(), 16),
_ => panic!("expected binary"),
}
}
#[test]
fn encode_16byte_routes_uuid_when_marked() {
let bytes = [0u8; 16];
let v = encode_16byte_identifier(bytes, true);
assert!(matches!(v, AmqpExtValue::Uuid(_)));
}
#[test]
fn truncated_descriptor_first_8_bytes_be() {
let hash = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
];
let n = compute_truncated_descriptor(&hash).unwrap();
assert_eq!(n, 0x0102_0304_0506_0708);
}
#[test]
fn truncated_descriptor_too_short_errors() {
let hash = [0u8; 7];
assert!(compute_truncated_descriptor(&hash).is_err());
}
#[test]
fn full_descriptor_symbol_format() {
let bytes = [0xDE, 0xAD, 0xBE, 0xEF];
let s = make_full_descriptor_symbol(&bytes);
assert_eq!(s, "dds:type:deadbeef");
}
#[test]
fn route_descriptor_truncated() {
let hash = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
];
let (num, sym) = route_descriptor(DescriptorForm::DescTruncated, &hash).unwrap();
assert!(num.is_some());
assert!(sym.is_none());
}
#[test]
fn route_descriptor_full() {
let hash = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
];
let (num, sym) = route_descriptor(DescriptorForm::DescFull, &hash).unwrap();
assert!(num.is_none());
assert!(sym.is_some());
assert!(sym.unwrap().starts_with("dds:type:"));
}
#[test]
fn union_with_branch_has_two_elements() {
let u = make_union_body(
AmqpExtValue::Int(1),
Some(AmqpExtValue::Str("hello".into())),
);
match u {
AmqpExtValue::List(items) => {
assert_eq!(items.len(), 2);
assert_eq!(items[0], AmqpExtValue::Int(1));
}
_ => panic!("expected list"),
}
}
#[test]
fn union_empty_branch_omits_value() {
let u = make_union_body(AmqpExtValue::Int(99), None);
match u {
AmqpExtValue::List(items) => {
assert_eq!(items.len(), 1);
assert_eq!(items[0], AmqpExtValue::Int(99));
}
_ => panic!(),
}
}
#[test]
fn empty_sequence_yields_empty_list() {
match empty_sequence() {
AmqpExtValue::List(items) => assert!(items.is_empty()),
_ => panic!(),
}
}
#[test]
fn long_double_narrowing_is_identity_on_f64() {
let v = 1.234_567_890_123_456_7;
assert_eq!(narrow_long_double_to_double(v), v);
}
}