1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// rscamper - Rust wrapper for scamper instance connections
//
// Copyright (C) 2026 Dimitrios Giakatos
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3.
use std::ffi::CStr;
use crate::ffi::libscamperctrl::{self, ScamperInstT, ScamperTaskT};
use crate::ffi::scamper_file::{self, ScamperFileT, ScamperFileReadbufT};
use crate::vp::ScamperVp;
/// Per-instance state stored as the inst param (accessed by the ctrl callback).
pub(crate) struct InstData {
pub(crate) c_f: *mut ScamperFileT,
pub(crate) c_rb: *mut ScamperFileReadbufT,
pub(crate) eof: bool,
pub(crate) queued: usize,
pub(crate) tasks: Vec<*mut ScamperTaskT>,
}
/// A connection to a single scamper instance.
///
/// Created via `ScamperCtrl::add_unix`, `add_inet`, or `add_remote`.
/// Provides methods for querying the instance's state.
/// Issue measurements via the `ScamperCtrl::do_*` methods, passing a reference to this.
pub struct ScamperInst {
pub(crate) c: *mut ScamperInstT,
pub(crate) data: *mut InstData,
}
impl ScamperInst {
/// Create a new ScamperInst wrapping a raw pointer.
/// Sets up the readbuf and null file for warts parsing.
pub(crate) unsafe fn from_ptr(c: *mut ScamperInstT) -> Self {
let c_rb = unsafe { scamper_file::scamper_file_readbuf_alloc() };
let kind = b"warts\0";
let c_f = unsafe { scamper_file::scamper_file_opennull(
b'r' as i8,
kind.as_ptr() as *const libc::c_char,
) };
unsafe { scamper_file::scamper_file_setreadfunc(
c_f,
c_rb as *mut libc::c_void,
scamper_file::scamper_file_readbuf_read,
) };
let data = Box::into_raw(Box::new(InstData {
c_f,
c_rb,
eof: false,
queued: 0,
tasks: Vec::new(),
}));
unsafe { libscamperctrl::scamper_inst_param_set(c, data as *mut libc::c_void) };
ScamperInst { c, data }
}
/// The name of this instance (path or address).
pub fn name(&self) -> Option<String> {
let ptr = unsafe { libscamperctrl::scamper_inst_name_get(self.c) };
if ptr.is_null() { None } else {
Some(unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned())
}
}
/// True if this is a unix-domain socket instance.
pub fn is_unix(&self) -> bool {
unsafe { libscamperctrl::scamper_inst_is_unix(self.c) != 0 }
}
/// True if this is an inet (TCP) instance.
pub fn is_inet(&self) -> bool {
unsafe { libscamperctrl::scamper_inst_is_inet(self.c) != 0 }
}
/// True if this is a remote instance.
pub fn is_remote(&self) -> bool {
unsafe { libscamperctrl::scamper_inst_is_remote(self.c) != 0 }
}
/// True if this is a mux VP instance.
pub fn is_muxvp(&self) -> bool {
unsafe { libscamperctrl::scamper_inst_is_muxvp(self.c) != 0 }
}
/// True if the instance has signalled EOF (disconnected).
pub fn is_eof(&self) -> bool {
unsafe { (*self.data).eof }
}
/// The number of outstanding (unresolved) tasks for this instance.
pub fn taskc(&self) -> usize {
unsafe { (*self.data).tasks.len() }
}
/// The number of completed results queued but not yet consumed.
pub fn resultc(&self) -> usize {
unsafe { (*self.data).queued }
}
/// The vantage point associated with this instance, if any.
pub fn vp(&self) -> Option<ScamperVp> {
let ptr = unsafe { libscamperctrl::scamper_inst_vp_get(self.c) };
unsafe { ScamperVp::from_ptr(ptr) }
}
/// The country code of this instance's VP, if known.
pub fn cc(&self) -> Option<String> { self.vp()?.cc() }
/// The state code of this instance's VP, if known.
pub fn st(&self) -> Option<String> { self.vp()?.st() }
/// The place name of this instance's VP, if known.
pub fn place(&self) -> Option<String> { self.vp()?.place() }
/// The short name of this instance's VP, if known.
pub fn shortname(&self) -> Option<String> { self.vp()?.shortname() }
/// The IPv4 address of this instance's VP, if known.
pub fn ipv4(&self) -> Option<String> { self.vp()?.ipv4() }
/// The IPv4 ASN of this instance's VP, if known.
pub fn asn4(&self) -> Option<String> { self.vp()?.asn4() }
/// The location (lat, lon) of this instance's VP, if known.
pub fn loc(&self) -> Option<(f64, f64)> { self.vp()?.loc() }
/// The IATA code of this instance's VP, if known.
pub fn iata(&self) -> Option<String> { self.vp()?.iata() }
/// Signal that no further measurements will be issued on this instance.
///
/// **Must** be called after all `do_*` scheduling calls for this instance
/// and before collecting results. Without it, [`ScamperCtrl::responses`]
/// and [`ScamperCtrl::poll`] will block indefinitely because the event
/// loop keeps waiting for commands that will never arrive.
///
/// After calling `done()` you cannot submit new measurements on this
/// instance. Create a new instance via `add_inet` / `add_unix` /
/// `add_remote` if you need a second batch.
///
/// # Example
///
/// ```no_run
/// ctrl.do_ping(&inst, "1.1.1.1", /* … */)?;
/// ctrl.do_trace(&inst, "8.8.8.8", /* … */)?;
/// inst.done(); // must come after all do_* calls for this inst
///
/// for item in ctrl.responses(None) { /* … */ }
/// ```
pub fn done(&self) {
unsafe { libscamperctrl::scamper_inst_done(self.c) };
}
}
impl Drop for ScamperInst {
fn drop(&mut self) {
unsafe {
let data = Box::from_raw(self.data);
scamper_file::scamper_file_close(data.c_f);
scamper_file::scamper_file_readbuf_free(data.c_rb);
// data (and its tasks vec) is dropped here
drop(data);
libscamperctrl::scamper_inst_free(self.c);
}
}
}
impl PartialEq for ScamperInst {
fn eq(&self, other: &Self) -> bool { self.c == other.c }
}
impl Eq for ScamperInst {}
impl std::hash::Hash for ScamperInst {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
(self.c as usize).hash(state);
}
}
impl std::fmt::Display for ScamperInst {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.name() {
Some(n) => write!(f, "{}", n),
None => write!(f, "<ScamperInst {:p}>", self.c),
}
}
}
unsafe impl Send for ScamperInst {}
unsafe impl Sync for ScamperInst {}