fix(quic): restore Retry handling lost in versionnegotiation merge
The agent A worktree was based on main, so its QuicConnection.kt
didn't carry the Retry handling from d03e17981. Merging with
-X theirs replaced the file wholesale, dropping applyRetry +
extraSecretsListener / cipherSuites constructor params + the
RetryPacket import in the parser.
Restored:
- extraSecretsListener + cipherSuites ctor params (used in tlsListener
+ the TlsClient construction site).
- applyRetry method, now using the version-negotiation-introduced
LevelState.resetForVersionNegotiation helper (functionally
equivalent to the prior restoreFromRetry it replaced).
- RetryPacket import in QuicConnectionParser.
Also dedupes a duplicate `originalClientHello` field that the merge
left both copies of (one from agent 3's Retry work, one from agent A's
VN work). Single field now serves both reset paths.
https://claude.ai/code/session_01HcvfQq1ttPV9PkRoJb4nyT
This commit is contained in:
@@ -74,6 +74,23 @@ class QuicConnection(
|
||||
.toEpochMilliseconds()
|
||||
},
|
||||
val alpnList: List<ByteArray> = listOf(TlsConstants.ALPN_H3),
|
||||
/**
|
||||
* Optional second listener invoked after the connection's own
|
||||
* key-installation listener. Used by the interop runner endpoint to
|
||||
* dump SSLKEYLOG lines so Wireshark can decrypt captured pcaps.
|
||||
* Default `null` keeps production callers unaffected.
|
||||
*/
|
||||
val extraSecretsListener: TlsSecretsListener? = null,
|
||||
/**
|
||||
* TLS cipher suites to offer in the ClientHello. Override to e.g.
|
||||
* `intArrayOf(TlsConstants.CIPHER_TLS_CHACHA20_POLY1305_SHA256)` for the
|
||||
* `chacha20` interop testcase. Default matches [TlsClient]'s default.
|
||||
*/
|
||||
val cipherSuites: IntArray =
|
||||
intArrayOf(
|
||||
TlsConstants.CIPHER_TLS_AES_128_GCM_SHA256,
|
||||
TlsConstants.CIPHER_TLS_CHACHA20_POLY1305_SHA256,
|
||||
),
|
||||
/**
|
||||
* Version this connection puts in the FIRST Initial it sends. Defaults
|
||||
* to [QuicVersion.V1]; the interop runner sets it to
|
||||
@@ -145,15 +162,6 @@ class QuicConnection(
|
||||
var retryConsumed: Boolean = false
|
||||
internal set
|
||||
|
||||
/**
|
||||
* The verbatim ClientHello CRYPTO bytes the TLS layer emitted at
|
||||
* [start]. Captured so [applyRetry] can re-enqueue them after Retry
|
||||
* resets the Initial-level CRYPTO send buffer (TLS itself only
|
||||
* emits ClientHello once and we have no path to drive it to re-emit).
|
||||
* Null until [start] has been called.
|
||||
*/
|
||||
private var originalClientHello: ByteArray? = null
|
||||
|
||||
var handshakeComplete: Boolean = false
|
||||
private set
|
||||
|
||||
@@ -531,6 +539,41 @@ class QuicConnection(
|
||||
currentVersion = QuicVersion.V1
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a verified Retry packet per RFC 9000 §17.2.5 + RFC 9001 §5.8.
|
||||
* Validates the retry-integrity tag, swaps DCID, re-derives Initial keys,
|
||||
* resets the Initial PN space, and re-enqueues the cached ClientHello so
|
||||
* the next outbound Initial carries `Token = retryPacket.retryToken`.
|
||||
*
|
||||
* Returns false on bad tag, second Retry (RFC 9000 §17.2.5.2), or
|
||||
* pre-start (no original ClientHello captured) — all silently dropped.
|
||||
*/
|
||||
internal fun applyRetry(
|
||||
retryPacket: com.vitorpamplona.quic.packet.RetryPacket,
|
||||
originalPacketBytes: ByteArray,
|
||||
): Boolean {
|
||||
if (retryConsumed) return false
|
||||
if (!retryPacket.verifyIntegrityTag(originalPacketBytes, originalDestinationConnectionId.bytes)) {
|
||||
return false
|
||||
}
|
||||
val savedClientHello = originalClientHello ?: return false
|
||||
|
||||
destinationConnectionId = retryPacket.scid
|
||||
val proto = InitialSecrets.derive(destinationConnectionId.bytes)
|
||||
val hp = AesEcbHeaderProtection(PlatformAesOneBlock)
|
||||
initial.resetForVersionNegotiation(
|
||||
sendProtection =
|
||||
PacketProtection(bestAes128GcmAead(proto.clientKey), proto.clientKey, proto.clientIv, hp, proto.clientHp),
|
||||
receiveProtection =
|
||||
PacketProtection(bestAes128GcmAead(proto.serverKey), proto.serverKey, proto.serverIv, hp, proto.serverHp),
|
||||
)
|
||||
initial.cryptoSend.enqueue(savedClientHello)
|
||||
|
||||
retryToken = retryPacket.retryToken
|
||||
retryConsumed = true
|
||||
return true
|
||||
}
|
||||
|
||||
private fun buildLocalTransportParameters(): TransportParameters =
|
||||
TransportParameters(
|
||||
initialMaxData = config.initialMaxData,
|
||||
|
||||
@@ -22,6 +22,7 @@ package com.vitorpamplona.quic.connection
|
||||
|
||||
import com.vitorpamplona.quic.QuicCodecException
|
||||
import com.vitorpamplona.quic.connection.recovery.drainAckedSentPackets
|
||||
import com.vitorpamplona.quic.packet.RetryPacket
|
||||
import com.vitorpamplona.quic.frame.AckFrame
|
||||
import com.vitorpamplona.quic.frame.ConnectionCloseFrame
|
||||
import com.vitorpamplona.quic.frame.CryptoFrame
|
||||
|
||||
Reference in New Issue
Block a user