use std::ffi::CString;
use std::time::Duration;
use crate::error::{Error, Result, last_ffi_error};
use crate::session::{Session, Subscriber};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Regime {
Stable,
Degrading,
Volatile,
Recovering,
}
impl Regime {
fn from_raw(v: adamo_sys::adamo_regime_t) -> Self {
match v {
adamo_sys::ADAMO_REGIME_DEGRADING => Self::Degrading,
adamo_sys::ADAMO_REGIME_VOLATILE => Self::Volatile,
adamo_sys::ADAMO_REGIME_RECOVERING => Self::Recovering,
_ => Self::Stable,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct LatencyStats {
pub regime: Regime,
pub jitter_hint_ms: f32,
pub garch_sigma_ms: f32,
pub target_bitrate_kbps: u32,
pub loss_rate: f32,
pub queuing_delay_ms: f32,
pub timestamp_ms: u64,
}
impl LatencyStats {
pub fn parse(payload: &[u8]) -> Option<Self> {
let mut out = adamo_sys::adamo_latency_stats_t {
regime: 0,
jitter_hint_ms: 0.0,
garch_sigma_ms: 0.0,
target_bitrate_kbps: 0,
loss_rate: 0.0,
queuing_delay_ms: 0.0,
timestamp_ms: 0,
};
let rc = unsafe {
adamo_sys::adamo_parse_heartbeat(payload.as_ptr(), payload.len(), &mut out)
};
if rc != 0 {
return None;
}
Some(Self {
regime: Regime::from_raw(out.regime),
jitter_hint_ms: out.jitter_hint_ms,
garch_sigma_ms: out.garch_sigma_ms,
target_bitrate_kbps: out.target_bitrate_kbps,
loss_rate: out.loss_rate,
queuing_delay_ms: out.queuing_delay_ms,
timestamp_ms: out.timestamp_ms,
})
}
}
fn topic_via<F>(robot: &str, raw: F) -> Result<String>
where
F: Fn(*const std::os::raw::c_char, *mut std::os::raw::c_char, usize) -> isize,
{
let robot_c = CString::new(robot)?;
let needed = raw(robot_c.as_ptr(), std::ptr::null_mut(), 0);
if needed < 0 {
return Err(last_ffi_error());
}
let mut buf = vec![0u8; needed as usize + 1];
let written = raw(
robot_c.as_ptr(),
buf.as_mut_ptr() as *mut std::os::raw::c_char,
buf.len(),
);
if written < 0 {
return Err(last_ffi_error());
}
buf.truncate(written as usize);
String::from_utf8(buf).map_err(|_| Error::InvalidUtf8)
}
pub fn heartbeat_topic(robot: &str) -> Result<String> {
topic_via(robot, |r, o, c| unsafe { adamo_sys::adamo_heartbeat_topic(r, o, c) })
}
pub fn ping_topic(robot: &str) -> Result<String> {
topic_via(robot, |r, o, c| unsafe { adamo_sys::adamo_ping_topic(r, o, c) })
}
pub fn pong_topic(robot: &str) -> Result<String> {
topic_via(robot, |r, o, c| unsafe { adamo_sys::adamo_pong_topic(r, o, c) })
}
impl Session {
pub fn measure_rtt(&self, robot: &str, timeout: Duration) -> Result<Duration> {
let robot_c = CString::new(robot)?;
let mut out_us: u64 = 0;
let timeout_ms = timeout.as_millis().min(u64::MAX as u128) as u64;
let rc = unsafe {
adamo_sys::adamo_measure_rtt(
self.raw_ptr(),
robot_c.as_ptr(),
timeout_ms,
&mut out_us,
)
};
if rc == 0 {
Ok(Duration::from_micros(out_us))
} else {
Err(last_ffi_error())
}
}
pub fn watch_latency(&self, robot: &str) -> Result<Subscriber<'_>> {
self.subscribe(&heartbeat_topic(robot)?)
}
}