use std::net::{IpAddr, SocketAddr};
use tracing::Level;
use tracing_subscriber::fmt::{FormatFields, format::Writer};
use unicode_width::UnicodeWidthStr;
pub mod colors {
pub const RESET: &str = "\x1b[0m";
pub const BOLD: &str = "\x1b[1m";
pub const DIM: &str = "\x1b[2m";
pub const BLACK: &str = "\x1b[30m";
pub const RED: &str = "\x1b[31m";
pub const GREEN: &str = "\x1b[32m";
pub const YELLOW: &str = "\x1b[33m";
pub const BLUE: &str = "\x1b[34m";
pub const MAGENTA: &str = "\x1b[35m";
pub const CYAN: &str = "\x1b[36m";
pub const WHITE: &str = "\x1b[37m";
pub const BRIGHT_BLACK: &str = "\x1b[90m";
pub const BRIGHT_RED: &str = "\x1b[91m";
pub const BRIGHT_GREEN: &str = "\x1b[92m";
pub const BRIGHT_YELLOW: &str = "\x1b[93m";
pub const BRIGHT_BLUE: &str = "\x1b[94m";
pub const BRIGHT_MAGENTA: &str = "\x1b[95m";
pub const BRIGHT_CYAN: &str = "\x1b[96m";
pub const BRIGHT_WHITE: &str = "\x1b[97m";
}
pub mod symbols {
pub const CHECK: &str = "✓";
pub const CROSS: &str = "✗";
pub const INFO: &str = "ℹ";
pub const WARNING: &str = "⚠";
pub const ARROW_RIGHT: &str = "→";
pub const DOT: &str = "•";
pub const KEY: &str = "🔑";
pub const NETWORK: &str = "📡";
pub const GLOBE: &str = "🌐";
pub const ROCKET: &str = "🚀";
pub const HOURGLASS: &str = "⏳";
pub const CIRCULAR_ARROWS: &str = "⟳";
}
pub mod box_chars {
pub const TOP_LEFT: &str = "╭";
pub const TOP_RIGHT: &str = "╮";
pub const BOTTOM_LEFT: &str = "╰";
pub const BOTTOM_RIGHT: &str = "╯";
pub const HORIZONTAL: &str = "─";
pub const VERTICAL: &str = "│";
pub const T_LEFT: &str = "├";
pub const T_RIGHT: &str = "┤";
}
fn is_ipv6_link_local(ip: &std::net::Ipv6Addr) -> bool {
let octets = ip.octets();
(octets[0] == 0xfe) && ((octets[1] & 0xc0) == 0x80)
}
fn is_ipv6_unique_local(ip: &std::net::Ipv6Addr) -> bool {
let octets = ip.octets();
(octets[0] & 0xfe) == 0xfc
}
fn is_ipv6_multicast(ip: &std::net::Ipv6Addr) -> bool {
let octets = ip.octets();
octets[0] == 0xff
}
pub fn format_peer_id(peer_id: &[u8; 32]) -> String {
let hex = hex::encode(&peer_id[..4]);
format!("{}{}{}{}", colors::CYAN, hex, "...", colors::RESET)
}
pub fn format_address(addr: &SocketAddr) -> String {
let color = match addr.ip() {
IpAddr::V4(ip) => {
if ip.is_loopback() {
colors::DIM
} else if ip.is_private() {
colors::YELLOW
} else {
colors::GREEN
}
}
IpAddr::V6(ip) => {
if ip.is_loopback() {
colors::DIM
} else if ip.is_unspecified() {
colors::DIM
} else if is_ipv6_link_local(&ip) {
colors::YELLOW
} else if is_ipv6_unique_local(&ip) {
colors::CYAN
} else {
colors::BRIGHT_CYAN
}
}
};
format!("{}{}{}", color, addr, colors::RESET)
}
pub fn format_address_with_words(addr: &SocketAddr) -> String {
format_address(addr)
}
pub fn describe_address(addr: &SocketAddr) -> &'static str {
match addr.ip() {
IpAddr::V4(ip) => {
if ip.is_loopback() {
"loopback"
} else if ip.is_private() {
"private network"
} else if ip.is_link_local() {
"link-local"
} else {
"public"
}
}
IpAddr::V6(ip) => {
if ip.is_loopback() {
"IPv6 loopback"
} else if ip.is_unspecified() {
"IPv6 unspecified"
} else if is_ipv6_link_local(&ip) {
"IPv6 link-local"
} else if is_ipv6_unique_local(&ip) {
"IPv6 unique local"
} else if is_ipv6_multicast(&ip) {
"IPv6 multicast"
} else {
"IPv6 global"
}
}
}
}
pub fn draw_box(title: &str, width: usize) -> (String, String, String) {
let padding = width.saturating_sub(title.width() + 4);
let left_pad = padding / 2;
let right_pad = padding - left_pad;
let top = format!(
"{}{} {} {}{}{}",
box_chars::TOP_LEFT,
box_chars::HORIZONTAL.repeat(left_pad),
title,
box_chars::HORIZONTAL.repeat(right_pad),
box_chars::HORIZONTAL,
box_chars::TOP_RIGHT
);
let middle = format!("{} {{}} {}", box_chars::VERTICAL, box_chars::VERTICAL);
let bottom = format!(
"{}{}{}",
box_chars::BOTTOM_LEFT,
box_chars::HORIZONTAL.repeat(width - 2),
box_chars::BOTTOM_RIGHT
);
(top, middle, bottom)
}
pub fn print_banner(version: &str) {
let title = format!("ant-quic v{version}");
let (top, middle, bottom) = draw_box(&title, 60);
println!("{top}");
println!(
"{}",
middle.replace(
"{}",
"Starting QUIC P2P with NAT Traversal "
)
);
println!("{bottom}");
println!();
}
pub fn print_section(icon: &str, title: &str) {
println!("{} {}{}{}", icon, colors::BOLD, title, colors::RESET);
}
pub fn print_item(text: &str, indent: usize) {
let indent_str = " ".repeat(indent);
println!("{}{} {}", indent_str, symbols::DOT, text);
}
pub fn print_status(icon: &str, text: &str, color: &str) {
println!(" {} {}{}{}", icon, color, text, colors::RESET);
}
pub fn format_bytes(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = bytes as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", size as u64, UNITS[unit_index])
} else {
format!("{:.1} {}", size, UNITS[unit_index])
}
}
pub fn format_duration(duration: std::time::Duration) -> String {
let total_seconds = duration.as_secs();
let hours = total_seconds / 3600;
let minutes = (total_seconds % 3600) / 60;
let seconds = total_seconds % 60;
format!("{hours:02}:{minutes:02}:{seconds:02}")
}
pub fn format_timestamp(_timestamp: std::time::Instant) -> String {
use std::time::SystemTime;
let now = SystemTime::now();
let duration_since_epoch = now
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(std::time::Duration::ZERO);
let total_seconds = duration_since_epoch.as_secs();
let hours = (total_seconds % 86400) / 3600;
let minutes = (total_seconds % 3600) / 60;
let seconds = total_seconds % 60;
format!("{hours:02}:{minutes:02}:{seconds:02}")
}
pub struct ColoredLogFormatter;
impl<S, N> tracing_subscriber::fmt::FormatEvent<S, N> for ColoredLogFormatter
where
S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &tracing::Event<'_>,
) -> std::fmt::Result {
let metadata = event.metadata();
let level = metadata.level();
let (color, symbol) = match *level {
Level::ERROR => (colors::RED, symbols::CROSS),
Level::WARN => (colors::YELLOW, symbols::WARNING),
Level::INFO => (colors::GREEN, symbols::CHECK),
Level::DEBUG => (colors::BLUE, symbols::INFO),
Level::TRACE => (colors::DIM, symbols::DOT),
};
write!(&mut writer, "{color}{symbol} ")?;
ctx.field_format().format_fields(writer.by_ref(), event)?;
write!(&mut writer, "{}", colors::RESET)?;
writeln!(writer)
}
}
pub struct ProgressIndicator {
message: String,
frames: Vec<&'static str>,
current_frame: usize,
}
impl ProgressIndicator {
pub fn new(message: String) -> Self {
Self {
message,
frames: vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
current_frame: 0,
}
}
pub fn tick(&mut self) {
print!(
"\r{} {} {} ",
self.frames[self.current_frame],
colors::BLUE,
self.message
);
self.current_frame = (self.current_frame + 1) % self.frames.len();
use std::io::{self, Write};
let _ = io::stdout().flush(); }
pub fn finish_success(&self, message: &str) {
println!(
"\r{} {}{}{} {}",
symbols::CHECK,
colors::GREEN,
self.message,
colors::RESET,
message
);
}
pub fn finish_error(&self, message: &str) {
println!(
"\r{} {}{}{} {}",
symbols::CROSS,
colors::RED,
self.message,
colors::RESET,
message
);
}
}