use core::cmp::min;
use core::net::{Ipv4Addr, Ipv6Addr};
use core::str::{from_utf8, FromStr};
use rasn::types::OctetString;
pub fn static_engine_id(enterprise_number: u32, octets: &[u8]) -> OctetString {
let mut buf: [u8; 12] = [0; 12];
let enterprise_bytes = enterprise_number.to_be_bytes();
buf[..4].copy_from_slice(&enterprise_bytes);
buf[4..12].copy_from_slice(octets);
OctetString::from_slice(&buf)
}
pub fn ipv4_engine_id(enterprise_number: u32, address: &str) -> OctetString {
let mut buf: [u8; 9] = [0; 9];
let enterprise_bytes = enterprise_number.to_be_bytes();
let ipv4 = Ipv4Addr::from_str(address).unwrap(); buf[..4].copy_from_slice(&enterprise_bytes);
buf[0] |= 128;
buf[4] = 1;
buf[5..9].copy_from_slice(&ipv4.octets());
OctetString::from_slice(&buf)
}
pub fn ipv6_engine_id(enterprise_number: u32, address: &str) -> OctetString {
let mut buf: [u8; 21] = [0; 21];
let enterprise_bytes = enterprise_number.to_be_bytes();
let ipv6 = Ipv6Addr::from_str(address).unwrap(); buf[..4].copy_from_slice(&enterprise_bytes);
buf[0] |= 128;
buf[4] = 2;
buf[5..21].copy_from_slice(&ipv6.octets());
OctetString::from_slice(&buf)
}
fn mac_to_bytes(s: &str) -> Option<Vec<u8>> {
if s.len() == 17 {
(0..s.len())
.step_by(3)
.map(|i| {
s.get(i..i + 2)
.and_then(|sub| u8::from_str_radix(sub, 16).ok())
})
.collect()
} else {
None
}
}
pub fn mac_engine_id(enterprise_number: u32, address: &str) -> OctetString {
let mut buf: [u8; 11] = [0; 11];
let enterprise_bytes = enterprise_number.to_be_bytes();
let bytes = mac_to_bytes(address).unwrap(); buf[..4].copy_from_slice(&enterprise_bytes);
buf[0] |= 128;
buf[4] = 3;
buf[5..11].copy_from_slice(&bytes[..6]);
OctetString::from_slice(&buf)
}
pub fn text_engine_id(enterprise_number: u32, text: &str) -> OctetString {
let mut buf: [u8; 32] = [0; 32];
let enterprise_bytes = enterprise_number.to_be_bytes();
let bytes = text.as_bytes();
let byte_len = bytes.len();
buf[..4].copy_from_slice(&enterprise_bytes);
buf[0] |= 128;
buf[4] = 4;
buf[5..(byte_len + 5)].copy_from_slice(&bytes[..byte_len]);
let buf_len = 5 + byte_len;
let buf = &buf[..buf_len];
OctetString::from_slice(buf)
}
pub fn byte_engine_id(enterprise_number: u32, bytes: &[u8]) -> OctetString {
let mut buf: [u8; 32] = [0; 32];
let enterprise_bytes = enterprise_number.to_be_bytes();
let byte_len = min(bytes.len(), 27);
buf[..4].copy_from_slice(&enterprise_bytes);
buf[0] |= 128;
buf[4] = 5;
buf[5..(byte_len + 5)].copy_from_slice(&bytes[..byte_len]);
let buf_len = 5 + byte_len;
let buf = &buf[..buf_len];
OctetString::from_slice(buf)
}
pub const LOCAL_ENGINE_ID: OctetString = OctetString::from_static(b"\x80\x00\x00\x00\x06");
pub fn format_engine_id(engine_id: OctetString) -> String {
let mut vid = engine_id.to_vec();
if vid.len() < 5 {
return "Engine ID is too short, less than 5 bytes".to_string();
}
if vid.len() > 31 {
return "Engine ID is too long, more than 31 bytes".to_string();
}
if vid[0] & 0x80 == 0x0 {
if vid.len() != 12 {
"Static ID scheme must be exactly 12 bytes".to_string()
} else {
let enterprise = u32::from_be_bytes(vid[..4].try_into().unwrap()); format!(
"Static ID. Enterprise Number {} Bytes {:?}",
enterprise,
vid[4..].to_vec()
)
}
} else {
vid[0] &= 0x7F;
let enterprise = u32::from_be_bytes(vid[..4].try_into().unwrap());
match vid[4] {
0 => "Reserved scheme 0, should never be used".to_string(),
1 => {
if vid.len() != 9 {
"IPv4 scheme must be exactly 9 bytes".to_string()
} else {
let ipv4 = Ipv4Addr::new(vid[5], vid[6], vid[7], vid[8]);
format!("IPv4 Enterprise Number {} Address {}", enterprise, ipv4)
}
}
2 => {
if vid.len() != 21 {
"IPv6 scheme must be exactly 21 bytes".to_string()
} else {
let ipv6 = Ipv6Addr::from(
<&[u8] as TryInto<[u8; 16]>>::try_into(&vid[5..21]).unwrap(), );
format!("IPv6 Enterprise Number {} Address {}", enterprise, ipv6)
}
}
3 => {
if vid.len() != 11 {
"MAC scheme must be exactly 11 bytes".to_string()
} else {
format!(
"MAC Enterprise Number {} {:X}:{:X}:{:X}:{:X}:{:X}:{:X}",
enterprise, vid[5], vid[6], vid[7], vid[8], vid[9], vid[10]
)
}
}
4 => {
let bytes = vid[5..].to_vec();
let text = from_utf8(&bytes).unwrap(); format!("Text Enterprise Number {} Text {}", enterprise, text)
}
5 => {
format!(
"Byte Enterprise Number {} Bytes {:?}",
enterprise,
vid[5..].to_vec()
)
}
6 => {
if engine_id == LOCAL_ENGINE_ID {
"Local Engine ID for RFC5354".to_string()
} else {
"Scheme 6 is reserved for RFC5354, misused here".to_string()
}
}
7..127 => "Reserved scheme number for future use. Are you sure this is an Engine ID?"
.to_string(),
_ => "Private enterprise use formatting scheme. Are you sure this is an Engine ID?"
.to_string(),
}
}
}
pub fn engine_id_from_str(text: &str) -> OctetString {
let parts: Vec<&str> = text.splitn(3, ' ').collect();
let ent: u32 = u32::from_str(parts[0]).unwrap(); match parts[1] {
"Static" => {
let octets = hex::decode(parts[2]).unwrap(); static_engine_id(ent, &octets)
}
"1" => ipv4_engine_id(ent, parts[2]),
"2" => ipv6_engine_id(ent, parts[2]),
"3" => mac_engine_id(ent, parts[2]),
"4" => text_engine_id(ent, parts[2]),
"5" => {
let octets = hex::decode(parts[2]).unwrap(); byte_engine_id(ent, &octets)
}
_ => panic!("Unsupported scheme"),
}
}
#[cfg(test)]
mod tests {
use rasn::types::OctetString;
use crate::engine_id;
#[test]
fn test_rfc5343() {
assert_eq!(
"Local Engine ID for RFC5354",
engine_id::format_engine_id(engine_id::LOCAL_ENGINE_ID)
);
}
#[test]
fn test_static() {
assert_eq!(
engine_id::engine_id_from_str("1234 Static 6162636465666768"),
engine_id::static_engine_id(1234, b"abcdefgh")
);
assert_eq!(
"Static ID. Enterprise Number 1234 Bytes [97, 98, 99, 100, 101, 102, 103, 104]",
engine_id::format_engine_id(engine_id::static_engine_id(1234, b"abcdefgh"))
);
}
#[test]
fn test_engine_id_from_str() {
assert_eq!(
engine_id::engine_id_from_str("1234 1 127.0.0.1"),
engine_id::ipv4_engine_id(1234, "127.0.0.1")
);
assert_eq!(
"IPv4 Enterprise Number 1234 Address 127.0.0.1",
engine_id::format_engine_id(engine_id::ipv4_engine_id(1234, "127.0.0.1"))
);
}
#[test]
fn test_engine_id_mac_from_str() {
assert!(engine_id::mac_to_bytes("Too short").is_none());
assert_eq!(
engine_id::engine_id_from_str("1234 3 AA:BB:CC:DD:EE:FF"),
engine_id::mac_engine_id(1234, "AA:BB:CC:DD:EE:FF")
);
assert_eq!(
"MAC Enterprise Number 1234 AA:BB:CC:DD:EE:FF",
engine_id::format_engine_id(engine_id::mac_engine_id(1234, "AA:BB:CC:DD:EE:FF"))
);
}
#[test]
fn test_engine_id_ipv6_from_str() {
assert_eq!(
engine_id::engine_id_from_str("1234 2 ::1"),
engine_id::ipv6_engine_id(1234, "::1")
);
assert_eq!(
"IPv6 Enterprise Number 1234 Address ::1",
engine_id::format_engine_id(engine_id::ipv6_engine_id(1234, "::1"))
);
}
#[test]
fn test_engine_id_text_from_str() {
assert_eq!(
engine_id::engine_id_from_str("1234 4 text"),
engine_id::text_engine_id(1234, "text")
);
assert_eq!(
"Text Enterprise Number 1234 Text text",
engine_id::format_engine_id(engine_id::text_engine_id(1234, "text"))
);
}
#[test]
fn test_engine_id_bytes_from_str() {
assert_eq!(
engine_id::engine_id_from_str("1234 5 deadbeef"),
engine_id::byte_engine_id(1234, b"\xde\xad\xbe\xef")
);
assert_eq!(
"Byte Enterprise Number 1234 Bytes [222, 173, 190, 239]",
engine_id::format_engine_id(engine_id::byte_engine_id(1234, b"\xde\xad\xbe\xef"))
);
}
#[test]
fn test_format_bad() {
let vals = vec![
b"tiny".to_vec(),
b"way toooooooooooooooooooooo looooooooooooooooong".to_vec(),
b"Abcdwrong length".to_vec(),
b"\xfabcd\x00Notzero".to_vec(),
b"\xfabcd\x01".to_vec(),
b"\xfabcd\x02".to_vec(),
b"\xfabcd\x03".to_vec(),
b"\xfabcd\x06".to_vec(),
b"\xfabcd\x07".to_vec(),
b"\xfabcd\xa2".to_vec(),
];
for val in vals {
let eid = OctetString::from_slice(&val);
let _ = engine_id::format_engine_id(eid);
}
}
#[test]
#[should_panic]
fn test_bad_parse() {
let _ = engine_id::engine_id_from_str("1234 7 deadbeef");
}
}