Adversarial review's test-coverage gap finding: no test drives
`loop_driver::drive()`, the central function of the slice. F1, F2,
and F4 would have been caught by one. The obstacle was that `drive()`
called `session.socket.recv_from` on a concrete `UdpSocket`, so
unit tests couldn't feed synthetic STUN/RTP/DTLS bytes without a real
UDP loopback pair + hand-rolled valid packets on the wire.
Retrofit a sans-IO test seam shaped the way str0m itself is built:
- New `crates/rutster-media/src/packet_io.rs` defines `PacketIo`
(`recv_from`, `send_to`) with a blanket impl for `UdpSocket`.
- `RtcSession.socket` goes from `std::net::UdpSocket` to
`Box<dyn PacketIo + Send>`. Dispatch cost: a vtable call (~1 ns)
on a 20 ms tick at single-digit pps; ~10,000× headroom vs the 200 ms
latency bar. If step 4's dedicated timing thread measures it, swap
`Box<dyn>` → a generic `RtcSession<S>` parameterization (same
trait interface).
- `RtcSession::new()` keeps its behavior; new hidden `new_with_socket`
is the test constructor (injects a fake + a synthetic local_addr).
This is the infrastructural refactor only — no behavior change. F4
and F1+F3 land on top of it (separate commits, TDD-style: failing
test first, then fix).