async_traceroute/traceroute/
terminal.rs1use std::collections::HashMap;
2use std::net::Ipv4Addr;
3
4use futures::pin_mut;
5use futures_util::StreamExt;
6
7use crate::traceroute::probe::ProbeResult;
8use crate::traceroute::Traceroute;
9
10type Ttl = u8;
11
12pub struct TracerouteTerminal {
13 traceroute: Traceroute,
14 current_ttl: u8,
15 nqueries: u16,
16 max_ttl: u8,
17 hop_by_ttl: HashMap<Ttl, PrintableHop>,
18}
19
20impl TracerouteTerminal {
21 pub fn new(traceroute: Traceroute) -> Self {
22 let nqueries = traceroute.get_nqueries();
23 let max_ttl = traceroute.get_max_ttl();
24 Self {
25 traceroute,
26 current_ttl: 1,
27 nqueries,
28 max_ttl,
29 hop_by_ttl: HashMap::with_capacity(max_ttl as usize)
30 }
31 }
32
33 pub async fn print_trace(mut self) {
34 let traceroute_stream= self.traceroute.trace();
35 pin_mut!(traceroute_stream);
36
37 let mut skip = true;
38 self.hop_by_ttl.insert(1, PrintableHop::new(1));
39 Self::print_current_hop_index(self.current_ttl);
40
41 'outer: while let Some(probe_result) = traceroute_stream.next().await {
42 let ttl = match probe_result {
43 Ok(probe_result) => {
44 let probe_result_ttl = probe_result.ttl();
45
46 let hop = self.hop_by_ttl
47 .entry(probe_result_ttl)
48 .or_insert(PrintableHop::new(probe_result_ttl));
49
50 hop.add_printable_probe_result(
51 PrintableProbeResult::ProbeResult {
52 probe_result,
53 printed: false
54 }
55 );
56
57 probe_result_ttl
58 },
59 Err(probe_error) => {
60 let probe_error_ttl = probe_error.get_ttl();
61
62 let hop = self.hop_by_ttl
63 .entry(probe_error_ttl)
64 .or_insert(PrintableHop::new(probe_error_ttl));
65
66 hop.add_printable_probe_result(
67 PrintableProbeResult::Timeout {
68 printed: false
69 }
70 );
71
72 probe_error_ttl
73 },
74 };
75
76 if ttl > self.current_ttl && skip {
77 skip = false;
78
79 for current_ttl in self.current_ttl+1..=ttl {
80 for _ in 0..self.nqueries {
81 print!("* ");
82 }
83
84 println!();
85 Self::print_current_hop_index(current_ttl);
86 }
87
88 self.current_ttl = ttl;
89 }
90
91 while let Some(hop) = self.hop_by_ttl.get_mut(&self.current_ttl) {
92 hop.print();
93
94 if hop.tot_completed_queries() == self.nqueries as usize {
95 self.current_ttl += 1;
96
97 if self.current_ttl > self.max_ttl {
98 break 'outer;
99 } else {
100 println!();
101 Self::print_current_hop_index(self.current_ttl);
102 }
103 } else {
104 break;
105 }
106 }
107 }
108
109 println!()
110 }
111 fn print_current_hop_index(ttl: u8) {
112 if ttl < 10 {
113 print!(" {} ", ttl);
114 } else {
115 print!("{} ", ttl);
116 }
117 }
118}
119
120enum PrintableProbeResult {
121 Timeout { printed: bool },
122 ProbeResult { probe_result: ProbeResult, printed: bool },
123}
124
125
126impl PrintableProbeResult {
127 fn mark_as_printed(&mut self) {
128 let printed = match self {
129 PrintableProbeResult::Timeout { ref mut printed } => printed,
130 PrintableProbeResult::ProbeResult {ref mut printed, .. } => printed,
131 };
132
133 *printed = true;
134 }
135
136 fn is_printed(&self) -> bool {
137 match self {
138 PrintableProbeResult::Timeout { printed } => *printed,
139 PrintableProbeResult::ProbeResult { printed, .. } => *printed
140 }
141 }
142}
143
144struct PrintableHop {
145 ttl: u8,
146 probe_results: Vec<PrintableProbeResult>,
147 printed_hostnames: Vec<String>,
148 printed_ip_addr: Vec<Ipv4Addr>,
149}
150
151impl PrintableHop {
152 fn new(ttl: u8) -> PrintableHop {
153 Self {
154 ttl,
155 probe_results: Vec::with_capacity(10),
156 printed_hostnames: Vec::with_capacity(10),
157 printed_ip_addr: Vec::with_capacity(10),
158 }
159 }
160
161 fn add_printable_probe_result(&mut self, printable_probe_result: PrintableProbeResult) {
162 self.probe_results.push(printable_probe_result)
163 }
164
165 fn tot_completed_queries(&self) -> usize {
166 self.probe_results.len()
167 }
168
169 fn print(&mut self) {
170 let printed_hostnames = &mut self.printed_hostnames;
171 let printed_ip_addr = &mut self.printed_ip_addr;
172
173 for printable_probe_result in self.probe_results.iter_mut() {
174 if printable_probe_result.is_printed() {
175 continue;
176 }
177
178 printable_probe_result.mark_as_printed();
179
180 match printable_probe_result {
181 PrintableProbeResult::Timeout { .. } => {
182 print!("* ");
183 },
184 PrintableProbeResult::ProbeResult { probe_result, .. } => {
185 let rtt = probe_result.rtt();
186 let hostname = probe_result.get_hostname();
187 if hostname.is_none() || printed_hostnames.contains(&hostname.as_ref().unwrap()) {
188 if printed_ip_addr.contains(&probe_result.from_address()) {
189 let mut rtt_micros = rtt.as_micros().to_string();
190 rtt_micros.insert(2, '.');
191 print!("{:2} ms ", rtt_micros);
192 } else {
193 print!("{probe_result} ");
194 printed_ip_addr.push(probe_result.from_address());
195 }
196 } else {
197 print!("{probe_result} ");
198 printed_hostnames.push(hostname.unwrap());
199 printed_ip_addr.push(probe_result.from_address());
200 }
201 }
202 }
203 }
204 }
205}