1use std::net::{IpAddr, SocketAddr};
6use unicode_width::UnicodeWidthStr;
7use tracing::Level;
8use tracing_subscriber::fmt::{format::Writer, FormatFields};
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!(
176 "{} {{}} {}",
177 box_chars::VERTICAL,
178 box_chars::VERTICAL
179 );
180
181 let bottom = format!(
182 "{}{}{}",
183 box_chars::BOTTOM_LEFT,
184 box_chars::HORIZONTAL.repeat(width - 2),
185 box_chars::BOTTOM_RIGHT
186 );
187
188 (top, middle, bottom)
189}
190
191pub fn print_banner(version: &str) {
193 let title = format!("ant-quic v{}", version);
194 let (top, middle, bottom) = draw_box(&title, 60);
195
196 println!("{}", top);
197 println!("{}", middle.replace("{}", "Starting QUIC P2P with NAT Traversal "));
198 println!("{}", bottom);
199 println!();
200}
201
202pub fn print_section(icon: &str, title: &str) {
204 println!("{} {}{}{}", icon, colors::BOLD, title, colors::RESET);
205}
206
207pub fn print_item(text: &str, indent: usize) {
209 let indent_str = " ".repeat(indent);
210 println!("{}{} {}", indent_str, symbols::DOT, text);
211}
212
213pub fn print_status(icon: &str, text: &str, color: &str) {
215 println!(" {} {}{}{}", icon, color, text, colors::RESET);
216}
217
218pub fn format_bytes(bytes: u64) -> String {
220 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
221 let mut size = bytes as f64;
222 let mut unit_index = 0;
223
224 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
225 size /= 1024.0;
226 unit_index += 1;
227 }
228
229 if unit_index == 0 {
230 format!("{} {}", size as u64, UNITS[unit_index])
231 } else {
232 format!("{:.1} {}", size, UNITS[unit_index])
233 }
234}
235
236pub fn format_duration(duration: std::time::Duration) -> String {
238 let total_seconds = duration.as_secs();
239 let hours = total_seconds / 3600;
240 let minutes = (total_seconds % 3600) / 60;
241 let seconds = total_seconds % 60;
242
243 format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
244}
245
246pub fn format_timestamp(_timestamp: std::time::Instant) -> String {
248 use std::time::SystemTime;
249
250 let now = SystemTime::now();
252 let duration_since_epoch = now.duration_since(SystemTime::UNIX_EPOCH)
253 .unwrap_or_else(|_| std::time::Duration::ZERO);
254
255 let total_seconds = duration_since_epoch.as_secs();
256 let hours = (total_seconds % 86400) / 3600;
257 let minutes = (total_seconds % 3600) / 60;
258 let seconds = total_seconds % 60;
259
260 format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
261}
262
263pub struct ColoredLogFormatter;
265
266impl<S, N> tracing_subscriber::fmt::FormatEvent<S, N> for ColoredLogFormatter
267where
268 S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
269 N: for<'a> FormatFields<'a> + 'static,
270{
271 fn format_event(
272 &self,
273 ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>,
274 mut writer: Writer<'_>,
275 event: &tracing::Event<'_>,
276 ) -> std::fmt::Result {
277 let metadata = event.metadata();
278 let level = metadata.level();
279
280 let (color, symbol) = match *level {
282 Level::ERROR => (colors::RED, symbols::CROSS),
283 Level::WARN => (colors::YELLOW, symbols::WARNING),
284 Level::INFO => (colors::GREEN, symbols::CHECK),
285 Level::DEBUG => (colors::BLUE, symbols::INFO),
286 Level::TRACE => (colors::DIM, symbols::DOT),
287 };
288
289 write!(&mut writer, "{}{} ", color, symbol)?;
291
292 ctx.field_format().format_fields(writer.by_ref(), event)?;
294
295 write!(&mut writer, "{}", colors::RESET)?;
296
297 writeln!(writer)
298 }
299}
300
301pub struct ProgressIndicator {
303 message: String,
304 frames: Vec<&'static str>,
305 current_frame: usize,
306}
307
308impl ProgressIndicator {
309 pub fn new(message: String) -> Self {
310 Self {
311 message,
312 frames: vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
313 current_frame: 0,
314 }
315 }
316
317 pub fn tick(&mut self) {
318 print!("\r{} {} {} ",
319 self.frames[self.current_frame],
320 colors::BLUE,
321 self.message
322 );
323 self.current_frame = (self.current_frame + 1) % self.frames.len();
324 use std::io::{self, Write};
325 io::stdout().flush().unwrap();
326 }
327
328 pub fn finish_success(&self, message: &str) {
329 println!("\r{} {}{}{} {}",
330 symbols::CHECK,
331 colors::GREEN,
332 self.message,
333 colors::RESET,
334 message
335 );
336 }
337
338 pub fn finish_error(&self, message: &str) {
339 println!("\r{} {}{}{} {}",
340 symbols::CROSS,
341 colors::RED,
342 self.message,
343 colors::RESET,
344 message
345 );
346 }
347}