irrc/
query.rs

1use std::convert::TryInto;
2use std::fmt;
3use std::iter::{once, Once};
4use std::str::FromStr;
5use std::time::Duration;
6
7use rpsl::names::{AsSet, AutNum, Mntner, RouteSet};
8
9use crate::{error::Error, parse, pipeline::ResponseContent};
10
11/// IRRd query variants.
12// TODO: !a, !j, maybe !J
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum Query {
15    /// Returns the current version of the server.
16    Version,
17    /// Identifies the client to the server.
18    ///
19    /// This should usually be used via
20    /// [`client_id()`][crate::IrrClient::client_id], rather than being issued
21    /// directly.
22    SetClientId(String),
23    /// Sets the server-side timeout of the connection.
24    ///
25    /// This should usually be used via
26    /// [`server_timeout()`][crate::IrrClient::server_timeout],
27    /// rather than being issued directly.
28    SetTimeout(Duration),
29    /// Returns the list of sources currently selected for query resolution.
30    GetSources,
31    /// Sets the list of sources to be used for subsequent query resolution.
32    SetSources(Vec<String>),
33    /// Re-sets the list of sources to all those available on the server.
34    UnsetSources,
35    /// Returns all (direct) members of an `as-set`.
36    AsSetMembers(AsSet),
37    /// Returns all members of an `as-set`, recursively expanding `as-set`
38    /// members as necessary.
39    AsSetMembersRecursive(AsSet),
40    /// Returns all (direct) members of a `route-set`.
41    RouteSetMembers(RouteSet),
42    /// Returns all members of an `route-set`, recursively expanding members
43    /// as necessary.
44    RouteSetMembersRecursive(RouteSet),
45    /// Returns all IPv4 prefixes corresponding to a `route` object having
46    /// `origin:` set to the provided AS.
47    Ipv4Routes(AutNum),
48    /// Returns all IPv6 prefixes corresponding to a `route6` object having
49    /// `origin:` set to the provided AS.
50    Ipv6Routes(AutNum),
51    /// Returns an RPSL object exactly matching the provided key, of the
52    /// specified RPSL object class.
53    RpslObject(RpslObjectClass, String),
54    /// Returns all RPSL objects with the specified maintainer in their
55    /// `mnt-by:` attribute.
56    MntBy(Mntner),
57    /// Returns the unique `origin:`s of `route` or `route6` objects exactly
58    /// matching the provided prefix.
59    Origins(String),
60    /// Returns all RPSL `route` or `route6` objects exactly matching the
61    /// provided prefix.
62    RoutesExact(String),
63    /// Returns all RPSL `route` or `route6` objects one level less-specific
64    /// (excluding exeact matches) than the provided prefix.
65    RoutesLess(String),
66    /// Returns all RPSL `route` or `route6` objects one level less-specific
67    /// (including exeact matches) than the provided prefix.
68    RoutesLessEqual(String),
69    /// Returns all RPSL `route` or `route6` objects one level more-specific
70    /// (excluding exeact matches) than the provided prefix.
71    RoutesMore(String),
72}
73
74impl Query {
75    pub(crate) fn cmd(&self) -> String {
76        match self {
77            Self::Version => "!v\n".to_owned(),
78            Self::SetClientId(id) => format!("!n{id}\n"),
79            Self::SetTimeout(dur) => format!("!t{}\n", dur.as_secs()),
80            Self::GetSources => "!s-lc\n".to_owned(),
81            Self::SetSources(sources) => format!("!s{}\n", sources.join(",")),
82            Self::UnsetSources => "!s-*\n".to_owned(),
83            Self::AsSetMembers(q) => format!("!i{q}\n"),
84            Self::AsSetMembersRecursive(q) => format!("!i{q},1\n"),
85            Self::RouteSetMembers(q) => format!("!i{q}\n"),
86            Self::RouteSetMembersRecursive(q) => format!("!i{q},1\n"),
87            Self::Ipv4Routes(q) => format!("!g{q}\n"),
88            Self::Ipv6Routes(q) => format!("!6{q}\n"),
89            Self::RpslObject(class, q) => format!("!m{class},{q}\n"),
90            Self::MntBy(q) => format!("!o{q}\n"),
91            Self::Origins(q) => format!("!r{q},o\n"),
92            Self::RoutesExact(q) => format!("!r{q}\n"),
93            Self::RoutesLess(q) => format!("!r{q},l\n"),
94            Self::RoutesLessEqual(q) => format!("!r{q},L\n"),
95            Self::RoutesMore(q) => format!("!r{q},M\n"),
96        }
97    }
98
99    pub(crate) const fn expect_data(&self) -> bool {
100        matches!(
101            self,
102            Self::Version
103                | Self::GetSources
104                | Self::AsSetMembers(_)
105                | Self::RouteSetMembers(_)
106                | Self::AsSetMembersRecursive(_)
107                | Self::RouteSetMembersRecursive(_)
108                | Self::Ipv4Routes(_)
109                | Self::Ipv6Routes(_)
110                | Self::RpslObject(..)
111                | Self::MntBy(_)
112                | Self::Origins(_)
113                | Self::RoutesExact(_)
114                | Self::RoutesLess(_)
115                | Self::RoutesLessEqual(_)
116                | Self::RoutesMore(_)
117        )
118    }
119
120    pub(crate) fn parse_item<T>(&self, input: &[u8]) -> Result<(usize, ResponseContent<T>), Error>
121    where
122        T: FromStr + fmt::Debug,
123        T::Err: std::error::Error + Send + Sync + 'static,
124    {
125        let (_, (consumed, item)) = match self {
126            _ if !self.expect_data() => parse::noop(input)?,
127            Self::Version => parse::all(input)?,
128            Self::RpslObject(..)
129            | Self::MntBy(_)
130            | Self::RoutesExact(_)
131            | Self::RoutesLess(_)
132            | Self::RoutesLessEqual(_)
133            | Self::RoutesMore(_) => parse::paragraph(input)?,
134            _ => parse::word(input)?,
135        };
136        let content = item
137            .try_into()
138            .map_err(|err| Error::ParseItem(err, consumed))?;
139        Ok((consumed, content))
140    }
141}
142
143impl IntoIterator for Query {
144    type Item = Self;
145    type IntoIter = Once<Self>;
146    fn into_iter(self) -> Self::IntoIter {
147        once(self)
148    }
149}
150
151/// RPSL object classes.
152#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display)]
153#[cfg_attr(test, derive(strum::EnumIter))]
154pub enum RpslObjectClass {
155    /// `mntner` object class.
156    #[strum(to_string = "mntner")]
157    Mntner,
158    /// `person` object class.
159    #[strum(to_string = "person")]
160    Person,
161    /// `role` object class.
162    #[strum(to_string = "role")]
163    Role,
164    /// `route` object class.
165    #[strum(to_string = "route")]
166    Route,
167    /// `route6` object class.
168    #[strum(to_string = "route6")]
169    Route6,
170    /// `aut-num` object class.
171    #[strum(to_string = "aut-num")]
172    AutNum,
173    /// `inet-rtr` object class.
174    #[strum(to_string = "inet-rtr")]
175    InetRtr,
176    /// `as-set` object class.
177    #[strum(to_string = "as-set")]
178    AsSet,
179    /// `route-set` object class.
180    #[strum(to_string = "route-set")]
181    RouteSet,
182    /// `filter-set` object class.
183    #[strum(to_string = "filter-set")]
184    FilterSet,
185    /// `rtr-set` object class.
186    #[strum(to_string = "rtr-set")]
187    RtrSet,
188    /// `peering-set` object class.
189    #[strum(to_string = "peering-set")]
190    PeeringSet,
191}
192
193#[cfg(test)]
194// TODO: remove `unknown_lints` dance when `clippy::ignored_unit_patterns` is stabilised
195#[allow(unknown_lints)]
196#[allow(clippy::ignored_unit_patterns)]
197#[warn(unknown_lints)]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn query_is_singleton_iterator() {
203        let q = Query::Version;
204        let mut iter = q.clone().into_iter();
205        assert_eq!(iter.next(), Some(q));
206        assert_eq!(iter.next(), None);
207    }
208
209    mod proptests {
210        use proptest::{prelude::*, strategy::Union};
211        use strum::IntoEnumIterator;
212
213        use super::*;
214
215        impl Arbitrary for RpslObjectClass {
216            type Parameters = ();
217            type Strategy = Union<Just<Self>>;
218            fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
219                Union::new(Self::iter().map(Just))
220            }
221        }
222
223        impl Arbitrary for Query {
224            type Parameters = ();
225            type Strategy = BoxedStrategy<Self>;
226
227            fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
228                prop_oneof![
229                    Just(Self::Version),
230                    any::<String>().prop_map(Self::SetClientId),
231                    any::<Duration>().prop_map(Self::SetTimeout),
232                    Just(Self::GetSources),
233                    any::<Vec<String>>().prop_map(Self::SetSources),
234                    Just(Self::UnsetSources),
235                    any::<AsSet>().prop_map(Self::AsSetMembers),
236                    any::<AsSet>().prop_map(Self::AsSetMembersRecursive),
237                    any::<RouteSet>().prop_map(Self::RouteSetMembers),
238                    any::<RouteSet>().prop_map(Self::RouteSetMembersRecursive),
239                    any::<AutNum>().prop_map(Self::Ipv4Routes),
240                    any::<AutNum>().prop_map(Self::Ipv6Routes),
241                    any::<(RpslObjectClass, String)>()
242                        .prop_map(|(class, object)| Self::RpslObject(class, object)),
243                    any::<Mntner>().prop_map(Self::MntBy),
244                    any::<String>().prop_map(Self::Origins),
245                    any::<String>().prop_map(Self::RoutesExact),
246                    any::<String>().prop_map(Self::RoutesLess),
247                    any::<String>().prop_map(Self::RoutesLessEqual),
248                    any::<String>().prop_map(Self::RoutesMore),
249                ]
250                .boxed()
251            }
252        }
253
254        proptest! {
255            #[test]
256            fn cmd_begins_with_bang(q in any::<Query>()) {
257                assert!(q.cmd().starts_with('!'));
258            }
259
260            #[test]
261            fn cmd_ends_with_newline(q in any::<Query>()) {
262                assert!(q.cmd().ends_with('\n'));
263            }
264
265            #[test]
266            #[allow(unused_must_use)]
267            fn parse_item_never_panics(q in any::<Query>(), input in any::<Vec<u8>>()) {
268                q.parse_item::<String>(&input);
269            }
270        }
271    }
272}