use core::{ffi, mem, ptr, time::Duration};
use ::log::*;
use crate::ipv4;
use crate::private::common::*;
use crate::private::waitable::*;
use crate::sys::*;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Configuration {
pub count: u32,
pub interval: Duration,
pub timeout: Duration,
pub data_size: u32,
pub tos: u8,
}
impl Default for Configuration {
fn default() -> Self {
Configuration {
count: 5,
interval: Duration::from_secs(1),
timeout: Duration::from_secs(1),
data_size: 56,
tos: 0,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Info {
pub addr: ipv4::Ipv4Addr,
pub seqno: u32,
pub ttl: u8,
pub elapsed_time: Duration,
pub recv_len: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Reply {
Timeout,
Success(Info),
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Summary {
pub transmitted: u32,
pub received: u32,
pub time: Duration,
}
#[derive(Debug, Default)]
pub struct EspPing(u32);
unsafe impl Send for EspPing {}
unsafe impl Sync for EspPing {}
impl EspPing {
pub fn new(interface_index: u32) -> Self {
Self(interface_index)
}
pub fn ping(&mut self, ip: ipv4::Ipv4Addr, conf: &Configuration) -> Result<Summary, EspError> {
info!(
"About to run a summary ping {} with configuration {:?}",
ip, conf
);
let mut tracker = Tracker::new(Some(nop_callback));
self.run_ping(ip, conf, &mut tracker)?;
Ok(tracker.summary)
}
pub fn ping_details<F: FnMut(&Summary, &Reply) + Send>(
&mut self,
ip: ipv4::Ipv4Addr,
conf: &Configuration,
reply_callback: F,
) -> Result<Summary, EspError> {
info!(
"About to run a detailed ping {} with configuration {:?}",
ip, conf
);
let mut tracker = Tracker::new(Some(reply_callback));
self.run_ping(ip, conf, &mut tracker)?;
Ok(tracker.summary)
}
fn run_ping<F: FnMut(&Summary, &Reply) + Send>(
&self,
ip: ipv4::Ipv4Addr,
conf: &Configuration,
tracker: &mut Tracker<F>,
) -> Result<(), EspError> {
#[cfg(not(esp_idf_lwip_ipv6))]
let ta = ip4_addr_t {
addr: u32::from_be_bytes(ip.octets()),
};
#[cfg(esp_idf_lwip_ipv6)]
let ta = ip_addr_t {
u_addr: ip_addr__bindgen_ty_1 {
ip4: Newtype::<ip4_addr_t>::from(ip).0,
},
type_: 0,
};
#[allow(clippy::needless_update)]
#[allow(clippy::useless_conversion)]
let config = esp_ping_config_t {
count: conf.count,
interval_ms: conf.interval.as_millis() as u32,
timeout_ms: conf.timeout.as_millis() as u32,
data_size: conf.data_size,
tos: conf.tos.into(),
target_addr: ta,
task_stack_size: 4096,
task_prio: 2,
interface: self.0,
ttl: 64,
..Default::default()
};
let callbacks = esp_ping_callbacks_t {
on_ping_success: Some(EspPing::on_ping_success::<F>),
on_ping_timeout: Some(EspPing::on_ping_timeout::<F>),
on_ping_end: Some(EspPing::on_ping_end::<F>),
cb_args: tracker as *mut Tracker<F> as *mut ffi::c_void,
};
let mut handle: esp_ping_handle_t = ptr::null_mut();
let handle_ref = &mut handle;
esp!(unsafe {
esp_ping_new_session(&config, &callbacks, handle_ref as *mut *mut ffi::c_void)
})?;
if handle.is_null() {
return Err(EspError::from_infallible::<ESP_ERR_INVALID_ARG>());
}
info!("Ping session established, got handle {:?}", handle);
{
let mut running = tracker.waitable.state.lock();
*running = true;
}
esp!(unsafe { esp_ping_start(handle) })?;
info!("Ping session started");
info!("Waiting for the ping session to complete");
tracker.waitable.wait_while(|running| Ok(*running))?;
esp!(unsafe { esp_ping_stop(handle) })?;
info!("Ping session stopped");
esp!(unsafe { esp_ping_delete_session(handle) })?;
info!("Ping session {:?} removed", &handle);
Ok(())
}
unsafe extern "C" fn on_ping_success<F: FnMut(&Summary, &Reply) + Send>(
handle: esp_ping_handle_t,
args: *mut ffi::c_void,
) {
info!("Ping success callback invoked");
let tracker_ptr: *mut Tracker<F> = args as _;
let tracker = tracker_ptr.as_mut().unwrap();
let mut seqno: ffi::c_ushort = 0;
esp_ping_get_profile(
handle,
esp_ping_profile_t_ESP_PING_PROF_SEQNO,
&mut seqno as *mut ffi::c_ushort as *mut ffi::c_void,
mem::size_of_val(&seqno) as u32,
);
let mut ttl: ffi::c_uchar = 0;
esp_ping_get_profile(
handle,
esp_ping_profile_t_ESP_PING_PROF_TTL,
&mut ttl as *mut ffi::c_uchar as *mut ffi::c_void,
mem::size_of_val(&ttl) as u32,
);
let mut target_addr_raw = [0_u8; mem::size_of::<ip_addr_t>()];
let target_addr: &mut ip_addr_t = mem::transmute(&mut target_addr_raw);
esp_ping_get_profile(
handle,
esp_ping_profile_t_ESP_PING_PROF_IPADDR,
target_addr as *mut ip_addr_t as *mut ffi::c_void,
mem::size_of::<ip_addr_t>() as _,
);
let mut elapsed_time: ffi::c_uint = 0;
esp_ping_get_profile(
handle,
esp_ping_profile_t_ESP_PING_PROF_TIMEGAP,
&mut elapsed_time as *mut ffi::c_uint as *mut ffi::c_void,
mem::size_of_val(&elapsed_time) as u32,
);
let mut recv_len: ffi::c_uint = 0;
esp_ping_get_profile(
handle,
esp_ping_profile_t_ESP_PING_PROF_SIZE,
&mut recv_len as *mut ffi::c_uint as *mut ffi::c_void,
mem::size_of_val(&recv_len) as u32,
);
#[cfg(not(esp_idf_lwip_ipv6))]
let addr = ipv4::Ipv4Addr::from(target_addr.addr);
#[cfg(esp_idf_lwip_ipv6)]
let addr = ipv4::Ipv4Addr::from(target_addr.u_addr.ip4.addr);
info!(
"From {} icmp_seq={} ttl={} time={}ms bytes={}",
addr, seqno, ttl, elapsed_time, recv_len
);
if let Some(reply_callback) = tracker.reply_callback.as_mut() {
Self::update_summary(handle, &mut tracker.summary);
reply_callback(
&tracker.summary,
&Reply::Success(Info {
addr,
seqno: seqno as u32,
ttl,
recv_len,
elapsed_time: Duration::from_millis(elapsed_time as u64),
}),
);
}
}
unsafe extern "C" fn on_ping_timeout<F: FnMut(&Summary, &Reply) + Send>(
handle: esp_ping_handle_t,
args: *mut ffi::c_void,
) {
info!("Ping timeout callback invoked");
let tracker_ptr: *mut Tracker<F> = args as _;
let tracker = tracker_ptr.as_mut().unwrap();
let mut seqno: ffi::c_ushort = 0;
esp_ping_get_profile(
handle,
esp_ping_profile_t_ESP_PING_PROF_SEQNO,
&mut seqno as *mut ffi::c_ushort as *mut ffi::c_void,
mem::size_of_val(&seqno) as u32,
);
let mut target_addr_raw = [0_u8; mem::size_of::<ip_addr_t>()];
let target_addr: &mut ip_addr_t = mem::transmute(&mut target_addr_raw);
esp_ping_get_profile(
handle,
esp_ping_profile_t_ESP_PING_PROF_IPADDR,
target_addr as *mut ip_addr_t as *mut ffi::c_void,
mem::size_of::<ip_addr_t>() as _,
);
info!("From {} icmp_seq={} timeout", "???", seqno);
if let Some(reply_callback) = tracker.reply_callback.as_mut() {
Self::update_summary(handle, &mut tracker.summary);
reply_callback(&tracker.summary, &Reply::Timeout);
}
}
#[allow(clippy::mutex_atomic)]
unsafe extern "C" fn on_ping_end<F: FnMut(&Summary, &Reply) + Send>(
handle: esp_ping_handle_t,
args: *mut ffi::c_void,
) {
info!("Ping end callback invoked");
let tracker_ptr: *mut Tracker<F> = args as _;
let tracker = tracker_ptr.as_mut().unwrap();
Self::update_summary(handle, &mut tracker.summary);
info!(
"{} packets transmitted, {} received, time {}ms",
tracker.summary.transmitted,
tracker.summary.received,
tracker.summary.time.as_millis()
);
let mut running = tracker.waitable.state.lock();
*running = false;
tracker.waitable.cvar.notify_all();
}
unsafe fn update_summary(handle: esp_ping_handle_t, summary: &mut Summary) {
let mut transmitted: ffi::c_uint = 0;
esp_ping_get_profile(
handle,
esp_ping_profile_t_ESP_PING_PROF_REQUEST,
&mut transmitted as *mut ffi::c_uint as *mut ffi::c_void,
mem::size_of_val(&transmitted) as u32,
);
let mut received: ffi::c_uint = 0;
esp_ping_get_profile(
handle,
esp_ping_profile_t_ESP_PING_PROF_REPLY,
&mut received as *mut ffi::c_uint as *mut ffi::c_void,
mem::size_of_val(&received) as u32,
);
let mut total_time: ffi::c_uint = 0;
esp_ping_get_profile(
handle,
esp_ping_profile_t_ESP_PING_PROF_DURATION,
&mut total_time as *mut ffi::c_uint as *mut ffi::c_void,
mem::size_of_val(&total_time) as u32,
);
summary.transmitted = transmitted;
summary.received = received;
summary.time = Duration::from_millis(total_time as u64);
}
}
struct Tracker<F: FnMut(&Summary, &Reply) + Send> {
summary: Summary,
waitable: Waitable<bool>,
reply_callback: Option<F>,
}
impl<F: FnMut(&Summary, &Reply) + Send> Tracker<F> {
#[allow(clippy::mutex_atomic)]
pub fn new(reply_callback: Option<F>) -> Self {
Self {
summary: Default::default(),
waitable: Waitable::new(false),
reply_callback,
}
}
}
fn nop_callback(_summary: &Summary, _reply: &Reply) {}