afe3aaf020
Soak target #4 — minimum viable path validation. Lands the spec-required peer-initiated case so a server probing the path (e.g. after a NAT rebind, or post-CID rotation) sees a matching PATH_RESPONSE and doesn't declare the path dead. Pre-fix the parser decoded PATH_CHALLENGE / PATH_RESPONSE bytes but threw the result away — a peer's challenge went silently into the void. After ~3 RTT of no response, a strict peer would mark the path dead and tear the connection down (visible to audio-rooms users as a sudden cut on a phone that briefly switched cells). Implementation: - Add PathChallengeFrame / PathResponseFrame data classes; wire decode and encode (was decode-and-discard previously). - Add `pendingPathResponses` queue on QuicConnection (bounded at MAX_PENDING_PATH_RESPONSES = 64 to defend against challenge flood; excess silently dropped — peer retries on PTO). - Parser handler queues a response on inbound PATH_CHALLENGE. - Writer drains the queue in buildApplicationPacket. RFC 9000 §13.3 doesn't list PATH_RESPONSE as ack-eliciting-and- retransmittable; if a response is lost, the peer's next PATH_CHALLENGE re-queues it and we respond again. Out of scope for this landing (multi-day each, parked unless production evidence requires): - Client-initiated migration: requires UdpSocket replacement, new-CID acquisition tracking, validating new path BEFORE moving traffic to it. - Anti-amplification on unvalidated paths (RFC 9000 §8.1). Tests (PathValidationTest, 6 cases): - PATH_CHALLENGE / PATH_RESPONSE codec round-trip + 8-byte length validation. - End-to-end: peer PATH_CHALLENGE → client PATH_RESPONSE with byte-equal payload. - Multi-challenge fan-in: 3 challenges → 3 distinct responses (in any order; matched by content). - Flood cap: 256 challenges → ≤ MAX_PENDING_PATH_RESPONSES responses, connection stays CONNECTED. https://claude.ai/code/session_018KPKWRg5baX5Anf7zfEyec