nsap_address/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![allow(non_camel_case_types)]
4mod bcd;
5mod data;
6mod display;
7mod error;
8mod isoiec646;
9mod parse;
10mod utils;
11use crate::display::{fmt_naddr, fmt_naddr_type};
12use crate::parse::parse_nsap;
13use crate::utils::{u8_to_decimal_bytes, u16_to_decimal_bytes};
14pub use bcd::*;
15use core::convert::TryFrom;
16use core::fmt::Display;
17use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4};
18use core::str::FromStr;
19pub use data::*;
20pub use error::*;
21pub use isoiec646::*;
22
23#[cfg(feature = "alloc")]
24extern crate alloc;
25#[cfg(feature = "alloc")]
26use alloc::string::String;
27#[cfg(feature = "alloc")]
28use alloc::vec::Vec;
29
30/// Authority and Format Identifier (AFI): part of an NSAP address
31pub type AFI = u8;
32
33/// Network identifier, encoded as Binary-Coded Decimal (BCD), per IETF RFC 1277
34pub type Rfc1277NetworkId = u8;
35
36/// Transport set, per IETF RFC 1277
37pub type Rfc1277TransportSet = u16;
38
39/// The default ISO Transport over TCP transport set (t-set) per IETF RFC 1277
40pub const DEFAULT_ITOT_TRANSPORT_SET: Rfc1277TransportSet = 1;
41
42/// Socket information, per IETF RFC 1277
43pub type Rfc1277SocketInfo = (Rfc1277NetworkId, SocketAddrV4, Rfc1277TransportSet);
44
45/// X.213 NSAP Domain-Specific Part Syntax
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum DSPSyntax {
48    /// Binary-Coded Decimal (BCD) with 0b1111 used as padding to produce an
49    /// integral number of octets
50    Decimal,
51    /// Opaque binary encoding
52    Binary,
53    /// ISO/IEC 646 characters, which is basically ASCII
54    IsoIec646Chars,
55    /// Characters from a national character set
56    NationalChars,
57}
58
59/// X.213 NSAP network address type
60#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
61pub enum X213NetworkAddressType {
62    /// IDI based on ITU-T Recommendation X.121 address for use in X.25 Networks
63    ///
64    /// Quoting ITU-T Recommendation X.213 (2001):
65    ///
66    /// > The IDI consists of an international public data network number of up
67    /// > to 14 digits allocated according to ITU-T Rec. X.121, commencing with
68    /// > the Data Network Identification Code. The full X.121 number
69    /// > identifies an authority responsible for allocating and assigning
70    /// > values of the DSP.
71    ///
72    /// See: <https://www.itu.int/rec/T-REC-X.121-200010-I/en>
73    X121,
74    /// IDI based on International Organization for Standardization (ISO) Data Country Code (DCC)
75    ///
76    /// Quoting / Paraphrasing ITU-T Recommendation X.213 (2001):
77    ///
78    /// The IDI consists of a fixed length 3-digit numeric code allocated
79    /// according to ISO 3166-1. The DSP is allocated and assigned by the ISO
80    /// member body or sponsored organization to which the ISO DCC value has
81    /// been assigned, or by an organization designated by the holder of the
82    /// ISO DCC value to carry out this responsibility.
83    ///
84    /// See: <https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes>
85    ISO_DCC,
86    /// IDI based on ITU-T Recommendation F.69 address for use in Telex
87    ///
88    /// Quoting ITU-T Recommendation X.213 (2001):
89    ///
90    /// > The IDI consists of a telex number of up to 8 digits, allocated
91    /// > according to ITU-T Rec. F.69, commencing with a 2- or
92    /// > 3-digit destination code. The full telex number identifies an
93    /// > authority responsible for allocating and assigning values of the DSP.
94    ///
95    /// A particular IDI for this network type is used to provide a namespace
96    /// for IP networking within NSAP addressing: `00728722`. Its usage is
97    /// described in IETF RFC
98    ///
99    /// See: <https://www.itu.int/rec/T-REC-F.69-199406-I/en>
100    F69,
101    /// IDI based on ITU-T Rec. E.163 address for use in the PSTN
102    ///
103    /// This is a phone number. This network type was deprecated at or before
104    /// 2001 and you should use E.164 addressing instead.
105    ///
106    /// See: <https://www.itu.int/rec/T-REC-E.163/en>
107    E163,
108    /// IDI based on ITU-T Rec. E.164 address for use in the ISDN
109    ///
110    /// This is a phone number.
111    ///
112    /// Quoting ITU-T Recommendation X.213 (2001):
113    ///
114    /// > The IDI consists of an international public telecommunication
115    /// > numbering plan number of up to 15 digits allocated
116    /// > according to ITU-T Rec. E.164, commencing with the E.164
117    /// > international number country code. The full E.164 number
118    /// > identifies an authority responsible for allocating and assigning
119    /// > values of the DSP.
120    ///
121    /// See: <https://www.itu.int/rec/T-REC-E.164/en>
122    E164,
123    /// IDI is an assigned ISO/IEC 6523-1 International Code Designator (ICD)
124    ISO_6523_ICD,
125    /// IPv4 or IPv6 address, depending on the IDI, which is assigned by IANA
126    ///
127    /// For either version, the IP address is encoded in binary format, and
128    /// padded with zeroes to be exactly 20 bytes in total, after the AFI and
129    /// IDI (which identifies the version).
130    ///
131    /// See: <https://www.rfc-editor.org/rfc/rfc4548.html>
132    IANA_ICP,
133    /// International Network Designator (IND)
134    ITU_T_IND,
135    /// Locally-assigned DSP
136    LOCAL,
137    /// Special URL encoding defined (without a name) in ITU-T Rec. X.519.
138    ///
139    /// See: <https://www.itu.int/rec/T-REC-X.519>, Section 11.4
140    URL,
141}
142
143impl Display for X213NetworkAddressType {
144    /// Prints a human-readable string, per the procedures defined in
145    /// [IETF RFC 1278](https://datatracker.ietf.org/doc/rfc1278/).
146    #[inline]
147    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
148        fmt_naddr_type(self, f)
149    }
150}
151
152impl TryFrom<AFI> for X213NetworkAddressType {
153    type Error = ();
154
155    #[inline]
156    fn try_from(value: AFI) -> Result<Self, Self::Error> {
157        afi_to_network_type(value).ok_or(())
158    }
159}
160
161/// ITU-T Recommendation X.213 NSAP Address
162///
163/// This is composed of three parts, encoded in this order:
164///
165/// - Authority and Format Identifier (AFI): always a single byte, which
166///   identifies the network type and the syntax of the encoding that follows
167/// - Initial Domain Identifier (IDI): the authority for allocating further
168///   address identifiers, which is always encoded as binary-coded decimal,
169///   and padded with either 0x0 or 0x1 depending on whether leading zeroes are
170///   significant or not until the maximum length is reached in digits.
171/// - Domain-Specific Part (DSP): the remainder of the information that
172///   completes the address and is allocated by the authority identified by the
173///   IDI. It can have four abstract syntaxes: BCD, binary, ISO/IEC 646 string,
174///   and a national character encoding.
175///
176/// Together, the AFI and IDI are referred to as the Initial Domain Part (IDP).
177///
178/// This type does not implement `PartialEq`, `Eq`, or `Hash`, because:
179///
180/// 1. Unrecognized encodings could mean that two values cannot be compared for
181///    equality because their semantics are unknown.
182/// 2. Even among recognized encodings, it is not clear whether or not the
183///    decimal encoding should always be considered equal to the binary
184///    encoding.
185/// 3. The semantics of the DSP encodings seems to be undefined for most AFIs.
186///
187/// A simple `Eq` or `Hash` implementation could just use the raw octets, but
188/// this could contradict cases where two different encoding should be treated
189/// as equal. Letting the caller explicitly hash or compare the octets is more
190/// clear as to what the underlying behavior is.
191///
192#[derive(Debug)]
193pub enum X213NetworkAddress<'a> {
194    /// Allocation on the heap
195    #[cfg(feature = "alloc")]
196    Heap(Vec<u8>),
197    /// Allocation on the stack
198    /// Even though NSAPs are capped at 20 bytes, this inline buffer accepts up
199    /// to 22 just so that programming errors are less likely to result in
200    /// reading out-of-bounds.
201    Inline((u8, [u8; 22])),
202    /// Reference to an existing allocation
203    Borrowed(&'a [u8]),
204}
205
206impl<'a> X213NetworkAddress<'a> {
207    /// Get the bytes of the encoded NSAP address
208    #[inline]
209    pub fn get_octets(&'a self) -> &'a [u8] {
210        match &self {
211            #[cfg(feature = "alloc")]
212            X213NetworkAddress::Heap(o) => o.as_ref(),
213            X213NetworkAddress::Inline((sz, buf)) => &buf[0..(*sz).clamp(0u8, 20u8) as usize],
214            X213NetworkAddress::Borrowed(o) => *o,
215        }
216    }
217
218    /// Get the Authority and Format Identifier (AFI): part of an NSAP address
219    #[inline]
220    pub fn afi(&self) -> u8 {
221        if self.get_octets().len() > 0 {
222            self.get_octets()[0]
223        } else {
224            panic!("Zero-length IDP in an X.213 network address")
225        }
226    }
227
228    /// Create an NSAP address from its binary encoding as a `Vec<u8>` without checking validity
229    #[cfg(feature = "alloc")]
230    pub fn from_vec_unchecked(octets: Vec<u8>) -> X213NetworkAddress<'static> {
231        X213NetworkAddress::Heap(octets)
232    }
233
234    /// Create an NSAP address from its binary encoding as a `Vec<u8>` after checking validity
235    #[cfg(feature = "alloc")]
236    pub fn from_vec(octets: Vec<u8>) -> Result<X213NetworkAddress<'static>, NAddressParseError> {
237        validate_raw_nsap(octets.as_ref())?;
238        Ok(X213NetworkAddress::Heap(octets))
239    }
240
241    /// Create an NSAP address from its binary encoding as a `&[u8]` without checking validity
242    pub fn from_slice_unchecked(octets: &'a [u8]) -> X213NetworkAddress<'a> {
243        X213NetworkAddress::Borrowed(octets)
244    }
245
246    /// Create an NSAP address from its binary encoding as a `&[u8]` after checking validity
247    pub fn from_slice(octets: &'a [u8]) -> Result<X213NetworkAddress<'a>, NAddressParseError> {
248        validate_raw_nsap(octets.as_ref())?;
249        Ok(X213NetworkAddress::Borrowed(octets))
250    }
251
252    /// Get network type info for this NSAP address
253    #[inline]
254    pub fn get_network_type_info(&self) -> Option<X213NetworkAddressInfo> {
255        get_nsap_address_schema(self.afi())
256    }
257
258    /// Get the network type for this NSAP address
259    #[inline]
260    pub fn get_network_type(&self) -> Option<X213NetworkAddressType> {
261        afi_to_network_type(self.afi())
262    }
263
264    /// Iterate over the IDI digits for this NSAP address
265    ///
266    /// Returns `None` if the AFI is unrecognized, and therefore, that the
267    /// NSAP address cannot be parsed, since the end of the IDI cannot be
268    /// determined.
269    pub fn idi_digits(&'a self) -> Option<BCDDigitsIter<'a>> {
270        let addr_type_info = get_nsap_address_schema(self.afi())?;
271        let leading_0_sig = addr_type_info.leading_zeroes_in_idi;
272        let is_dsp_decimal = matches!(addr_type_info.dsp_syntax, DSPSyntax::Decimal);
273        let idi_len = addr_type_info.max_idi_len_digits as usize;
274        let idi_len_in_bytes = (idi_len >> 1) + (idi_len % 2);
275        let odd_len_idi: bool = (idi_len % 2) > 0;
276        let octets = self.get_octets();
277        let idi = &octets[1..1 + idi_len_in_bytes];
278        Some(BCDDigitsIter::new(
279            idi,
280            leading_0_sig,
281            is_dsp_decimal && odd_len_idi,
282            false,
283            true,
284        ))
285    }
286
287    /// Iterate over the IDI digits for this NSAP address, if the DSP is in decimal
288    ///
289    /// Returns `None` if the AFI is unrecognized, and therefore, that the
290    /// NSAP address cannot be parsed, since the end of the IDI cannot be
291    /// determined. Also returns `None` if the DSP syntax is not decimal.
292    pub fn dsp_digits(&'a self) -> Option<BCDDigitsIter<'a>> {
293        let addr_type_info = get_nsap_address_schema(self.afi())?;
294        let is_dsp_decimal = matches!(addr_type_info.dsp_syntax, DSPSyntax::Decimal);
295        if !is_dsp_decimal {
296            return None;
297        }
298        let idi_len = addr_type_info.max_idi_len_digits as usize;
299        let idi_len_in_bytes = (idi_len >> 1) + (idi_len % 2);
300        let odd_len_idi: bool = (idi_len % 2) > 0;
301        let octets = self.get_octets();
302        // This needs to take the byte before if odd number of IDI digits
303        let (dsp, start_on_lsn) = if is_dsp_decimal && odd_len_idi {
304            (&octets[idi_len_in_bytes..], true)
305        } else {
306            (&octets[1 + idi_len_in_bytes..], false)
307        };
308        Some(BCDDigitsIter::new(
309            dsp,
310            false, // No leading zeroes supported in DSPs
311            false, // Only ignore the last nybble if it is 0x0F
312            start_on_lsn,
313            false,
314        ))
315    }
316
317    /// Get the encoded URL
318    ///
319    /// This returns `None` if this NSAP does not encode a URL
320    pub fn get_url(&'a self) -> Option<&'a str> {
321        let octets = self.get_octets();
322        // It couldn't be a valid URL in two characters, AFAIK.
323        if octets.len() <= 5 || octets[0] != AFI_URL {
324            return None;
325        }
326        str::from_utf8(&octets[3..]).ok()
327    }
328
329    /// Get the encoded IP address
330    ///
331    /// This only returns an IP address for IANA ICP-based NSAP addresses
332    ///
333    /// This returns `None` if this NSAP does not encode an IP address
334    /// See: <https://www.rfc-editor.org/rfc/rfc4548.html>
335    pub fn get_ip(&self) -> Option<IpAddr> {
336        let octets = self.get_octets();
337        if octets.len() < 7 || octets[0] != AFI_IANA_ICP_BIN {
338            return None;
339        }
340        // See doc comments on AFI_IANA_ICP_DEC for why it is not supported.
341        match (octets[1], octets[2]) {
342            (0, 0) => {
343                // IPv6
344                if octets.len() < 19 {
345                    return None;
346                }
347                let ip = Ipv6Addr::from([
348                    octets[3], octets[4], octets[5], octets[6], octets[7], octets[8], octets[9],
349                    octets[10], octets[11], octets[12], octets[13], octets[14], octets[15],
350                    octets[16], octets[17], octets[18],
351                ]);
352                Some(IpAddr::V6(ip))
353            }
354            (0, 1) => {
355                // IPv4
356                let ip = Ipv4Addr::from([octets[3], octets[4], octets[5], octets[6]]);
357                Some(IpAddr::V4(ip))
358            }
359            _ => None,
360        }
361    }
362
363    /// Get the RFC 1277 socket address info
364    ///
365    /// Specifically, if this returns `Some(_)`, it contains a tuple of the
366    /// IP network, the socket address, and optionally, the transport-set, as
367    /// defined in IETF RFC 1277, in that order.
368    ///
369    /// This returns `None` if this NSAP does not encode an ITOT socket address
370    pub fn get_rfc1277_socket(&self) -> Option<Rfc1277SocketInfo> {
371        let octets = self.get_octets();
372        if !octets.starts_with(RFC_1277_PREFIX.as_slice()) {
373            return None;
374        }
375        let dsp = &octets[RFC_1277_PREFIX.len() + 1..];
376        if dsp.len() < 6 {
377            return None;
378        }
379        let mut bcd = BCDDigitsIter::new(dsp, false, false, false, false);
380        let oct1digs = [bcd.next()? + 0x30, bcd.next()? + 0x30, bcd.next()? + 0x30];
381        let oct2digs = [bcd.next()? + 0x30, bcd.next()? + 0x30, bcd.next()? + 0x30];
382        let oct3digs = [bcd.next()? + 0x30, bcd.next()? + 0x30, bcd.next()? + 0x30];
383        let oct4digs = [bcd.next()? + 0x30, bcd.next()? + 0x30, bcd.next()? + 0x30];
384        let oct1str = unsafe { str::from_utf8_unchecked(oct1digs.as_slice()) };
385        let oct2str = unsafe { str::from_utf8_unchecked(oct2digs.as_slice()) };
386        let oct3str = unsafe { str::from_utf8_unchecked(oct3digs.as_slice()) };
387        let oct4str = unsafe { str::from_utf8_unchecked(oct4digs.as_slice()) };
388        let oct1: u8 = oct1str.parse().ok()?;
389        let oct2: u8 = oct2str.parse().ok()?;
390        let oct3: u8 = oct3str.parse().ok()?;
391        let oct4: u8 = oct4str.parse().ok()?;
392        let ip = Ipv4Addr::new(oct1, oct2, oct3, oct4);
393        if dsp.len() < 9 {
394            return Some((
395                octets[5],
396                SocketAddrV4::new(ip, ITOT_OVER_IPV4_DEFAULT_PORT),
397                DEFAULT_ITOT_TRANSPORT_SET,
398            ));
399        }
400        let portstr = [
401            bcd.next()? + 0x30,
402            bcd.next()? + 0x30,
403            bcd.next()? + 0x30,
404            bcd.next()? + 0x30,
405            bcd.next()? + 0x30,
406        ];
407        let portstr = unsafe { str::from_utf8_unchecked(portstr.as_slice()) };
408        let port: u16 = portstr.parse().ok()?;
409        if dsp.len() < 11 {
410            return Some((
411                octets[5],
412                SocketAddrV4::new(ip, ITOT_OVER_IPV4_DEFAULT_PORT),
413                DEFAULT_ITOT_TRANSPORT_SET,
414            ));
415        }
416        let tsetstr = [
417            bcd.next()? + 0x30,
418            bcd.next()? + 0x30,
419            bcd.next()? + 0x30,
420            bcd.next()? + 0x30,
421            bcd.next()? + 0x30,
422        ];
423        let tsetstr = unsafe { str::from_utf8_unchecked(tsetstr.as_slice()) };
424        let tset: Rfc1277TransportSet = tsetstr.parse().ok()?;
425        Some((octets[5], SocketAddrV4::new(ip, port), tset))
426    }
427
428    /// Create a new IANA ICP NSAP address from an IP address
429    pub fn from_ip(ip: &IpAddr) -> Self {
430        match ip {
431            IpAddr::V4(v4) => X213NetworkAddress::from_ipv4(v4),
432            IpAddr::V6(v6) => X213NetworkAddress::from_ipv6(v6),
433        }
434    }
435
436    /// Create a new IANA ICP NSAP address from an IPv4 address
437    pub fn from_ipv4(ip: &Ipv4Addr) -> Self {
438        let mut out: [u8; 22] = [0; 22];
439        out[0..3].copy_from_slice(&[AFI_IANA_ICP_BIN, 0, 1]);
440        out[3..7].copy_from_slice(ip.octets().as_slice());
441        // IANA ICP NSAP addresses are always 20 bytes
442        return X213NetworkAddress::Inline((20, out));
443    }
444
445    /// Create a new IANA ICP NSAP address from an IPv6 address
446    pub fn from_ipv6(ip: &Ipv6Addr) -> Self {
447        let mut out: [u8; 22] = [0; 22];
448        out[0..3].copy_from_slice(&[AFI_IANA_ICP_BIN, 0, 0]);
449        out[3..19].copy_from_slice(ip.octets().as_slice());
450        // IANA ICP NSAP addresses are always 20 bytes
451        return X213NetworkAddress::Inline((20, out));
452    }
453
454    /// Create a new X.519 ITOT URL NSAP address from a URL
455    #[cfg(feature = "alloc")]
456    pub fn from_itot_url(url: &str) -> Self {
457        let mut out: Vec<u8> = Vec::with_capacity(3 + url.len());
458        out.extend(&[AFI_URL, 0, 0]);
459        out.extend(url.as_bytes());
460        return X213NetworkAddress::Heap(out);
461    }
462
463    /// Create a new X.519 Non-OSI (LDAP, IDM, etc.) URL NSAP address from a URL
464    #[cfg(feature = "alloc")]
465    pub fn from_non_osi_url(url: &str) -> Self {
466        let mut out: Vec<u8> = Vec::with_capacity(3 + url.len());
467        out.extend(&[AFI_URL, 0, 1]);
468        out.extend(url.as_bytes());
469        return X213NetworkAddress::Heap(out);
470    }
471
472    /// Create an ITOT NSAP address from a socket address and optional transport set
473    ///
474    /// Note that this only supports IPv4 due to the encoding.
475    pub fn from_socket_addr_v4(network: u8, addr: &SocketAddrV4, tset: Option<u16>) -> Self {
476        let mut out: [u8; 22] = [0; 22];
477        out[0..5].copy_from_slice(RFC_1277_PREFIX.as_slice());
478        out[5] = network;
479        let mut bcd_buf = BCDBuffer::new();
480        addr.ip()
481            .octets()
482            .map(|o| u8_to_decimal_bytes(o))
483            .iter()
484            .for_each(|dec_oct| bcd_buf.push_ascii_bytes(dec_oct.as_slice()));
485        let port = addr.port();
486        if port != ITOT_OVER_IPV4_DEFAULT_PORT
487            || tset.is_some_and(|t| t != DEFAULT_ITOT_TRANSPORT_SET)
488        {
489            let port_str = u16_to_decimal_bytes(port);
490            bcd_buf.push_ascii_bytes(port_str.as_slice());
491            if let Some(tset) = tset {
492                let tset_str = u16_to_decimal_bytes(tset);
493                bcd_buf.push_ascii_bytes(tset_str.as_slice());
494            } else {
495                bcd_buf.push_nybble(0xF);
496            }
497        }
498        let bcd_len = bcd_buf.len_in_bytes();
499        debug_assert_eq!(bcd_len, bcd_buf.as_ref().len());
500        debug_assert!(bcd_len < 19);
501        out[6..6 + bcd_len].copy_from_slice(bcd_buf.as_ref());
502        X213NetworkAddress::Inline((6 + bcd_len as u8, out))
503    }
504
505    /// Convert to a `String` using the `NS+<hex>` syntax
506    ///
507    /// This is desirable for portability / interoperability: the `NS+<hex>`
508    /// syntax is the easiest display syntax to parse and leaves no ambiguity of
509    /// encoding. This is a great choice if you are exporting an NSAP address in
510    /// string format for use in other systems.
511    ///
512    /// The output looks like `NS+A433BB93C1`.
513    #[cfg(feature = "alloc")]
514    pub fn to_ns_string(&self) -> String {
515        let octets = self.get_octets();
516        let len = 3 + (octets.len() << 1);
517        let mut out: Vec<u8> = Vec::with_capacity(len);
518        out.extend(b"NS+");
519        // Just so we don't zero the vec only to write over it all again.
520        unsafe {
521            out.set_len(len);
522        }
523        faster_hex::hex_encode(octets, &mut out[3..]).expect("hex output buffer mis-sized");
524        unsafe { String::from_utf8_unchecked(out) }
525    }
526
527    /// Display using the `NS+<hex>` syntax
528    ///
529    /// This is desirable for portability / interoperability: the `NS+<hex>`
530    /// syntax is the easiest display syntax to parse and leaves no ambiguity of
531    /// encoding. This is a great choice if you are exporting an NSAP address in
532    /// string format for use in other systems.
533    ///
534    /// The output looks like `NS+A433BB93C1`.
535    pub fn fmt_as_ns_string(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
536        f.write_str("NS+")?;
537        for byte in self.get_octets() {
538            f.write_fmt(format_args!("{:02X}", *byte))?;
539        }
540        Ok(())
541    }
542}
543
544fn validate_decimal(bytes: &[u8]) -> bool {
545    for byte in bytes {
546        if (byte & 0b0000_1111) > 9 {
547            return false;
548        }
549        if (byte & 0b1111_0000) > 0b1001_0000 {
550            return false;
551        }
552    }
553    true
554}
555
556fn validate_raw_nsap<'a>(octets: &'a [u8]) -> Result<(), NAddressParseError> {
557    let len = octets.len();
558    if len < 2 {
559        // I don't think one byte can be a valid address.
560        return Err(NAddressParseError::TooShort);
561    }
562    /* ITU-T Rec. X.213, Section A.5.4 states that the maximum length MUST
563    be 20 octets, but ITU-T Rec. X.519 section 11.4 basically overrules
564    that. As such, we are just setting a limit of 248 bytes just to close up
565    any attack vectors related to large NSAP addresses. */
566    if octets[0] != AFI_URL && len > 20 {
567        return Err(NAddressParseError::TooLong);
568    }
569
570    match octets[0] {
571        crate::AFI_URL => {
572            if len > 248 {
573                return Err(NAddressParseError::TooLong);
574            }
575            if len <= 5 {
576                // I think you can't have a valid URL under two characters.
577                return Err(NAddressParseError::TooShort);
578            }
579            if !validate_decimal(&octets[1..3]) {
580                return Err(NAddressParseError::NonDigitsInIDI);
581            }
582        }
583        crate::AFI_IANA_ICP_BIN => {
584            if len > 20 {
585                return Err(NAddressParseError::TooLong);
586            }
587            if len < 20 {
588                return Err(NAddressParseError::TooShort);
589            }
590            if !validate_decimal(&octets[1..3]) {
591                return Err(NAddressParseError::NonDigitsInIDI);
592            }
593        }
594        _ => (),
595    };
596
597    if len >= RFC_1277_PREFIX.len() + 7 && octets.starts_with(RFC_1277_PREFIX.as_slice()) {
598        match octets[RFC_1277_PREFIX.len()] {
599            crate::data::RFC_1277_WELL_KNOWN_NETWORK_DARPA_NSF_INTERNET
600            | crate::data::ITU_X519_DSP_PREFIX_LDAP
601            | crate::data::ITU_X519_DSP_PREFIX_IDM_OVER_IPV4
602            // | crate::data::ITU_X519_DSP_PREFIX_ITOT_OVER_IPV4 (duplicate)
603            => {
604                let end_of_digits = match len {
605                    12 => 12,
606                    15 => 14,
607                    17 => 17,
608                    _ => return Err(NAddressParseError::MalformedDSP),
609                };
610                if !validate_decimal(&octets[6..end_of_digits]) {
611                    return Err(NAddressParseError::MalformedDSP);
612                }
613            },
614            _ => (),
615        };
616    }
617    Ok(())
618}
619
620impl<'a> TryFrom<&'a [u8]> for X213NetworkAddress<'a> {
621    type Error = NAddressParseError;
622
623    fn try_from(octets: &'a [u8]) -> Result<Self, Self::Error> {
624        validate_raw_nsap(octets)?;
625        Ok(X213NetworkAddress::Borrowed(octets))
626    }
627}
628
629#[cfg(feature = "alloc")]
630impl<'a> TryFrom<Vec<u8>> for X213NetworkAddress<'a> {
631    type Error = NAddressParseError;
632
633    fn try_from(octets: Vec<u8>) -> Result<Self, Self::Error> {
634        validate_raw_nsap(octets.as_ref())?;
635        Ok(X213NetworkAddress::Heap(octets))
636    }
637}
638
639impl<'a> From<&IpAddr> for X213NetworkAddress<'a> {
640    #[inline]
641    fn from(value: &IpAddr) -> Self {
642        X213NetworkAddress::from_ip(value)
643    }
644}
645
646impl<'a> From<&Ipv4Addr> for X213NetworkAddress<'a> {
647    #[inline]
648    fn from(value: &Ipv4Addr) -> Self {
649        X213NetworkAddress::from_ipv4(value)
650    }
651}
652
653impl<'a> From<&Ipv6Addr> for X213NetworkAddress<'a> {
654    #[inline]
655    fn from(value: &Ipv6Addr) -> Self {
656        X213NetworkAddress::from_ipv6(value)
657    }
658}
659
660impl<'a> Display for X213NetworkAddress<'a> {
661    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
662        fmt_naddr(self, f)
663    }
664}
665
666impl<'a> FromStr for X213NetworkAddress<'a> {
667    type Err = RFC1278ParseError;
668
669    #[inline]
670    fn from_str(s: &str) -> Result<Self, RFC1278ParseError> {
671        parse_nsap(s)
672    }
673}
674
675#[cfg(test)]
676mod tests {
677
678    extern crate alloc;
679    use crate::data::{
680        AFI_IANA_ICP_BIN, AFI_ISO_DCC_DEC, AFI_X121_DEC_LEADING_ZERO,
681        RFC_1277_WELL_KNOWN_NETWORK_DARPA_NSF_INTERNET,
682    };
683    use alloc::string::{String, ToString};
684    use alloc::vec::Vec;
685    use alloc::format;
686    use core::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4};
687    use core::str::FromStr;
688
689    use super::X213NetworkAddress;
690
691    use super::data::AFI_F69_DEC_LEADING_ZERO;
692
693    #[test]
694    fn test_display_01() {
695        let input = [
696            0x36u8, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, // IDI = 102030405
697            0x12, 0x34, 0x56, 0x78, 0x90,
698        ];
699        let addr = X213NetworkAddress::try_from(input.as_slice()).unwrap();
700        let addr_str = addr.to_string();
701        assert_eq!(addr_str, "X121+102030405+d1234567890");
702    }
703
704    #[cfg(feature = "nonstddisplay")]
705    #[test]
706    fn test_display_02_url() {
707        let input = b"\xFF\x00\x01https://wildboarsoftware.com/x500directory";
708        let addr = X213NetworkAddress::try_from(input.as_slice()).unwrap();
709        let addr_str = addr.to_string();
710        assert_eq!(
711            addr_str,
712            "URL+0001+https://wildboarsoftware.com/x500directory"
713        );
714    }
715
716    #[test]
717    fn test_display_02_itot() {
718        let input = &[
719            0x54, 0, 0x72, 0x87, 0x22, 3, 1, 0, 0, 0, 0, 6, 0, 0, 0x90, 0, 2,
720        ];
721        let addr = X213NetworkAddress::try_from(input.as_slice()).unwrap();
722        let addr_str = addr.to_string();
723        assert_eq!(addr_str, "TELEX+00728722+RFC-1006+03+10.0.0.6+9+2");
724    }
725
726    #[cfg(feature = "nonstddisplay")]
727    #[test]
728    fn test_display_03_ip() {
729        let input = &[
730            AFI_IANA_ICP_BIN,
731            0,
732            1,
733            192,
734            168,
735            1,
736            100,
737            0,
738            0,
739            0,
740            0,
741            0,
742            0,
743            0,
744            0,
745            0,
746            0,
747            0,
748            0,
749            0,
750        ];
751        let addr = X213NetworkAddress::try_from(input.as_slice()).unwrap();
752        let addr_str = addr.to_string();
753        assert_eq!(addr_str, "IP4+192.168.1.100");
754    }
755
756    #[test]
757    fn test_get_url() {
758        let input = b"\xFF\x00\x01https://wildboarsoftware.com/x500directory";
759        let addr = X213NetworkAddress::try_from(input.as_slice()).unwrap();
760        assert_eq!(
761            addr.get_url().unwrap(),
762            "https://wildboarsoftware.com/x500directory"
763        );
764    }
765
766    #[test]
767    fn test_from_itot_socket_addr() {
768        let sock = SocketAddrV4::from_str("192.168.1.100:8000").unwrap();
769        let addr = X213NetworkAddress::from_socket_addr_v4(
770            RFC_1277_WELL_KNOWN_NETWORK_DARPA_NSF_INTERNET,
771            &sock,
772            None,
773        );
774        // assert_eq!(addr, "https://wildboarsoftware.com/x500directory");
775        assert_eq!(
776            addr.get_octets(),
777            &[
778                AFI_F69_DEC_LEADING_ZERO, // AFI
779                0x00,
780                0x72,
781                0x87,
782                0x22, // IDI
783                0x03, // The DSP prefix "03"
784                0x19,
785                0x21,
786                0x68,
787                0x00,
788                0x11,
789                0x00,
790                0x08,
791                0x00,
792                0x0F,
793            ]
794        );
795    }
796
797    #[cfg(feature = "nonstd")]
798    #[test]
799    fn test_ip_overflow_1() {
800        let input = "IP4+999.999.2.100";
801        let maybe_addr = X213NetworkAddress::from_str(input);
802        assert!(maybe_addr.is_err());
803    }
804
805    #[test]
806    fn test_ip_overflow_2() {
807        let input = "TELEX+00728722+RFC-1006+03+256.0.0.2+9+2";
808        let maybe_addr = X213NetworkAddress::from_str(input);
809        assert!(maybe_addr.is_err());
810    }
811
812    #[test]
813    fn test_ip_overflow_3() {
814        let input = "TELEX+00728722+RFC-1006+03+0.255.255.255+99999+88888";
815        let maybe_addr = X213NetworkAddress::from_str(input);
816        assert!(maybe_addr.is_err());
817    }
818
819    #[test]
820    #[ignore]
821    fn test_ip_overflow_4() {
822        let input: &[u8] = &[
823            0x54, 0x00, 0x72, 0x87, 0x22, 0x03, 0x99, 0x90, 0x00, 0x00, 0x00,
824            0x06, // 999.0.0.6
825        ];
826        let maybe_addr = X213NetworkAddress::try_from(input);
827        assert!(maybe_addr.is_err());
828    }
829
830    #[test]
831    fn test_get_itot_socket_adder() {
832        let input = "TELEX+00728722+RFC-1006+03+255.0.0.2+65535+2";
833        let addr = X213NetworkAddress::from_str(input).unwrap();
834        let (_, sock, _) = addr.get_rfc1277_socket().unwrap();
835        assert_eq!(sock.ip(), &Ipv4Addr::new(255, 0, 0, 2));
836        assert_eq!(sock.port(), 65535);
837    }
838
839    // Up to 14 digits, leading zeroes significant
840    #[test]
841    fn test_idi_digits_x121() {
842        let input = "X121+00123456789+x0824";
843        let addr = X213NetworkAddress::from_str(input).unwrap();
844        let digits: Vec<u8> = addr.idi_digits().unwrap().collect();
845        assert_eq!(digits.as_slice(), &[0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
846    }
847
848    #[test]
849    fn test_idi_digits_dcc() {
850        let input = "X121+023+x0824";
851        let addr = X213NetworkAddress::from_str(input).unwrap();
852        let digits: Vec<u8> = addr.idi_digits().unwrap().collect();
853        assert_eq!(digits.as_slice(), &[0, 2, 3]);
854    }
855
856    // Up to 8 digits, leading zero significant
857    #[test]
858    fn test_idi_digits_telex() {
859        let input = "TELEX+01234+x0824";
860        let addr = X213NetworkAddress::from_str(input).unwrap();
861        let digits: Vec<u8> = addr.idi_digits().unwrap().collect();
862        assert_eq!(digits.as_slice(), &[0, 1, 2, 3, 4]);
863    }
864
865    // Up to 12 digits, leading zero significant
866    #[test]
867    fn test_idi_digits_pstn() {
868        let input = "PSTN+8883334022+x0824";
869        let addr = X213NetworkAddress::from_str(input).unwrap();
870        let digits: Vec<u8> = addr.idi_digits().unwrap().collect();
871        assert_eq!(digits.as_slice(), &[8, 8, 8, 3, 3, 3, 4, 0, 2, 2]);
872    }
873
874    // Up to 15 digits, leading zero significant
875    #[test]
876    fn test_idi_digits_idsn() {
877        let input = "ISDN+0018883334022+x0824";
878        let addr = X213NetworkAddress::from_str(input).unwrap();
879        let digits: Vec<u8> = addr.idi_digits().unwrap().collect();
880        assert_eq!(digits.as_slice(), &[0, 0, 1, 8, 8, 8, 3, 3, 3, 4, 0, 2, 2]);
881    }
882
883    #[test]
884    fn test_idi_digits_icd() {
885        let input = "ICD+0023+x0824";
886        let addr = X213NetworkAddress::from_str(input).unwrap();
887        let digits: Vec<u8> = addr.idi_digits().unwrap().collect();
888        assert_eq!(digits.as_slice(), &[2, 3]);
889    }
890
891    #[cfg(feature = "nonstd")]
892    #[test]
893    fn test_idi_digits_icp() {
894        let input = "ICP+0001+x0824";
895        let addr = X213NetworkAddress::from_str(input).unwrap();
896        let digits: Vec<u8> = addr.idi_digits().unwrap().collect();
897        assert_eq!(digits.as_slice(), &[1]);
898    }
899
900    #[cfg(feature = "nonstd")]
901    #[test]
902    fn test_idi_digits_ind() {
903        let input = "IND+0123+x0824";
904        let addr = X213NetworkAddress::from_str(input).unwrap();
905        let digits: Vec<u8> = addr.idi_digits().unwrap().collect();
906        assert_eq!(digits.as_slice(), &[1, 2, 3]);
907    }
908
909    #[test]
910    fn test_idi_digits_local() {
911        let input = "LOCAL++x0824";
912        let addr = X213NetworkAddress::from_str(input).unwrap();
913        let digits: Vec<u8> = addr.idi_digits().unwrap().collect();
914        assert_eq!(digits.as_slice(), &[]);
915    }
916
917    #[cfg(feature = "nonstd")]
918    #[test]
919    fn test_idi_digits_url() {
920        let input = "URL+0001+x0824";
921        let addr = X213NetworkAddress::from_str(input).unwrap();
922        let digits: Vec<u8> = addr.idi_digits().unwrap().collect();
923        assert_eq!(digits.as_slice(), &[1]);
924    }
925
926    #[test]
927    fn test_dsp_digits_x121() {
928        let input = "X121+00123456789+d2929";
929        let addr = X213NetworkAddress::from_str(input).unwrap();
930        let digits: Vec<u8> = addr.dsp_digits().unwrap().collect();
931        assert_eq!(digits.as_slice(), &[2, 9, 2, 9]);
932        assert_eq!(
933            addr.get_octets(),
934            &[
935                AFI_X121_DEC_LEADING_ZERO,
936                0x11,
937                0x10,
938                0x01,
939                0x23,
940                0x45,
941                0x67,
942                0x89,
943                0x29,
944                0x29,
945            ]
946        );
947    }
948
949    #[test]
950    fn test_dsp_digits_dcc() {
951        let input = "DCC+840+d1298";
952        let addr = X213NetworkAddress::from_str(input).unwrap();
953        let digits: Vec<u8> = addr.dsp_digits().unwrap().collect();
954        assert_eq!(digits.as_slice(), &[1, 2, 9, 8]);
955        assert_eq!(
956            addr.get_octets(),
957            &[AFI_ISO_DCC_DEC, 0x84, 0x01, 0x29, 0x8F,]
958        );
959    }
960
961    #[test]
962    fn test_from_ipv4() {
963        let input = Ipv4Addr::new(192, 168, 1, 255);
964        let addr = X213NetworkAddress::from_ipv4(&input);
965        assert_eq!(
966            addr.get_octets(),
967            &[
968                AFI_IANA_ICP_BIN,
969                0,
970                1, // Ipv4
971                192,
972                168,
973                1,
974                255, // The IP address
975                0,
976                0,
977                0,
978                0,
979                0,
980                0,
981                0,
982                0,
983                0,
984                0,
985                0,
986                0,
987                0, // Required padding
988            ]
989        );
990    }
991
992    #[test]
993    fn test_from_ipv6() {
994        let input = Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8);
995        let addr = X213NetworkAddress::from_ipv6(&input);
996        assert_eq!(
997            addr.get_octets(),
998            &[
999                AFI_IANA_ICP_BIN,
1000                0,
1001                0, // Ipv4
1002                0,
1003                1,
1004                0,
1005                2,
1006                0,
1007                3,
1008                0,
1009                4,
1010                0,
1011                5,
1012                0,
1013                6,
1014                0,
1015                7,
1016                0,
1017                8, // IP address
1018                0, // Required padding
1019            ]
1020        );
1021    }
1022
1023    #[test]
1024    fn test_from_itot_url() {
1025        let addr = X213NetworkAddress::from_itot_url("https://himom.org");
1026        assert_eq!(addr.get_octets(), b"\xFF\x00\x00https://himom.org");
1027    }
1028
1029    #[test]
1030    fn test_to_ns_string() {
1031        let input = "DCC+840+d1298";
1032        let addr = X213NetworkAddress::from_str(input).unwrap();
1033        assert_eq!(addr.to_ns_string().as_str(), "NS+388401298f");
1034    }
1035
1036    #[test]
1037    fn test_fuzz_failure_01() {
1038        let input: (&[u8], &str, u64, u8) = (
1039            [
1040                0,
1041                0,
1042            ].as_slice(),
1043            "+\0\0+",
1044            82,
1045            0,
1046        );
1047        let (input_b, input_s, input_u64, input_u8) = input;
1048        let _ = X213NetworkAddress::try_from(input_b);
1049        let _ = X213NetworkAddress::from_str(input_s);
1050        let ss1 =  [ "X121+", input_s ].join("");
1051        let ss2 =  [ "DCC+", input_s ].join("");
1052        let ss3 =  [ "TELEX+", input_s ].join("");
1053        let ss4 =  [ "PSTN+", input_s ].join("");
1054        let ss5 =  [ "ISDN+", input_s ].join("");
1055        let ss6 =  [ "ICD+", input_s ].join("");
1056        let ss7 =  [ "ICP+", input_s ].join("");
1057        let ss8 =  [ "IND+", input_s ].join("");
1058        let ss9 =  [ "LOCAL+", input_s ].join("");
1059        let ss10 = [ "URL+", input_s ].join("");
1060        let ss11 = [ "IP4+", input_s ].join("");
1061        let ss12 = [ "IP6+", input_s ].join("");
1062
1063        let mut hexstr: Vec<u8> = Vec::with_capacity(input_b.len() << 1);
1064        unsafe { hexstr.set_len(input_b.len() << 1) };
1065        faster_hex::hex_encode(input_b, hexstr.as_mut_slice()).unwrap();
1066        let hexstr = unsafe { String::from_utf8_unchecked(hexstr) };
1067        let sb1 =  [ "X121+", hexstr.as_str() ].join("");
1068        let sb2 =  [ "DCC+", hexstr.as_str() ].join("");
1069        let sb3 =  [ "TELEX+", hexstr.as_str() ].join("");
1070        let sb4 =  [ "PSTN+", hexstr.as_str() ].join("");
1071        let sb5 =  [ "ISDN+", hexstr.as_str() ].join("");
1072        let sb6 =  [ "ICD+", hexstr.as_str() ].join("");
1073        let sb7 =  [ "ICP+", hexstr.as_str() ].join("");
1074        let sb8 =  [ "IND+", hexstr.as_str() ].join("");
1075        let sb9 =  [ "LOCAL+", hexstr.as_str() ].join("");
1076        let sb10 = [ "URL+", hexstr.as_str() ].join("");
1077        let sb11 = [ "IP4+", hexstr.as_str() ].join("");
1078        let sb12 = [ "IP6+", hexstr.as_str() ].join("");
1079
1080        let url = format!("URL+{}+{}", input_u8, input_s);
1081        let x25_1 = format!("TELEX+00728722+X.25(80)+02+{}", input_u64);
1082        let x25_2 = format!("TELEX+00728722+X.25(80)+02+{}+CUDF+{}", input_u64, input_u8);
1083        let x25_3 = format!("TELEX+00728722+X.25(80)+{}+{}+CUDF+{}", input_u8, input_u8, input_u8);
1084        let itot1 = format!("TELEX+00728722+RFC-1006+{}+{}.{}.{}.{}", input_u8, input_u8, input_u8, input_u8, input_u8);
1085        let itot2 = format!("TELEX+00728722+RFC-1006+03+2.3.4.5+{}", input_u8);
1086        let itot3 = format!("TELEX+00728722+RFC-1006+03+2.3.4.5+{}+{}", input_u8, input_u8);
1087        let x121 = format!("X121+{}", input_u64);
1088
1089        let _ = X213NetworkAddress::from_str(ss1.as_str());
1090        let _ = X213NetworkAddress::from_str(ss2.as_str());
1091        let _ = X213NetworkAddress::from_str(ss3.as_str());
1092        let _ = X213NetworkAddress::from_str(ss4.as_str());
1093        let _ = X213NetworkAddress::from_str(ss5.as_str());
1094        let _ = X213NetworkAddress::from_str(ss6.as_str());
1095        let _ = X213NetworkAddress::from_str(ss7.as_str());
1096        let _ = X213NetworkAddress::from_str(ss8.as_str());
1097        let _ = X213NetworkAddress::from_str(ss9.as_str());
1098        let _ = X213NetworkAddress::from_str(ss10.as_str());
1099        let _ = X213NetworkAddress::from_str(ss11.as_str());
1100        let _ = X213NetworkAddress::from_str(ss12.as_str());
1101        let _ = X213NetworkAddress::from_str(sb1.as_str());
1102        let _ = X213NetworkAddress::from_str(sb2.as_str());
1103        let _ = X213NetworkAddress::from_str(sb3.as_str());
1104        let _ = X213NetworkAddress::from_str(sb4.as_str());
1105        let _ = X213NetworkAddress::from_str(sb5.as_str());
1106        let _ = X213NetworkAddress::from_str(sb6.as_str());
1107        let _ = X213NetworkAddress::from_str(sb7.as_str());
1108        let _ = X213NetworkAddress::from_str(sb8.as_str());
1109        let _ = X213NetworkAddress::from_str(sb9.as_str());
1110        let _ = X213NetworkAddress::from_str(sb10.as_str());
1111        let _ = X213NetworkAddress::from_str(sb11.as_str());
1112        let _ = X213NetworkAddress::from_str(sb12.as_str());
1113        let _ = X213NetworkAddress::from_str(url.as_str());
1114        let _ = X213NetworkAddress::from_str(x25_1.as_str());
1115        let _ = X213NetworkAddress::from_str(x25_2.as_str());
1116        let _ = X213NetworkAddress::from_str(x25_3.as_str());
1117        let _ = X213NetworkAddress::from_str(itot1.as_str());
1118        let _ = X213NetworkAddress::from_str(itot2.as_str());
1119        let _ = X213NetworkAddress::from_str(itot3.as_str());
1120        let _ = X213NetworkAddress::from_str(x121.as_str());
1121    }
1122
1123}