Implement native PacketDump capture (#87)
Some checks failed
Native Rust workspace compile / compile (push) Has been cancelled

This commit is contained in:
2026-08-11 08:06:25 +00:00
parent f0fd975d0f
commit f0420f6c8b
8 changed files with 1298 additions and 13 deletions

View File

@@ -293,6 +293,7 @@ impl<T: 'static> EventRegistry<T> {
pub struct PacketReceivedEventArgs {
packet: Packet,
simulator: Simulator,
raw_data: Option<Vec<u8>>,
}
#[derive(Clone)]
@@ -304,7 +305,19 @@ pub(crate) struct RawPacketReceivedEventArgs {
impl PacketReceivedEventArgs {
pub fn new(packet: Packet, simulator: Simulator) -> Result<Self, Error> {
Ok(Self { packet, simulator })
Self::new_with_raw_data(packet, simulator, None)
}
pub fn new_with_raw_data(
packet: Packet,
simulator: Simulator,
raw_data: Option<Vec<u8>>,
) -> Result<Self, Error> {
Ok(Self {
packet,
simulator,
raw_data,
})
}
#[must_use]
@@ -316,6 +329,13 @@ impl PacketReceivedEventArgs {
pub fn simulator(&self) -> Simulator {
self.simulator.clone()
}
/// Returns the original datagram when this event came from the native UDP
/// receive path. Synthetic decoded events intentionally return `None`.
#[must_use]
pub fn raw_data(&self) -> Option<Vec<u8>> {
self.raw_data.clone()
}
}
#[derive(Clone)]
@@ -552,6 +572,7 @@ struct AsyncPacketBatch {
specific_handlers: Vec<EventHandler<PacketReceivedEventArgs>>,
packet: Packet,
simulator: Simulator,
raw_data: Option<Vec<u8>>,
}
enum PacketWorkerCommand {
@@ -674,7 +695,19 @@ impl PacketEventDictionary {
packet: Packet,
simulator: Simulator,
) -> Result<(), Error> {
self.raise_event(packet_type, packet, simulator)
self.raise_event(packet_type, packet, simulator, None)
}
/// Dispatches a decoded packet together with its original wire datagram.
/// This is primarily useful to deterministic packet-capture fixtures.
pub fn invoke_raise_event_with_raw_data(
&self,
packet_type: PacketType,
packet: Packet,
simulator: Simulator,
raw_data: Vec<u8>,
) -> Result<(), Error> {
self.raise_event(packet_type, packet, simulator, Some(raw_data))
}
fn raise_event(
@@ -682,6 +715,7 @@ impl PacketEventDictionary {
packet_type: PacketType,
packet: Packet,
simulator: Simulator,
raw_data: Option<Vec<u8>>,
) -> Result<(), Error> {
let (defaults, specific) = {
let table = mutex(&self.state.callbacks);
@@ -692,7 +726,11 @@ impl PacketEventDictionary {
};
let default_async = defaults.is_async;
let specific_async = specific.is_async;
let args = PacketReceivedEventArgs::new(packet.clone(), simulator.clone())?;
let args = PacketReceivedEventArgs::new_with_raw_data(
packet.clone(),
simulator.clone(),
raw_data.clone(),
)?;
if !default_async {
for handler in &defaults.handlers {
@@ -731,6 +769,7 @@ impl PacketEventDictionary {
specific_handlers,
packet,
simulator,
raw_data,
}))
.map_err(|_| Error::InvalidOperation)
}
@@ -740,7 +779,11 @@ fn packet_event_worker(receiver: Receiver<PacketWorkerCommand>, state: Weak<Pack
while state.strong_count() != 0 {
match receiver.recv_timeout(Duration::from_millis(100)) {
Ok(PacketWorkerCommand::Dispatch(batch)) => {
let Ok(args) = PacketReceivedEventArgs::new(batch.packet, batch.simulator) else {
let Ok(args) = PacketReceivedEventArgs::new_with_raw_data(
batch.packet,
batch.simulator,
batch.raw_data,
) else {
continue;
};
for handler in batch
@@ -2090,7 +2133,7 @@ impl NetworkManagerInner {
continue;
};
inner.process_internal_packet(&packet, &simulator, raw_data.as_deref());
if let Some(data) = raw_data {
if let Some(data) = raw_data.as_ref() {
inner.events.raw_packet_received.emit_with(|| {
RawPacketReceivedEventArgs {
packet_type: packet.type_,
@@ -2099,10 +2142,12 @@ impl NetworkManagerInner {
}
});
}
let _ =
inner
.packet_events
.raise_event(packet.type_, packet, simulator);
let _ = inner.packet_events.raise_event(
packet.type_,
packet,
simulator,
raw_data,
);
}
Err(RecvTimeoutError::Timeout) => {}
Err(RecvTimeoutError::Disconnected) => break,

View File

@@ -183,6 +183,42 @@ fn packet_callbacks_preserve_filtering_order_async_policy_and_reentrancy() {
);
}
#[test]
fn decoded_packet_callbacks_preserve_optional_raw_datagrams() {
let client = GridClient::new().expect("client");
let sim = simulator(&client);
let events = PacketEventDictionary::new(client).expect("packet events");
let observed = Arc::new(Mutex::new(None));
let captured = Arc::clone(&observed);
events
.register_event(
PacketType::Default,
Arc::new(move |event| *captured.lock().unwrap() = event.raw_data()),
false,
)
.unwrap();
let raw = vec![0x40, 0, 0, 0, 0, 0, 1, 7, 0, 0, 0, 0];
events
.invoke_raise_event_with_raw_data(
PacketType::StartPingCheck,
base_packet(PacketType::StartPingCheck),
sim.clone(),
raw.clone(),
)
.unwrap();
assert_eq!(*observed.lock().unwrap(), Some(raw));
*observed.lock().unwrap() = Some(vec![1]);
events
.invoke_raise_event(
PacketType::StartPingCheck,
base_packet(PacketType::StartPingCheck),
sim,
)
.unwrap();
assert_eq!(*observed.lock().unwrap(), None);
}
#[test]
fn synchronous_specific_dispatch_suppresses_default_async_chain_like_reference() {
let mixed_events = PacketEventDictionary::new(GridClient::new().unwrap()).unwrap();