Skip to content

Workflow status is tracked in GitHub: https://github.com/emulebb/emulebb-rust/issues/1. This local document is retained as an engineering spec/evidence record.

RUST-FEAT-001 - eD2K — Implement client UDP source reask and queue-slot persistence

Summary

The eD2K client↔client UDP reask family (OP_REASKFILEPING 0x90, OP_REASKACK 0x91, OP_FILENOTFOUND 0x92, OP_QUEUEFULL 0x93, and the LowID variants OP_REASKCALLBACKUDP 0x94 / OP_DIRECTCALLBACKREQ 0x95) is implemented and enabled by default in emulebb-rust. This item remains open until the live interoperability proof is refreshed after the latest parity fixes. Full design: docs/design/udp-source-reask.md.

Current State

Update 2026-06-14 — code-complete off by default. The pure + state + loop layers were implemented behind enable_udp_reask (off): codec/policy/registry/ state, the shared-Kad-UDP-port transport, uploader reciprocity, downloader detach, and TCP-fallback re-engage (~64 tests, clippy-clean). The temporary omission was recorded in policy/rust-client-omissions.toml while the feature was staged.

Update 2026-06-17 — enabled by default; metadata hardening in progress. Kad source conversion now preserves non-zero FT_SOURCEUPORT for HighID as well as firewalled-buddy sources, so UDP-reask reachability metadata is not lost before a TCP hello can refresh it. Remaining work is live interoperability validation and closing any defects found during that validation.

Parity closure note 2026-06-19. This item is no longer a broad implementation blocker for core MFC parity. The remaining blocker is evidence: regenerate the overnight/local parity campaign after the latest parity fixes and run the targeted Rust/eMuleBB UDP reask proof owned by RUST-CI-002.

  • crates/emulebb-ed2k/src/ed2k_client_udp/ owns the client UDP reask codec, dispatch, obfuscation, source set, reciprocity, buddy relay, and runtime.
  • crates/emulebb-ed2k/src/config.rs defaults enable_udp_reask to true.
  • crates/emulebb-core/src/lib.rs spawns the reask loop while connected and wires detach/re-engage events back into transfer scheduling.
  • policy/rust-client-omissions.toml no longer records UDP reask as an active omission.

Why This Matters

UDP reask is the backbone of eMule's queue economy. On a busy swarm, uploaders reclaim idle TCP sockets, so a held-TCP queued model loses the slot as soon as the connection drops. The remaining risk is not the implementation itself; it is whether the refreshed evidence proves Rust keeps MFC-compatible queue behavior across Rust↔Rust, Rust↔eMuleBB, Rust↔aMule, and public smoke scenarios.

Representative Sites

  • crates/emulebb-ed2k/src/ed2k_client_udp/ — UDP source reask transport, codec, runtime, source set, outbound framing, reciprocity, and buddy relay.
  • crates/emulebb-ed2k/src/ed2k_tcp/download/session/reask_detach.rs — queued source detach eligibility.
  • crates/emulebb-core/src/lib.rs — reask loop spawn, detach/re-engage event wiring, and direct-download scheduling integration.
  • crates/emulebb-ed2k/src/ed2k_tcp/listener/session/upload_queue.rs and crates/emulebb-ed2k/src/ed2k_transfer/reask_reciprocity.rs — upload queue state used to answer inbound reasks.
  • Reference (stock): emulebb-main/srchybrid/DownloadClient.cpp (UDPReaskForDownload, UDPReaskACK, UDPReaskFNF), ClientUDPSocket.cpp (reask receive/answer), opcodes.h (FILEREASKTIME 29 min, MIN_REQUESTTIME 10 min, UDPMAXQUEUETIME 20 s).

Intended Shape

Per the design doc: add one shared client UDP transport module (ed2k_client_udp/) that owns the recv loop, de-obfuscates, dispatches the OP_EMULEPROT reask opcodes, and fans replies back to the owning transfer/source by (peer_ip, peer_udp_port) correlation under a pending anti-spoof gate. Each download transfer drives its own reask ticker (no global scheduler — honours the independent per-transfer-task download model); UDP-eligible queued sources release their TCP socket and keep position by datagram, with TCP reconnect-reask as the bounded fallback. Implement both downloader and uploader (reciprocity) sides with exact stock framing (udp_version-gated partstatus/complete-count tails) and stock obfuscation choice. Phase the LowID buddy reask (OP_REASKCALLBACKUDP) and OP_DIRECTCALLBACKREQ separately.

Scope Constraints

  • Out of RC2 scope — capture and stage; do not build under the freeze.
  • IPv4-only; reuse existing obfuscation primitives; introduce focused modules for independently nameable responsibilities; no prerequisite big-refactor of legacy .rs files.
  • Phase 1 = HighID UDP reask + TCP fallback + reciprocity. Phase 2 = LowID buddy reask + direct callback.
  • Non-goal: A4AF cross-file source dedup (separate parked design); the two compose but neither requires the other.

Acceptance Criteria

  • [x] Phase 0: omission reconciled: UDP reask is no longer recorded as an active omission and advertised udp_version matches implemented support.
  • [x] Client UDP transport module bound to the local eD2K UDP port, with obfuscated + plaintext recv/send and opcode dispatch keyed on socket + protocol byte.
  • [x] Downloader sends OP_REASKFILEPING with stock-exact framing (hash16 + udp_version-gated partstatus + complete-count) only when UDP-eligible (peer UDP port + version, not firewalled, no live TCP, no proxy).
  • [x] Downloader handles OP_REASKACK (rank + optional partstatus), OP_QUEUEFULL (rank 0, stay), OP_FILENOTFOUND (drop source); unsolicited replies dropped by the pending gate.
  • [x] UDP-eligible queued sources release the TCP socket and retain queue position across TCP teardown via UDP reask on a FILEREASKTIME-based cadence (×2 NNP, ≥ MIN_REQUESTTIME), with failure-ratio backoff to TCP.
  • [x] Non-UDP-eligible sources fall back to bounded TCP reconnect-reask.
  • [x] Uploader answers well-formed inbound OP_REASKFILEPING from known waiting clients with a correct queue position (silent/OP_QUEUEFULL per stock rules otherwise).
  • [x] Per-transfer reask cadence introduces no cross-transfer shared scheduler.
  • [ ] Refreshed live interoperability evidence proves the implemented behavior against the current post-RUST-BUG-099 parity baseline.

Validation

  • Unit: per-opcode encode/decode round-trips incl. udp_version > 2/> 3 tails; pending-gate drop; backoff threshold; reask-interval math.
  • Rust↔Rust: queued downloader releases TCP and keeps position purely via UDP reask across an accelerated cadence.
  • Rust↔aMule / Rust↔eMuleBB short-path witness (gentle, widely-spaced, single-pass; confirm before any live-wire run per live-wire policy).
  • packet_trace labels added for the new opcodes so the harness can assert the exchange.
  • Core parity close: run the targeted Rust/eMuleBB reask proof from RUST-CI-002 after current HEAD evidence is regenerated.

Notes