rscamper 0.2.2

Rust interface to scamper network measurement tool
Documentation
# rscamper

A Rust interface to [scamper](https://www.caida.org/catalog/software/scamper/)
network measurement tool.

`rscamper` wraps the scamper C libraries (`libscamperfile` and `libscamperctrl`) through Rust
FFI, providing idiomatic Rust types for all measurement data structures and a friendly
control loop.

## Features

- **File I/O** — read and write scamper warts/JSON files via `ScamperFile`
- **Control loop** — connect to a running scamper daemon and issue measurements via `ScamperCtrl`
- **All measurement types**`trace`, `ping`, `tracelb`, `dealias`, `host`, `http`, `tbit`,
  `sniff`, `udpprobe`, `owamp`, `sting`, `neighbourdisc`
- **Mux / VP support** — enumerate and connect to vantage points via `ScamperMux` and `ScamperVp`

## Prerequisites

`rscamper` links against `libscamperfile` and `libscamperctrl`, which are part of the scamper
distribution. Build and install scamper from source following the
[official instructions](https://www.caida.org/catalog/software/scamper/).

By default, the libraries are searched for in `/usr/local/lib` and `/usr/lib`. If you installed
scamper to a custom prefix, set the `SCAMPER_LIB_DIR` environment variable at build time:

```sh
SCAMPER_LIB_DIR=/opt/scamper/lib cargo build
```

## Installation

Run the following Cargo command in your project directory:

```sh
cargo add rscamper
```

## Usage

### Reading a warts file

```rust
use rscamper::{ScamperFile, ScamperObject};

let file = ScamperFile::open("results.warts", 'r', None).unwrap();
for obj in file {
    if let ScamperObject::Ping(ping) = obj {
        println!("{}", ping.to_json().unwrap_or_default());
    }
}
```

### One-shot measurement

Schedule a single measurement, wait for the result, and save it to disk.
`inst.done()` is a required lifecycle signal — without it `responses()` and `poll()` block forever.

```rust
use rscamper::{ScamperCtrl, ScamperFile, ScamperObject};

let mut ctrl = ScamperCtrl::new(false, None).unwrap();
let inst = ctrl.add_inet(31337, None).unwrap();

ctrl.do_ping(
    &inst, "1.1.1.1",
    None, None, None, None, None, None, None, None,
    None, None, None, None, None, None, None, None,
    None, None, None, None, None,
).unwrap();
inst.done(); // no more commands on this instance

// Pull exactly one result — blocks until it is ready.
// Equivalent: ctrl.responses(None).next().unwrap()
let item = ctrl.poll().unwrap();

let mut f = ScamperFile::open("result.warts", 'w', Some("warts")).unwrap();
f.write(&item.obj).unwrap();
f.close();
```

### Streaming multiple measurements

Schedule several measurements and process each result the moment it arrives,
without waiting for the full batch to finish.

```rust
use std::time::Duration;
use rscamper::{ScamperCtrl, ScamperObject};

let mut ctrl = ScamperCtrl::new(false, None).unwrap();
let inst = ctrl.add_inet(31337, None).unwrap();

for dst in &["1.1.1.1", "8.8.8.8"] {
    ctrl.do_ping(
        &inst, dst,
        None, None, None, None, None, None, None, None,
        None, None, None, None, None, None, None, None,
        None, None, None, None, None,
    ).unwrap();
}
inst.done();

// poll() blocks until one result is ready, then returns it immediately.
// The loop ends when all tasks are complete.
while let Some(item) = ctrl.poll() {
    if let ScamperObject::Ping(ping) = &item.obj {
        if let Some(stats) = ping.stats() {
            println!("min RTT: {:?}", stats.min_rtt());
        }
    }
}
```

### Iterator style

Use `responses()` when you want a `for` loop. The argument controls whether
the iterator has a timeout:

| Call | Behaviour |
|---|---|
| `responses(None)` | Waits indefinitely — ends only when all tasks are done |
| `responses(Some(duration))` | Stops early if the deadline elapses, even if tasks are still running |

`None` is safe when you trust scamper to finish every measurement (e.g. each
`do_*` call has its own `wait_timeout`). `Some(duration)` is a safety net for
the whole batch — useful when you cannot predict how long the full set will
take.

### Choosing between `poll()` and `responses()`

Both deliver results one at a time as they arrive. The difference is timeout
control and syntax:

| | `poll()` | `responses(Some(d))` |
|---|---|---|
| Syntax | `while let Some(r) = ctrl.poll()` | `for r in ctrl.responses(Some(d))` |
| Per-result wait | Indefinite — blocks until one result is ready | Same |
| Batch timeout | **None** — no way to stop the loop early | **Yes** — stops the whole iteration if the deadline elapses |
| Use when | You control timeouts per measurement via `wait_timeout` | You need a hard cap on total wall-clock time for the batch |

Use `poll()` when each `do_*` call already has a `wait_timeout` — scamper
will time out individual measurements and `poll()` will eventually return
`None`. Use `responses(Some(duration))` when you want a single deadline that
covers the entire batch regardless of how individual measurements behave.

```rust
use std::time::Duration;
use rscamper::{ScamperCtrl, ScamperObject};

let mut ctrl = ScamperCtrl::new(false, None).unwrap();
let inst = ctrl.add_inet(31337, None).unwrap();

ctrl.do_ping(
    &inst, "1.1.1.1",
    None, None, None, None, None, None, None, None,
    None, None, None, None, None, None, None, None,
    None, None, None, None, None,
).unwrap();
inst.done();

// No timeout — wait as long as it takes.
for item in ctrl.responses(None) {
    if let ScamperObject::Ping(ping) = &item.obj {
        if let Some(stats) = ping.stats() {
            println!("min RTT: {:?}", stats.min_rtt());
        }
    }
}

// With a 10-second safety timeout on the whole batch.
for item in ctrl.responses(Some(Duration::from_secs(10))) {
    if let ScamperObject::Ping(ping) = &item.obj {
        if let Some(stats) = ping.stats() {
            println!("min RTT: {:?}", stats.min_rtt());
        }
    }
}
```

### Mux / vantage points

```rust
use rscamper::ScamperCtrl;

let mut ctrl = ScamperCtrl::new(false, None).unwrap();
let _mux_inst = ctrl.add_inet(31337, None).unwrap();

for vp in ctrl.vps() {
    let inst = ctrl.add_vp(&vp).unwrap();
    // issue measurements on inst ...
    inst.done();
}
```

## License

GPL-3.0-only — see the [GNU General Public License](https://www.gnu.org/licenses/gpl-3.0.html)
for details.