prettyping-rs 1.0.2

Rust port of prettyping with a pure Rust ping engine
Documentation
use std::net::IpAddr;

use thiserror::Error;

pub const DEFAULT_LAST: u32 = 60;
const MAX_LAST: u32 = 10_000;
const MAX_COLUMNS: u16 = 10_000;
const MAX_LINES: u16 = 10_000;
const MAX_PACKET_SIZE: u32 = 65_507;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddressFamily {
    Any,
    Ipv4,
    Ipv6,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Config {
    pub host: String,
    pub color: bool,
    pub multicolor: bool,
    pub unicode: bool,
    pub legend: bool,
    pub globalstats: bool,
    pub recentstats: bool,
    pub terminal: Option<bool>,
    pub last: u32,
    pub columns: Option<u16>,
    pub lines: Option<u16>,
    pub rttmin: Option<u32>,
    pub rttmax: Option<u32>,
    pub family: AddressFamily,
    pub count: Option<u32>,
    pub interval_secs: Option<f64>,
    pub timeout_secs: Option<f64>,
    pub packet_size: Option<u32>,
    pub ttl: Option<u16>,
}

#[derive(Debug, Clone)]
pub struct ConfigInput {
    pub host: String,
    pub color: bool,
    pub multicolor: bool,
    pub unicode: bool,
    pub legend: bool,
    pub globalstats: bool,
    pub recentstats: bool,
    pub terminal: Option<bool>,
    pub last: u32,
    pub columns: Option<u16>,
    pub lines: Option<u16>,
    pub rttmin: Option<u32>,
    pub rttmax: Option<u32>,
    pub force_ipv4: bool,
    pub force_ipv6: bool,
    pub count: Option<u32>,
    pub interval_secs: Option<f64>,
    pub timeout_secs: Option<f64>,
    pub packet_size: Option<u32>,
    pub ttl: Option<u16>,
}

#[derive(Debug, Error, PartialEq)]
pub enum ConfigError {
    #[error("host must not be empty")]
    EmptyHost,
    #[error("--last must be between 0 and {MAX_LAST}")]
    LastOutOfRange,
    #[error("--columns must be between 1 and {MAX_COLUMNS}")]
    ColumnsOutOfRange,
    #[error("--lines must be between 1 and {MAX_LINES}")]
    LinesOutOfRange,
    #[error("--rttmin must be greater than 0")]
    RttMinOutOfRange,
    #[error("--rttmax must be greater than 0")]
    RttMaxOutOfRange,
    #[error("--rttmin must be strictly smaller than --rttmax")]
    InvalidRttRange,
    #[error("cannot use -4 and -6 together")]
    FamilyConflict,
    #[error("host address family conflicts with selected family flag")]
    HostFamilyConflict,
    #[error("-c/--count must be greater than 0")]
    CountOutOfRange,
    #[error("-i/--interval must be a finite value greater than 0")]
    IntervalOutOfRange,
    #[error("-W/--timeout must be a finite value greater than 0")]
    TimeoutOutOfRange,
    #[error("-s/--size must be at most {MAX_PACKET_SIZE}")]
    PacketSizeOutOfRange,
    #[error("-t/--ttl must be between 1 and 255")]
    TtlOutOfRange,
}

pub fn normalize(input: ConfigInput) -> Result<Config, ConfigError> {
    if input.host.trim().is_empty() {
        return Err(ConfigError::EmptyHost);
    }

    if input.last > MAX_LAST {
        return Err(ConfigError::LastOutOfRange);
    }

    if let Some(columns) = input.columns
        && (columns == 0 || columns > MAX_COLUMNS)
    {
        return Err(ConfigError::ColumnsOutOfRange);
    }

    if let Some(lines) = input.lines
        && (lines == 0 || lines > MAX_LINES)
    {
        return Err(ConfigError::LinesOutOfRange);
    }

    if let Some(rttmin) = input.rttmin
        && rttmin == 0
    {
        return Err(ConfigError::RttMinOutOfRange);
    }

    if let Some(rttmax) = input.rttmax
        && rttmax == 0
    {
        return Err(ConfigError::RttMaxOutOfRange);
    }

    if let (Some(rttmin), Some(rttmax)) = (input.rttmin, input.rttmax)
        && rttmin >= rttmax
    {
        return Err(ConfigError::InvalidRttRange);
    }

    let family = match (input.force_ipv4, input.force_ipv6) {
        (true, true) => return Err(ConfigError::FamilyConflict),
        (true, false) => AddressFamily::Ipv4,
        (false, true) => AddressFamily::Ipv6,
        (false, false) => AddressFamily::Any,
    };

    if let Ok(parsed_host) = input.host.parse::<IpAddr>() {
        match (family, parsed_host) {
            (AddressFamily::Ipv4, IpAddr::V6(_)) | (AddressFamily::Ipv6, IpAddr::V4(_)) => {
                return Err(ConfigError::HostFamilyConflict);
            }
            _ => {}
        }
    }

    if let Some(count) = input.count
        && count == 0
    {
        return Err(ConfigError::CountOutOfRange);
    }

    if let Some(interval_secs) = input.interval_secs
        && (!interval_secs.is_finite() || interval_secs <= 0.0)
    {
        return Err(ConfigError::IntervalOutOfRange);
    }

    if let Some(timeout_secs) = input.timeout_secs
        && (!timeout_secs.is_finite() || timeout_secs <= 0.0)
    {
        return Err(ConfigError::TimeoutOutOfRange);
    }

    if let Some(packet_size) = input.packet_size
        && packet_size > MAX_PACKET_SIZE
    {
        return Err(ConfigError::PacketSizeOutOfRange);
    }

    if let Some(ttl) = input.ttl
        && !(1..=255).contains(&ttl)
    {
        return Err(ConfigError::TtlOutOfRange);
    }

    Ok(Config {
        host: input.host,
        color: input.color,
        multicolor: input.multicolor,
        unicode: input.unicode,
        legend: input.legend,
        globalstats: input.globalstats,
        recentstats: input.recentstats,
        terminal: input.terminal,
        last: input.last,
        columns: input.columns,
        lines: input.lines,
        rttmin: input.rttmin,
        rttmax: input.rttmax,
        family,
        count: input.count,
        interval_secs: input.interval_secs,
        timeout_secs: input.timeout_secs,
        packet_size: input.packet_size,
        ttl: input.ttl,
    })
}