- Rename the misleadingly-overloaded Load(BDict) to LoadInfoDictionary (it loads the
info dictionary, e.g. from ut_metadata), distinct from Load(Stream)/Load(MagnetLink).
Demo updated.
- MetadataFileInfo.PathSeparator: '\' -> '/' (portable, BitTorrent/URL convention;
the old Windows-only backslash could also collide with a real path component).
The disk-based-build responsibility (the genuinely separable concern flagged for the
'split') was already extracted to TorrentBuilder in Phase 10; the remaining parse
populates the Metadata value object itself, so a separate parser type reaching into
its internals would be ceremony rather than real separation -- not done.
Breaking: Metadata.Load(BDict) -> LoadInfoDictionary; Filename uses '/'. Suite green (187).
The 11-positional-parameter Announce was hard to call correctly and the HTTP
client silently ignored several of its parameters. Replaced with an AnnounceRequest
options object (sensible defaults for Event/NumWant/ListenPort) honored consistently.
Demo + tracker tests updated.
Breaking: ITrackerClient.Announce(url,hash,peerId,...11 args) -> Announce(AnnounceRequest).
Suite green (187).
They were nested in BaseScraper, forcing ITrackerClient to depend on the concrete
base class. Now top-level types owned by ITrackerClient. ScrapeInfo's
ScraperType-switched ctor (positional a,b,c leaving half the fields zero) becomes
named ScrapeInfo.FromHttp/FromUdp factories. Demo updated. Suite green (187).
Eliminates the 758-line UTPSocket god class that fused two fundamentally
different roles (listener vs data socket) behind isListening/isServerChild flags.
- UTPConnection (IO/UTPConnection.cs): the data-transfer connection — client
(owns socket + own receive loop) or accepted child (shares the listener's
socket, fed via HandleIncomingPacket). Connect/Send/Receive/BeginReceive,
reliability (retransmit/reorder/ack), MSE-friendly synchronous Receive pump.
- UTPListener (IO/UTPListener.cs): passive open — Listen/Accept, the demux loop,
activeChildren registry, accept-waiter registration; creates UTPConnection
children and routes datagrams to them.
- PacketType promoted to top-level UTPPacketType (IO/UTPPacketType.cs); refs in
UTPPacketCodec, UTPRetransmissionTracker and tests updated.
- UTPSocket.cs deleted. Demo uses UTPConnection (client); passive-open tests use
UTPListener + UTPConnection.
Logic moved verbatim (not rewritten) to preserve the validated wire behaviour.
The inherent client-vs-accepted-child duality remains inside UTPConnection (uTP
shares one UDP socket across a listener's accepted connections). Validated
against the real two-instance loopback e2e tests. Full suite green (176; 39 UTP).
Note: the ISocket interface-segregation + socket-factory portion of Phase 3 was
NOT done — MSE (6 ISocket signatures), PeerWireConnection and LocalPeerDiscovery
are deeply coupled to the fat ISocket, making that a wide, high-risk rewrite for
mostly-structural gain. The concrete LSP defect it targeted was already fixed in
Phase 3.0. See plan.md.
UTHolepunchExtension implements ut_holepunch (Rendezvous/Connect/Error
messages) as an IBTExtension, same scope as the other extensions
(UTPeerExchange etc): wire format, send methods, and events only — the
consumer decides when to rendezvous, tracks which peers are connected to
whom, and retries the connection. Registered in the Demo app alongside
PEX/tracker-exchange/metadata.
The payload byte layout is implemented to the best available
understanding of BEP-55 (msg_type, addr_type, address, port, and an
error_code for the Error variant) — flagged in both the code and README
for verification against the published BEP text before relying on it for
interop, the same caveat already applied to this library's other
experimental pieces (uTP).
Tested via a real PeerWireClient + ExtendedProtocolExtensions +
FakePeerConnection (the pattern the existing PeerWireClient integration
tests use — no mocking framework, matches project convention): send-path
wire format, decode of Rendezvous/Connect/Error messages, and an
encode/decode round-trip through the extension's own logic. Live-verified
via Demo against real DHT/tracker peers for the ubuntu torrent fixture for
45s with the extension registered — handshakes completed normally and no
exceptions; no peer happened to trigger an actual holepunch exchange in
this window (expected — it needs a specific NAT-traversal scenario), but
that's consistent with this task's scope being the protocol plumbing, not
guaranteed real-peer interop.
Extracts the retransmission/reordering logic into two pure, socket-free
classes (UTPRetransmissionTracker, UTPReorderBuffer) following the same
pattern already used for UTPCongestionControl, so the algorithms are
deterministically unit-testable without real sockets or timers:
- UTPRetransmissionTracker: tracks in-flight packets, RTO via
Jacobson/Karels RTT estimation, exponential backoff, Karn's algorithm
(no RTT sample from retransmitted packets), gives up after
MaxRetransmits.
- UTPReorderBuffer: buffers ST_DATA packets that arrive ahead of a gap and
releases them in order once the gap fills, replacing the previous
"blindly advance ack to whatever arrives" logic.
UTPSocket wires these in: a Timer drives periodic retransmission
independent of inbound traffic, and BeginReceive/EndReceive were reworked
so out-of-order packets are buffered rather than delivered immediately,
with a delivery queue draining reassembled-in-order payloads on
subsequent calls.
Verified: dotnet test (120 passing, including 16 new deterministic tests),
plus a live run via a new Demo -utp flag against real DHT/tracker peers
for Demo/TestTorrents/ubuntu-26.04-desktop-amd64.iso.torrent — ran cleanly
for 45s with no exceptions across 6 real peer connection attempts. No real
peer completed the uTP handshake; this traces to a separate, pre-existing
gap (UTPSocket has no passive-open/listener support — Listen/Accept are
TCP-shaped stubs that don't work for a UDP transport), not a regression
from this change. Documented in the README's existing uTP experimental
note.