irtt-client 0.6.0

IRTT-compatible Rust client library
Documentation
//! Runtime-free blocking client example.
//!
//! Opens a session against a local IRTT-compatible server, sends a few
//! probes, and prints the events observed. Uses only [`Client`], which has no
//! Tokio dependency and works in a default (`--no-default-features`) build.
//!
//! Run a server first, then run this example:
//!
//! ```text
//! cargo run -p irtt-rs --bin irtt-server --features server
//! cargo run -p irtt-client --example blocking_client
//! ```
//!
//! Without a reachable server this exits quickly with an open-timeout error
//! instead of hanging, because the example shortens `open.timeouts` for a
//! fast demonstration; a real caller would normally keep the default retry
//! schedule.

use std::time::{Duration, Instant};

use irtt_client::{Client, ClientConfig, ClientEvent, OpenOutcome, OpenPolicy, SessionRequest};

const RUN_DURATION: Duration = Duration::from_secs(2);

fn main() {
    let config = ClientConfig {
        open: OpenPolicy {
            // A single short attempt keeps this example fast when no server is
            // reachable; production callers should generally keep the default.
            timeouts: vec![Duration::from_millis(300)],
            ..Default::default()
        },
        request: SessionRequest {
            duration: Some(RUN_DURATION),
            interval: Duration::from_millis(200),
            ..Default::default()
        },
        ..Default::default()
    };

    let mut client = match Client::connect("127.0.0.1:2112", config) {
        Ok(client) => client,
        Err(err) => {
            eprintln!("failed to prepare client socket: {err}");
            return;
        }
    };

    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
        eprintln!("failed to configure receive timeout: {err}");
        return;
    }

    let negotiated = match client.open() {
        Ok(OpenOutcome::Started(started)) => {
            println!("session opened: {started:?}");
            started.negotiation
        }
        Ok(OpenOutcome::NoTestCompleted(completed)) => {
            println!("{completed:?}");
            return;
        }
        Err(err) => {
            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
            return;
        }
    };

    // This loop owns cadence and duration. The transport sends whenever called.
    let interval = negotiated.accepted.interval;
    let start = Instant::now();
    let end = start
        + negotiated
            .accepted
            .duration
            .expect("example requests a finite run");
    let deadline = end + client.probe_timeout();
    let mut next_send = start;
    while Instant::now() < deadline && !client.is_peer_closed() {
        let now = Instant::now();
        if now >= end && !client.has_pending_probes() {
            break;
        }
        if now < end && now >= next_send {
            match client.send_probe() {
                Ok(receipt) => {
                    print_event(&receipt.into());
                    while next_send <= Instant::now() {
                        next_send += interval;
                    }
                }
                Err(err) => {
                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
                        print_event(&(*receipt).into());
                    }
                    eprintln!("send failed: {err}");
                    break;
                }
            }
        }
        match client.recv_once() {
            Ok(events) => events.iter().for_each(print_event),
            Err(err) => {
                eprintln!("receive failed: {err}");
                break;
            }
        }
        match client.poll_timeouts() {
            Ok(events) => events.iter().for_each(print_event),
            Err(err) => {
                eprintln!("timeout polling failed: {err}");
                break;
            }
        }
    }

    if client.is_peer_closed() {
        return;
    }
    match client.close() {
        Ok(events) => events.iter().for_each(print_event),
        Err(err) => eprintln!("close failed: {err}"),
    }
}

fn print_event(event: &ClientEvent) {
    match event {
        ClientEvent::EchoReply { seq, rtt, .. } => {
            println!("seq {seq}: rtt {:?}", rtt.effective);
        }
        ClientEvent::EchoLoss { seq, .. } => println!("seq {seq}: lost"),
        other => println!("{other:?}"),
    }
}