Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/emulebb-ed2k/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ description = "ED2K transfer runtime extracted for emulebb-rust"
workspace = true

[features]
# Compile-time gate for the ed2k_packet_v1 packet dump (the Cargo equivalent of
# Compile-time gate for exact protocol diagnostics (the Cargo equivalent of
# eMuleBB's EMULEBB_ENABLE_PACKET_DIAGNOSTICS #ifdef). Off by default so release
# builds compile the dump out entirely; enable for diagnostic/live-wire builds.
# When enabled, the dump is still runtime-gated by the EMULEBB_RUST_LOG_DIR env var.
# builds compile capture out entirely; enable for diagnostic/live-wire builds.
# When enabled, emission is still runtime-gated by the EMULEBB_RUST_LOG_DIR env var.
packet-diagnostics = [
"emulebb-diagnostics/enabled",
"emulebb-kad-dht/packet-diagnostics",
Expand Down
7 changes: 4 additions & 3 deletions crates/emulebb-ed2k/src/ed2k_server/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ use super::{ResolvedServerEntry, ServerSession};
/// protocol byte unless the server advertised packed payload support.
#[cfg(any(feature = "packet-diagnostics", test))]
const OP_EDONKEYPROT_MARKER: u8 = 0xE3;
/// One server-flow packet in the converged `ed2k_packet_v1` shape (flow="server"),
/// so client<->server traces diff 1:1 against eMuleBB's server-packet emitter.
/// Carries server-session extras (role/trace_id) the diff harness ignores.
/// One server-flow diagnostic record, normalized into the converged
/// `diag_event_v2` envelope so client<->server traces diff 1:1 against eMuleBB's
/// server-packet emitter. Carries server-session extras (role/trace_id) the diff
/// harness ignores.
#[cfg(any(feature = "packet-diagnostics", test))]
#[derive(Debug)]
struct Ed2kServerDumpRecord<'a> {
Expand Down
105 changes: 83 additions & 22 deletions crates/emulebb-ed2k/src/ed2k_tcp/diag_event.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
//! `family:"ed2k_tcp"` `diag_event_v2` mapping.
//!
//! Re-shapes an [`Ed2kTcpDumpRecord`] (the legacy `ed2k_packet_v1` record) into
//! the converged `ed2k_tcp` `diag_event_v2` envelope and forwards
//! it to the shared writer. Kept in its own module because diagnostics-envelope
//! mapping is a separate responsibility from packet capture. Both writes are
//! behind the `packet-diagnostics` feature: the only caller
//! (`dump.rs::dump_ed2k_tcp_record`) is invoked solely from the feature-gated
//! send/recv/meta builders.
//! Maps an [`Ed2kTcpDiagnosticRecord`] into the converged `ed2k_tcp`
//! `diag_event_v2` envelope and forwards it to the shared writer. Kept in its
//! own module because diagnostics-envelope mapping is a separate responsibility
//! from packet capture.

#![cfg_attr(
not(feature = "packet-diagnostics"),
Expand All @@ -18,13 +15,19 @@

use serde_json::{Map, Value, json};

use super::dump::Ed2kTcpDumpRecord;
use super::dump::Ed2kTcpDiagnosticRecord;

/// Emit the converged `ed2k_tcp` `diag_event_v2` record from the same data that
/// built the legacy `ed2k_packet_v1` record. Optional `keys`/`body` fields are
/// omitted when absent rather than faked (`peerHash`/`fileHash` are not known at
/// this layer, so they are not emitted).
pub(super) fn emit_ed2k_tcp_diag_event(record: &Ed2kTcpDumpRecord<'_>) {
/// Emit one converged `ed2k_tcp` `diag_event_v2` record. Optional `keys`/`body`
/// fields are omitted when absent rather than faked (`peerHash`/`fileHash` are
/// not known at this layer, so they are not emitted).
pub(super) fn emit_ed2k_tcp_diag_event(record: &Ed2kTcpDiagnosticRecord<'_>) {
let (event, severity, keys, body) = ed2k_tcp_diag_event_parts(record);
crate::diag_event::emit("ed2k_tcp", event, severity, keys, body);
}

fn ed2k_tcp_diag_event_parts(
record: &Ed2kTcpDiagnosticRecord<'_>,
) -> (&'static str, &'static str, Value, Value) {
let mut keys = Map::new();
keys.insert("peer".to_string(), json!(record.remote_addr));
if let Some(protocol_marker) = record.protocol_marker {
Expand Down Expand Up @@ -82,6 +85,9 @@ pub(super) fn emit_ed2k_tcp_diag_event(record: &Ed2kTcpDumpRecord<'_>) {
body.insert("transportMode".to_string(), json!(record.transport_mode));
body.insert("flow".to_string(), json!(record.flow));
body.insert("phase".to_string(), json!(record.phase));
body.insert("traceKey".to_string(), json!(record.trace_key));
body.insert("stateId".to_string(), json!(record.state_id));
body.insert("stateLabel".to_string(), json!(record.state_label));
if let Some(note) = record.note.as_deref() {
body.insert("note".to_string(), json!(note));
}
Expand All @@ -94,16 +100,71 @@ pub(super) fn emit_ed2k_tcp_diag_event(record: &Ed2kTcpDumpRecord<'_>) {
} else {
"info"
};
let event = if record.schema == "ed2k_handshake_v1" {
"obfuscation_handshake"
} else {
"packet"
};
crate::diag_event::emit(
"ed2k_tcp",
event,
(
record.event,
severity,
Value::Object(keys),
Value::Object(body),
);
)
}

#[cfg(test)]
mod tests {
use super::*;

fn record<'a>(
event: &'static str,
direction: &'a str,
outcome: &'a str,
) -> Ed2kTcpDiagnosticRecord<'a> {
Ed2kTcpDiagnosticRecord {
event,
trace_key: "native_download:198.51.100.2:4662".to_string(),
state_id: "native_download.session".to_string(),
state_label: "session",
flow: "native_download",
phase: "session",
direction,
outcome,
remote_addr: "198.51.100.2:4662".to_string(),
transport_mode: "obfuscated",
obfuscated: Some(true),
protocol: None,
protocol_marker: None,
opcode: None,
opcode_name: None,
raw_len: None,
raw_hex: None,
clear_frame_len: None,
clear_frame_hex: None,
attempted_len: None,
written_len: None,
payload_len: None,
payload_hex: None,
payload_hex_truncated: None,
note: None,
}
}

#[test]
fn peer_tcp_state_event_preserves_trace_context() {
let record = record("state", "meta", "meta");
let (event, severity, keys, body) = ed2k_tcp_diag_event_parts(&record);

assert_eq!(event, "state");
assert_eq!(severity, "info");
assert_eq!(keys["peer"], "198.51.100.2:4662");
assert_eq!(body["traceKey"], "native_download:198.51.100.2:4662");
assert_eq!(body["stateId"], "native_download.session");
assert_eq!(body["stateLabel"], "session");
}

#[test]
fn peer_tcp_handshake_keeps_distinct_event_classification() {
let record = record("obfuscation_handshake", "send", "send_succeeded");
let (event, _, _, body) = ed2k_tcp_diag_event_parts(&record);

assert_eq!(event, "obfuscation_handshake");
assert_eq!(body["traceKey"], "native_download:198.51.100.2:4662");
}
}
Loading
Loading