Skip to main content

esp_idf_svc/
ping.rs

1//! Send ICMP echo requests (Ping)
2use core::{ffi, mem, ptr, time::Duration};
3
4use ::log::*;
5
6use crate::ipv4;
7use crate::private::common::*;
8use crate::private::waitable::*;
9use crate::sys::*;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct Configuration {
13    pub count: u32,
14    pub interval: Duration,
15    pub timeout: Duration,
16    pub data_size: u32,
17    pub tos: u8,
18}
19
20impl Default for Configuration {
21    fn default() -> Self {
22        Configuration {
23            count: 5,
24            interval: Duration::from_secs(1),
25            timeout: Duration::from_secs(1),
26            data_size: 56,
27            tos: 0,
28        }
29    }
30}
31
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct Info {
34    pub addr: ipv4::Ipv4Addr,
35    pub seqno: u32,
36    pub ttl: u8,
37    pub elapsed_time: Duration,
38    pub recv_len: u32,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub enum Reply {
43    Timeout,
44    Success(Info),
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, Default)]
48pub struct Summary {
49    pub transmitted: u32,
50    pub received: u32,
51    pub time: Duration,
52}
53
54#[derive(Debug, Default)]
55pub struct EspPing(u32);
56
57unsafe impl Send for EspPing {}
58unsafe impl Sync for EspPing {}
59
60impl EspPing {
61    pub fn new(interface_index: u32) -> Self {
62        Self(interface_index)
63    }
64
65    pub fn ping(&mut self, ip: ipv4::Ipv4Addr, conf: &Configuration) -> Result<Summary, EspError> {
66        info!("About to run a summary ping {ip} with configuration {conf:?}");
67
68        let mut tracker = Tracker::new(Some(nop_callback));
69
70        self.run_ping(ip, conf, &mut tracker)?;
71
72        Ok(tracker.summary)
73    }
74
75    pub fn ping_details<F: FnMut(&Summary, &Reply) + Send>(
76        &mut self,
77        ip: ipv4::Ipv4Addr,
78        conf: &Configuration,
79        reply_callback: F,
80    ) -> Result<Summary, EspError> {
81        info!("About to run a detailed ping {ip} with configuration {conf:?}");
82
83        let mut tracker = Tracker::new(Some(reply_callback));
84
85        self.run_ping(ip, conf, &mut tracker)?;
86
87        Ok(tracker.summary)
88    }
89
90    fn run_ping<F: FnMut(&Summary, &Reply) + Send>(
91        &self,
92        ip: ipv4::Ipv4Addr,
93        conf: &Configuration,
94        tracker: &mut Tracker<F>,
95    ) -> Result<(), EspError> {
96        #[cfg(not(esp_idf_lwip_ipv6))]
97        let ta = ip4_addr_t {
98            addr: u32::from_be_bytes(ip.octets()),
99        };
100        #[cfg(esp_idf_lwip_ipv6)]
101        let ta = ip_addr_t {
102            u_addr: ip_addr__bindgen_ty_1 {
103                ip4: Newtype::<ip4_addr_t>::from(ip).0,
104            },
105            type_: 0,
106        };
107        #[allow(clippy::needless_update)]
108        #[allow(clippy::useless_conversion)]
109        let config = esp_ping_config_t {
110            count: conf.count,
111            interval_ms: conf.interval.as_millis() as u32,
112            timeout_ms: conf.timeout.as_millis() as u32,
113            data_size: conf.data_size,
114            tos: conf.tos.into(),
115            target_addr: ta,
116            task_stack_size: 4096,
117            task_prio: 2,
118            interface: self.0,
119            ttl: 64,
120            ..Default::default()
121        };
122
123        let callbacks = esp_ping_callbacks_t {
124            on_ping_success: Some(EspPing::on_ping_success::<F>),
125            on_ping_timeout: Some(EspPing::on_ping_timeout::<F>),
126            on_ping_end: Some(EspPing::on_ping_end::<F>),
127            cb_args: tracker as *mut Tracker<F> as *mut ffi::c_void,
128        };
129
130        let mut handle: esp_ping_handle_t = ptr::null_mut();
131        let handle_ref = &mut handle;
132
133        esp!(unsafe {
134            esp_ping_new_session(&config, &callbacks, handle_ref as *mut *mut ffi::c_void)
135        })?;
136
137        if handle.is_null() {
138            return Err(EspError::from_infallible::<ESP_ERR_INVALID_ARG>());
139        }
140
141        info!("Ping session established, got handle {handle:?}");
142
143        {
144            let mut running = tracker.waitable.state.lock();
145            *running = true;
146        }
147
148        esp!(unsafe { esp_ping_start(handle) })?;
149        info!("Ping session started");
150
151        info!("Waiting for the ping session to complete");
152
153        tracker.waitable.wait_while(|running| Ok(*running))?;
154
155        esp!(unsafe { esp_ping_stop(handle) })?;
156        info!("Ping session stopped");
157
158        esp!(unsafe { esp_ping_delete_session(handle) })?;
159
160        info!("Ping session {:?} removed", handle);
161
162        Ok(())
163    }
164
165    unsafe extern "C" fn on_ping_success<F: FnMut(&Summary, &Reply) + Send>(
166        handle: esp_ping_handle_t,
167        args: *mut ffi::c_void,
168    ) {
169        info!("Ping success callback invoked");
170
171        let tracker_ptr: *mut Tracker<F> = args as _;
172        let tracker = tracker_ptr.as_mut().unwrap();
173
174        let mut seqno: ffi::c_ushort = 0;
175        esp_ping_get_profile(
176            handle,
177            esp_ping_profile_t_ESP_PING_PROF_SEQNO,
178            &mut seqno as *mut ffi::c_ushort as *mut ffi::c_void,
179            mem::size_of_val(&seqno) as u32,
180        );
181
182        let mut ttl: ffi::c_uchar = 0;
183        esp_ping_get_profile(
184            handle,
185            esp_ping_profile_t_ESP_PING_PROF_TTL,
186            &mut ttl as *mut ffi::c_uchar as *mut ffi::c_void,
187            mem::size_of_val(&ttl) as u32,
188        );
189
190        let mut target_addr_raw = [0_u8; mem::size_of::<ip_addr_t>()];
191        let target_addr: &mut ip_addr_t = mem::transmute(&mut target_addr_raw);
192
193        esp_ping_get_profile(
194            handle,
195            esp_ping_profile_t_ESP_PING_PROF_IPADDR,
196            target_addr as *mut ip_addr_t as *mut ffi::c_void,
197            mem::size_of::<ip_addr_t>() as _,
198        );
199
200        let mut elapsed_time: ffi::c_uint = 0;
201        esp_ping_get_profile(
202            handle,
203            esp_ping_profile_t_ESP_PING_PROF_TIMEGAP,
204            &mut elapsed_time as *mut ffi::c_uint as *mut ffi::c_void,
205            mem::size_of_val(&elapsed_time) as u32,
206        );
207
208        let mut recv_len: ffi::c_uint = 0;
209        esp_ping_get_profile(
210            handle,
211            esp_ping_profile_t_ESP_PING_PROF_SIZE,
212            &mut recv_len as *mut ffi::c_uint as *mut ffi::c_void,
213            mem::size_of_val(&recv_len) as u32,
214        );
215
216        #[cfg(not(esp_idf_lwip_ipv6))]
217        let addr = ipv4::Ipv4Addr::from(target_addr.addr);
218        #[cfg(esp_idf_lwip_ipv6)]
219        let addr = ipv4::Ipv4Addr::from(target_addr.u_addr.ip4.addr);
220
221        info!("From {addr} icmp_seq={seqno} ttl={ttl} time={elapsed_time}ms bytes={recv_len}");
222
223        if let Some(reply_callback) = tracker.reply_callback.as_mut() {
224            Self::update_summary(handle, &mut tracker.summary);
225
226            reply_callback(
227                &tracker.summary,
228                &Reply::Success(Info {
229                    addr,
230                    seqno: seqno as u32,
231                    ttl,
232                    recv_len,
233                    elapsed_time: Duration::from_millis(elapsed_time as u64),
234                }),
235            );
236        }
237    }
238
239    unsafe extern "C" fn on_ping_timeout<F: FnMut(&Summary, &Reply) + Send>(
240        handle: esp_ping_handle_t,
241        args: *mut ffi::c_void,
242    ) {
243        info!("Ping timeout callback invoked");
244
245        let tracker_ptr: *mut Tracker<F> = args as _;
246        let tracker = tracker_ptr.as_mut().unwrap();
247
248        let mut seqno: ffi::c_ushort = 0;
249        esp_ping_get_profile(
250            handle,
251            esp_ping_profile_t_ESP_PING_PROF_SEQNO,
252            &mut seqno as *mut ffi::c_ushort as *mut ffi::c_void,
253            mem::size_of_val(&seqno) as u32,
254        );
255
256        let mut target_addr_raw = [0_u8; mem::size_of::<ip_addr_t>()];
257        let target_addr: &mut ip_addr_t = mem::transmute(&mut target_addr_raw);
258
259        esp_ping_get_profile(
260            handle,
261            esp_ping_profile_t_ESP_PING_PROF_IPADDR,
262            target_addr as *mut ip_addr_t as *mut ffi::c_void,
263            mem::size_of::<ip_addr_t>() as _,
264        );
265
266        info!("From {} icmp_seq={} timeout", "???", seqno);
267
268        if let Some(reply_callback) = tracker.reply_callback.as_mut() {
269            Self::update_summary(handle, &mut tracker.summary);
270
271            reply_callback(&tracker.summary, &Reply::Timeout);
272        }
273    }
274
275    #[allow(clippy::mutex_atomic)]
276    unsafe extern "C" fn on_ping_end<F: FnMut(&Summary, &Reply) + Send>(
277        handle: esp_ping_handle_t,
278        args: *mut ffi::c_void,
279    ) {
280        info!("Ping end callback invoked");
281
282        let tracker_ptr: *mut Tracker<F> = args as _;
283        let tracker = tracker_ptr.as_mut().unwrap();
284
285        Self::update_summary(handle, &mut tracker.summary);
286
287        info!(
288            "{} packets transmitted, {} received, time {}ms",
289            tracker.summary.transmitted,
290            tracker.summary.received,
291            tracker.summary.time.as_millis()
292        );
293
294        let mut running = tracker.waitable.state.lock();
295        *running = false;
296
297        tracker.waitable.cvar.notify_all();
298    }
299
300    unsafe fn update_summary(handle: esp_ping_handle_t, summary: &mut Summary) {
301        let mut transmitted: ffi::c_uint = 0;
302        esp_ping_get_profile(
303            handle,
304            esp_ping_profile_t_ESP_PING_PROF_REQUEST,
305            &mut transmitted as *mut ffi::c_uint as *mut ffi::c_void,
306            mem::size_of_val(&transmitted) as u32,
307        );
308
309        let mut received: ffi::c_uint = 0;
310        esp_ping_get_profile(
311            handle,
312            esp_ping_profile_t_ESP_PING_PROF_REPLY,
313            &mut received as *mut ffi::c_uint as *mut ffi::c_void,
314            mem::size_of_val(&received) as u32,
315        );
316
317        let mut total_time: ffi::c_uint = 0;
318        esp_ping_get_profile(
319            handle,
320            esp_ping_profile_t_ESP_PING_PROF_DURATION,
321            &mut total_time as *mut ffi::c_uint as *mut ffi::c_void,
322            mem::size_of_val(&total_time) as u32,
323        );
324
325        summary.transmitted = transmitted;
326        summary.received = received;
327        summary.time = Duration::from_millis(total_time as u64);
328    }
329}
330
331struct Tracker<F: FnMut(&Summary, &Reply) + Send> {
332    summary: Summary,
333    waitable: Waitable<bool>,
334    reply_callback: Option<F>,
335}
336
337impl<F: FnMut(&Summary, &Reply) + Send> Tracker<F> {
338    #[allow(clippy::mutex_atomic)]
339    pub fn new(reply_callback: Option<F>) -> Self {
340        Self {
341            summary: Default::default(),
342            waitable: Waitable::new(false),
343            reply_callback,
344        }
345    }
346}
347
348fn nop_callback(_summary: &Summary, _reply: &Reply) {}