1use std::{fmt, io};
4use std::str::FromStr;
5use chrono::{DateTime, Utc};
6use rpki::resources::{Asn, Prefix};
7use rpki::rtr::payload::RouteOrigin;
8use serde::Deserialize;
9use crate::payload::{PayloadInfo, PayloadSnapshot};
10use crate::utils::date::format_iso_date;
11
12
13#[derive(Clone, Debug)]
17pub struct RouteValidityList<'a> {
18 routes: Vec<RouteValidity<'a>>,
19 created: DateTime<Utc>,
20}
21
22impl<'a> RouteValidityList<'a> {
23 fn from_requests(
25 requests: &RequestList, snapshot: &'a PayloadSnapshot
26 ) -> Self {
27 RouteValidityList {
28 routes: requests.routes.iter().map(|route| {
29 RouteValidity::new(route.prefix, route.asn, snapshot)
30 }).collect(),
31 created: snapshot.created(),
32 }
33 }
34
35 pub fn write_plain<W: io::Write>(
36 &self,
37 target: &mut W
38 ) -> Result<(), io::Error> {
39 for route in &self.routes {
40 route.write_plain(target)?;
41 }
42 Ok(())
43 }
44
45 pub fn write_json<W: io::Write>(
46 &self,
47 target: &mut W
48 ) -> Result<(), io::Error> {
49 writeln!(target, "{{\n \"validated_routes\": [")?;
50 let mut first = true;
51 for route in &self.routes {
52 if first {
53 first = false;
54 }
55 else {
56 writeln!(target, ",")?;
57 }
58 write!(target, " ")?;
59 route.write_single_json(" ", target)?;
60 }
61 writeln!(target,
62 "\n ],\
63 \n \"generatedTime\": \"{}\"\
64 \n}}",
65 format_iso_date(self.created),
66 )
67 }
68
69 pub fn iter_state(
70 &self
71 ) -> impl Iterator<Item = (Prefix, Asn, RouteState)> + '_ {
72 self.routes.iter().map(|route| {
73 (route.prefix, route.asn, route.state())
74 })
75 }
76}
77
78
79#[derive(Clone, Debug)]
83pub struct RouteValidity<'a> {
84 prefix: Prefix,
86
87 asn: Asn,
89
90 matched: Vec<(RouteOrigin, &'a PayloadInfo)>,
92
93 bad_asn: Vec<(RouteOrigin, &'a PayloadInfo)>,
95
96 bad_len: Vec<(RouteOrigin, &'a PayloadInfo)>,
98}
99
100impl<'a> RouteValidity<'a> {
101 pub fn new(
102 prefix: Prefix,
103 asn: Asn,
104 snapshot: &'a PayloadSnapshot
105 ) -> Self {
106 let mut matched = Vec::new();
107 let mut bad_asn = Vec::new();
108 let mut bad_len = Vec::new();
109 for item in snapshot.origins() {
110 if item.0.prefix.prefix().covers(prefix) {
111 if prefix.len() > item.0.prefix.resolved_max_len() {
112 bad_len.push(item);
113 }
114 else if item.0.asn != asn {
115 bad_asn.push(item);
116 }
117 else {
118 matched.push(item)
119 }
120 }
121 }
122 RouteValidity { prefix, asn, matched, bad_asn, bad_len }
123 }
124
125 pub fn prefix(&self) -> Prefix {
126 self.prefix
127 }
128
129 pub fn asn(&self) -> Asn {
130 self.asn
131 }
132
133 pub fn state(&self) -> RouteState {
134 if self.matched.is_empty() {
135 if self.bad_asn.is_empty() && self.bad_len.is_empty() {
136 RouteState::NotFound
137 }
138 else {
139 RouteState::Invalid
140 }
141 }
142 else {
143 RouteState::Valid
144 }
145 }
146
147 pub fn reason(&self) -> Option<&'static str> {
148 if self.matched.is_empty() {
149 if !self.bad_asn.is_empty() {
150 Some("as")
151 }
152 else if !self.bad_len.is_empty() {
153 Some("length")
154 }
155 else {
156 None
157 }
158 }
159 else {
160 None
161 }
162 }
163
164 pub fn description(&self) -> &'static str {
165 if self.matched.is_empty() {
166 if !self.bad_asn.is_empty() {
167 DESCRIPTION_BAD_ASN
168 }
169 else if !self.bad_len.is_empty() {
170 DESCRIPTION_BAD_LEN
171 }
172 else {
173 DESCRIPTION_NOT_FOUND
174 }
175 }
176 else {
177 DESCRIPTION_VALID
178 }
179 }
180
181 pub fn matched(&self) -> &[(RouteOrigin, &'a PayloadInfo)] {
182 &self.matched
183 }
184
185 pub fn bad_asn(&self) -> &[(RouteOrigin, &'a PayloadInfo)] {
186 &self.bad_asn
187 }
188
189 pub fn bad_len(&self) -> &[(RouteOrigin, &'a PayloadInfo)] {
190 &self.bad_len
191 }
192
193 pub fn write_plain<W: io::Write>(
194 &self,
195 target: &mut W
196 ) -> Result<(), io::Error> {
197 writeln!(target, "{} => {}: {}", self.prefix, self.asn, self.state())
198 }
199
200 pub fn into_json(self, current: &PayloadSnapshot) -> Vec<u8> {
201 let mut res = Vec::new();
202 self.write_json(current, &mut res).unwrap();
203 res
204 }
205
206 pub fn write_json<W: io::Write>(
207 &self,
208 current: &PayloadSnapshot,
209 target: &mut W
210 ) -> Result<(), io::Error> {
211 write!(target, "{{\n \"validated_route\": ")?;
212 self.write_single_json(" ", target)?;
213 writeln!(target,
214 ",\n \"generatedTime\": \"{}\"\
215 \n}}",
216 format_iso_date(current.created()),
217 )
218 }
219
220 fn write_single_json<W: io::Write>(
221 &self,
222 indent: &str,
223 target: &mut W
224 ) -> Result<(), io::Error> {
225 writeln!(target, "{{\n\
226 {indent} \"route\": {{\n\
227 {indent} \"origin_asn\": \"{}\",\n\
228 {indent} \"prefix\": \"{}\"\n\
229 {indent} }},\n\
230 {indent} \"validity\": {{\n\
231 {indent} \"state\": \"{}\",",
232 self.asn,
233 self.prefix,
234 self.state(),
235 indent = indent,
236 )?;
237 if let Some(reason) = self.reason() {
238 writeln!(target, "{indent} \"reason\": \"{reason}\",")?;
239 }
240 writeln!(
241 target,
242 "{indent} \"description\": \"{}\",\n\
243 {indent} \"VRPs\": {{",
244 self.description(), indent = indent
245 )?;
246
247 Self::write_vrps_json(
248 indent, "matched", &self.matched, target
249 )?;
250 writeln!(target, ",")?;
251 Self::write_vrps_json(
252 indent, "unmatched_as", &self.bad_asn, target
253 )?;
254 writeln!(target, ",")?;
255 Self::write_vrps_json(
256 indent, "unmatched_length", &self.bad_len, target
257 )?;
258
259 write!(
260 target, "\n\
261 {indent} }}\n\
262 {indent} }}\n\
263 {indent}}}"
264 )
265 }
266
267 fn write_vrps_json<W: io::Write>(
268 indent: &str,
269 category: &str,
270 vrps: &[(RouteOrigin, &'a PayloadInfo)],
271 target: &mut W
272 ) -> Result<(), io::Error> {
273 write!(target, "{indent} \"{category}\": [")?;
274 let mut first = true;
275 for item in vrps.iter() {
276 if first {
277 first = false;
278 }
279 else {
280 write!(target, ",")?;
281 }
282
283 write!(
284 target,
285 "\n\
286 {indent} {{\n\
287 {indent} \"asn\": \"{}\",\n\
288 {indent} \"prefix\": \"{}\",\n\
289 {indent} \"max_length\": \"{}\"\n\
290 {indent} }}",
291 item.0.asn,
292 item.0.prefix.prefix(),
293 item.0.prefix.resolved_max_len(),
294 indent = indent
295 )?
296 }
297 write!(target, "\n{indent} ]")
298 }
299
300}
301
302
303#[derive(Clone, Copy, Debug)]
327pub enum RouteState {
328 Valid,
332
333 Invalid,
337
338 NotFound
342}
343
344impl fmt::Display for RouteState {
345 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
346 f.write_str(match *self {
347 RouteState::Valid => "valid",
348 RouteState::Invalid => "invalid",
349 RouteState::NotFound => "not-found",
350 })
351 }
352}
353
354
355#[derive(Clone, Debug, Default, Deserialize)]
362pub struct RequestList {
363 routes: Vec<Request>,
365}
366
367impl RequestList {
368 pub fn from_plain_reader<R: io::BufRead>(
370 reader: R
371 ) -> Result<Self, io::Error>
372 {
373 let mut res = Self::default();
374
375 for (line_no, line) in reader.lines().enumerate() {
376 let line = line?;
377 let mut tokens = line.split_whitespace();
378
379 let prefix = match tokens.next() {
382 Some(prefix) => {
383 match Prefix::from_str(prefix) {
384 Ok(prefix) => prefix,
385 Err(_) => {
386 return Err(io::Error::other(
387 format!(
388 "line {}: expecting prefix, got '{}'",
389 line_no + 1, prefix
390 )
391 ))
392 }
393 }
394 }
395 None => continue
396 };
397
398 match tokens.next() {
399 Some("=>") => { }
400 Some(token) => {
401 return Err(io::Error::other(
402 format!(
403 "line {}: expecting '=>', got '{}'",
404 line_no + 1, token
405 )
406 ))
407 }
408 None => {
409 return Err(io::Error::other(
410 format!(
411 "line {}: expecting '=>', got end of line",
412 line_no + 1
413 )
414 ))
415 }
416 }
417
418 let asn = match tokens.next() {
419 Some(asn) => {
420 match Asn::from_str(asn) {
421 Ok(asn) => asn,
422 Err(_) => {
423 return Err(io::Error::other(
424 format!(
425 "line {}: expecting AS number, got '{}'",
426 line_no + 1, asn
427 )
428 ))
429 }
430 }
431 }
432 None => {
433 return Err(io::Error::other(
434 format!(
435 "line {}: expecting AS number, got end of line",
436 line_no + 1
437 )
438 ))
439 }
440 };
441
442 match tokens.next() {
443 Some("#") | None => { }
444 Some(token) => {
445 return Err(io::Error::other(
446 format!(
447 "line {}: expecting '#' or end of line, got '{}'",
448 line_no + 1, token
449 )
450 ))
451 }
452 }
453
454 res.routes.push(Request { prefix, asn });
455 }
456
457 Ok(res)
458 }
459
460 pub fn from_json_reader<R: io::Read>(
462 reader: &mut R
463 ) -> Result<Self, serde_json::Error> {
464 serde_json::from_reader(reader)
465 }
466
467 pub fn single(prefix: Prefix, asn: Asn) -> Self {
469 RequestList {
470 routes: vec![Request { prefix, asn }]
471 }
472 }
473
474 pub fn validity<'a>(
476 &self,
477 snapshot: &'a PayloadSnapshot
478 ) -> RouteValidityList<'a> {
479 RouteValidityList::from_requests(self, snapshot)
480 }
481}
482
483
484#[derive(Clone, Debug, Deserialize)]
488struct Request {
489 prefix: Prefix,
491
492 #[serde(deserialize_with = "Asn::deserialize_from_any")]
494 asn: Asn,
495}
496
497
498const DESCRIPTION_VALID: &str = "At least one VRP Matches the Route Prefix";
503const DESCRIPTION_BAD_ASN: &str = "At least one VRP Covers the Route Prefix, \
504 but no VRP ASN matches the route origin \
505 ASN";
506const DESCRIPTION_BAD_LEN: &str = "At least one VRP Covers the Route Prefix, \
507 but the Route Prefix length is greater \
508 than the maximum length allowed by VRP(s) \
509 matching this route origin ASN";
510const DESCRIPTION_NOT_FOUND: &str = "No VRP Covers the Route Prefix";
511
512
513#[cfg(test)]
516mod test {
517 use super::*;
518
519 #[test]
520 fn request_list_from_json_reader() {
521 let _ = RequestList::from_json_reader(
522 &mut include_bytes!("../test/validate/beacons.json").as_ref()
523 );
524 }
525}
526