Skip to content

Commit 2d17ddc

Browse files
authored
Added packet capture feature (#6)
1 parent 0f68a11 commit 2d17ddc

File tree

4 files changed

+110
-4
lines changed

4 files changed

+110
-4
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ tokio = { version = "1.6.1", features = ["rt", "rt-multi-thread","macros"] }
3030
serde_support = ["serde"]
3131
shared_radio = ["flume"]
3232
async = ["flume/async"]
33+
packet_capture = []
3334

3435
[package.metadata.docs.rs]
3536
all-features = true

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ can be used to share a radio dongle between threads.
3232
The feature `async` enables async functions in the `SharedRadio` struct as well
3333
as to create the `Crazyradio` struct.
3434

35+
The feature `packet_capture` enables packet capturing to Wireshark.
36+
3537
## Serde support
3638

3739
To enable Serde support for serializing and deserializing ```Channels```, enable the feature "serde_support".

src/capture.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//! Wireshark packet capture support for Crazyradio
2+
//!
3+
//! This module provides a callback mechanism for capturing packets
4+
//! sent and received via the Crazyradio.
5+
6+
use std::sync::OnceLock;
7+
8+
/// Direction: transmit (to device)
9+
pub const DIRECTION_TX: u8 = 0;
10+
/// Direction: receive (from device)
11+
pub const DIRECTION_RX: u8 = 1;
12+
13+
/// Captured packet event data
14+
#[derive(Debug, Clone)]
15+
pub struct CaptureEvent<'a> {
16+
/// Packet direction: [`DIRECTION_TX`] or [`DIRECTION_RX`]
17+
pub direction: u8,
18+
/// Radio channel (0-125)
19+
pub channel: u8,
20+
/// 5-byte radio address
21+
pub address: &'a [u8; 5],
22+
/// Serial number of the radio device
23+
pub serial: &'a str,
24+
/// Packet payload data
25+
pub data: &'a [u8],
26+
}
27+
28+
/// Packet capture callback type
29+
pub type CaptureCallback = Box<dyn Fn(CaptureEvent<'_>) + Send + Sync>;
30+
31+
/// Global capture callback (set once at initialization)
32+
static CAPTURE_CALLBACK: OnceLock<CaptureCallback> = OnceLock::new();
33+
34+
/// Set the packet capture callback
35+
///
36+
/// This should be called once at initialization to enable packet capture.
37+
/// Subsequent calls will be silently ignored. Note that since this is in the
38+
/// direct path of shuffling packages to/from the Crazyradio, the callback
39+
/// should be efficient and avoid blocking operations.
40+
pub fn set_callback(callback: CaptureCallback) {
41+
let _ = CAPTURE_CALLBACK.set(callback);
42+
}
43+
44+
/// Send a packet to the capture callback (if set)
45+
pub(crate) fn capture_packet(direction: u8, channel: u8, address: &[u8; 5], serial: &str, data: &[u8]) {
46+
if let Some(callback) = CAPTURE_CALLBACK.get() {
47+
callback(CaptureEvent {
48+
direction,
49+
channel,
50+
address,
51+
serial,
52+
data,
53+
});
54+
}
55+
}

src/lib.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//! - **shared_radio** enables [SharedCrazyradio] object that allows to share a radio between threads
1010
//! - **async** enables async function to create a [Crazyradio] object and use the [SharedCrazyradio]
1111
//! - **serde** emables [serde](https://crates.io/crates/serde) serialization/deserialization of the [Channel] struct
12+
//! - **packet_capture** enables functionality to capture packets by registering a callback which is called for each in/out packet
1213
1314
#![deny(missing_docs)]
1415

@@ -17,6 +18,9 @@ mod shared_radio;
1718
#[cfg(feature = "shared_radio")]
1819
pub use crate::shared_radio::{SharedCrazyradio, WeakSharedCrazyradio};
1920

21+
#[cfg(feature = "packet_capture")]
22+
pub mod capture;
23+
2024
use core::time::Duration;
2125
#[cfg(feature = "serde_support")]
2226
use serde::{Deserialize, Serialize};
@@ -138,6 +142,10 @@ pub struct Crazyradio {
138142
address: [u8; 5],
139143
datarate: Datarate,
140144
ack_enable: bool,
145+
146+
/// Radio serial number (for capture identification)
147+
#[cfg(feature = "packet_capture")]
148+
serial: String,
141149
}
142150

143151
impl Crazyradio {
@@ -190,6 +198,9 @@ impl Crazyradio {
190198
return Err(Error::DongleVersionNotSupported);
191199
}
192200

201+
#[cfg(feature = "packet_capture")]
202+
let serial = get_serial(&device_desciptor, &device_handle).unwrap_or_default();
203+
193204
let mut cr = Crazyradio {
194205
device_desciptor,
195206
device_handle,
@@ -202,6 +213,9 @@ impl Crazyradio {
202213
datarate: Datarate::Dr2M,
203214

204215
ack_enable: true,
216+
217+
#[cfg(feature = "packet_capture")]
218+
serial,
205219
};
206220

207221
cr.reset()?;
@@ -517,8 +531,18 @@ impl Crazyradio {
517531
/// be truncated. The length of the ack payload is returned
518532
/// in Ack::length.
519533
pub fn send_packet(&mut self, data: &[u8], ack_data: &mut [u8]) -> Result<Ack> {
520-
if self.inline_mode {
521-
self.send_inline(data, Some(ack_data))
534+
// Capture TX packet
535+
#[cfg(feature = "packet_capture")]
536+
capture::capture_packet(
537+
capture::DIRECTION_TX,
538+
self.channel.into(),
539+
&self.address,
540+
&self.serial,
541+
data,
542+
);
543+
544+
let ack = if self.inline_mode {
545+
self.send_inline(data, Some(ack_data))?
522546
} else {
523547
self.device_handle
524548
.write_bulk(0x01, data, Duration::from_secs(1))?;
@@ -536,13 +560,27 @@ impl Crazyradio {
536560
.copy_from_slice(&received_data[1..33]);
537561
}
538562

539-
Ok(Ack {
563+
Ack {
540564
received: received_data[0] & 0x01 != 0,
541565
power_detector: received_data[0] & 0x02 != 0,
542566
retry: ((received_data[0] & 0xf0) >> 4) as usize,
543567
length: received - 1,
544-
})
568+
}
569+
};
570+
571+
// Capture RX packet (ACK payload)
572+
#[cfg(feature = "packet_capture")]
573+
if ack.received && ack.length > 0 {
574+
capture::capture_packet(
575+
capture::DIRECTION_RX,
576+
self.channel.into(),
577+
&self.address,
578+
&self.serial,
579+
&ack_data[..ack.length.min(ack_data.len())],
580+
);
545581
}
582+
583+
Ok(ack)
546584
}
547585

548586
/// Send a data packet without caring for Ack (for broadcast communication).
@@ -551,6 +589,16 @@ impl Crazyradio {
551589
///
552590
/// * `data`: Up to 32 bytes of data to be send.
553591
pub fn send_packet_no_ack(&mut self, data: &[u8]) -> Result<()> {
592+
// Capture TX packet
593+
#[cfg(feature = "packet_capture")]
594+
capture::capture_packet(
595+
capture::DIRECTION_TX,
596+
self.channel.into(),
597+
&self.address,
598+
&self.serial,
599+
data,
600+
);
601+
554602
if self.inline_mode {
555603
self.send_inline(data, None)?;
556604
} else {

0 commit comments

Comments
 (0)