netpulse-cli 0.1.1

A zero-config, single-binary network quality monitor with percentile stats, jitter, and MTR-style traceroute
Documentation
// src/probers/mod.rs — Prober trait and shared types
//
// Each probe type (ICMP, TCP, UDP) implements this trait.
// This is Rust's approach to polymorphism — no inheritance,
// just traits — enabling a clean, extensible probe architecture.

pub mod icmp;
pub mod tcp;
pub mod udp;

use crate::error::NetPulseError;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::Serialize;

/// A single probe measurement result.
#[derive(Debug, Clone, Serialize)]
pub struct ProbeResult {
    /// Target host or IP that was probed.
    pub target: String,
    /// Round-trip time in microseconds. None means packet loss.
    pub rtt_us: Option<u64>,
    /// UTC timestamp of when the probe was sent.
    pub timestamp: DateTime<Utc>,
    /// Sequence number of this probe (for reorder detection).
    pub seq: u64,
    /// The IP address that responded. Useful for traceroute (ICMP Time Exceeded).
    pub responder_ip: Option<String>,
}

impl ProbeResult {
    pub fn loss(target: &str, seq: u64) -> Self {
        Self {
            target: target.to_string(),
            rtt_us: None,
            timestamp: Utc::now(),
            seq,
            responder_ip: None,
        }
    }

    pub fn success(target: &str, seq: u64, rtt_us: u64, responder_ip: Option<String>) -> Self {
        Self {
            target: target.to_string(),
            rtt_us: Some(rtt_us),
            timestamp: Utc::now(),
            seq,
            responder_ip,
        }
    }

    /// Returns true if the probe resulted in packet loss.
    pub fn is_loss(&self) -> bool {
        self.rtt_us.is_none()
    }
}

/// The core abstraction for all probe types.
///
/// Every prober must be Send + Sync so it can be shared across
/// async Tokio tasks without data races.
#[async_trait]
pub trait Prober: Send + Sync {
    /// Fire a single probe toward `target` and return the result.
    async fn probe(
        &self,
        target: &str,
        seq: u64,
        ttl: Option<u32>,
    ) -> Result<ProbeResult, NetPulseError>;

    /// Human-readable name of this probe type, e.g. "icmp" or "tcp".
    fn name(&self) -> &'static str;
}