use crate::DEFAULT_ITOT_TRANSPORT_SET;
#[cfg(feature = "nonstddisplay")]
use crate::data::{AFI_IANA_ICP_BIN, AFI_URL, IANA_ICP_IDI_IPV4, IANA_ICP_IDI_IPV6};
use crate::data::{
AFI_STR_DCC, AFI_STR_ICD, AFI_STR_ICP, AFI_STR_IND, AFI_STR_ISDN, AFI_STR_LOCAL, AFI_STR_PSTN,
AFI_STR_TELEX, AFI_STR_URL, AFI_STR_X121, ITOT_OVER_IPV4_DEFAULT_PORT,
ITU_X519_DSP_PREFIX_IDM_OVER_IPV4, ITU_X519_DSP_PREFIX_LDAP, RFC_1277_PREFIX,
RFC_1277_WELL_KNOWN_NETWORK_DARPA_NSF_INTERNET, is_group_afi,
};
use crate::isoiec646::local_iso_iec_646_byte_to_char;
use crate::{DSPSyntax, X213NetworkAddress, X213NetworkAddressType};
use core::fmt::{Display, Write};
use core::net::Ipv4Addr;
#[cfg(feature = "nonstddisplay")]
use core::net::Ipv6Addr;
#[inline]
pub const fn naddr_network_type_to_str(nt: X213NetworkAddressType) -> &'static str {
match nt {
X213NetworkAddressType::X121 => AFI_STR_X121,
X213NetworkAddressType::ISO_DCC => AFI_STR_DCC,
X213NetworkAddressType::F69 => AFI_STR_TELEX,
X213NetworkAddressType::E163 => AFI_STR_PSTN,
X213NetworkAddressType::E164 => AFI_STR_ISDN,
X213NetworkAddressType::ISO_6523_ICD => AFI_STR_ICD,
X213NetworkAddressType::IANA_ICP => AFI_STR_ICP, X213NetworkAddressType::ITU_T_IND => AFI_STR_IND, X213NetworkAddressType::LOCAL => AFI_STR_LOCAL,
X213NetworkAddressType::URL => AFI_STR_URL, }
}
#[inline]
pub(crate) fn fmt_naddr_type(
t: &X213NetworkAddressType,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
f.write_str(naddr_network_type_to_str(*t))
}
fn ipv4_from_slice(bytes: &[u8]) -> Option<Ipv4Addr> {
debug_assert_eq!(bytes.len(), 6);
let oct1: u32 = (((bytes[0] & 0xF0) >> 4) as u32 * 100)
+ (((bytes[0] & 0x0F) >> 0) as u32 * 10)
+ (((bytes[1] & 0xF0) >> 4) as u32 * 1);
let oct2: u32 = (((bytes[1] & 0x0F) >> 0) as u32 * 100)
+ (((bytes[2] & 0xF0) >> 4) as u32 * 10)
+ (((bytes[2] & 0x0F) >> 0) as u32 * 1);
let oct3: u32 = (((bytes[3] & 0xF0) >> 4) as u32 * 100)
+ (((bytes[3] & 0x0F) >> 0) as u32 * 10)
+ (((bytes[4] & 0xF0) >> 4) as u32 * 1);
let oct4: u32 = (((bytes[4] & 0x0F) >> 0) as u32 * 100)
+ (((bytes[5] & 0xF0) >> 4) as u32 * 10)
+ (((bytes[5] & 0x0F) >> 0) as u32 * 1);
let oct1: u8 = oct1.try_into().ok()?;
let oct2: u8 = oct2.try_into().ok()?;
let oct3: u8 = oct3.try_into().ok()?;
let oct4: u8 = oct4.try_into().ok()?;
Some(Ipv4Addr::new(oct1, oct2, oct3, oct4))
}
pub(crate) fn fmt_naddr(
naddr: &X213NetworkAddress<'_>,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
let octets = naddr.get_octets();
#[cfg(feature = "nonstddisplay")]
match octets.get(0..3) {
Some(octs) if octs[0] == AFI_URL => {
if let Ok(url) = str::from_utf8(&octets[3..]) {
if !url.contains('_') {
return write!(f, "URL+{:02X}{:02X}+{}", octs[1], octs[2], url);
}
}
}
_ => (),
};
#[cfg(feature = "nonstddisplay")]
if octets[0] == AFI_IANA_ICP_BIN && octets.len() == 20 {
let icp = &octets[1..3];
if icp == IANA_ICP_IDI_IPV6.as_slice() {
let ip = Ipv6Addr::from([
octets[3], octets[4], octets[5], octets[6], octets[7], octets[8], octets[9],
octets[10], octets[11], octets[12], octets[13], octets[14], octets[15], octets[16],
octets[17], octets[18],
]);
return write!(f, "IP6+{}", ip);
}
if icp == IANA_ICP_IDI_IPV4.as_slice() {
let ip = Ipv4Addr::from([octets[3], octets[4], octets[5], octets[6]]);
return write!(f, "IP4+{}", ip);
}
}
let is_rfc1278_ip: bool = octets.starts_with(RFC_1277_PREFIX.as_slice())
&& octets.len() >= RFC_1277_PREFIX.len() + 7
&& matches!(
octets[5],
RFC_1277_WELL_KNOWN_NETWORK_DARPA_NSF_INTERNET
| ITU_X519_DSP_PREFIX_IDM_OVER_IPV4
| ITU_X519_DSP_PREFIX_LDAP
);
if is_rfc1278_ip {
let ip_and_stuff = &octets[RFC_1277_PREFIX.len() + 1..];
let ip = ipv4_from_slice(&ip_and_stuff[0..6]);
let port: u32 = if octets.len() >= RFC_1277_PREFIX.len() + 1 + 6 + 3 {
(((ip_and_stuff[6] & 0xF0) >> 4) as u32 * 10000)
+ (((ip_and_stuff[6] & 0x0F) >> 0) as u32 * 1000)
+ (((ip_and_stuff[7] & 0xF0) >> 4) as u32 * 100)
+ (((ip_and_stuff[7] & 0x0F) >> 0) as u32 * 10)
+ (((ip_and_stuff[8] & 0xF0) >> 4) as u32 * 1)
} else {
ITOT_OVER_IPV4_DEFAULT_PORT as u32
};
let tset: u32 = if octets.len() >= RFC_1277_PREFIX.len() + 1 + 6 + 5 {
(((ip_and_stuff[8] & 0x0F) >> 0) as u32 * 10000)
+ (((ip_and_stuff[9] & 0xF0) >> 4) as u32 * 1000)
+ (((ip_and_stuff[9] & 0x0F) >> 0) as u32 * 100)
+ (((ip_and_stuff[10] & 0xF0) >> 4) as u32 * 10)
+ (((ip_and_stuff[10] & 0x0F) >> 0) as u32 * 1)
} else {
DEFAULT_ITOT_TRANSPORT_SET as u32
};
let port: u16 = port.try_into().unwrap_or(0);
let tset: u16 = tset.try_into().unwrap_or(DEFAULT_ITOT_TRANSPORT_SET);
if let Some(ip) = ip {
write!(f, "TELEX+00728722+RFC-1006+03+{}", ip)?;
if port != ITOT_OVER_IPV4_DEFAULT_PORT {
write!(f, "+{}", port)?;
}
if tset != DEFAULT_ITOT_TRANSPORT_SET {
write!(f, "+{}", tset)?;
}
return Ok(());
}
}
let (info, idi_digits) = match (naddr.get_network_type_info(), naddr.idi_digits()) {
(Some(i), Some(d)) => (i, d),
_ => return naddr.fmt_as_ns_string(f),
};
let is_non_standard: bool = matches!(
info.network_type,
X213NetworkAddressType::URL
| X213NetworkAddressType::IANA_ICP
| X213NetworkAddressType::ITU_T_IND
);
let cant_display: bool =
(is_non_standard && !cfg!(feature = "nonstddisplay")) || is_group_afi(naddr.afi());
if cant_display {
return naddr.fmt_as_ns_string(f);
}
info.network_type.fmt(f)?;
f.write_char('+')?;
for digit in idi_digits {
f.write_char((digit + 0x30) as char)?;
}
f.write_char('+')?;
let idi_len = info.max_idi_len_digits as usize;
let idi_len_in_bytes: usize = idi_len >> 1;
match info.dsp_syntax {
DSPSyntax::Decimal => {
match naddr.dsp_digits() {
Some(dsp_digits) => {
f.write_char('d')?;
for digit in dsp_digits {
f.write_char((digit + 0x30).into())?;
}
}
None => {
f.write_char('x')?;
match naddr.get_octets().get(1 + idi_len_in_bytes..) {
Some(dsp) => {
for byte in dsp {
f.write_fmt(format_args!("{:02X}", *byte))?;
}
}
None => (),
};
}
};
}
DSPSyntax::Binary | DSPSyntax::NationalChars => {
let dsp = &octets[1 + idi_len_in_bytes..];
f.write_char('x')?;
for byte in dsp {
f.write_fmt(format_args!("{:02X}", *byte))?;
}
}
DSPSyntax::IsoIec646Chars => {
let dsp = &octets[1 + idi_len_in_bytes..];
let decode = dsp
.iter()
.map(|b| local_iso_iec_646_byte_to_char(*b).unwrap_or('?'));
for c in decode {
f.write_char(c)?;
}
}
};
Ok(())
}
#[cfg(test)]
mod tests {
extern crate alloc;
use crate::X213NetworkAddress;
#[cfg(feature = "nonstddisplay")]
use crate::data::AFI_IANA_ICP_BIN;
use alloc::string::ToString;
#[test]
fn test_display_01() {
let input = [
0x36u8, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x12, 0x34, 0x56, 0x78, 0x90,
];
let addr = X213NetworkAddress::try_from(input.as_slice()).unwrap();
let addr_str = addr.to_string();
assert_eq!(addr_str, "X121+102030405+d1234567890");
}
#[cfg(feature = "nonstddisplay")]
#[test]
fn test_display_02_url() {
let input = b"\xFF\x00\x01https://wildboarsoftware.com/x500directory";
let addr = X213NetworkAddress::try_from(input.as_slice()).unwrap();
let addr_str = addr.to_string();
assert_eq!(
addr_str,
"URL+0001+https://wildboarsoftware.com/x500directory"
);
}
#[test]
fn test_display_02_itot() {
let input = &[
0x54, 0, 0x72, 0x87, 0x22, 3, 1, 0, 0, 0, 0, 6, 0, 0, 0x90, 0, 2,
];
let addr = X213NetworkAddress::try_from(input.as_slice()).unwrap();
let addr_str = addr.to_string();
assert_eq!(addr_str, "TELEX+00728722+RFC-1006+03+10.0.0.6+9+2");
}
#[cfg(feature = "nonstddisplay")]
#[test]
fn test_display_03_ip() {
let input = &[
AFI_IANA_ICP_BIN,
0,
1,
192,
168,
1,
100,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
];
let addr = X213NetworkAddress::try_from(input.as_slice()).unwrap();
let addr_str = addr.to_string();
assert_eq!(addr_str, "IP4+192.168.1.100");
}
}