1use std::net::{IpAddr, SocketAddr};
6use tracing::Level;
7use tracing_subscriber::fmt::{FormatFields, format::Writer};
8use unicode_width::UnicodeWidthStr;
9pub mod colors {
13 pub const RESET: &str = "\x1b[0m";
14 pub const BOLD: &str = "\x1b[1m";
15 pub const DIM: &str = "\x1b[2m";
16
17 pub const BLACK: &str = "\x1b[30m";
19 pub const RED: &str = "\x1b[31m";
20 pub const GREEN: &str = "\x1b[32m";
21 pub const YELLOW: &str = "\x1b[33m";
22 pub const BLUE: &str = "\x1b[34m";
23 pub const MAGENTA: &str = "\x1b[35m";
24 pub const CYAN: &str = "\x1b[36m";
25 pub const WHITE: &str = "\x1b[37m";
26
27 pub const BRIGHT_BLACK: &str = "\x1b[90m";
29 pub const BRIGHT_RED: &str = "\x1b[91m";
30 pub const BRIGHT_GREEN: &str = "\x1b[92m";
31 pub const BRIGHT_YELLOW: &str = "\x1b[93m";
32 pub const BRIGHT_BLUE: &str = "\x1b[94m";
33 pub const BRIGHT_MAGENTA: &str = "\x1b[95m";
34 pub const BRIGHT_CYAN: &str = "\x1b[96m";
35 pub const BRIGHT_WHITE: &str = "\x1b[97m";
36}
37
38pub mod symbols {
40 pub const CHECK: &str = "✓";
41 pub const CROSS: &str = "✗";
42 pub const INFO: &str = "ℹ";
43 pub const WARNING: &str = "⚠";
44 pub const ARROW_RIGHT: &str = "→";
45 pub const DOT: &str = "•";
46 pub const KEY: &str = "🔑";
47 pub const NETWORK: &str = "📡";
48 pub const GLOBE: &str = "🌐";
49 pub const ROCKET: &str = "🚀";
50 pub const HOURGLASS: &str = "⏳";
51 pub const CIRCULAR_ARROWS: &str = "⟳";
52}
53
54pub mod box_chars {
56 pub const TOP_LEFT: &str = "╭";
57 pub const TOP_RIGHT: &str = "╮";
58 pub const BOTTOM_LEFT: &str = "╰";
59 pub const BOTTOM_RIGHT: &str = "╯";
60 pub const HORIZONTAL: &str = "─";
61 pub const VERTICAL: &str = "│";
62 pub const T_LEFT: &str = "├";
63 pub const T_RIGHT: &str = "┤";
64}
65
66fn is_ipv6_link_local(ip: &std::net::Ipv6Addr) -> bool {
68 let octets = ip.octets();
69 (octets[0] == 0xfe) && ((octets[1] & 0xc0) == 0x80)
70}
71
72fn is_ipv6_unique_local(ip: &std::net::Ipv6Addr) -> bool {
74 let octets = ip.octets();
75 (octets[0] & 0xfe) == 0xfc
76}
77
78fn is_ipv6_multicast(ip: &std::net::Ipv6Addr) -> bool {
80 let octets = ip.octets();
81 octets[0] == 0xff
82}
83
84pub fn format_peer_id(peer_id: &[u8; 32]) -> String {
86 let hex = hex::encode(&peer_id[..4]);
87 format!("{}{}{}{}", colors::CYAN, hex, "...", colors::RESET)
88}
89
90pub fn format_address(addr: &SocketAddr) -> String {
92 let color = match addr.ip() {
93 IpAddr::V4(ip) => {
94 if ip.is_loopback() {
95 colors::DIM
96 } else if ip.is_private() {
97 colors::YELLOW
98 } else {
99 colors::GREEN
100 }
101 }
102 IpAddr::V6(ip) => {
103 if ip.is_loopback() {
104 colors::DIM
105 } else if ip.is_unspecified() {
106 colors::DIM
107 } else if is_ipv6_link_local(&ip) {
108 colors::YELLOW
109 } else if is_ipv6_unique_local(&ip) {
110 colors::CYAN
111 } else {
112 colors::BRIGHT_CYAN
113 }
114 }
115 };
116
117 format!("{}{}{}", color, addr, colors::RESET)
118}
119
120pub fn format_address_with_words(addr: &SocketAddr) -> String {
122 format_address(addr)
125}
126
127pub fn describe_address(addr: &SocketAddr) -> &'static str {
129 match addr.ip() {
130 IpAddr::V4(ip) => {
131 if ip.is_loopback() {
132 "loopback"
133 } else if ip.is_private() {
134 "private network"
135 } else if ip.is_link_local() {
136 "link-local"
137 } else {
138 "public"
139 }
140 }
141 IpAddr::V6(ip) => {
142 if ip.is_loopback() {
143 "IPv6 loopback"
144 } else if ip.is_unspecified() {
145 "IPv6 unspecified"
146 } else if is_ipv6_link_local(&ip) {
147 "IPv6 link-local"
148 } else if is_ipv6_unique_local(&ip) {
149 "IPv6 unique local"
150 } else if is_ipv6_multicast(&ip) {
151 "IPv6 multicast"
152 } else {
153 "IPv6 global"
154 }
155 }
156 }
157}
158
159pub fn draw_box(title: &str, width: usize) -> (String, String, String) {
161 let padding = width.saturating_sub(title.width() + 4);
162 let left_pad = padding / 2;
163 let right_pad = padding - left_pad;
164
165 let top = format!(
166 "{}{} {} {}{}{}",
167 box_chars::TOP_LEFT,
168 box_chars::HORIZONTAL.repeat(left_pad),
169 title,
170 box_chars::HORIZONTAL.repeat(right_pad),
171 box_chars::HORIZONTAL,
172 box_chars::TOP_RIGHT
173 );
174
175 let middle = format!("{} {{}} {}", box_chars::VERTICAL, box_chars::VERTICAL);
176
177 let bottom = format!(
178 "{}{}{}",
179 box_chars::BOTTOM_LEFT,
180 box_chars::HORIZONTAL.repeat(width - 2),
181 box_chars::BOTTOM_RIGHT
182 );
183
184 (top, middle, bottom)
185}
186
187pub fn print_banner(version: &str) {
189 let title = format!("ant-quic v{version}");
190 let (top, middle, bottom) = draw_box(&title, 60);
191
192 println!("{top}");
193 println!(
194 "{}",
195 middle.replace(
196 "{}",
197 "Starting QUIC P2P with NAT Traversal "
198 )
199 );
200 println!("{bottom}");
201 println!();
202}
203
204pub fn print_section(icon: &str, title: &str) {
206 println!("{} {}{}{}", icon, colors::BOLD, title, colors::RESET);
207}
208
209pub fn print_item(text: &str, indent: usize) {
211 let indent_str = " ".repeat(indent);
212 println!("{}{} {}", indent_str, symbols::DOT, text);
213}
214
215pub fn print_status(icon: &str, text: &str, color: &str) {
217 println!(" {} {}{}{}", icon, color, text, colors::RESET);
218}
219
220pub fn format_bytes(bytes: u64) -> String {
222 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
223 let mut size = bytes as f64;
224 let mut unit_index = 0;
225
226 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
227 size /= 1024.0;
228 unit_index += 1;
229 }
230
231 if unit_index == 0 {
232 format!("{} {}", size as u64, UNITS[unit_index])
233 } else {
234 format!("{:.1} {}", size, UNITS[unit_index])
235 }
236}
237
238pub fn format_duration(duration: std::time::Duration) -> String {
240 let total_seconds = duration.as_secs();
241 let hours = total_seconds / 3600;
242 let minutes = (total_seconds % 3600) / 60;
243 let seconds = total_seconds % 60;
244
245 format!("{hours:02}:{minutes:02}:{seconds:02}")
246}
247
248pub fn format_timestamp(_timestamp: std::time::Instant) -> String {
250 use std::time::SystemTime;
251
252 let now = SystemTime::now();
254 let duration_since_epoch = now
255 .duration_since(SystemTime::UNIX_EPOCH)
256 .unwrap_or(std::time::Duration::ZERO);
257
258 let total_seconds = duration_since_epoch.as_secs();
259 let hours = (total_seconds % 86400) / 3600;
260 let minutes = (total_seconds % 3600) / 60;
261 let seconds = total_seconds % 60;
262
263 format!("{hours:02}:{minutes:02}:{seconds:02}")
264}
265
266pub struct ColoredLogFormatter;
268
269impl<S, N> tracing_subscriber::fmt::FormatEvent<S, N> for ColoredLogFormatter
270where
271 S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
272 N: for<'a> FormatFields<'a> + 'static,
273{
274 fn format_event(
275 &self,
276 ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>,
277 mut writer: Writer<'_>,
278 event: &tracing::Event<'_>,
279 ) -> std::fmt::Result {
280 let metadata = event.metadata();
281 let level = metadata.level();
282
283 let (color, symbol) = match *level {
285 Level::ERROR => (colors::RED, symbols::CROSS),
286 Level::WARN => (colors::YELLOW, symbols::WARNING),
287 Level::INFO => (colors::GREEN, symbols::CHECK),
288 Level::DEBUG => (colors::BLUE, symbols::INFO),
289 Level::TRACE => (colors::DIM, symbols::DOT),
290 };
291
292 write!(&mut writer, "{color}{symbol} ")?;
294
295 ctx.field_format().format_fields(writer.by_ref(), event)?;
297
298 write!(&mut writer, "{}", colors::RESET)?;
299
300 writeln!(writer)
301 }
302}
303
304pub struct ProgressIndicator {
306 message: String,
307 frames: Vec<&'static str>,
308 current_frame: usize,
309}
310
311impl ProgressIndicator {
312 pub fn new(message: String) -> Self {
313 Self {
314 message,
315 frames: vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
316 current_frame: 0,
317 }
318 }
319
320 pub fn tick(&mut self) {
321 print!(
322 "\r{} {} {} ",
323 self.frames[self.current_frame],
324 colors::BLUE,
325 self.message
326 );
327 self.current_frame = (self.current_frame + 1) % self.frames.len();
328 use std::io::{self, Write};
329 io::stdout().flush().unwrap();
330 }
331
332 pub fn finish_success(&self, message: &str) {
333 println!(
334 "\r{} {}{}{} {}",
335 symbols::CHECK,
336 colors::GREEN,
337 self.message,
338 colors::RESET,
339 message
340 );
341 }
342
343 pub fn finish_error(&self, message: &str) {
344 println!(
345 "\r{} {}{}{} {}",
346 symbols::CROSS,
347 colors::RED,
348 self.message,
349 colors::RESET,
350 message
351 );
352 }
353}