1use 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) {}