fbacef1f2a
Implements the 10 actionable items from the QUIC code review in issue #2861; the lone P4 item (encrypt-under-streamsLock) is already tracked as deferred phase 2 work in quic/plans/2026-05-08-lock-split-design.md and is unchanged. Android-only correctness (would silently break on API 26–32): - A1 JdkCertificateValidator: wrap Signature.getInstance("Ed25519") in try/catch and surface NoSuchAlgorithmException as a clean QuicCodecException so an Ed25519 leaf cert no longer crashes the TLS read loop on pre-API-33 Android. - A2 TlsRunningSha256 (jvmAndroid): keep a parallel byte accumulator and fall back to one-shot SHA-256 on the rare API-26–28 Conscrypt builds whose OpenSSLMessageDigestJDK throws CloneNotSupportedException from MessageDigest.clone(). Latches the fallback after first failure so we don't pay the JCA throw per snapshot. Hot-path performance: - P1 HeaderProtection: add maskAt(hpKey, src, srcOffset) + extend AesOneBlockEncrypt with encryptInto(key, src, srcOffset, dst, dstOffset) so the per-packet HP path no longer allocates a 16-byte sample slice AND no longer allocates a 16-byte JCA Cipher output (the jvmAndroid impl uses Cipher.doFinal's range overload). Updated 5 call sites in ShortHeaderPacket and LongHeaderPacket. - P2 aeadNonce: add aeadNonceInto(staticIv, packetNumber, dst) so call sites with a persistent 12-byte scratch can build the nonce without per-packet allocation; aeadNonce keeps its existing shape via the new helper. Threading the scratch through Short/LongHeaderPacket and the writer is deferred (similar shape to the documented P4 phase 2 work). - P3 QuicConnectionParser: decode the frame list once per inbound packet, feed both qlog (frameNamesFor) and dispatch from the single decode. Pre-fix every qlog-attached packet ran decodeFrames twice. - P5 QuicConnectionWriter: iterate pendingMaxStreamData / pendingNewConnectionId directly instead of allocating entries.toList() per drain. Protocol / security: - S1 QuicConnectionParser: cap MAX_STREAMS at 2^60 (RFC 9000 §19.11); a peer sending a larger value now triggers STREAM_LIMIT_ERROR close rather than overflowing the local nextLocalBidi/UniIndex counters. - S2 QuicConnection.effectiveResumption: drop the cached session ticket when (now - issuedAt) ≥ min(ticketLifetimeSec, 7 days) per RFC 8446 §4.6.1; expired tickets would otherwise silently fail server-side and lose any 0-RTT bytes. - S3 QuicConnection: refuse to offer 0-RTT when our current alpnList doesn't include the resumed session's negotiated ALPN, and treat 0-RTT as rejected on EE if the new ALPN differs from the cached one (RFC 9001 §4.6.1). - S4 TlsExtension.encodeSignatureAlgorithms: drop rsa_pkcs1_sha256. The validator already rejects it in CertificateVerify per RFC 8446 §4.2.3 — advertising it lied about what we accept. All quic JVM unit tests pass; spotless applied. https://claude.ai/code/session_01EBHtGLy5o7FUR5qfcpHUUx