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