rusty-ping 0.1.0

A pretty ping/ICMP utility
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

fn parse_bsd_part(part: &str) -> Result<u32, String> {
    let part = part.trim();
    if part.is_empty() {
        return Err("empty part".into());
    }

    // Determine base and skip prefix if hex or octal
    let (base, start_idx) = if part.starts_with("0x") || part.starts_with("0X") {
        (16, 2)
    } else if part.starts_with('0') && part.len() > 1 {
        (8, 1)
    } else {
        (10, 0)
    };

    let num_str = &part[start_idx..];

    // If base is 8 or 16, num_str must not be empty (e.g., "0x" or "0o" is invalid)
    if (base == 8 || base == 16) && num_str.is_empty() {
        return Err("invalid number format".into());
    }

    u32::from_str_radix(num_str, base).map_err(|e| format!("invalid number: {}", e))
}

pub fn parse_bsd_ipv4(s: &str) -> Result<Ipv4Addr, String> {
    let parts: Vec<&str> = s.split('.').collect();
    let n = parts.len();

    if !(1..=4).contains(&n) {
        return Err("invalid number of parts".into());
    }

    let mut parsed_parts: Vec<u32> = Vec::with_capacity(n);
    for part in parts {
        let parsed = parse_bsd_part(part)?;
        parsed_parts.push(parsed);
    }

    let addr: u32 = match n {
        4 => {
            if parsed_parts[0] > 0xFF
                || parsed_parts[1] > 0xFF
                || parsed_parts[2] > 0xFF
                || parsed_parts[3] > 0xFF
            {
                return Err("invalid IP part out of range".into());
            }
            (parsed_parts[0] << 24)
                | (parsed_parts[1] << 16)
                | (parsed_parts[2] << 8)
                | parsed_parts[3]
        }
        3 => {
            if parsed_parts[0] > 0xFF || parsed_parts[1] > 0xFF || parsed_parts[2] > 0xFFFF {
                return Err("invalid IP part out of range".into());
            }
            (parsed_parts[0] << 24) | (parsed_parts[1] << 16) | parsed_parts[2]
        }
        2 => {
            if parsed_parts[0] > 0xFF || parsed_parts[1] > 0xFFFFFF {
                return Err("invalid IP part out of range".into());
            }
            (parsed_parts[0] << 24) | parsed_parts[1]
        }
        1 => parsed_parts[0],
        _ => {
            return Err("invalid number of parts".into());
        }
    };

    Ok(Ipv4Addr::from(addr))
}

pub fn parse_ip(s: &str) -> Result<IpAddr, String> {
    // First try to parse as IPv6
    if let Ok(ip) = s.parse::<Ipv6Addr>() {
        return Ok(IpAddr::V6(ip));
    }

    // Try to parse as BSD valid IPv4
    if let Ok(ipv4) = parse_bsd_ipv4(s) {
        return Ok(IpAddr::V4(ipv4));
    }

    Err(format!("invalid IP address: {}", s))
}