1use std::fmt::Write;
19
20use itertools::{join, Itertools};
21
22use crate::{
23 bgp::{BgpEvent, BgpRibEntry, BgpRoute},
24 config::{Config, ConfigExpr, ConfigExprKey, ConfigModifier, ConfigPatch, RouteMapEdit},
25 event::{BasicEventQueue, Event, FmtPriority},
26 forwarding_state::{ForwardingState, TO_DST},
27 network::Network,
28 ospf::{
29 global::GlobalOspfProcess,
30 local::{LocalOspfProcess, OspfRibEntry},
31 Edge, ExternalEdge, InternalEdge, OspfImpl, OspfProcess,
32 },
33 policies::{FwPolicy, PathCondition, PathConditionCNF, PolicyError, Waypoint},
34 record::{ConvergenceRecording, ConvergenceTrace, FwDelta},
35 route_map::{RouteMap, RouteMapDirection, RouteMapMatch, RouteMapSet, RouteMapState},
36 router::StaticRoute,
37 types::{ConfigError, DeviceError, NetworkError, Prefix, PrefixMap, PrefixSet, RouterId},
38};
39
40pub trait NetworkFormatter<'n, P: Prefix, Q, Ospf: OspfImpl> {
42 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String;
44
45 fn fmt_multiline(&self, net: &'n Network<P, Q, Ospf>) -> String {
50 self.fmt_multiline_indent(net, 0)
51 }
52
53 fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, _indent: usize) -> String {
59 self.fmt(net)
60 }
61}
62
63pub trait NetworkFormatterExt<'n, P: Prefix, Q, Ospf: OspfImpl> {
66 fn fmt_ext(&self, net: &'n Network<P, Q, Ospf>) -> String;
68}
69
70impl<'n, P, Q, Ospf, T> NetworkFormatter<'n, P, Q, Ospf> for &T
71where
72 P: Prefix,
73 Ospf: OspfImpl,
74 T: NetworkFormatter<'n, P, Q, Ospf>,
75{
76 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
77 T::fmt(*self, net)
78 }
79
80 fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
81 T::fmt_multiline_indent(*self, net, indent)
82 }
83}
84
85impl<'n, P, Q, Ospf, T> NetworkFormatter<'n, P, Q, Ospf> for &mut T
86where
87 P: Prefix,
88 Ospf: OspfImpl,
89 T: NetworkFormatter<'n, P, Q, Ospf>,
90{
91 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
92 T::fmt(*self, net)
93 }
94
95 fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
96 T::fmt_multiline_indent(*self, net, indent)
97 }
98}
99
100macro_rules! fmt_with_display {
101 ($t:ty) => {
102 impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for $t {
103 fn fmt(&self, _net: &'n Network<P, Q, Ospf>) -> String {
104 self.to_string()
105 }
106 }
107 };
108 ($($t:ty),*) => {
109 $(fmt_with_display!{$t})*
110 }
111}
112
113fmt_with_display! {u8, u16, u32, u64, u128, usize}
114fmt_with_display! {i8, i16, i32, i64, i128, isize}
115fmt_with_display! {f32, f64, &str, String}
116fmt_with_display! {std::net::Ipv4Addr, ipnet::Ipv4Net}
117fmt_with_display! {crate::types::SinglePrefix, crate::types::SimplePrefix, crate::types::Ipv4Prefix}
118fmt_with_display! {std::io::Error}
119
120macro_rules! fmt_iterable {
121 ($t:ty, $k:ident, $k_multiline:ident) => {
122 impl<'n, P, Q, Ospf, T> NetworkFormatter<'n, P, Q, Ospf> for $t
123 where
124 P: Prefix,
125 Ospf: OspfImpl,
126 T: NetworkFormatter<'n, P, Q, Ospf>,
127 {
128 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
129 self.iter().$k(net)
130 }
131
132 fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
133 self.iter().$k_multiline(net, indent)
134 }
135 }
136 };
137}
138
139fmt_iterable! {&[T], fmt_list, fmt_list_multiline}
140fmt_iterable! {Vec<T>, fmt_list, fmt_list_multiline}
141fmt_iterable! {std::collections::HashSet<T>, fmt_set, fmt_set_multiline}
142fmt_iterable! {std::collections::BTreeSet<T>, fmt_set, fmt_set_multiline}
143fmt_iterable! {std::collections::VecDeque<T>, fmt_list, fmt_list_multiline}
144fmt_iterable! {std::collections::BinaryHeap<T>, fmt_list, fmt_list_multiline}
145
146macro_rules! fmt_mapping {
147 ($t:ty) => {
148 impl<'n, P, Q, Ospf, K, V> NetworkFormatter<'n, P, Q, Ospf> for $t
149 where
150 P: Prefix,
151 Ospf: OspfImpl,
152 K: NetworkFormatter<'n, P, Q, Ospf>,
153 V: NetworkFormatter<'n, P, Q, Ospf>,
154 {
155 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
156 self.iter().fmt_map(net)
157 }
158
159 fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
160 self.iter().fmt_map_multiline(net, indent)
161 }
162 }
163 };
164}
165
166fmt_mapping! {std::collections::HashMap<K, V>}
167fmt_mapping! {std::collections::BTreeMap<K, V>}
168
169macro_rules! fmt_prefix_trie {
170 ($t:ty) => {
171 impl<'n, P, Q, Ospf, K, V> NetworkFormatter<'n, P, Q, Ospf> for $t
172 where
173 P: Prefix,
174 Ospf: OspfImpl,
175 K: NetworkFormatter<'n, P, Q, Ospf> + prefix_trie::Prefix,
176 V: NetworkFormatter<'n, P, Q, Ospf>,
177 {
178 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
179 self.iter().fmt_map(net)
180 }
181
182 fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
183 self.iter().fmt_map_multiline(net, indent)
184 }
185 }
186 };
187}
188
189fmt_prefix_trie! {prefix_trie::PrefixMap<K, V>}
190fmt_prefix_trie! {prefix_trie::trieview::TrieView<'_, K, V>}
191
192macro_rules! fmt_tuple {
193 () => {
194 impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for () {
195 fn fmt(&self, _net: &'n Network<P, Q, Ospf>) -> String {
196 "()".to_string()
197 }
198 }
199 };
200 ($t1:ident, ) => {
201 impl<'n, P, Q, Ospf, $t1> NetworkFormatter<'n, P, Q, Ospf> for ($t1,)
202 where
203 P: Prefix,
204 Ospf: OspfImpl,
205 $t1: NetworkFormatter<'n, P, Q, Ospf>,
206 {
207 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
208 #[allow(non_snake_case)]
209 let ($t1,) = self;
210 let mut s = "(".to_string();
211 s.push_str(&$t1.fmt(net));
212 s.push(')');
213 s
214 }
215
216 fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
217 #[allow(non_snake_case)]
218 let ($t1,) = self;
219 let mut s = "(".to_string();
220 s.push_str(&$t1.fmt_multiline_indent(net, indent));
221 s.push(')');
222 s
223 }
224 }
225 };
226 ($t1:ident, $($t:ident),+) => {
227 impl<'n, P, Q, Ospf, $t1, $($t),*> NetworkFormatter<'n, P, Q, Ospf> for ($t1, $($t),*)
228 where
229 P: Prefix,
230 Ospf: OspfImpl,
231 $t1: NetworkFormatter<'n, P, Q, Ospf>,
232 $($t: NetworkFormatter<'n, P, Q, Ospf>),*
233 {
234 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
235 #[allow(non_snake_case)]
236 let ($t1, $($t),*) = self;
237 let mut s = "(".to_string();
238 s.push_str(&$t1.fmt(net));
239 $({
240 s.push_str(", ");
241 s.push_str(&$t.fmt(net));
242 })*
243 s.push(')');
244 s
245 }
246
247 fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
248 let spc = " ".repeat(indent);
249 #[allow(non_snake_case)]
250 let ($t1, $($t),*) = self;
251 let mut s = "(\n ".to_string();
252 s.push_str(&spc);
253 s.push_str(&$t1.fmt_multiline_indent(net, indent + 2));
254 $({
255 s.push_str(",\n ");
256 s.push_str(&spc);
257 s.push_str(&$t.fmt_multiline_indent(net, indent + 2));
258 })*
259 s.push('\n');
260 s.push_str(&spc);
261 s.push(')');
262 s
263 }
264 }
265 };
266}
267
268fmt_tuple!();
269fmt_tuple!(T1,);
270fmt_tuple!(T1, T2);
271fmt_tuple!(T1, T2, T3);
272fmt_tuple!(T1, T2, T3, T4);
273fmt_tuple!(T1, T2, T3, T4, T5);
274fmt_tuple!(T1, T2, T3, T4, T5, T6);
275fmt_tuple!(T1, T2, T3, T4, T5, T6, T7);
276fmt_tuple!(T1, T2, T3, T4, T5, T6, T7, T8);
277
278impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouterId {
279 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
280 match net.get_router(*self) {
281 Ok(r) => r.name().to_string(),
282 Err(_) => "?".to_string(),
283 }
284 }
285}
286
287impl<'n, P, Q, Ospf, T> NetworkFormatter<'n, P, Q, Ospf> for Option<T>
288where
289 P: Prefix,
290 Ospf: OspfImpl,
291 T: NetworkFormatter<'n, P, Q, Ospf>,
292{
293 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
294 match self {
295 Some(x) => format!("Some({})", x.fmt(net)),
296 None => "None".to_string(),
297 }
298 }
299
300 fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
301 match self {
302 Some(x) => format!("Some({})", x.fmt_multiline_indent(net, indent)),
303 None => "None".to_string(),
304 }
305 }
306}
307
308impl<'n, P, Q, Ospf, T, E> NetworkFormatter<'n, P, Q, Ospf> for Result<T, E>
309where
310 P: Prefix,
311 Ospf: OspfImpl,
312 T: NetworkFormatter<'n, P, Q, Ospf>,
313 E: NetworkFormatter<'n, P, Q, Ospf>,
314{
315 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
316 match self {
317 Ok(x) => format!("Ok({})", x.fmt(net)),
318 Err(x) => format!("Err({})", x.fmt(net)),
319 }
320 }
321
322 fn fmt_multiline_indent(&self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
323 match self {
324 Ok(x) => format!("Ok({})", x.fmt_multiline_indent(net, indent)),
325 Err(x) => format!("Err({})", x.fmt_multiline_indent(net, indent)),
326 }
327 }
328}
329
330pub trait NetworkFormatterSequence<'n, P, Q, Ospf>
332where
333 P: Prefix,
334 Ospf: OspfImpl,
335{
336 fn fmt_set(self, net: &'n Network<P, Q, Ospf>) -> String;
338
339 fn fmt_set_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String;
341
342 fn fmt_list(self, net: &'n Network<P, Q, Ospf>) -> String;
344
345 fn fmt_list_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String;
347
348 fn fmt_path(self, net: &'n Network<P, Q, Ospf>) -> String;
350}
351
352impl<'n, P, Q, Ospf, I, T> NetworkFormatterSequence<'n, P, Q, Ospf> for I
353where
354 P: Prefix,
355 Ospf: OspfImpl,
356 I: IntoIterator<Item = T>,
357 T: NetworkFormatter<'n, P, Q, Ospf>,
358{
359 fn fmt_set(self, net: &'n Network<P, Q, Ospf>) -> String {
360 format!(
361 "{{{}}}",
362 self.into_iter().map(|t| t.fmt(net).to_string()).join(", ")
363 )
364 }
365
366 fn fmt_set_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
367 let spc = " ".repeat(indent);
368 format!(
369 "{{\n{spc} {}\n{spc}}}",
370 self.into_iter()
371 .map(|t| t.fmt_multiline_indent(net, indent + 2))
372 .join(&format!(",\n{spc} "))
373 )
374 }
375
376 fn fmt_list(self, net: &'n Network<P, Q, Ospf>) -> String {
377 format!(
378 "[{}]",
379 self.into_iter().map(|t| t.fmt(net).to_string()).join(", ")
380 )
381 }
382
383 fn fmt_list_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
384 let spc = " ".repeat(indent);
385 format!(
386 "[\n{spc} {}\n{spc}]",
387 self.into_iter()
388 .map(|t| t.fmt_multiline_indent(net, indent + 2).to_string())
389 .join(&format!(",\n{spc} "))
390 )
391 }
392
393 fn fmt_path(self, net: &'n Network<P, Q, Ospf>) -> String {
394 self.into_iter()
395 .map(|t| t.fmt(net).to_string())
396 .join(" -> ")
397 }
398}
399
400pub trait NetworkFormatterMap<'n, P, Q, Ospf>
402where
403 P: Prefix,
404 Ospf: OspfImpl,
405{
406 fn fmt_map(self, net: &'n Network<P, Q, Ospf>) -> String;
408
409 fn fmt_map_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String;
411}
412
413impl<'n, P, Q, Ospf, I, K, V> NetworkFormatterMap<'n, P, Q, Ospf> for I
414where
415 P: Prefix,
416 Ospf: OspfImpl,
417 I: IntoIterator<Item = (K, V)>,
418 K: NetworkFormatter<'n, P, Q, Ospf>,
419 V: NetworkFormatter<'n, P, Q, Ospf>,
420{
421 fn fmt_map(self, net: &'n Network<P, Q, Ospf>) -> String {
422 format!(
423 "{{{}}}",
424 self.into_iter()
425 .map(|(k, v)| format!("{}: {}", k.fmt(net), v.fmt(net)))
426 .join(", ")
427 )
428 }
429
430 fn fmt_map_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
431 let spc = " ".repeat(indent);
432 format!(
433 "{{\n{spc} {}\n{spc}}}",
434 self.into_iter()
435 .map(|(k, v)| format!(
436 "{}: {}",
437 k.fmt(net),
438 v.fmt_multiline_indent(net, indent + 2)
439 ))
440 .join(&format!(",\n{spc} "))
441 )
442 }
443}
444
445pub trait NetworkFormatterNestedSequence<'n, P, Q, Ospf>
447where
448 P: Prefix,
449 Ospf: OspfImpl,
450{
451 fn fmt_path_options(self, net: &'n Network<P, Q, Ospf>) -> String;
453
454 fn fmt_path_set(self, net: &'n Network<P, Q, Ospf>) -> String;
456
457 fn fmt_path_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String;
459}
460
461impl<'n, P, Q, Ospf, I, T> NetworkFormatterNestedSequence<'n, P, Q, Ospf> for I
462where
463 P: Prefix,
464 Ospf: OspfImpl,
465 I: IntoIterator<Item = T>,
466 T: NetworkFormatterSequence<'n, P, Q, Ospf>,
467{
468 fn fmt_path_options(self, net: &'n Network<P, Q, Ospf>) -> String {
469 self.into_iter().map(|p| p.fmt_path(net)).join(" | ")
470 }
471
472 fn fmt_path_set(self, net: &'n Network<P, Q, Ospf>) -> String {
473 format!(
474 "{{{}}}",
475 self.into_iter().map(|p| p.fmt_path(net)).join(", ")
476 )
477 }
478
479 fn fmt_path_multiline(self, net: &'n Network<P, Q, Ospf>, indent: usize) -> String {
480 let spc = " ".repeat(indent);
481 format!(
482 "{{\n{spc} {}\n}}",
483 self.into_iter()
484 .map(|p| p.fmt_path(net))
485 .join(&format!(",\n {spc}"))
486 )
487 }
488}
489
490impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ForwardingState<P> {
495 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
496 let mut result = String::new();
497 let f = &mut result;
498 for (router, table) in self.state.iter() {
499 writeln!(f, "{}:", router.fmt(net)).unwrap();
500 for (prefix, next_hops) in table.iter() {
501 let next_hops_str = if next_hops.is_empty() {
502 "XX".to_string()
503 } else if next_hops == &[*TO_DST] {
504 "DST".to_string()
505 } else {
506 next_hops.iter().map(|r| r.fmt(net)).join("|")
507 };
508 writeln!(
509 f,
510 " {} -> {}; reversed: [{}]",
511 prefix,
512 next_hops_str,
513 self.reversed
514 .get(router)
515 .and_then(|table| table.get(prefix))
516 .map(|s| s.iter().map(|r| r.fmt(net)).join(", "))
517 .unwrap_or_default(),
518 )
519 .unwrap();
520 }
521 }
522 result
523 }
524}
525
526impl<'n, P: Prefix, Q, Ospf: OspfImpl, T: FmtPriority> NetworkFormatter<'n, P, Q, Ospf>
531 for Event<P, T>
532{
533 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
534 match self {
535 Event::Bgp { p, src, dst, e } => format!(
536 "BGP Event: {} -> {}: {} {}",
537 src.fmt(net),
538 dst.fmt(net),
539 e.fmt(net),
540 p.fmt()
541 ),
542 Event::Ospf {
543 p,
544 src,
545 dst,
546 area,
547 e,
548 } => format!(
549 "OSPF Event: {} -> {} ({area}): {} {}",
550 src.fmt(net),
551 dst.fmt(net),
552 e.fmt(net),
553 p.fmt()
554 ),
555 }
556 }
557}
558
559impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for BgpEvent<P> {
560 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
561 match self {
562 BgpEvent::Withdraw(prefix) => format!("Withdraw {prefix}"),
563 BgpEvent::Update(route) => format!("Update {}", route.fmt(net)),
564 }
565 }
566}
567
568impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for BgpRoute<P> {
573 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
574 format!(
575 "{{ {}, path: [{}], next hop: {}{}{}{} }}",
576 self.prefix,
577 self.as_path.iter().join(", "),
578 self.next_hop.fmt(net),
579 if let Some(local_pref) = self.local_pref {
580 format!(", local pref: {local_pref}")
581 } else {
582 String::new()
583 },
584 if let Some(med) = self.med {
585 format!(", MED: {med}")
586 } else {
587 String::new()
588 },
589 if self.community.is_empty() {
590 String::new()
591 } else {
592 format!(", community: {}", join(self.community.iter(), ";"))
593 },
594 )
595 }
596}
597
598impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for BgpRibEntry<P> {
603 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
604 format!(
605 "{p}, as_path: {path:?}, weight: {w}, local_pref: {lp}, MED: {med}, IGP Cost: {cost}, next_hop: {nh}, from: {next}{comm}",
606 p = self.route.prefix,
607 path = self.route.as_path.iter().map(|x| x.0).collect::<Vec<u32>>(),
608 w = self.weight,
609 lp = self.route.local_pref.unwrap_or(100),
610 med = self.route.med.unwrap_or(0),
611 cost = self.igp_cost.unwrap_or_default(),
612 nh = self.route.next_hop.fmt(net),
613 next = self.from_id.fmt(net),
614 comm = if self.route.community.is_empty() {
615 String::from("")
616 } else {
617 format!(", communities = [{}]", self.route.community.iter().join(", "))
618 },
619 )
620 }
621}
622
623impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouteMapMatch<P> {
628 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
629 match self {
630 RouteMapMatch::Prefix(_pl) => {
631 format!("Prefix in {{{}}}", _pl.iter().join(", "))
632 }
633 RouteMapMatch::AsPath(c) => format!("{c}"),
634 RouteMapMatch::NextHop(nh) => format!("NextHop == {}", nh.fmt(net)),
635 RouteMapMatch::Community(c) => format!("Community {c}"),
636 RouteMapMatch::DenyCommunity(c) => format!("Deny Community {c}"),
637 }
638 }
639}
640
641impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouteMapSet {
642 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
643 match self {
644 RouteMapSet::NextHop(nh) => format!("NextHop = {}", nh.fmt(net)),
645 RouteMapSet::Weight(Some(w)) => format!("Weight = {w}"),
646 RouteMapSet::Weight(None) => "clear Weight".to_string(),
647 RouteMapSet::LocalPref(Some(lp)) => format!("LocalPref = {lp}"),
648 RouteMapSet::LocalPref(None) => "clear LocalPref".to_string(),
649 RouteMapSet::Med(Some(med)) => format!("MED = {med}"),
650 RouteMapSet::Med(None) => "clear MED".to_string(),
651 RouteMapSet::IgpCost(w) => format!("IgpCost = {w:.2}"),
652 RouteMapSet::SetCommunity(c) => format!("Set community {c}"),
653 RouteMapSet::DelCommunity(c) => format!("Remove community {c}"),
654 }
655 }
656}
657
658impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouteMap<P> {
659 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
660 format!(
661 "{} {}{}.",
662 match self.state {
663 RouteMapState::Allow => "allow",
664 RouteMapState::Deny => "deny ",
665 },
666 if self.conds.is_empty() {
667 String::from("*")
668 } else {
669 self.conds.iter().map(|c| c.fmt(net)).join(" AND ")
670 },
671 if self.set.is_empty() {
672 String::from("")
673 } else {
674 format!("; {}", self.set.iter().map(|s| s.fmt(net)).join(", "))
675 }
676 )
677 }
678}
679
680impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ConfigExpr<P> {
685 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
686 match self {
687 ConfigExpr::IgpLinkWeight {
688 source,
689 target,
690 weight,
691 } => format!(
692 "IGP Link Weight: {} -> {}: {}",
693 source.fmt(net),
694 target.fmt(net),
695 weight
696 ),
697 ConfigExpr::OspfArea {
698 source,
699 target,
700 area,
701 } => format!(
702 "OSPF Area: {} -- {}: {}",
703 source.fmt(net),
704 target.fmt(net),
705 area
706 ),
707 ConfigExpr::BgpSession {
708 source,
709 target,
710 target_is_client: true,
711 } => format!(
712 "BGP Session: {} -> {} (RR client)",
713 source.fmt(net),
714 target.fmt(net),
715 ),
716 ConfigExpr::BgpSession { source, target, .. } => {
717 format!("BGP Session: {} -> {}", source.fmt(net), target.fmt(net),)
718 }
719 ConfigExpr::BgpRouteMap {
720 router,
721 neighbor,
722 direction,
723 map,
724 } => format!(
725 "BGP Route Map on {} from {} [{}:{}]: {}",
726 router.fmt(net),
727 neighbor.fmt(net),
728 match direction {
729 RouteMapDirection::Incoming => "in",
730 RouteMapDirection::Outgoing => "out",
731 },
732 map.order,
733 map.fmt(net)
734 ),
735 ConfigExpr::StaticRoute {
736 router,
737 prefix,
738 target,
739 } => format!(
740 "Static Route: {}: {} via {}",
741 router.fmt(net),
742 prefix,
743 target.fmt(net)
744 ),
745 ConfigExpr::LoadBalancing { router } => {
746 format!("Load Balancing: {}", router.fmt(net))
747 }
748 ConfigExpr::AdvertiseRoute {
749 router,
750 prefix,
751 as_path,
752 med,
753 community,
754 } => {
755 let mut options = Vec::new();
756 if !as_path.is_empty() {
757 options.push(format!("AS path [{}]", as_path.iter().join(" ")));
758 }
759 if let Some(med) = med {
760 options.push(format!("MED {med}"));
761 }
762 if !community.is_empty() {
763 options.push(format!("Community [{}]", community.iter().join(" ")));
764 }
765 let opt = if options.is_empty() {
766 String::new()
767 } else {
768 format!(" with {}", options.into_iter().join("; "))
769 };
770 format!("Advertise Route on {} for {prefix}{opt}", router.fmt(net))
771 }
772 }
773 }
774}
775
776impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ConfigExprKey<P> {
777 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
778 match self {
779 ConfigExprKey::IgpLinkWeight { source, target } => format!(
780 "IGP Link Weight: {} -> {}",
781 source.fmt(net),
782 target.fmt(net),
783 ),
784 ConfigExprKey::OspfArea { router_a, router_b } => {
785 format!("OSPF Area: {} -- {}", router_a.fmt(net), router_b.fmt(net),)
786 }
787 ConfigExprKey::BgpSession {
788 speaker_a,
789 speaker_b,
790 } => format!(
791 "BGP Session: {} <-> {}",
792 speaker_a.fmt(net),
793 speaker_b.fmt(net),
794 ),
795 ConfigExprKey::BgpRouteMap {
796 router,
797 neighbor,
798 direction,
799 order,
800 } => format!(
801 "BGP Route Map on {} from {} [{}:{}]",
802 router.fmt(net),
803 neighbor.fmt(net),
804 direction,
805 order
806 ),
807 ConfigExprKey::StaticRoute { router, prefix } => {
808 format!("Static Route: {}: {}", router.fmt(net), prefix,)
809 }
810 ConfigExprKey::LoadBalancing { router } => {
811 format!("Load Balancing: {}", router.fmt(net))
812 }
813 ConfigExprKey::AdvertiseRoute { router, prefix } => {
814 format!("Advertise Route on {} for {prefix}", router.fmt(net))
815 }
816 }
817 }
818}
819
820impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ConfigModifier<P> {
821 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
822 match self {
823 ConfigModifier::Insert(e) => format!("INSERT {}", e.fmt(net)),
824 ConfigModifier::Remove(e) => format!("REMOVE {}", e.fmt(net)),
825 ConfigModifier::Update { from: _, to } => format!("MODIFY {}", to.fmt(net)),
826 ConfigModifier::BatchRouteMapEdit { router, updates } => format!(
827 "BATCH at {}: {}",
828 router.fmt(net),
829 updates.iter().map(|u| u.fmt(net)).join(", ")
830 ),
831 }
832 }
833}
834
835impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouteMapEdit<P> {
836 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
837 let dir = match self.direction {
838 RouteMapDirection::Incoming => "in",
839 RouteMapDirection::Outgoing => "out",
840 };
841 let peer = self.neighbor.fmt(net);
842 match (self.old.as_ref(), self.new.as_ref()) {
843 (None, None) => String::new(),
844 (Some(old), None) => format!("del [{peer}:{dir}:{}]", old.order),
845 (None, Some(new)) => format!("add [{peer}:{dir}:{}] {}", new.order, new.fmt(net)),
846 (Some(_), Some(new)) => format!("upd [{peer}:{dir}:{}] {}", new.order, new.fmt(net)),
847 }
848 }
849}
850
851impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ConfigPatch<P> {
852 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
853 let mut result = String::new();
854 let f = &mut result;
855 writeln!(f, "ConfigPatch {{").unwrap();
856 for modifier in self.modifiers.iter() {
857 writeln!(f, " {}", modifier.fmt(net)).unwrap();
858 }
859 writeln!(f, "}}").unwrap();
860 result
861 }
862}
863
864impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for Config<P> {
865 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
866 let mut result = String::new();
867 let f = &mut result;
868 writeln!(f, "Config {{").unwrap();
869 for expr in self.iter() {
870 writeln!(f, " {}", expr.fmt(net)).unwrap();
871 }
872 writeln!(f, "}}").unwrap();
873 result
874 }
875}
876
877impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatterExt<'n, P, Q, Ospf> for FwDelta {
882 fn fmt_ext(&self, net: &'n Network<P, Q, Ospf>) -> String {
883 format!(
884 "{}: {} => {}",
885 self.0.fmt(net),
886 self.1.iter().map(|r| r.fmt(net)).join("|"),
887 self.2.iter().map(|r| r.fmt(net)).join("|"),
888 )
889 }
890}
891
892impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatterExt<'n, P, Q, Ospf> for ConvergenceTrace {
893 fn fmt_ext(&self, net: &'n Network<P, Q, Ospf>) -> String {
894 self.iter()
895 .enumerate()
896 .map(|(i, (deltas, time))| {
897 format!(
898 "step {}{}: {}",
899 i,
900 time.as_ref()
901 .map(|t| format!("at time {t}"))
902 .unwrap_or_default(),
903 deltas.iter().map(|x| x.fmt_ext(net)).join(", ")
904 )
905 })
906 .join("\n")
907 }
908}
909
910impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ConvergenceRecording {
911 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
912 self.trace().fmt_ext(net)
913 }
914}
915
916impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for FwPolicy<P> {
921 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
922 match self {
923 Self::Reachable(r, p) => {
924 format!("Reachable({}, {})", r.fmt(net), p)
925 }
926 Self::NotReachable(r, p) => format!("Isolation({}, {})", r.fmt(net), p),
927 Self::PathCondition(r, p, c) => {
928 format!("Path({}, {}, {})", r.fmt(net), p, c.fmt(net))
929 }
930 Self::LoopFree(r, p) => {
931 format!("LoopFree({}, {})", r.fmt(net), p)
932 }
933 Self::LoadBalancing(r, p, k) => format!("LoadBalancing({}, {}, {})", r.fmt(net), p, k),
934 Self::LoadBalancingVertexDisjoint(r, p, k) => {
935 format!("LoadBalancingVertexDisjoint({}, {}, {})", r.fmt(net), p, k)
936 }
937 Self::LoadBalancingEdgeDisjoint(r, p, k) => {
938 format!("LoadBalancingEdgeDisjoint({}, {}, {})", r.fmt(net), p, k)
939 }
940 }
941 }
942}
943
944impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for Edge {
945 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
946 match self {
947 Edge::Internal(i) => i.fmt(net),
948 Edge::External(e) => e.fmt(net),
949 }
950 }
951}
952
953impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for InternalEdge {
954 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
955 format!(
956 "{} -> {} (weight {}, area {})",
957 self.src.fmt(net),
958 self.dst.fmt(net),
959 self.weight,
960 self.area
961 )
962 }
963}
964
965impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for ExternalEdge {
966 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
967 format!("{} -> {}", self.int.fmt(net), self.ext.fmt(net))
968 }
969}
970
971impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for PathCondition {
972 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
973 match self {
974 Self::Node(r) => format!("[* {} *]", r.fmt(net)),
975 Self::Edge(a, b) => format!("[* ({},{}) *]", a.fmt(net), b.fmt(net)),
976 Self::And(v) if v.is_empty() => String::from("(true)"),
977 Self::And(v) => format!("({})", v.iter().map(|c| c.fmt(net)).join(" && ")),
978 Self::Or(v) if v.is_empty() => String::from("(false)"),
979 Self::Or(v) => format!("({})", v.iter().map(|c| c.fmt(net)).join(" || ")),
980 Self::Not(c) => format!("!{}", c.fmt(net)),
981 Self::Positional(v) => format!("[{}]", v.iter().map(|p| p.fmt(net)).join(" ")),
982 }
983 }
984}
985
986impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for Waypoint {
987 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
988 match self {
989 Waypoint::Any => "?".to_string(),
990 Waypoint::Star => "*".to_string(),
991 Waypoint::Fix(r) => r.fmt(net),
992 }
993 }
994}
995
996impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for PathConditionCNF {
997 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
998 PathCondition::from(self.clone()).fmt(net)
999 }
1000}
1001
1002impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for PolicyError<P> {
1003 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1004 match self {
1005 PolicyError::BlackHole { router, prefix } => {
1006 format!("Black hole for {} at {}", prefix, router.fmt(net),)
1007 }
1008 PolicyError::ForwardingLoop { path, prefix } => format!(
1009 "Forwarding loop for {}: {} -> {}",
1010 prefix,
1011 path.iter().fmt_list(net),
1012 path.first().unwrap().fmt(net),
1013 ),
1014 PolicyError::PathCondition {
1015 path,
1016 condition,
1017 prefix,
1018 } => format!(
1019 "Path condition invalidated for {}: path: {}, condition: {}",
1020 prefix,
1021 path.iter().fmt_path(net),
1022 condition.fmt(net)
1023 ),
1024 PolicyError::UnallowedPathExists {
1025 router,
1026 prefix,
1027 paths,
1028 } => format!(
1029 "{} can reach unallowed {} via path(s) {}",
1030 router.fmt(net),
1031 prefix,
1032 paths.iter().fmt_path_set(net)
1033 ),
1034 PolicyError::InsufficientPathsExist { router, prefix, k } => format!(
1035 "{} cannot reach {} via {} paths",
1036 router.fmt(net),
1037 prefix,
1038 k
1039 ),
1040 PolicyError::NoConvergence => String::from("No Convergence"),
1041 }
1042 }
1043}
1044
1045impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for StaticRoute {
1046 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1047 match self {
1048 StaticRoute::Direct(r) => r.fmt(net).to_string(),
1049 StaticRoute::Indirect(r) => format!("{} (indirect)", r.fmt(net)),
1050 StaticRoute::Drop => "drop".to_string(),
1051 }
1052 }
1053}
1054
1055impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for NetworkError {
1056 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1057 match self {
1058 NetworkError::DeviceError(e) => e.fmt(net),
1059 NetworkError::ConfigError(e) => e.fmt(net).to_string(),
1060 NetworkError::UnknownAS(asn) => format!("No router in {asn} exists."),
1061 NetworkError::DeviceNotFound(r) => format!("Device with id={} not found!", r.index()),
1062 NetworkError::DeviceNameNotFound(n) => format!("Device with name={n} not found!"),
1063 NetworkError::LinkNotFound(src, dst) => format!(
1064 "No link between {} and {} exists!",
1065 src.fmt(net),
1066 dst.fmt(net)
1067 ),
1068 NetworkError::ForwardingLoop {
1069 to_loop,
1070 first_loop,
1071 } => format!(
1072 "Forwarding loop found! {}, {}",
1073 to_loop.fmt_list(net),
1074 first_loop.fmt_list(net)
1075 ),
1076 NetworkError::ForwardingBlackHole(p) => {
1077 format!("Black hole found! {}", p.fmt_path(net))
1078 }
1079 NetworkError::InvalidBgpSessionType(src, dst, ty) => format!(
1080 "BGP session of type {} cannot be established from {} to {}!",
1081 ty,
1082 src.fmt(net),
1083 dst.fmt(net)
1084 ),
1085 NetworkError::InconsistentBgpSession(src, dst) => format!(
1086 "{} and {} maintain an inconsistent BGP session!",
1087 src.fmt(net),
1088 dst.fmt(net)
1089 ),
1090 NetworkError::NoConvergence => String::from("Network could not converge!"),
1091 NetworkError::InvalidBgpTable(r) => {
1092 format!("Router {} has an invalid BGP table!", r.fmt(net))
1093 }
1094 NetworkError::JsonError(e) => format!("Json error occurred: {e}"),
1095 NetworkError::InconsistentOspfState(k) => {
1096 format!("OSPF state is inconsistent for key {}", k.fmt(net))
1097 }
1098 }
1099 }
1100}
1101
1102impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for DeviceError {
1103 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1104 match self {
1105 DeviceError::RouterNotFound(r) => {
1106 format!("Router {} was not found in the IGP table!", r.fmt(net))
1107 }
1108 DeviceError::NoBgpSession(r) => {
1109 format!("No BGP session established with {}!", r.fmt(net))
1110 }
1111 DeviceError::AlreadyOspfNeighbors(r, n) => {
1112 format!(
1113 "Router {} is already an OSPF neighbor of {}.",
1114 n.fmt(net),
1115 r.fmt(net)
1116 )
1117 }
1118 DeviceError::NotAnOspfNeighbor(r, n) => {
1119 format!(
1120 "Router {} is not an OSPF neighbor of {}",
1121 n.fmt(net),
1122 r.fmt(net)
1123 )
1124 }
1125 DeviceError::WrongRouter(executing, recipiant) => format!(
1126 "Router {} cannot execute an event destined for {}",
1127 executing.fmt(net),
1128 recipiant.fmt(net)
1129 ),
1130 }
1131 }
1132}
1133
1134impl<'n, P: Prefix + std::fmt::Debug, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf>
1135 for ConfigError
1136{
1137 fn fmt(&self, _net: &'n Network<P, Q, Ospf>) -> String {
1138 match self {
1139 ConfigError::ConfigExprOverload { old, new } => {
1140 format!("Adding `{old:?}` would overwrite `{new:?}`!",)
1141 }
1142 ConfigError::ConfigModifier(m) => format!("Could not apply modifier: {m:?}"),
1143 }
1144 }
1145}
1146
1147impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for BasicEventQueue<P> {
1151 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1152 self.0.iter().map(|e| e.fmt(net)).join("\n")
1153 }
1154}
1155
1156impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for OspfRibEntry {
1160 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1161 let kind = if self.inter_area { "R" } else { "I" };
1162 let nhs = self.fibs.iter().map(|r| r.fmt(net)).join(" || ");
1163 let nhs = if nhs.is_empty() {
1164 "XX".to_string()
1165 } else {
1166 nhs
1167 };
1168 let cost = self.cost.into_inner();
1169 format!("{} -> {nhs} (cost: {cost} {kind})", self.router_id.fmt(net))
1170 }
1171}
1172
1173impl<'n, P: Prefix, Q, Ospf: OspfImpl<Process = GlobalOspfProcess>> NetworkFormatter<'n, P, Q, Ospf>
1174 for GlobalOspfProcess
1175{
1176 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1177 OspfProcess::fmt(self, net)
1178 }
1179}
1180
1181impl<'n, P: Prefix, Q, Ospf: OspfImpl<Process = LocalOspfProcess>> NetworkFormatter<'n, P, Q, Ospf>
1182 for LocalOspfProcess
1183{
1184 fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
1185 OspfProcess::fmt(self, net)
1186 }
1187}
1188
1189#[cfg(test)]
1190mod test {
1191 use crate::prelude::*;
1192 use maplit::{btreemap, btreeset};
1193 use pretty_assertions::assert_eq;
1194
1195 use super::*;
1196
1197 type Net = Network<SimplePrefix, BasicEventQueue<SimplePrefix>, GlobalOspf>;
1198
1199 #[test]
1200 fn fmt_list() {
1201 let net = Net::default();
1202 assert_eq!(vec!["a", "b", "c"].fmt(&net), "[a, b, c]");
1203 assert_eq!(btreeset!["a", "b", "c"].fmt(&net), "{a, b, c}");
1204 assert_eq!(vec!["a", "b", "c"].fmt_set(&net), "{a, b, c}");
1205 assert_eq!(vec!["a", "b", "c"].fmt_list(&net), "[a, b, c]");
1206 assert_eq!(vec!["a", "b", "c"].fmt_path(&net), "a -> b -> c");
1207 }
1208
1209 #[test]
1210 fn fmt_list_multiline() {
1211 let net = Net::default();
1212 assert_eq!(
1213 vec!["a", "b", "c"].fmt_multiline(&net),
1214 "[\n a,\n b,\n c\n]"
1215 );
1216 assert_eq!(
1217 vec!["a", "b", "c"].fmt_set_multiline(&net, 2),
1218 "{\n a,\n b,\n c\n }"
1219 );
1220 assert_eq!(
1221 vec!["a", "b", "c"].fmt_list_multiline(&net, 2),
1222 "[\n a,\n b,\n c\n ]"
1223 );
1224 }
1225
1226 #[test]
1227 fn fmt_nested_list() {
1228 let net = Net::default();
1229 let orig = vec![vec!["a", "b"], vec!["c", "d"]];
1230 let x = orig.as_slice();
1231 assert_eq!(x.fmt(&net), "[[a, b], [c, d]]");
1232 assert_eq!(
1233 x.fmt_multiline(&net),
1234 "[\n [\n a,\n b\n ],\n [\n c,\n d\n ]\n]"
1235 );
1236 assert_eq!(
1237 x.fmt_set_multiline(&net, 0),
1238 "{\n [\n a,\n b\n ],\n [\n c,\n d\n ]\n}"
1239 );
1240 assert_eq!(
1241 x.fmt_list_multiline(&net, 0),
1242 "[\n [\n a,\n b\n ],\n [\n c,\n d\n ]\n]"
1243 );
1244 }
1245
1246 #[test]
1247 fn fmt_map() {
1248 let net = Net::default();
1249 let orig = btreemap! { "a" => 1, "b" => 2};
1250 let x = &orig;
1251 assert_eq!(x.fmt(&net), "{a: 1, b: 2}");
1252 assert_eq!(x.fmt_multiline(&net), "{\n a: 1,\n b: 2\n}");
1253 }
1254
1255 #[test]
1256 fn fmt_nested_map() {
1257 let net = Net::default();
1258 let orig = btreemap! { "a" => vec![1, 2, 3], "b" => vec![4, 5, 6]};
1259 let x = &orig;
1260 assert_eq!(x.fmt(&net), "{a: [1, 2, 3], b: [4, 5, 6]}");
1261 assert_eq!(
1262 x.fmt_multiline(&net),
1263 "{\n a: [\n 1,\n 2,\n 3\n ],\n b: [\n 4,\n 5,\n 6\n ]\n}"
1264 );
1265 }
1266}