Skip to main content

presentation_address/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "x690"), no_std)]
3#![allow(non_snake_case)]
4
5#[cfg(feature = "x690")]
6pub mod asn1;
7use core::fmt::{Display, Write};
8use core::iter::{FusedIterator, Iterator};
9use core::str::FromStr;
10use core::write;
11#[cfg(feature = "x690")]
12use x690::X690Element;
13
14extern crate alloc;
15use alloc::collections::BTreeSet;
16use alloc::vec::Vec;
17
18/// An OSI network layer selector
19///
20/// These are unbounded in length in the abstract, but even in the OSI
21/// protocols:
22///
23/// - The T-selector has a de facto limit of 254 octets (per ITU-T Rec. X.224)
24/// - The S-selector has a de jure limit of 16 octets (per ITU-T Rec. X.225)
25/// - The P-selector has no explicit limit in size
26///
27/// NSAPs are restricted by ITU-T Rec. X.213 to 20 octets, but ITU-T Rec. X.519
28/// adds an exception for encoding URLs within NSAPs for usage within X.500
29/// directories.
30///
31/// As such, the selectors in this crate are simply represented using `Vec<u8>`.
32pub type Selector = Vec<u8>;
33
34/// OSI Presentation Address
35///
36/// This ASN.1-based data structure comes from ITU-T Recommendation X.520.
37///
38/// ### ASN.1 Definition:
39///
40/// ```asn1
41/// PresentationAddress ::= SEQUENCE {
42///   pSelector   [0]  OCTET STRING OPTIONAL,
43///   sSelector   [1]  OCTET STRING OPTIONAL,
44///   tSelector   [2]  OCTET STRING OPTIONAL,
45///   nAddresses  [3]  SET SIZE (1..MAX) OF OCTET STRING,
46///   ... }
47/// ```
48///
49#[derive(Debug, Clone)]
50pub struct PresentationAddress {
51    /// The P-selector: subaddress for the OSI presentation layer
52    pub pSelector: Option<Selector>,
53    /// The S-selector: subaddress for the OSI session layer
54    pub sSelector: Option<Selector>,
55    /// The T-selector: subaddress for the OSI transport layer
56    pub tSelector: Option<Selector>,
57    /// N-addresses: network addresses
58    pub nAddresses: Vec<Selector>,
59    #[cfg(feature = "x690")]
60    pub _unrecognized: Vec<X690Element>,
61}
62
63impl PresentationAddress {
64    /// Create a new `PresentationAddress`
65    #[inline]
66    pub fn new(
67        pSelector: Option<Selector>,
68        sSelector: Option<Selector>,
69        tSelector: Option<Selector>,
70        nAddresses: Vec<Selector>,
71        #[cfg(feature = "x690")] _unrecognized: Vec<X690Element>,
72    ) -> Self {
73        PresentationAddress {
74            pSelector,
75            sSelector,
76            tSelector,
77            nAddresses,
78            #[cfg(feature = "x690")]
79            _unrecognized,
80        }
81    }
82
83    /// Returns `true` if `self` has the same selectors and a subset of N-addresses of `other`
84    ///
85    /// Note that the ordering of N-addresses does not matter.
86    ///
87    /// In the naming of this function, the term "naively" is used to mean that
88    /// N-addresses are compared naively: byte-for-byte. This isn't totally
89    /// accurate, since the same underlying network address could be represented
90    /// in multiple ways in some cases. This is, in part, why `PartialEq` or
91    /// `Eq` is not implemented for `PresentationAddress.`
92    pub fn is_naively_subset_of(&self, other: &Self) -> bool {
93        if self.pSelector.as_ref() != other.pSelector.as_ref() {
94            return false;
95        }
96        if self.sSelector.as_ref() != other.sSelector.as_ref() {
97            return false;
98        }
99        if self.tSelector.as_ref() != other.tSelector.as_ref() {
100            return false;
101        }
102        if self.nAddresses.len() == 0 {
103            return false;
104        }
105        // It cannot be a subset of the other if the other has fewer N-addresses
106        if self.nAddresses.len() > other.nAddresses.len() {
107            // Empty PresentationAddresses should match nothing.
108            return false;
109        }
110        let othern = BTreeSet::from_iter(other.nAddresses.iter());
111        for naddr in self.nAddresses.iter() {
112            if !othern.contains(naddr) {
113                return false;
114            }
115        }
116        true
117    }
118
119    /// Returns `true` if `self` has the same selectors and the same N-addresses of `other`
120    ///
121    /// Note that the ordering of N-addresses does not matter.
122    ///
123    /// In the naming of this function, the term "naively" is used to mean that
124    /// N-addresses are compared naively: byte-for-byte. This isn't totally
125    /// accurate, since the same underlying network address could be represented
126    /// in multiple ways in some cases. This is, in part, why `PartialEq` or
127    /// `Eq` is not implemented for `PresentationAddress.`
128    pub fn is_naively_exactly(&self, other: &Self) -> bool {
129        if self.pSelector.as_ref() != other.pSelector.as_ref() {
130            return false;
131        }
132        if self.sSelector.as_ref() != other.sSelector.as_ref() {
133            return false;
134        }
135        if self.tSelector.as_ref() != other.tSelector.as_ref() {
136            return false;
137        }
138        if self.nAddresses.len() == 0 {
139            // Empty PresentationAddresses should match nothing.
140            return false;
141        }
142        // It cannot be a subset of the other if the other has fewer N-addresses
143        if self.nAddresses.len() != other.nAddresses.len() {
144            return false;
145        }
146        let selfn = BTreeSet::from_iter(self.nAddresses.iter());
147        let mut othern = BTreeSet::from_iter(other.nAddresses.iter());
148        // This prevents duplicates from being a problem.
149        if selfn.len() != othern.len() {
150            return false;
151        }
152        for naddr in selfn.iter() {
153            if !othern.remove(naddr) {
154                return false;
155            }
156        }
157        true
158    }
159}
160
161fn sel_str_to_bytes(s: &str) -> Result<Vec<u8>, ()> {
162    // A string with a length of one cannot be a valid selector string.
163    if s.len() == 1 {
164        return Err(());
165    }
166    match s.chars().next() {
167        Some('"') => {
168            // string of <other>
169            if let Some(end_quote_idx) = s[1..].find('"') {
170                let inner_str = &s[1..=end_quote_idx];
171                Ok(inner_str.as_bytes().to_vec())
172            } else {
173                Err(())
174            }
175        }
176        Some('\'') => {
177            // hexstring
178            if let Some(end_quote_idx) = s[1..].find('\'') {
179                let inner_str = &s[1..=end_quote_idx];
180                let bytelen = inner_str.len() >> 1;
181                let mut bytes: Vec<u8> = Vec::with_capacity(bytelen);
182                unsafe {
183                    bytes.set_len(bytelen);
184                }
185                faster_hex::hex_decode(inner_str.as_bytes(), bytes.as_mut_slice())
186                    .map_err(|_| ())?;
187                Ok(bytes)
188            } else {
189                Err(())
190            }
191        }
192        Some('#') => {
193            // u16
194            let sel = u16::from_str(&s[1..]).map_err(|_| ())?;
195            Ok(sel.to_be_bytes().to_vec())
196        }
197        None => Ok(Vec::new()),
198        _ => Err(()),
199    }
200}
201
202fn naddr_str_to_bytes(s: &str) -> Result<Vec<u8>, ()> {
203    #[cfg(feature = "nsap-address")]
204    {
205        let nsap = nsap_address::X213NetworkAddress::from_str(s).map_err(|_| ())?;
206        Ok(nsap.get_octets().to_vec())
207    }
208    #[cfg(not(feature = "nsap-address"))]
209    {
210        if s.starts_with("NS+") {
211            let bytelen = s[3..].len() >> 1;
212            let mut bytes: Vec<u8> = Vec::with_capacity(bytelen);
213            unsafe {
214                bytes.set_len(bytelen);
215            }
216            faster_hex::hex_decode(s[3..].as_bytes(), bytes.as_mut_slice()).map_err(|_| ())?;
217            Ok(bytes)
218        } else {
219            // No other syntaxes supported
220            Err(())
221        }
222    }
223}
224
225impl FromStr for PresentationAddress {
226    type Err = ();
227
228    /// Parses the `PresentationAddress` according to
229    /// [IETF RFC 1278](https://datatracker.ietf.org/doc/html/rfc1278)
230    fn from_str(s: &str) -> Result<Self, Self::Err> {
231        let mut selectors = RFC1278SelectorIterator::new(s);
232        let s1 = selectors.next();
233        let s2 = selectors.next();
234        let s3 = selectors.next();
235        debug_assert!(selectors.next().is_none());
236        let (psel, ssel, tsel) = if s3.is_some() {
237            (s1, s2, s3)
238        } else if s2.is_some() {
239            (None, s1, s2)
240        } else if s1.is_some() {
241            (None, None, s1)
242        } else {
243            (None, None, None)
244        };
245
246        let psel = match psel {
247            Some(sel) => Some(sel_str_to_bytes(sel)?),
248            None => None,
249        };
250        let ssel = match ssel {
251            Some(sel) => Some(sel_str_to_bytes(sel)?),
252            None => None,
253        };
254        let tsel = match tsel {
255            Some(sel) => Some(sel_str_to_bytes(sel)?),
256            None => None,
257        };
258
259        let nsaps_len = selectors.remainder().split('_').count();
260        let mut nsaps: Vec<Vec<u8>> = Vec::with_capacity(nsaps_len + 1);
261        for nsap in selectors.remainder().split('_') {
262            nsaps.push(naddr_str_to_bytes(nsap)?);
263        }
264        #[cfg(feature = "x690")]
265        {
266            Ok(PresentationAddress::new(psel, ssel, tsel, nsaps, vec![]))
267        }
268        #[cfg(not(feature = "x690"))]
269        {
270            Ok(PresentationAddress::new(psel, ssel, tsel, nsaps))
271        }
272    }
273}
274
275// <selector>  ::= '"' <otherstring> '"'        -- IA5
276//                 | "#" <digitstring>          -- US GOSIP            40
277//                 | "'" <hexstring> "'H"       -- Hex
278//                 | ""                         -- Empty but present
279fn print_selector(sel: &[u8], f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
280    let len = sel.len();
281    /*
282    Rationale:
283
284    We check that len is greater than two before deciding to print as IA5,
285    because:
286    1. If the len is 2 or less, it is unlikely that the bytes actually encode
287       a real ASCII. On the other hand, there is less than a 1% chance that all
288       three or more characters are <other> by random chance.
289    2. Two bytes are used in US GOSIP to encode selectors as an unsigned
290       integer, so this prevents these selectors from being occassionally
291       misrepresented in strings as ASCII.
292
293    We check that the string is less than or equal to 16 bytes, because it is
294    unlikely that an ASCII string longer than 16 bytes won't contain some
295    non-<other> character, which would invalidate our requirements for printing.
296    Sixteen bytes is the size limit for selectors in the ITU-T Rec. X.225 OSI
297    Session protocol.
298    */
299    if len > 2 && len <= 16 && sel.iter().all(|b| is_other_char(*b as char)) {
300        let selstr = unsafe { str::from_utf8_unchecked(sel) };
301        f.write_char('"')?;
302        f.write_str(selstr)?;
303        return f.write_char('"');
304    }
305    f.write_char('\'')?;
306    for byte in sel {
307        write!(f, "{:02X}", *byte)?;
308    }
309    f.write_char('\'')?;
310    f.write_char('H')
311}
312
313fn print_ns_string(naddr: &[u8], f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
314    f.write_str("NS+")?;
315    for byte in naddr {
316        f.write_fmt(format_args!("{:02X}", *byte))?;
317    }
318    Ok(())
319}
320
321fn print_naddr(naddr: &[u8], f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
322    #[cfg(feature = "nsap-address")]
323    {
324        match nsap_address::X213NetworkAddress::try_from(naddr) {
325            Ok(nsap) => nsap.fmt(f),
326            Err(_) => print_ns_string(naddr, f),
327        }
328    }
329    #[cfg(not(feature = "nsap-address"))]
330    {
331        print_ns_string(naddr, f)
332    }
333}
334
335impl Display for PresentationAddress {
336    /// Displays the `PresentationAddress` according to
337    /// [IETF RFC 1278](https://datatracker.ietf.org/doc/html/rfc1278)
338    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
339        let mut print_selectors: bool = false;
340        if let Some(sel) = self.pSelector.as_deref() {
341            print_selector(sel, f)?;
342            f.write_char('/')?;
343            print_selectors = true;
344        }
345        if let Some(sel) = self.sSelector.as_deref() {
346            print_selector(sel, f)?;
347            f.write_char('/')?;
348            print_selectors = true;
349        } else if print_selectors {
350            f.write_char('/')?;
351        }
352        if let Some(sel) = self.tSelector.as_deref() {
353            print_selector(sel, f)?;
354            f.write_char('/')?;
355        } else if print_selectors {
356            f.write_char('/')?;
357        }
358        for naddr in self.nAddresses.iter() {
359            print_naddr(naddr.as_slice(), f)?;
360        }
361        Ok(())
362    }
363}
364
365/// Whether the character `c` is an `<other>`, per RFC 1278.
366#[inline]
367const fn is_other_char(c: char) -> bool {
368    c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.'
369}
370
371/// Iterator over the selectors of an IETF RFC 1278 string
372///
373/// This iterator exists because the NSAPs could have forward slashes in them,
374/// so splitting the presentation address by forward slashes. Technically, this
375/// isn't supposed to happen in the syntax given by IETF RFC 1278, but the
376/// nsap-address library supports non-standard URL syntaxes, and with the
377/// ability to print URLs, the ability to handle forward slashes in the DSP of
378/// NSAPs becomes more important.
379pub struct RFC1278SelectorIterator<'a> {
380    s: &'a str,
381    selectors_read: u8,
382}
383
384impl<'a> RFC1278SelectorIterator<'a> {
385    /// Create a new `RFC1278SelectorIterator`
386    pub fn new(s: &'a str) -> Self {
387        RFC1278SelectorIterator {
388            s,
389            selectors_read: 0,
390        }
391    }
392
393    /// Get the remainder of the string not yet parsed
394    ///
395    /// This can be used to obtain the network addresses part of the
396    /// presentation address: just iterate over all selectors and the string
397    /// that remains is the network addresses part.
398    pub fn remainder(&self) -> &'a str {
399        self.s
400    }
401}
402
403// <selector>  ::= '"' <otherstring> '"'        -- IA5
404//                                              -- For chars not in this
405//                                              -- string use hex
406//                 | "#" <digitstring>          -- US GOSIP            40
407//                 | "'" <hexstring> "'H"       -- Hex
408//                 | ""                         -- Empty but present
409impl<'a> Iterator for RFC1278SelectorIterator<'a> {
410    type Item = &'a str;
411
412    fn next(&mut self) -> Option<Self::Item> {
413        if self.selectors_read >= 3 {
414            return None;
415        }
416        let first_char = self.s.chars().next()?;
417        match first_char {
418            '\'' | '"' | '#' => {
419                // It is fine to split by forward slash, because
420                let (sel, rest) = self.s.split_once('/')?;
421                self.s = rest;
422                self.selectors_read += 1;
423                Some(sel)
424            }
425            // We are at the start of the Network addresses now. All done.
426            _ => None,
427        }
428    }
429}
430
431impl<'a> FusedIterator for RFC1278SelectorIterator<'a> {}
432
433#[cfg(test)]
434mod tests {
435
436    use core::str::FromStr;
437
438    extern crate alloc;
439    use alloc::string::ToString;
440    use alloc::vec;
441
442    use super::{PresentationAddress, RFC1278SelectorIterator};
443
444    #[test]
445    fn test_from_sel_iter() {
446        let input = if cfg!(feature = "nsap-address") {
447            "'01020304'H/\"HIMOM\"/#65535/TELEX+00728722+RFC-1006+03+10.0.0.6+9+2"
448        } else {
449            "'01020304'H/\"HIMOM\"/#65535/NS+5400728722030100000000060000900002"
450        };
451        let mut seliter = RFC1278SelectorIterator::new(input);
452        let s1 = seliter.next();
453        let s2 = seliter.next();
454        let s3 = seliter.next();
455        let s4 = seliter.next();
456        let s5 = seliter.next();
457        assert_eq!(s1, Some("'01020304'H"));
458        assert_eq!(s2, Some("\"HIMOM\""));
459        assert_eq!(s3, Some("#65535"));
460        assert_eq!(s4, None);
461        assert_eq!(s5, None);
462    }
463
464    #[test]
465    fn test_from_str_01() {
466        let input = if cfg!(feature = "nsap-address") {
467            "'01020304'H/\"HIMOM\"/#65534/TELEX+00728722+RFC-1006+03+10.0.0.6+9+2"
468        } else {
469            "'01020304'H/\"HIMOM\"/#65534/NS+5400728722030100000000060000900002"
470        };
471        let paddr = PresentationAddress::from_str(input).unwrap();
472        assert_eq!(
473            paddr.pSelector.unwrap().as_slice(),
474            [1u8, 2, 3, 4,].as_slice()
475        );
476        assert_eq!(paddr.sSelector.unwrap().as_slice(), b"HIMOM");
477        assert_eq!(
478            paddr.tSelector.unwrap().as_slice(),
479            65534u16.to_be_bytes().as_slice()
480        );
481        assert_eq!(paddr.nAddresses.len(), 1);
482        assert_eq!(
483            paddr.nAddresses[0].as_slice(),
484            &[
485                // TELEX+00728722+RFC-1006+03+10.0.0.6+9+2
486                0x54, 0x00, 0x72, 0x87, 0x22, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
487                0x90, 0x00, 0x02,
488            ]
489        );
490    }
491
492    #[test]
493    fn test_from_str_02() {
494        let input = if cfg!(feature = "nsap-address") {
495            "'01020304'H/\"HIMOM\"/#65534/TELEX+00728722+RFC-1006+03+10.0.0.6+9+2_NS+FF00013132333435"
496        } else {
497            "'01020304'H/\"HIMOM\"/#65534/NS+5400728722030100000000060000900002_NS+FF00013132333435"
498        };
499        let paddr = PresentationAddress::from_str(input).unwrap();
500        assert_eq!(
501            paddr.pSelector.unwrap().as_slice(),
502            [1u8, 2, 3, 4,].as_slice()
503        );
504        assert_eq!(paddr.sSelector.unwrap().as_slice(), b"HIMOM");
505        assert_eq!(
506            paddr.tSelector.unwrap().as_slice(),
507            65534u16.to_be_bytes().as_slice()
508        );
509        assert_eq!(paddr.nAddresses.len(), 2);
510        assert_eq!(
511            paddr.nAddresses[0].as_slice(),
512            &[
513                // TELEX+00728722+RFC-1006+03+10.0.0.6+9+2
514                0x54, 0x00, 0x72, 0x87, 0x22, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
515                0x90, 0x00, 0x02,
516            ]
517        );
518        assert_eq!(
519            paddr.nAddresses[1].as_slice(),
520            &[0xFF, 0x00, 0x01, 0x31, 0x32, 0x33, 0x34, 0x35]
521        );
522    }
523
524    #[test]
525    fn test_display_01() {
526        let paddr = PresentationAddress::new(
527            Some(vec![1, 2, 3, 4]),
528            Some(b"HIMOM".to_vec()),
529            Some(vec![0xFF, 0xFE]),
530            vec![
531                // TELEX+00728722+RFC-1006+03+10.0.0.6+9+2
532                vec![
533                    0x54, 0, 0x72, 0x87, 0x22, 3, 1, 0, 0, 0, 0, 6, 0, 0, 0x90, 0, 2,
534                ],
535            ],
536            #[cfg(feature = "x690")]
537            vec![],
538        );
539        let expected = if cfg!(feature = "nsap-address") {
540            "'01020304'H/\"HIMOM\"/'FFFE'H/TELEX+00728722+RFC-1006+03+10.0.0.6+9+2"
541        } else {
542            "'01020304'H/\"HIMOM\"/'FFFE'H/NS+5400728722030100000000060000900002"
543        };
544        assert_eq!(paddr.to_string().as_str(), expected);
545    }
546}