rpsl/
primitive.rs

1use std::fmt;
2use std::str::FromStr;
3
4use ip::{traits::Prefix as _, AfiClass, Any, Ipv4, Ipv6};
5
6use time::{format_description::FormatItem, macros::format_description};
7
8#[cfg(any(test, feature = "arbitrary"))]
9use proptest::{arbitrary::ParamsFor, prelude::*};
10
11use crate::{
12    error::{err, ParseError, ParseResult},
13    names::AutNum,
14    parser::{
15        debug_construction, impl_case_insensitive_str_primitive, impl_from_str, impl_str_primitive,
16        next_into_or, next_parse_or, rule_mismatch, ParserRule, TokenPair,
17    },
18};
19
20#[cfg(any(test, feature = "arbitrary"))]
21use self::arbitrary::{impl_free_form_arbitrary, impl_rpsl_name_arbitrary, prop_filter_keywords};
22
23/// Address-family classes for which IP address parsing is implemented.
24pub trait ParserAfi: AfiClass + 'static {
25    /// Address family specific `ParserRule` for IP addresses.
26    const LITERAL_ADDR_RULE: ParserRule;
27    /// Address family specific `ParserRule` for IP prefixes.
28    const LITERAL_PREFIX_RULE: ParserRule;
29}
30impl ParserAfi for Ipv4 {
31    const LITERAL_ADDR_RULE: ParserRule = ParserRule::ipv4_addr;
32    const LITERAL_PREFIX_RULE: ParserRule = ParserRule::ipv4_prefix;
33}
34impl ParserAfi for Ipv6 {
35    const LITERAL_ADDR_RULE: ParserRule = ParserRule::ipv6_addr;
36    const LITERAL_PREFIX_RULE: ParserRule = ParserRule::ipv6_prefix;
37}
38impl ParserAfi for Any {
39    const LITERAL_ADDR_RULE: ParserRule = ParserRule::ip_addr_choice;
40    const LITERAL_PREFIX_RULE: ParserRule = ParserRule::ip_prefix_choice;
41}
42
43/// IP address literal.
44#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
45pub struct IpAddress<A: ParserAfi> {
46    inner: A::Address,
47}
48
49impl<A: ParserAfi> IpAddress<A> {
50    /// Construct a new [`Self`].
51    pub const fn new(address: A::Address) -> Self {
52        Self { inner: address }
53    }
54
55    /// Extract the underlying [`A::Address`][ip::traits::Address] value, consuming `self`.
56    pub const fn into_inner(self) -> A::Address {
57        self.inner
58    }
59}
60
61impl<A: ParserAfi> FromStr for IpAddress<A> {
62    type Err = ParseError;
63
64    fn from_str(s: &str) -> Result<Self, Self::Err> {
65        Ok(Self::new(s.parse()?))
66    }
67}
68
69impl<A: ParserAfi> TryFrom<TokenPair<'_>> for IpAddress<A> {
70    type Error = ParseError;
71
72    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
73        debug_construction!(pair => IpAddress);
74        match pair.as_rule() {
75            rule if rule == A::LITERAL_ADDR_RULE => Ok(pair.as_str().parse()?),
76            _ => Err(rule_mismatch!(pair => "IP address")),
77        }
78    }
79}
80
81impl<A: ParserAfi> fmt::Display for IpAddress<A> {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        self.inner.fmt(f)
84    }
85}
86
87#[cfg(any(test, feature = "arbitrary"))]
88impl<A> Arbitrary for IpAddress<A>
89where
90    A: ParserAfi + 'static,
91    A::Address: Arbitrary,
92    <A::Address as Arbitrary>::Strategy: 'static,
93{
94    type Parameters = ParamsFor<A::Address>;
95    type Strategy = BoxedStrategy<Self>;
96    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
97        any_with::<A::Address>(params).prop_map(Self::new).boxed()
98    }
99}
100
101/// IP prefix literal.
102#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
103pub struct IpPrefix<A: ParserAfi> {
104    inner: A::Prefix,
105}
106
107impl<A: ParserAfi> IpPrefix<A> {
108    /// Construct a new [`Self`].
109    pub const fn new(prefix: A::Prefix) -> Self {
110        Self { inner: prefix }
111    }
112
113    /// Extract the underlying [`A::Prefix`][ip::traits::Prefix] value, consuming `self`.
114    pub const fn into_inner(self) -> A::Prefix {
115        self.inner
116    }
117}
118
119impl<A: ParserAfi> FromStr for IpPrefix<A> {
120    type Err = ParseError;
121
122    fn from_str(s: &str) -> Result<Self, Self::Err> {
123        Ok(Self::new(s.parse()?))
124    }
125}
126
127impl<A: ParserAfi> TryFrom<TokenPair<'_>> for IpPrefix<A> {
128    type Error = ParseError;
129
130    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
131        debug_construction!(pair => Prefix);
132        match pair.as_rule() {
133            rule if rule == A::LITERAL_PREFIX_RULE => Ok(pair.as_str().parse()?),
134            _ => Err(rule_mismatch!(pair => "IP prefix")),
135        }
136    }
137}
138
139impl<A: ParserAfi> fmt::Display for IpPrefix<A> {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        self.inner.fmt(f)
142    }
143}
144
145#[cfg(any(test, feature = "arbitrary"))]
146impl<A> Arbitrary for IpPrefix<A>
147where
148    A: ParserAfi + 'static,
149    A::Prefix: Arbitrary,
150{
151    type Parameters = ParamsFor<A::Prefix>;
152    type Strategy = BoxedStrategy<Self>;
153    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
154        any_with::<A::Prefix>(params).prop_map(Self::new).boxed()
155    }
156}
157
158/// IP prefix range literal.
159#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
160pub struct IpPrefixRange<A: ParserAfi> {
161    prefix: IpPrefix<A>,
162    op: RangeOperator,
163}
164
165impl<A: ParserAfi> IpPrefixRange<A> {
166    /// Construct a new [`IpPrefixRange<A>`].
167    pub const fn new(prefix: IpPrefix<A>, op: RangeOperator) -> Self {
168        Self { prefix, op }
169    }
170
171    /// Get the IP prefix represented by this [`IpPrefixRange<A>`].
172    pub const fn prefix(&self) -> IpPrefix<A> {
173        self.prefix
174    }
175
176    /// Get the [`RangeOperator`] for this [`IpPrefixRange<A>`].
177    pub const fn operator(&self) -> RangeOperator {
178        self.op
179    }
180}
181
182impl<A: ParserAfi> TryFrom<TokenPair<'_>> for IpPrefixRange<A> {
183    type Error = ParseError;
184
185    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
186        debug_construction!(pair => PrefixRange);
187        let mut pairs = pair.into_inner();
188        let prefix = next_parse_or!(pairs => "failed to get inner prefix")?;
189        let op = match pairs.next() {
190            Some(inner) => inner.try_into()?,
191            None => RangeOperator::None,
192        };
193        Ok(Self { prefix, op })
194    }
195}
196
197impl<A: ParserAfi> fmt::Display for IpPrefixRange<A> {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        write!(f, "{}{}", self.prefix, self.op)
200    }
201}
202
203#[cfg(any(test, feature = "arbitrary"))]
204impl<A> Arbitrary for IpPrefixRange<A>
205where
206    A: ParserAfi + 'static,
207    A::Prefix: Arbitrary,
208    <A::Prefix as ip::traits::Prefix>::Length: AsRef<u8>,
209{
210    type Parameters = ParamsFor<IpPrefix<A>>;
211    type Strategy = BoxedStrategy<Self>;
212    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
213        any_with::<IpPrefix<A>>(params)
214            .prop_flat_map(|prefix| {
215                let len = *prefix.into_inner().prefix_len().as_ref();
216                let max_len = *prefix.into_inner().max_prefix_len().as_ref();
217                (Just(prefix), any_with::<RangeOperator>((len, max_len)))
218            })
219            .prop_map(|(prefix, op)| Self::new(prefix, op))
220            .boxed()
221    }
222}
223
224/// RSPL range operator. See [RFC2622].
225///
226/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-2
227#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
228pub enum RangeOperator {
229    /// No range operator.
230    None,
231    /// Exclusive more specific operator (`^-`).
232    LessExcl,
233    /// Inclusive more specific operator (`^+`).
234    LessIncl,
235    /// Exact length operator (`^n`).
236    Exact(u8),
237    /// Length range operator (`^m-n`).
238    Range(u8, u8),
239}
240
241impl TryFrom<TokenPair<'_>> for RangeOperator {
242    type Error = ParseError;
243
244    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
245        debug_construction!(pair => PrefixOp);
246        match pair.as_rule() {
247            ParserRule::less_excl => Ok(Self::LessExcl),
248            ParserRule::less_incl => Ok(Self::LessIncl),
249            ParserRule::exact => Ok(Self::Exact(
250                next_parse_or!(pair.into_inner() => "failed to get operand for range operation")?,
251            )),
252            ParserRule::range => {
253                let mut pairs = pair.into_inner();
254                Ok(Self::Range(
255                    next_parse_or!(pairs => "failed to get lower operand for range operation")?,
256                    next_parse_or!(pairs => "failed to get upper operand for range operation")?,
257                ))
258            }
259            _ => Err(err!(
260                "expected a prefix range operation, got {:?}: {}",
261                pair.as_rule(),
262                pair.as_str()
263            )),
264        }
265    }
266}
267
268impl fmt::Display for RangeOperator {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        match self {
271            Self::None => write!(f, ""),
272            Self::LessExcl => write!(f, "^-"),
273            Self::LessIncl => write!(f, "^+"),
274            Self::Exact(n) => write!(f, "^{n}"),
275            Self::Range(m, n) => write!(f, "^{m}-{n}"),
276        }
277    }
278}
279
280#[cfg(any(test, feature = "arbitrary"))]
281impl Arbitrary for RangeOperator {
282    type Parameters = (u8, u8);
283    type Strategy = BoxedStrategy<Self>;
284    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
285        prop_oneof![
286            Just(Self::None),
287            Just(Self::LessExcl),
288            Just(Self::LessIncl),
289            (args.0..=args.1).prop_map(Self::Exact),
290            (args.0..=args.1)
291                .prop_flat_map(move |lower| (Just(lower), lower..=args.1))
292                .prop_map(|(lower, upper)| Self::Range(lower, upper))
293        ]
294        .boxed()
295    }
296}
297
298/// Components (seperated by `:`) in RPSL set names.
299/// See [RFC2622].
300///
301/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-5
302#[allow(variant_size_differences)]
303#[derive(Clone, Debug, Hash, PartialEq, Eq)]
304pub enum SetNameComp {
305    /// Component containing the name of an `aut-num`.
306    AutNum(AutNum),
307    /// Component containing the `PeerAS` token.
308    PeerAs(PeerAs),
309    /// Component containing a set name, according to the class of the set.
310    Name(SetNameCompName),
311}
312
313impl TryFrom<TokenPair<'_>> for SetNameComp {
314    type Error = ParseError;
315
316    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
317        debug_construction!(pair => SetNameComp);
318        match pair.as_rule() {
319            ParserRule::aut_num => Ok(Self::AutNum(pair.as_str().parse()?)),
320            ParserRule::peeras => Ok(Self::PeerAs(PeerAs)),
321            ParserRule::filter_set_name
322            | ParserRule::route_set_name
323            | ParserRule::as_set_name
324            | ParserRule::rtr_set_name
325            | ParserRule::peering_set_name => Ok(Self::Name(pair.try_into()?)),
326            _ => Err(rule_mismatch!(pair => "set name component")),
327        }
328    }
329}
330
331impl fmt::Display for SetNameComp {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        match self {
334            Self::AutNum(autnum) => autnum.fmt(f),
335            Self::PeerAs(_) => write!(f, "PeerAS"),
336            Self::Name(name) => name.fmt(f),
337        }
338    }
339}
340
341#[cfg(any(test, feature = "arbitrary"))]
342impl Arbitrary for SetNameComp {
343    type Parameters = ParamsFor<SetNameCompName>;
344    type Strategy = BoxedStrategy<Self>;
345    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
346        prop_oneof![
347            any::<AutNum>().prop_map(Self::AutNum),
348            Just(Self::PeerAs(PeerAs)),
349            any_with::<SetNameCompName>(params).prop_map(Self::Name),
350        ]
351        .boxed()
352    }
353}
354
355/// '`PeerAS`' RPSL keyword token.
356/// See [RFC2622].
357///
358/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-5.4
359#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
360pub struct PeerAs;
361
362/// RPSL set class name component.
363/// See [RFC2622].
364///
365/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-5
366#[derive(Clone, Debug)]
367pub struct SetNameCompName(String);
368impl_case_insensitive_str_primitive!(
369    ParserRule::filter_set_name
370     | ParserRule::route_set_name
371     | ParserRule::as_set_name
372     | ParserRule::rtr_set_name
373     | ParserRule::peering_set_name => SetNameCompName
374);
375
376#[cfg(any(test, feature = "arbitrary"))]
377impl Arbitrary for SetNameCompName {
378    type Parameters = ParamsFor<String>;
379    type Strategy = BoxedStrategy<Self>;
380    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
381        prop_filter_keywords(any_with::<String>(params))
382            .prop_map(Self)
383            .boxed()
384    }
385}
386
387/// RPSL `descr` attribute string.
388/// See [RFC2622].
389///
390/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-3.1
391#[derive(Clone, Debug)]
392pub struct ObjectDescr(String);
393impl_case_insensitive_str_primitive!(ParserRule::object_descr => ObjectDescr);
394#[cfg(any(test, feature = "arbitrary"))]
395impl_free_form_arbitrary!(ObjectDescr);
396
397/// RPSL `nic-hanndle`.
398/// See [RFC2622].
399///
400/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-2
401#[derive(Clone, Debug)]
402pub struct NicHdl(String);
403impl_case_insensitive_str_primitive!(ParserRule::nic_hdl => NicHdl);
404#[cfg(any(test, feature = "arbitrary"))]
405impl_rpsl_name_arbitrary!(NicHdl);
406
407/// RPSL `remarks` attribute string.
408/// See [RFC2622].
409///
410/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-3.1
411#[derive(Clone, Debug)]
412pub struct Remarks(String);
413impl_case_insensitive_str_primitive!(ParserRule::remarks => Remarks);
414#[cfg(any(test, feature = "arbitrary"))]
415impl_free_form_arbitrary!(Remarks);
416
417/// RPSL `registry-name`.
418/// See [RFC2622].
419///
420/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-2
421#[derive(Clone, Debug)]
422pub struct RegistryName(String);
423impl_case_insensitive_str_primitive!(ParserRule::registry_name => RegistryName);
424#[cfg(any(test, feature = "arbitrary"))]
425impl_rpsl_name_arbitrary!(RegistryName);
426
427/// RPSL `address` attribute string.
428/// See [RFC2622].
429///
430/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-3.2
431#[derive(Clone, Debug)]
432pub struct Address(String);
433impl_case_insensitive_str_primitive!(ParserRule::address => Address);
434#[cfg(any(test, feature = "arbitrary"))]
435impl_free_form_arbitrary!(Address);
436
437/// RPSL `email-address`.
438/// See [RFC2622].
439///
440/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-2
441#[derive(Clone, Debug)]
442pub struct EmailAddress(String);
443impl_case_insensitive_str_primitive!(ParserRule::email_addr => EmailAddress);
444
445#[cfg(any(test, feature = "arbitrary"))]
446impl Arbitrary for EmailAddress {
447    type Parameters = ();
448    type Strategy = BoxedStrategy<Self>;
449    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
450        (any::<DnsName>(), any::<DnsName>())
451            .prop_map(|(user, host)| format!("{user}@{host}"))
452            .prop_map(Self)
453            .boxed()
454    }
455}
456
457/// RPSL `phone` or `fax-no` attribute string.
458/// See [RFC2622].
459///
460/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-3.2
461#[derive(Clone, Debug)]
462pub struct TelNumber(String);
463impl_case_insensitive_str_primitive!(ParserRule::tel_number => TelNumber);
464
465#[cfg(any(test, feature = "arbitrary"))]
466impl Arbitrary for TelNumber {
467    type Parameters = ();
468    type Strategy = BoxedStrategy<Self>;
469    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
470        r"\+[0-9][0-9 ]*( ext\. [0-9]+)?".prop_map(Self).boxed()
471    }
472}
473
474/// Email address regular expression used in the `MAIL-FROM` authentication
475/// scheme.
476/// See [RFC2622].
477///
478/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-3.1
479#[derive(Clone, Debug)]
480pub struct EmailAddressRegex(String);
481impl_case_insensitive_str_primitive!(ParserRule::email_addr_regexp => EmailAddressRegex);
482#[cfg(any(test, feature = "arbitrary"))]
483impl_free_form_arbitrary!(EmailAddressRegex);
484
485/// PGP key fingerprint used in the `PGP-FROM` authentication scheme.
486/// See [RFC2725].
487///
488/// [RFC2725]: https://datatracker.ietf.org/doc/html/rfc2725#section-8
489#[derive(Clone, Debug)]
490pub struct PgpFromFingerprint(String);
491impl_case_insensitive_str_primitive!(ParserRule::pgp_from_fingerpr => PgpFromFingerprint);
492#[cfg(any(test, feature = "arbitrary"))]
493impl_free_form_arbitrary!(PgpFromFingerprint);
494
495/// UNIX crypt hash value used in the `CRYPT-PW` authentication scheme.
496/// See [RFC2622].
497///
498/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-3.1
499#[derive(Clone, Debug, Hash, PartialEq, Eq)]
500pub struct CryptHash(String);
501impl_str_primitive!(ParserRule::crypt_hash => CryptHash);
502#[cfg(any(test, feature = "arbitrary"))]
503impl_free_form_arbitrary!(CryptHash);
504
505/// RPSL `trouble` attribute string.
506/// See [RFC2622].
507///
508/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-3.3
509#[derive(Clone, Debug)]
510pub struct Trouble(String);
511impl_case_insensitive_str_primitive!(ParserRule::trouble => Trouble);
512#[cfg(any(test, feature = "arbitrary"))]
513impl_free_form_arbitrary!(Trouble);
514
515/// RPSL `key-cert` object owner.
516/// See [RFC2726].
517///
518/// [RFC2726]: https://datatracker.ietf.org/doc/html/rfc2726#section-2.1
519#[derive(Clone, Debug)]
520pub struct KeyOwner(String);
521impl_case_insensitive_str_primitive!(ParserRule::owner => KeyOwner);
522#[cfg(any(test, feature = "arbitrary"))]
523impl_free_form_arbitrary!(KeyOwner);
524
525/// Key fingerprint appearing in an RPSL `key-cert` object.
526/// See [RFC2726].
527///
528/// [RFC2726]: https://datatracker.ietf.org/doc/html/rfc2726#section-2.1
529#[derive(Clone, Debug)]
530pub struct Fingerprint(String);
531impl_case_insensitive_str_primitive!(ParserRule::key_fingerprint => Fingerprint);
532
533#[cfg(any(test, feature = "arbitrary"))]
534impl Arbitrary for Fingerprint {
535    type Parameters = ();
536    type Strategy = BoxedStrategy<Self>;
537    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
538        r"[0-9A-Fa-f]+".prop_map(Self).boxed()
539    }
540}
541
542/// ASCII armoured certificate appearing in an RPSL `key-cert` object.
543/// See [RFC2726].
544///
545/// [RFC2726]: https://datatracker.ietf.org/doc/html/rfc2726#section-2.1
546#[derive(Clone, Debug, Hash, PartialEq, Eq)]
547pub struct Certificate(String);
548impl_str_primitive!(ParserRule::key_certif => Certificate);
549#[cfg(any(test, feature = "arbitrary"))]
550impl_free_form_arbitrary!(Certificate);
551
552/// Autonomous system name, contained in the `as-name` RPSL attribute.
553/// See [RFC2622].
554///
555/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-6
556#[derive(Clone, Debug)]
557pub struct AsName(String);
558impl_case_insensitive_str_primitive!(ParserRule::as_name => AsName);
559#[cfg(any(test, feature = "arbitrary"))]
560impl_rpsl_name_arbitrary!(AsName);
561
562/// IP network name, contained in the `netname` RIPE-81 attribute.
563/// See [RFC1786].
564///
565/// [RFC1786]: https://datatracker.ietf.org/doc/html/rfc1786
566#[derive(Clone, Debug)]
567pub struct Netname(String);
568impl_case_insensitive_str_primitive!(ParserRule::netname => Netname);
569#[cfg(any(test, feature = "arbitrary"))]
570impl_rpsl_name_arbitrary!(Netname);
571
572/// [ISO-3166] two letter country code.
573///
574/// [ISO-3166]: https://www.iso.org/obp/ui/#search
575#[derive(Clone, Debug)]
576pub struct CountryCode(String);
577impl_case_insensitive_str_primitive!(ParserRule::country_code => CountryCode);
578
579#[cfg(any(test, feature = "arbitrary"))]
580impl Arbitrary for CountryCode {
581    type Parameters = ();
582    type Strategy = BoxedStrategy<Self>;
583    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
584        r"[A-Za-z]{2}".prop_map(Self).boxed()
585    }
586}
587
588/// RPSL `dns-name`.
589/// See [RFC2622].
590///
591/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-2
592#[derive(Clone, Debug)]
593pub struct DnsName(String);
594impl_case_insensitive_str_primitive!(ParserRule::dns_name => DnsName);
595
596#[cfg(any(test, feature = "arbitrary"))]
597impl Arbitrary for DnsName {
598    type Parameters = ();
599    type Strategy = BoxedStrategy<Self>;
600    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
601        prop_filter_keywords(r"[A-Za-z][0-9A-Za-z_-]*(\.[A-Za-z][0-9A-Za-z_-]*)*")
602            .prop_map(Self)
603            .boxed()
604    }
605}
606
607/// RPSL `date`.
608/// See [RFC2622].
609///
610/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-2
611#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
612pub struct Date(time::Date);
613
614const DATE_FMT: &[FormatItem<'_>] = format_description!("[year][month][day]");
615
616impl AsRef<time::Date> for Date {
617    fn as_ref(&self) -> &time::Date {
618        &self.0
619    }
620}
621
622impl FromStr for Date {
623    type Err = ParseError;
624    fn from_str(s: &str) -> ParseResult<Self> {
625        Ok(Self(time::Date::parse(s, DATE_FMT)?))
626    }
627}
628
629impl TryFrom<TokenPair<'_>> for Date {
630    type Error = ParseError;
631
632    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
633        debug_construction!(pair => Date);
634        match pair.as_rule() {
635            ParserRule::date => pair.as_str().parse(),
636            _ => Err(rule_mismatch!(pair => "date")),
637        }
638    }
639}
640
641impl fmt::Display for Date {
642    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
643        write!(f, "{}", self.0.format(DATE_FMT).map_err(|_| fmt::Error)?)
644    }
645}
646
647#[cfg(any(test, feature = "arbitrary"))]
648impl Arbitrary for Date {
649    type Parameters = ();
650    type Strategy = BoxedStrategy<Self>;
651    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
652        (time::macros::date!(0000 - 01 - 01).to_julian_day()..time::Date::MAX.to_julian_day())
653            .prop_map(|day| Self(time::Date::from_julian_day(day).unwrap()))
654            .boxed()
655    }
656}
657
658/// RPSL `key-cert` object signing method.
659/// See [RFC2726].
660///
661/// [RFC2726]: https://datatracker.ietf.org/doc/html/rfc2726#section-2.1
662#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
663pub enum SigningMethod {
664    /// `PGP` signing method.
665    Pgp,
666    /// `X509` signing method.
667    X509,
668}
669
670impl TryFrom<TokenPair<'_>> for SigningMethod {
671    type Error = ParseError;
672
673    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
674        debug_construction!(pair => SigningMethod);
675        match pair.as_rule() {
676            ParserRule::signing_method_pgp => Ok(Self::Pgp),
677            ParserRule::signing_method_x509 => Ok(Self::X509),
678            _ => Err(rule_mismatch!(pair => "signing method")),
679        }
680    }
681}
682
683impl fmt::Display for SigningMethod {
684    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685        match self {
686            Self::Pgp => write!(f, "PGP"),
687            Self::X509 => write!(f, "X509"),
688        }
689    }
690}
691
692#[cfg(any(test, feature = "arbitrary"))]
693impl Arbitrary for SigningMethod {
694    type Parameters = ();
695    type Strategy = BoxedStrategy<Self>;
696    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
697        prop_oneof![Just(Self::Pgp), Just(Self::X509)].boxed()
698    }
699}
700
701/// RPSL `protocol` name.
702/// See [RFC2622].
703///
704/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-7
705#[derive(Clone, Debug, Hash, PartialEq, Eq)]
706pub enum Protocol {
707    /// Border Gateway Protocol version 4.
708    Bgp4,
709    /// BGP version 4 with Multi-Protocol extensions.
710    MpBgp,
711    /// Open Shortest Path First.
712    Ospf,
713    /// Routing Information Protocol "next-gen".
714    RipNg,
715    /// Routing Information Protocol.
716    Rip,
717    /// Interior Gateway Routing Protocol.
718    Igrp,
719    /// ISO Intermediate System to Intermediate System protocol.
720    IsIs,
721    /// Static routing information.
722    Static,
723    /// Dynamic Vector Multicast Routing Protocol.
724    Dvmrp,
725    /// Protocol Independent Multicast - Dense Mode.
726    PimDm,
727    /// Protocol Independent Multicast - Sparse Mode.
728    PimSm,
729    /// Core Based Trees.
730    Cbt,
731    /// Multicast Open Shortest Path First.
732    Mospf,
733    /// Unknown routing protocol variant.
734    Unknown(UnknownProtocol),
735}
736
737impl TryFrom<TokenPair<'_>> for Protocol {
738    type Error = ParseError;
739
740    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
741        debug_construction!(pair => Protocol);
742        match pair.as_rule() {
743            ParserRule::protocol_bgp4 => Ok(Self::Bgp4),
744            ParserRule::protocol_mpbgp => Ok(Self::MpBgp),
745            ParserRule::protocol_ospf => Ok(Self::Ospf),
746            ParserRule::protocol_ripng => Ok(Self::RipNg),
747            ParserRule::protocol_rip => Ok(Self::Rip),
748            ParserRule::protocol_igrp => Ok(Self::Igrp),
749            ParserRule::protocol_isis => Ok(Self::IsIs),
750            ParserRule::protocol_static => Ok(Self::Static),
751            ParserRule::protocol_dvmrp => Ok(Self::Dvmrp),
752            ParserRule::protocol_pim_dm => Ok(Self::PimDm),
753            ParserRule::protocol_pim_sm => Ok(Self::PimSm),
754            ParserRule::protocol_cbt => Ok(Self::Cbt),
755            ParserRule::protocol_mospf => Ok(Self::Mospf),
756            ParserRule::protocol_unknown => Ok(Self::Unknown(pair.try_into()?)),
757            _ => Err(rule_mismatch!(pair => "protocol name")),
758        }
759    }
760}
761
762impl fmt::Display for Protocol {
763    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
764        match self {
765            Self::Bgp4 => write!(f, "BGP4"),
766            Self::MpBgp => write!(f, "MPBGP"),
767            Self::Ospf => write!(f, "OSPF"),
768            Self::RipNg => write!(f, "RIPng"),
769            Self::Rip => write!(f, "RIP"),
770            Self::Igrp => write!(f, "IGRP"),
771            Self::IsIs => write!(f, "IS-IS"),
772            Self::Static => write!(f, "STATIC"),
773            Self::Dvmrp => write!(f, "DVMRP"),
774            Self::PimDm => write!(f, "PIM-DM"),
775            Self::PimSm => write!(f, "PIM-SM"),
776            Self::Cbt => write!(f, "CBT"),
777            Self::Mospf => write!(f, "MOSPF"),
778            Self::Unknown(name) => write!(f, "{name}"),
779        }
780    }
781}
782
783#[cfg(any(test, feature = "arbitrary"))]
784impl Arbitrary for Protocol {
785    type Parameters = ();
786    type Strategy = BoxedStrategy<Self>;
787    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
788        prop_oneof![
789            Just(Self::Bgp4),
790            Just(Self::MpBgp),
791            Just(Self::Ospf),
792            Just(Self::RipNg),
793            Just(Self::Rip),
794            Just(Self::Igrp),
795            Just(Self::IsIs),
796            Just(Self::Static),
797            Just(Self::Dvmrp),
798            Just(Self::PimDm),
799            Just(Self::PimSm),
800            Just(Self::Cbt),
801            Just(Self::Mospf),
802            any::<UnknownProtocol>().prop_map(Self::Unknown)
803        ]
804        .boxed()
805    }
806}
807
808/// An unknown `protocol` name.
809#[derive(Clone, Debug)]
810pub struct UnknownProtocol(String);
811impl_case_insensitive_str_primitive!(ParserRule::protocol_unknown => UnknownProtocol);
812#[cfg(any(test, feature = "arbitrary"))]
813impl_rpsl_name_arbitrary!(UnknownProtocol);
814
815/// RPSL `protocol` option name.
816/// See [RFC2622].
817///
818/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-9
819#[derive(Clone, Debug)]
820pub struct PeerOptKey(String);
821impl_case_insensitive_str_primitive!(ParserRule::peer_opt_key => PeerOptKey);
822#[cfg(any(test, feature = "arbitrary"))]
823impl_rpsl_name_arbitrary!(PeerOptKey);
824
825/// RPSL `protocol` option value.
826/// See [RFC2622].
827///
828/// [RFC2622]: https://datatracker.ietf.org/doc/html/rfc2622#section-9
829#[derive(Clone, Debug)]
830pub struct PeerOptVal(String);
831impl_case_insensitive_str_primitive!(ParserRule::peer_opt_val => PeerOptVal);
832
833#[cfg(any(test, feature = "arbitrary"))]
834impl Arbitrary for PeerOptVal {
835    type Parameters = ();
836    type Strategy = BoxedStrategy<Self>;
837    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
838        r"[^\);\pC]+".prop_map(Self).boxed()
839    }
840}
841
842/// RPSL `afi` names.
843/// See [RFC4012].
844///
845/// [RFC4012]: https://datatracker.ietf.org/doc/html/rfc4012#section-2.1
846#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
847pub struct AfiSafi {
848    afi: Afi,
849    safi: Option<Safi>,
850}
851
852impl_from_str!(ParserRule::afi_safi => AfiSafi);
853
854impl TryFrom<TokenPair<'_>> for AfiSafi {
855    type Error = ParseError;
856
857    #[allow(clippy::similar_names)]
858    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
859        debug_construction!(pair => AfiSafi);
860        match pair.as_rule() {
861            ParserRule::afi_safi => {
862                let mut pairs = pair.into_inner();
863                let afi = next_into_or!(pairs => "failed to get afi name")?;
864                let safi = if let Some(inner_pair) = pairs.next() {
865                    Some(inner_pair.try_into()?)
866                } else {
867                    None
868                };
869                Ok(Self { afi, safi })
870            }
871            _ => Err(rule_mismatch!(pair => "afi/safi identifier")),
872        }
873    }
874}
875
876impl fmt::Display for AfiSafi {
877    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878        write!(f, "{}", self.afi)?;
879        if let Some(safi) = &self.safi {
880            write!(f, ".{safi}")?;
881        }
882        Ok(())
883    }
884}
885
886#[cfg(any(test, feature = "arbitrary"))]
887impl Arbitrary for AfiSafi {
888    type Parameters = ParamsFor<Option<Safi>>;
889    type Strategy = BoxedStrategy<Self>;
890    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
891        (any::<Afi>(), any_with::<Option<Safi>>(params))
892            .prop_map(|(afi, safi)| Self { afi, safi })
893            .boxed()
894    }
895}
896
897/// RPSL address family indicator.
898/// See [RFC4012].
899///
900/// [RFC4012]: https://datatracker.ietf.org/doc/html/rfc4012#section-2.1
901#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
902pub enum Afi {
903    /// `ipv4` address family.
904    Ipv4,
905    /// `ipv6` address family.
906    Ipv6,
907    /// `any` token.
908    Any,
909}
910
911impl TryFrom<TokenPair<'_>> for Afi {
912    type Error = ParseError;
913
914    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
915        debug_construction!(pair => Afi);
916        match pair.as_rule() {
917            ParserRule::afi_ipv4 => Ok(Self::Ipv4),
918            ParserRule::afi_ipv6 => Ok(Self::Ipv6),
919            ParserRule::afi_any => Ok(Self::Any),
920            _ => Err(rule_mismatch!(pair => "afi identifier")),
921        }
922    }
923}
924
925impl fmt::Display for Afi {
926    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
927        match self {
928            Self::Ipv4 => write!(f, "ipv4"),
929            Self::Ipv6 => write!(f, "ipv6"),
930            Self::Any => write!(f, "any"),
931        }
932    }
933}
934
935#[cfg(any(test, feature = "arbitrary"))]
936impl Arbitrary for Afi {
937    type Parameters = ();
938    type Strategy = BoxedStrategy<Self>;
939    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
940        prop_oneof![Just(Self::Ipv4), Just(Self::Ipv6), Just(Self::Any)].boxed()
941    }
942}
943
944/// RPSL subsequent address family indicator.
945/// See [RFC4012}].
946///
947/// [RFC4012]: https://datatracker.ietf.org/doc/html/rfc4012#section-2.1
948#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
949pub enum Safi {
950    /// `unicast` SAFI.
951    Unicast,
952    /// `multicast` SAFI.
953    Multicast,
954}
955
956impl TryFrom<TokenPair<'_>> for Safi {
957    type Error = ParseError;
958
959    fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
960        debug_construction!(pair => SafiName);
961        match pair.as_rule() {
962            ParserRule::safi_unicast => Ok(Self::Unicast),
963            ParserRule::safi_multicast => Ok(Self::Multicast),
964            _ => Err(rule_mismatch!(pair => "safi identifier")),
965        }
966    }
967}
968
969impl fmt::Display for Safi {
970    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
971        match self {
972            Self::Unicast => write!(f, "unicast"),
973            Self::Multicast => write!(f, "multicast"),
974        }
975    }
976}
977
978#[cfg(any(test, feature = "arbitrary"))]
979impl Arbitrary for Safi {
980    type Parameters = ();
981    type Strategy = BoxedStrategy<Self>;
982    fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
983        prop_oneof![Just(Self::Unicast), Just(Self::Multicast)].boxed()
984    }
985}
986
987/// Helpers for implementing [`Arbitrary`] for primitive types.
988#[cfg(any(test, feature = "arbitrary"))]
989pub mod arbitrary {
990    use proptest::strategy::Strategy;
991
992    use regex::RegexSetBuilder;
993
994    /// Filter the values yielded by a [`Strategy<Value = String>`] for
995    /// reserved RPSL keywords.
996    #[allow(clippy::missing_panics_doc)]
997    pub fn prop_filter_keywords<S>(strategy: S) -> impl Strategy<Value = String>
998    where
999        S: Strategy<Value = String>,
1000    {
1001        let keywords = RegexSetBuilder::new([
1002            "^ANY$",
1003            "^AS-ANY$",
1004            "^RS-ANY$",
1005            "^PeerAS$",
1006            "^AND$",
1007            "^OR$",
1008            "^NOT$",
1009            "^ATOMIC$",
1010            "^FROM$",
1011            "^TO$",
1012            "^AT$",
1013            "^ACTION$",
1014            "^ACCEPT$",
1015            "^ANNOUNCE$",
1016            "^EXCEPT$",
1017            "^REFINE$",
1018            "^NETWORKS$",
1019            "^INTO$",
1020            "^INBOUND$",
1021            "^OUTBOUND$",
1022        ])
1023        .case_insensitive(true)
1024        .build()
1025        .expect("Unexpected regex compilation failure. Please file a bug.");
1026        strategy.prop_filter("names cannot collide with rpsl keywords", move |s| {
1027            !keywords.is_match(s)
1028        })
1029    }
1030
1031    macro_rules! impl_rpsl_name_arbitrary {
1032        ( $t:ty ) => {
1033            impl ::proptest::arbitrary::Arbitrary for $t {
1034                type Parameters = ();
1035                type Strategy = ::proptest::strategy::BoxedStrategy<Self>;
1036                fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1037                    let reserved =
1038                        ::regex::Regex::new(r"^(?i)AS\d|AS-|RS-|FLTR-|RTRS-|PRNG-").unwrap();
1039                    $crate::primitive::arbitrary::prop_filter_keywords("[A-Za-z][A-Za-z0-9_-]+")
1040                        .prop_filter_map("names cannot begin with a reserved sequence", move |s| {
1041                            if reserved.is_match(&s) {
1042                                None
1043                            } else {
1044                                Some(Self(s))
1045                            }
1046                        })
1047                        .boxed()
1048                }
1049            }
1050        };
1051    }
1052    pub(crate) use impl_rpsl_name_arbitrary;
1053
1054    macro_rules! impl_free_form_arbitrary {
1055        ( $t:ty ) => {
1056            impl ::proptest::arbitrary::Arbitrary for $t {
1057                type Parameters = ();
1058                type Strategy = ::proptest::strategy::BoxedStrategy<Self>;
1059                fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1060                    r"([^#\pC\s][^#\pC]*)?".prop_map(Self).boxed()
1061                }
1062            }
1063        };
1064    }
1065    pub(crate) use impl_free_form_arbitrary;
1066}