use a CONNECTION_CLOSE frame if it has sufficient state to do so. To support this process, a token is sent by endpoints.  The token is <del> carried in the NEW_CONNECTION_ID frame sent by either peer, and servers can specify the stateless_reset_token transport parameter during the handshake (clients cannot because their transport parameters don't have confidentiality protection).  This value is protected by encryption, so only client and server know this value. Tokens are invalidated when their associated connection ID is retired via a RETIRE_CONNECTION_ID frame (frame-retire-connection-id). </del>  An endpoint that receives packets that it cannot process sends a packet in the following layout: packet either cannot be associated with a connection, cannot be decrypted, or is marked as a duplicate packet.  The endpoint MUST then compare the last 16 bytes of the packet with all Stateless Reset <del> Tokens that are associated with connection IDs that the endpoint recently used to send packets from the IP address and port on which the datagram is received.  This includes Stateless Reset Tokens from </del>  NEW_CONNECTION_ID frames and the server's transport parameters.  An endpoint MUST NOT check for any Stateless Reset Tokens associated with connection IDs it has not used or for connection IDs that have that is reset by revealing the Stateless Reset Token MUST NOT be reused for new connections at nodes that share a static key. <del> The same Stateless Reset Token MAY be used for multiple connection IDs on the same connection.  However, reuse of a Stateless Reset Token might expose an endpoint to denial of service if associated connection IDs are forgotten while the associated token is still active at a peer.  An endpoint MUST ensure that packets with Destination Connection ID field values that correspond to a reused Stateless Reset Token are attributed to the same connection as long as the Stateless Reset Token is still usable, even when the connection ID has been retired.  Otherwise, an attacker might be able to send a packet with a retired connection ID and cause the endpoint to produce a Stateless Reset that it can use to disrupt the connection, just as with the attacks in reset-oracle. </del>  Note that Stateless Reset packets do not have any cryptographic protection. eliciting packet.  This recommendation is in keeping with standard practice for TCP RFC5681. <del> A receiver's delayed acknowledgment timer SHOULD NOT exceed the current RTT estimate or the value it indicates in the "max_ack_delay" transport parameter.  This ensures an acknowledgment is sent at least once per RTT when packets needing acknowledgement are received.  The sender can use the receiver's "max_ack_delay" value in determining timeouts for timer-based retransmission. </del>  In order to assist loss detection at the sender, an endpoint SHOULD send an ACK frame immediately on receiving an ack-eliciting packet determine whether an immediate or delayed acknowledgement should be generated after processing incoming packets. <del> Acknowledgements of packets carrying CRYPTO frames SHOULD be minimally delayed, to complete the handshake with minimal latency. Delaying them by a small amount, such as the local timer granularity, allows the endpoint to bundle any data sent in response with the ACK frame.  ACK frames SHOULD be sent immediately when the crypto stack indicates all data for that packet number space has been received. </del> Packets containing PADDING frames are considered to be in flight for congestion control purposes QUIC-RECOVERY.  Sending only PADDING frames might cause the sender to become limited by the congestion processed.  An endpoint MUST NOT include delays that is does not control when populating the Ack Delay field in an ACK frame. <del> An endpoint MUST NOT excessively delay acknowledgements of ack- eliciting packets.  An endpoint commits to a maximum delay using the max_ack_delay transport parameter; see transport-parameter- definitions.  max_ack_delay declares an explicit contract: an endpoint promises to never delay acknowledgments of an ack-eliciting packet by more than the indicated value.  If it does, any excess accrues to the RTT estimate and could result in delayed retransmissions from the peer.  For Initial and Handshake packets, a max_ack_delay of 0 is used. </del> 13.2.6. ACK frames MUST only be carried in a packet that has the same packet 18. <del> The format of the transport parameters is the TransportParameters struct from _figure-transport-parameters.  This is described using the presentation language from Section 3 of TLS13. </del> The "extension_data" field of the quic_transport_parameters extension <del> defined in QUIC-TLS contains a TransportParameters value.  TLS encoding rules are therefore used to describe the encoding of transport parameters. </del>  QUIC encodes transport parameters into a sequence of bytes, which are then included in the cryptographic handshake. The server's preferred address is used to effect a change in server address at the end of the handshake, as described in <del> preferred-address.  The format of this transport parameter is the PreferredAddress struct shown in fig-preferred-address.  This transport parameter is only sent by a server.  Servers MAY choose to only send a preferred address of one address family by sending an all-zero address and port (0.0.0.0:0 or ::.0) for the other family.  IP addresses are encoded in network byte order. </del>  The maximum number of connection IDs from the peer that an endpoint is willing to store.  This value includes only connection

This PR is purely editorial and does not change the protocol in any way. When we discussed in Cupertino, it emerged that there was consensus for this editorial change but not for all topics in that issue, so I split this out to its own PR.
So the variable length integers have a 2 byte length prefix followed by a two bit length encoding?
Are we doing URL here or not?
NAME this is an editorial PR that does not change any wire encoding. We are not doing here.
This not marked as editorial. It was decided in Cupertino to either do here or to
NAME that is not my recollection. Either way, this is a PR, we can discuss non-editorial changes on the appropriate issue.
As of , transport parameter identifiers are 16bit integers, with a registry where uses the is reserved for experimentation While keeping Specification Required makes sense for a good chunk of the registry space, I think there will be many experiments that are conducted before a full specification is written (some already exist today and I expect there to be many many more once the protocol is standardized). However, those experiments only have 256 values to play with, and that is bound to cause collisions and headaches down the road. I think the simplest solution to this problem is to use variable length integers for transport parameter identifiers. The TLS presentation language doesn't support them but since we already use VLIs for the values of several transport parameters we're no longer bound by the TLS presentation language. I think there was also support for doing this when it was discussed on , but that issue was closed when the values were changed to VLI with , without changing the identifiers. I'm happy to write the PR if this issue gathers support.
You aren't the first to suggest this one. Let's make sure we at least discuss this directly. We did discuss this in the context of the issues that you cite and decided not to, but that was never the central question. From our perspective, this isn't a significant change to code.
The same for us, too. Am I understanding correctly that the concern here is that extensions of TLS structures tend to use uint16_t as their type identifiers, and that the reusable parser might be constrained to that design pattern?
I thought that this might be the case originally, but it turns out that the blob for transport parameters is generated by QUIC code in mostly all of the cases I have seen. If there is someone out there who is relying on passing a TLS stack a structured grammar and letting the TLS stack perform serialization, then we might want to just leave this alone.
Thank you for the clarification. We do use the TLS stack to encode / decode the transport parameters, therefore we do want see the TLS grammar-based design being preserved. The reason we do not mind the extension type being a varint is because our codec is flexible enough to handle that. URL
We should definitely do something to increase the number of experimentation codepoints from 256. Changing to a varint sounds good. If not, then we should allocate more of the space(ie: [0xf000 - 0xffff]) to experiments.
I agree with NAME and I don't think we need to go varint here, that seems like a bit of an overkill. How about reserving 4096 codepoints for spec required, and leaving the rest (2^16 - 2^12 = 61440) for experimentation: [0x0000 - 0x0fff] uses the RFC8128 Specification Required policy [0x1000 - 0xffff] is reserved for experimentation
Apart from the fact that we want to limit changes at this stage, are there arguments against going varint here? It feels pretty odd that many transport parameters are TLVs where T and L are 16bit and V is varint.
I don't have much of an argument here besides the general principle that we shouldn't provide more flexibility than is necessary. I don't think it's odd that T is fixed-length while the V is varint, FWIW. If no implementations are bothered by the change to varint, then it's a bit of a bikeshed. Let's see if other implementers are bothered.
Split in two ? what the same size ? Don't forget there is also GREASE TP now
From looking at all the integer fields in QUICv1, it looks like at this point everything is a varint except the versions, packet numbers, connection ID lengths (which all have valid reasons for their lengths), and the transport parameter identifier and transport parameter lengths. Switching transport parameters to QUIC format (as opposed to TLS presentation language) feels fitting to me, saves bytes on the wire, and will facilitate innovation down the road.
I'm happy to make transport parameter identifier a varint. NAME Are you(implicitly) suggesting that transport parameter lengths should be varint as well?
I think so, yes. If we're changing implementations might as well make both changes at once (assuming no one wants one of them to be varint but not the other).
I'd be opposed to changing the transport parameter lengths to varints, as that means departure from the TLS notation that we use for defining TPs. It's very different from changing the IDs to varints, which is just changing the type of a particular variable. IIRC, we discussed if we should switch to a different notation, and decided to go with the TLS-based design. At least I have built (or maintained) code based on that.
I agree with Kazuho here: splitting it in half would make plenty of room
I'm in favor of the change that NAME is suggesting in . There's no reason for us to use TLS presentation language, if anything, it just makes the spec harder to read for people who are unfamiliar with the TLS presentation language (if I remember correctly, this came up multiple times on issues here and / or on Slack).
Remember the experience with the X-Options in SMTP or HTML? Do we really want to replicate that?
Discussed in Cupertino. NAME to open a new Editorial issue to only replace the TLS syntax with something easier to understand. NAME to also do a Design PR to shuffle the assigned ranges in the registry.
As discussed in Cupertino, I have closed previous PR and replaced it with: editorial-only PR IANA policy PR
I think that the RFC 6648 lessons need to be considered carefully here. I think that it would be better to use a combination of random selection and provisional registration as a means of handling experiments. Random selection means that we end up colliding with very low probability; provisional registration ensures that registrations are protected. Making the private use space larger does neither. I would prefer to instead remove the private use space completely.
SGTMLGTM. I'd also be for removing the unnecessary, prefixed and fine with changing to varint encoding where applicable.

use a CONNECTION_CLOSE frame if it has sufficient state to do so. To support this process, a token is sent by endpoints.  The token is  <ins> carried in the Stateless Reset Token field of a NEW_CONNECTION_ID frame.  Servers can also specify a stateless_reset_token transport parameter during the handshake that applies to the connection ID that it selected during the handshake; clients cannot use this transport parameter because their transport parameters don't have confidentiality protection.  These tokens are protected by encryption, so only client and server know their value.  Tokens are invalidated when their associated connection ID is retired via a RETIRE_CONNECTION_ID frame (frame-retire-connection-id). </ins> An endpoint that receives packets that it cannot process sends a packet in the following layout: packet either cannot be associated with a connection, cannot be decrypted, or is marked as a duplicate packet.  The endpoint MUST then compare the last 16 bytes of the packet with all Stateless Reset  <ins> Tokens corresponding to active connection IDs that the endpoint has used for sending packets to the IP address and port on which the datagram is received.  This includes Stateless Reset Tokens from </ins> NEW_CONNECTION_ID frames and the server's transport parameters.  An endpoint MUST NOT check for any Stateless Reset Tokens associated with connection IDs it has not used or for connection IDs that have that is reset by revealing the Stateless Reset Token MUST NOT be reused for new connections at nodes that share a static key.  <ins> The same Stateless Reset Token MUST NOT be used for multiple connection IDs.  Endpoints are not required to compare new values against all previous values, but a duplicate value MAY be treated as a connection error of type PROTOCOL_VIOLATION. </ins> Note that Stateless Reset packets do not have any cryptographic protection. eliciting packet.  This recommendation is in keeping with standard practice for TCP RFC5681.  <ins> An endpoint MUST NOT excessively delay acknowledgements of ack- eliciting packets.  An endpoint commits to a maximum delay using the max_ack_delay transport parameter; see transport-parameter- definitions.  max_ack_delay declares an explicit contract: an endpoint promises to never delay acknowledgments of an ack-eliciting packet by more than the indicated value.  If it does, any excess accrues to the RTT estimate and could result in delayed retransmissions from the peer.  For Initial and Handshake packets, a max_ack_delay of 0 is used.  The sender uses the receiver's "max_ack_delay" value in determining timeouts for timer-based retransmission, as detailed in Section 5.2.1 of QUIC-RECOVERY. </ins> In order to assist loss detection at the sender, an endpoint SHOULD send an ACK frame immediately on receiving an ack-eliciting packet determine whether an immediate or delayed acknowledgement should be generated after processing incoming packets.  Packets containing PADDING frames are considered to be in flight for congestion control purposes QUIC-RECOVERY.  Sending only PADDING frames might cause the sender to become limited by the congestion processed.  An endpoint MUST NOT include delays that is does not control when populating the Ack Delay field in an ACK frame.  13.2.6. ACK frames MUST only be carried in a packet that has the same packet 18.  The "extension_data" field of the quic_transport_parameters extension  <ins> defined in QUIC-TLS contains the QUIC transport parameters.  They are encoded as a length-prefixed sequence of transport parameters, as shown in transport-parameter-sequence: The Sequence Length field contains the length of the sequence of transport parameters, in bytes.  Each transport parameter is encoded as an (identifier, length, value) tuple, as shown in transport- parameter-encoding-fig: The Transport Param Length field contains the length of the Transport Parameter Value field. </ins> QUIC encodes transport parameters into a sequence of bytes, which are then included in the cryptographic handshake. The server's preferred address is used to effect a change in server address at the end of the handshake, as described in  <ins> preferred-address.  The format of this transport parameter is shown in fig-preferred-address.  This transport parameter is only sent by a server.  Servers MAY choose to only send a preferred address of one address family by sending an all-zero address and port (0.0.0.0:0 or ::.0) for the other family.  IP addresses are encoded in network byte order.  The CID Length field contains the length of the Connection ID field. </ins> The maximum number of connection IDs from the peer that an endpoint is willing to store.  This value includes only connection


SETTINGS frame MUST NOT reduce any limits or alter any values that might be violated by the client with its 0-RTT data.  The server MUST include all settings which differ from their default values.  If a <del> server accepts 0-RTT, but then sends a SETTINGS frame which reduces a setting the client understands or omits a value that was previously specified to have a non-default value, this MUST be treated as a connection error of type H3_SETTINGS_ERROR. </del>  7.2.5.

The HTTP draft currently specifies Is it intentional that a client MUST remember SETTINGS values that it doesn't support (to check that the server didn't omit a setting)?
The comment about checking for omitted settings says ... "omits a value that was previously specified to have a non-default value". In order for a client to know what the default value is for that setting, surely the client must support that setting, right?
You're right that for the client to know whether or not it's the default value it needs to support the setting (or at least know about it). Suppose the server sends SETTING BLORP (which the client knows nothing about) with value X in the original SETTINGS, but omits it on the 0-RTT resumed connection. As currently specified, if value X is not the default value, the client MUST close the connection, but if it is the default value, then that's fine. (Meaning the client needs to know all possible default values for all possible settings.) I think this needs an editorial fix to apply something like "a setting the client understands" to the bit about omitting a value as well.
Oh, I see your reading now. Interesting! Good point about an editorial tweak.
I think this could affect reserved settings (aka grease) too, just in a slightly different way. Some editorial tweak to address the problem would be good.

SETTINGS frame MUST NOT reduce any limits or alter any values that might be violated by the client with its 0-RTT data.  The server MUST include all settings which differ from their default values.  If a  <ins> server accepts 0-RTT but then sends settings that are not compatible with the previously specified settings, this MUST be treated as a connection error of type H3_SETTINGS_ERROR.  If a server accepts 0-RTT but then sends a SETTINGS frame that omits a setting value that the client understands (apart from reserved setting identifiers) that was previously specified to have a non-default value, this MUST be treated as a connection error of type H3_SETTINGS_ERROR. </ins> 7.2.5.


Servers MAY serve HTTP/3 on any UDP port, since an alternative always includes an explicit port. <del> 3.2.1. This document defines the "quic" parameter for Alt-Svc, which MAY be used to provide version-negotiation hints to HTTP/3 clients.  QUIC versions are four-byte sequences with no additional constraints on format.  Leading zeros SHOULD be omitted for brevity. Syntax of the "quic" parameter value: Where multiple versions are listed, the order of the values reflects the server's preference (with the first value being the most preferred version).  Reserved versions MAY be listed, but unreserved versions which are not supported by the alternative SHOULD NOT be present in the list.  Origins MAY omit supported versions for any reason. Clients MUST ignore any included versions which they do not support. The "quic" parameter MUST NOT occur more than once; clients SHOULD process only the first occurrence. For example, suppose a server supported both version 0x00000001 and the version rendered in ASCII as "Q034".  If it also opted to include the reserved version (from Section 15 of QUIC-TRANSPORT) 0x1abadaba, it could specify the following header field: A client acting on this header field would drop the reserved version (not supported), then attempt to connect to the alternative using the first version in the list which it does support, if any. </del> 3.3. HTTP/3 relies on QUIC as the underlying transport.  The QUIC version 11.2. <del> This document creates a new registration for version-negotiation hints in the "Hypertext Transfer Protocol (HTTP) Alt-Svc Parameter" registry established in RFC7838. "quic" This document, 11.3. </del> This document establishes a registry for HTTP/3 frame type codes. The "HTTP/3 Frame Type" registry governs a 62-bit space.  This space is split into three spaces that are governed by different policies. values of N (that is, "0x21", "0x40", ..., through "0x3FFFFFFFFFFFFFFE") MUST NOT be assigned by IANA. <del> 11.4. </del>  This document establishes a registry for HTTP/3 settings.  The "HTTP/3 Settings" registry governs a 62-bit space.  This space is values of N (that is, "0x21", "0x40", ..., through "0x3FFFFFFFFFFFFFFE") MUST NOT be assigned by IANA. <del> 11.5. </del>  This document establishes a registry for HTTP/3 error codes.  The "HTTP/3 Error Code" registry manages a 62-bit space.  The "HTTP/3 The entries in the following table are registered by this document. <del> 11.6. </del>  This document establishes a registry for HTTP/3 unidirectional stream types.  The "HTTP/3 Stream Type" registry governs a 62-bit space.

As discussed in Cupertino, we're punting the hard part of this issue to future versions, but we can remove the version hint until then.
According to URL, the Alt-Svc parameter value can be unquoted: This means that this can be written: instead of only: The current text in URL forbids the unquoted variant which is an additional constraint on the RFC 7838 grammar. Should unquoted parameter values also be permitted? Note that this does not change the situation when multiple versions are supported since an unquoted "token" type is not allowed to contain commas. See also the discussion in .
RFC 7838 allows unquoted strings, but there's nothing preventing new specifications of Alt-Svc parameters from being more restrictive. I'm not sure the added flexibility of allowing unquoted versions buys us much here, and it comes at an implementation cost.
If alt-svc can be specified in configuration files, it is likely to result in interop issues if unquoted is not permitted.
I support allowing unquoted token for alt-svc quic parameter. quic=1 and quic="1" are semantically the same, and there is no reason to disallow unquoted token. Moreover, requiring quoted-string means that we cannot use generic alt-svc parser because such parser can parse quic=1 without an error. You need to write your own parser to detect that the DQUOTE is really there. If you think that detecting it is not necessary, then requiring quoted-string is not necessary in the first place.
People and implementers will be surprised (and do wrong) if the rules and constraints of RFC7838 aren't kept.
To highlight my earlier point, on slack the following was posted The issue appears to not be the unquoted ma=3600, but it does suggest we will see unquoted strings in the wild, and URL is said to also use unquoted ma=3600.
But that's the ma= field, not the quic= field, right? (This issue is about the quic= field, I think)
As I said elsewhere, the current text in draft-23 stems from a Structured Headers issue related to wanting to advertise multiple versions of QUIC. URL Alt-Svc isn't a structured one but if it were (e.g. if someone wanted to write a generic Structured Header parser) then there was a danger that the rules preventing dictionary key repetition would have prevented an endpoint sending multiple parameters e.g. . To mitigate the risk, the quic parameter was changed to a DQUOTED comma-separated list as part of URL This issue addresses the correctness within the Alt-Svc spec but does it reopen the can related to Structured Headers?
I think that the problem here is independent from whether structured headers should be applied or not. - Structured header issue: duplicate keys. - Invalid grammar according to RFC 7838 since "token" does not include commas. - valid grammar according to both RFC 7838 and the QUIC description. However, if only a single version has to be advertised, then the quoting is not necessary: - valid grammar according to both RFC 7838 and the QUIC description. - valid grammar according to both RFC 7838, but QUIC currently forbids it. In I proposed to fix the grammar (editorial issue). However, at the same time I could also make the grammar match RFC 7838 (the last bullet point above). NAME I would mind not making the semantics more restrictive (e.g. restricting the values of the QUIC parameter to hexadecimal only), but restricting the parser grammar would impose extra restrictions on those who have to generate the header. It is conceivable that implementations will continue accepting the RFC 7838 grammar, and end up accepting while others would reject it and accept only. By making sure that the grammars align, I hope that this becomes a non-issue. So far NAME NAME and NAME seems to support this proposal and NAME is not sure. Can this be considered consensus? :smiley:
. This allows the server to indicate which versions of quic-transport it prefers. However, there is no guidance as to what to do in the absence of such hints. If a client receives (without any hints), is it expected to start with QUIC version 1, or with the client's preferred QUIC version? If a client receives , is it expected to start with (draft-ietf-quic-transport-23)? We may want to provide guidance in draft-quic-http to ensure everyone's on the same page.
I happy to be proven wrong but it seems like noone uses this parameter as specified and it raises all sorts of questions. I'm leaning towards killing it off. NB: I am aware that Google have long used this parameter but I'm not convinced it is interoperable and deployable.
The Version Hints presumably exist to avoid one roundtrip for the . Thus without hints, a client could probably decide to use any version it supports (1 or whatever latest version it desires to use). FWIW Google uses alt-svc, but I do not remember seeing the version hint parameters. For example:
in this case the "v" parameter is comparable to the "quic" parameter
Edit: nevermind, I need to read better. Removing the parameter was previously suggested in , but that did not seem to proceed.
NAME I also lean towards killing it off. However, that would prevent us from doing something like: h3=":443", quic="1,2" At some future date when we have QUICv1 and QUICv2 and we want to support HTTP/3 over either of them. Does that seem problematic? Of course, if the alpn continues to imply the transport version and HTTP+QUICv2 == HTTP/4 then we wouldn't ever need to advertise this.
That's a compelling argument, I'm just concerned that we aren't seeing this used much today and so when the time comes that you'll need it, it won't work as we all hoped. Perhaps we could defer the quic parameter until v2 - similar to other VN related issues.
I think the question is if we expect changes in the application protocol (i.e. HTTP/3) when we define QUIC v2. If we suspect that H3 would remain as-is for QUIC v2 (I'd assume that would be the case if v2 is going to be greasing or multi-path), then it would be reasonable to define the transport version identifier of the Alt-Svc header in H3. If we are uncertain, I do agree that it might be a good idea to defer it.
We also have a hard time testing this today - there is no standard legal valid combination of "h3-N" and quic versions that would allow a client to select one QUIC version over another. Perhaps this was a motivator for URL
It's fine to omit this parameter. As NAME notes, if the client supports multiple versions of QUIC, this helps avoid the need to do Version Negotiation. However, it's only an optimization -- in the absence of this information, the client does any version of QUIC it believes to be compatible with the ALPN token(s) it is offering. If the server needs to do VN, it will. (Details TBD, unfortunately.) Is this critical to HTTP/3? No, and it could reasonably be extricated to a separate document until such time that there are multiple QUIC versions. Most stacks currently support only one version at a time, so this doesn't serve much purpose. If HTTP/3 doesn't require breaking changes to keep up with QUICvN, this probably serves a purpose on the day there are several. Personal opinion: If anyone thinks this is half-baked, let's set it aside until we need it, with the rest of VN. If we're fairly confident this is what we want, however, it saves us the trouble of defining another separate RFC later.
I'm worried that relying on clients to "do the right thing" might cause serious performance issues down the road. I'd prefer to either have explicit guidance, or remove this mechanism until we understand it better as NAME suggests.
It was designed at a stage where QUIC had a fully-defined Version Negotiation mechanism. The client could offer any version -- in fact, was encouraged to offer a grease version -- on the first flight, and would learn the server's list of versions in response. But if you knew that list up front, either via Alt-Svc or because you've connected before, you could save a round-trip by selecting something that was on the list. And if your information was wrong, or stale... oh well, you get a VN packet and learn the correct list. Now, we don't have such a mechanism and receipt of a VN packet is fatal. There are two ways to look at this: There is only one version, so we can define this mechanism once it can be paired with a version negotiation mechanism. Getting the server's version wrong is fatal, and therefore this mechanism is essential.
Here's what 5.4.4 says However, RFC 8439 says: This is a little confusing, but I think the correct way to read this is that the ChaCha function takes the nonce as a 96-bit opaque quantity, and the fact that it's little-endian is an internal detail irrelevant to our purposes, so we should just replace the second sentence with "The remaining 12 bytes are used as the 96-bit ChaCha nonce". This is reinforced by the fact that the test vectors have the nonce as a byte block. The situation with the counter is more complicated because we are told it is both a counter and 32-bits (which might make you think it's an integer parameter), but it is also treated as a little-endian integer, which makes one think it's a bitstring. And then the test vectors treat it as a value, rather than as a bitstring. If you look at code it typically thinks of this as an integer: See, for instance: URL So, I think the right answer here is to think of ChaCha as having the following API: Note that because the value on the wire is in big-endian format, this would invert the nonce, but I think it's the right answer anyway. Otherwise, you're having to pass in the counter in the wrong format on big-endian platforms.
As a completely general note, I don't think it is right to change endianness of crypto wire formats. AES-GCM has a completely backwards neither little nor big endian encoding (byte and bit swapped so no platform wins) this remains so on the wire. Especially for HW processing this is significant. But little endian is also more performant on nearly all relevant platforms today so there is no need to go over board forcing big endian if it can be avoided without insulting RFC's.
Yeah, this should simplify that text some.
I like the proposed change, the text had somewhat confused me when implementing this in a previous life.
I don't think that the reinterpretation of the little-endian thing is safe, as per NAME comment. The takes a byte buffer as input rather than a . That is to allow for different block counter sizes. I'll make the editorial change, but leave the suggestion there alone.
I have a question out to the editor of the PKCS spec. It appears like there are no solid rules in the spec to support any particular position here. I'm going with my best guess at WWDJBD here, but I'm happy to make a change if there is a strong case made for something else.
NAME NAME can we flip this to please? I don't think that we need to change the substance here, but I want to confirm the little-endian/big-endian choice here. I hope to have more information to share on the question soon, but that might not be definitive. We might just have to make a call ourselves. Absent better information, I'm going to suggest that we continue with little-endian, but I this is far from an obvious conclusion.
The API I'm using for ChaCha20 takes a const uint8_t nonce[12], and I currently pass in a pointer (offset by 4 bytes) to the sample as the nonce, without doing any endianness conversions. I'm in favor of clarifying the language for this input. (Given that this has the design label instead of the editorial label, my design opinion is to keep with the simple thing of passing the bytes directly from the sample to the nonce input of ChaCha20 and not do any endianness conversions. I'm assuming that other crypto libraries have similar APIs though.)
The API I have takes . PKCS takes pointers just like the API NAME refers to (but it fails to specify the endianness, which is a critical oversight, compounded by the fact that the size of the counter can vary). I suspect that - as NAME says - the right idea is to note that ChaCha20 will interpret the nonce as three little-endian integers and that the counter might be expressed as a byte sequence that is then (probably) interpreted as a little-endian integer.
I understand and appreciate this being marked as “design”, though I consider the text that landed in master as editorial, as it (in my view) ended as adding practical advice while retaining the definition we have had.
Yes, but as a design issue, we want to confirm that "no action" is the correct outcome. It's cheap enough to do so.

Servers MAY serve HTTP/3 on any UDP port, since an alternative always includes an explicit port.  3.3. HTTP/3 relies on QUIC as the underlying transport.  The QUIC version 11.2.  This document establishes a registry for HTTP/3 frame type codes. The "HTTP/3 Frame Type" registry governs a 62-bit space.  This space is split into three spaces that are governed by different policies. values of N (that is, "0x21", "0x40", ..., through "0x3FFFFFFFFFFFFFFE") MUST NOT be assigned by IANA.  <ins> 11.3. </ins> This document establishes a registry for HTTP/3 settings.  The "HTTP/3 Settings" registry governs a 62-bit space.  This space is values of N (that is, "0x21", "0x40", ..., through "0x3FFFFFFFFFFFFFFE") MUST NOT be assigned by IANA.  <ins> 11.4. </ins> This document establishes a registry for HTTP/3 error codes.  The "HTTP/3 Error Code" registry manages a 62-bit space.  The "HTTP/3 The entries in the following table are registered by this document.  <ins> 11.5. </ins> This document establishes a registry for HTTP/3 unidirectional stream types.  The "HTTP/3 Stream Type" registry governs a 62-bit space.


A server can send a complete response prior to the client sending an entire request if the response does not depend on any portion of the <del> request that has not been sent and received.  When this is true, a server MAY abort reading the request stream with error code H3_EARLY_RESPONSE, send a complete response, and cleanly close the sending part of the stream.  Clients MUST NOT discard complete responses as a result of having their request terminated abruptly, though clients can always discard responses at their discretion for other reasons. </del>  4.1.1.

Doesn't directly reference the http-core text, because , but uses parallel language. Might want to add a reference to as part of .
Does it matter if the the STOPSENDING error code is something other than EARLY RESPONSE? Originally posted by NAME in URL Moving this out to a separate issue to avoid complicating ; my inclination is to propose the following rules: When a STOPSENDING is received, if the error code has semantics in the context in which you received it, follow the semantics. If the error code has no semantics in that context (or is unknown), then it has no direct effect on the viability of the request or the response. If resetting your request stream causes the server to be unable to generate a response, it will reset its stream. If it sends a complete response, the server clearly got everything it needed; the response is usable. If a response stream is reset after all bytes have been received, the response remains usable; no takebacks.
Several of these are already in the draft: Perhaps the clarification that's needed is to keep the "MAY abort reading," but separately say that "H3EARLYRESPONSE SHOULD be used as the error code"? (Which does raise the question of whether it needs to be a separate error code from H3NOERROR....)
There is a text that say: A server MAY send STOP_SENDING. What should the client do if the server does not send it? Is the client allowed to close the send stream or should it continue until everything has been sent? In the second case the client may receive the complete response before the complete request has been sent. Is there a use case where a sever can send a response before receiving the complete request, but still cares about the request data?
Good question. From a purely transactional point of view, if no part of the response depends on the remainder of the request, the client might as well stop sending. However, I can also imagine an API where the server wants to consume and store the remainder of what the client is sending even if the return value is determined already. The most generic way to handle this is to say that the server can express that it doesn't need the remainder of the response, and if it doesn't do so, the client SHOULD finish sending it.
Another potential case where the server would want to keep the client sending is where the server is performing some transformation (compress/decompress, transcode, en/decrypt, etc.) on the data sent by the client, and is doing the conversion in blocks as bytes are received rather than storing the full resource locally. The same recommendation stands -- unless the server exercises the option to tell the client to stop, the usual rules apply that the client needs to fully send the request to get a full response. Do we need text for this? I'd think that's already the default position.
Maybe add that blurb. I'd recommend marking this as editorial on that basis.
The text is now: entire request if the response does not depend on any portion of the request that has not been sent and received. When this is true, a server MAY abort reading the request stream with error code HTTPEARLYRESPONSE, send a complete response, and cleanly close the sending part of the stream. Clients MUST NOT discard complete responses as a result of having their request terminated abruptly, though clients can always discard responses at their discretion for other reasons.
Discussed in Cupertino; http-core will or should say that clients have to send the full request. Point to core or clarify.
http-core : I think the resolution here is to explicitly map STOPSENDING(EARLYRESPONSE) to the indication that it intends the stream to be fully closed after the response, and the absence of STOP_SENDING to the server's intent to continue reading the payload body.
Does it matter if the the STOP_SENDING error code is something other than EARLY RESPONSE?
Some references in HTTP/QUIC would benefit from invoking the HTTPter drafts instead, if they finish before we do. For example, the text is is unnecessarily awkward because it references concepts from RFC 7230 while disclaiming the wire format. The equivalent text in is far more appropriate.
I think this should be unparked before last call.
The intent was to make the cut-over if the dependency would not delay QUIC. It seems like they're progressing quickly enough that we could probably do that.

A server can send a complete response prior to the client sending an entire request if the response does not depend on any portion of the  <ins> request that has not been sent and received.  When the server does not need to receive the remainder of the request, it MAY abort reading the request stream with error code H3_EARLY_RESPONSE, send a complete response, and cleanly close the sending part of the stream. Clients MUST NOT discard complete responses as a result of having their request terminated abruptly, though clients can always discard responses at their discretion for other reasons.  If the server sends a partial or complete response but does not abort reading, clients SHOULD continue sending the body of the request and close the stream normally. </ins> 4.1.1.


first request occurs on QUIC stream 0, with subsequent requests on stream 4, 8, and so on.  In order to permit these streams to open, an HTTP/3 server SHOULD configure non-zero minimum values for the number <del> of permitted streams and the initial stream flow control window.  It is RECOMMENDED that at least 100 requests be permitted at a time, so as to not unnecessarily limit parallelism. </del>  HTTP/3 does not use server-initiated bidirectional streams, though an extension could define a use for these streams.  Clients MUST treat

At the interim, we talked about this not being an interop requirement, but "a really good idea" which still warrants RFC2119 language. So, keeping the all-caps, but taking the suggestion to make it active voice.

That was never an interoperability requirement, just a suggestion. So I think that this should be reverted. Originally posted by NAME in URL Look at a better way to phrase this guidance, while still retaining the guidance. (Note this text has also been touched by other PRs in the meantime.)

first request occurs on QUIC stream 0, with subsequent requests on stream 4, 8, and so on.  In order to permit these streams to open, an HTTP/3 server SHOULD configure non-zero minimum values for the number  <ins> of permitted streams and the initial stream flow control window.  So as to not unnecessarily limit parallelism, at least 100 requests SHOULD be permitted at a time. </ins> HTTP/3 does not use server-initiated bidirectional streams, though an extension could define a use for these streams.  Clients MUST treat


8.1.1.  Upon receiving the client's Initial packet, the server can request address validation by sending a Retry packet (packet-retry) containing a token.  This token MUST be repeated by the client in all A flow showing the use of a Retry packet is shown in fig-retry. <del> 8.1.2. </del>  A server MAY provide clients with an address validation token during one connection that can be used on a subsequent connection.  Address the token provided in a Retry for future connections.  Servers MAY discard any Initial packet that does not carry the expected token. <del> A token SHOULD be constructed in a way that allows the server to distinguish it from tokens that are sent in Retry packets as they are carried in the same field. The token MUST NOT include information that would allow it to be linked by an on-path observer to the connection on which it was issued.  For example, it cannot include the connection ID or addressing information unless the values are encrypted. </del> Unlike the token that is created for a Retry packet, there might be some time between when the token is created and when the token is subsequently used.  Thus, a token SHOULD have an expiration time, time.  A server can store the expiration time or include it in an encrypted form in the token.  It is unlikely that the client port number is the same on two different connections; validating the port is therefore unlikely to be successful. A token allows a server to correlate activity between the connection where the token was issued and any connection where it is used. Clients that want to break continuity of identity with a server MAY <del> discard tokens provided using the NEW_TOKEN frame.  A token obtained in a Retry packet MUST be used immediately during the connection attempt and cannot be used in subsequent connection attempts. A client SHOULD NOT reuse a token in different connections.  Reusing a token allows connections to be linked by entities on the network path; see migration-linkability.  A client MUST NOT reuse a token if it believes that its point of network attachment has changed since the token was last used; that is, if there is a change in its local IP address or network interface.  A client needs to start the connection process over if there is any change in its local address prior to completing the handshake. </del>  Clients might receive multiple tokens on a single connection.  Aside from preventing linkability, any token can be used in any connection times in a short period.  Servers are encouraged to allow tokens to be used only once, if possible. <del> 8.1.3. </del>  An address validation token MUST be difficult to guess.  Including a large enough random value in the token would be sufficient, but this

Question for reviewers: do you think it necessary to point out that this distinction doesn't need to be hidden from other entities? That's almost obvious based on the way that this results in different behaviour from servers, but should it be written down?
We currently say that these tokens SHOULD be distinguishable by a server on the basis that an error in a NEW_TOKEN token is recoverable, but an error in a Retry token is fatal. I don't see why this can't be MUST. If we make it "MUST", then we can rely on this property in the design.
Agreed, that'd be a useful property to rely on.
Does that property need to hold if there are bit flips in the token? If you’re encrypting the token, this would be easy to ensure: put the distinguishing bit under crypto cover. If you’re using a map lookup, you’ll probably have to work a little bit harder.
Please ignore my last post, I mixed up Retry packets and Initials. Tokens in the client’s Initial are part of the AAD, so we don’t have to worry about bit flips.
One suggestion to change a normative to a MUST, on existing text.Some small suggestions, but LG

8.1.1. <ins> A token sent in a NEW_TOKEN frames or a Retry packet MUST be constructed in a way that allows the server to identity how it was provided to a client.  These tokens are carried in the same field, but require different handling from servers. 8.1.2. </ins> Upon receiving the client's Initial packet, the server can request address validation by sending a Retry packet (packet-retry) containing a token.  This token MUST be repeated by the client in all A flow showing the use of a Retry packet is shown in fig-retry.  <ins> 8.1.3. </ins> A server MAY provide clients with an address validation token during one connection that can be used on a subsequent connection.  Address the token provided in a Retry for future connections.  Servers MAY discard any Initial packet that does not carry the expected token.  Unlike the token that is created for a Retry packet, there might be some time between when the token is created and when the token is subsequently used.  Thus, a token SHOULD have an expiration time, time.  A server can store the expiration time or include it in an encrypted form in the token. <ins> A token issued with NEW_TOKEN MUST NOT include information that would allow values to be linked by an on-path observer to the connection on which it was issued, unless the values are encrypted.  For example, it cannot include the previous connection ID or addressing information.  Information that allows the server to distinguish between tokens from Retry and NEW_TOKEN MAY be accessible to entities other than the server. </ins> It is unlikely that the client port number is the same on two different connections; validating the port is therefore unlikely to be successful. A token allows a server to correlate activity between the connection where the token was issued and any connection where it is used. Clients that want to break continuity of identity with a server MAY  <ins> discard tokens provided using the NEW_TOKEN frame.  In comparison, a token obtained in a Retry packet MUST be used immediately during the connection attempt and cannot be used in subsequent connection attempts. A client SHOULD NOT reuse a NEW_TOKEN token for different connection attempts.  Reusing a token allows connections to be linked by entities on the network path; see migration-linkability.  A client MUST NOT reuse a token if it believes that its point of network attachment has changed since the token was last used; that is, if there is a change in its local IP address or network interface.  A client needs to start the connection process over if there is any change in its local address prior to completing the handshake. </ins> Clients might receive multiple tokens on a single connection.  Aside from preventing linkability, any token can be used in any connection times in a short period.  Servers are encouraged to allow tokens to be used only once, if possible.  <ins> 8.1.4. </ins> An address validation token MUST be difficult to guess.  Including a large enough random value in the token would be sufficient, but this


tracks the Known Received Count in order to identify which dynamic table entries can be referenced without potentially blocking a stream.  The decoder tracks the Known Received Count in order to be <del> able to send Insert Count Increment instructions (see insert-count- increment). If a header block was potentially blocking, the encoder infers from receiving a Header Acknowledgement instruction (header- acknowledgement) that the decoder has received all dynamic table state necessary to process that header block.  If the Required Insert Count of the acknowledged header block is greater than the current Known Received Count, the encoder updates the Known Received Count to the value of the Required Insert Count of the acknowledged header block. To acknowledge dynamic table entries which are not referenced by header blocks, for example because the encoder or the decoder have chosen not to risk blocked streams, the decoder sends an Insert Count Increment instruction (see insert-count-increment). </del>  2.2. Insert Count has already been acknowledged, that MUST be treated as a connection error of type "HTTP_QPACK_DECODER_STREAM_ERROR".  4.4.2. When a stream is reset or reading is abandoned, the decoder emits a The Insert Count Increment instruction begins with the '00' two-bit pattern, followed by the Increment encoded as a 6-bit prefix integer. <del> The value of the Increment is the total number of dynamic table insertions and duplications processed by the decoder since the last time it sent a Header Acknowledgement instruction that increased the Known Received Count (see known-received-count) or an Insert Count Increment instruction.  The encoder uses this value to update the Known Received Count, as described in state-synchronization. </del>  An encoder that receives an Increment field equal to zero, or one that increases the Known Received Count beyond what the encoder has

Fixes issue . This PR fixes the race condition and clarifies that both endpoints must keep track of the Known Received Count. Also remove "If a header block was potentially blocking" phrase from Known Received Count section, because it is redundant with the clear instructions on whether to increase the Known Received Count in the next sentence. Also remove "for example because the encoder or the decoder have chosen not to risk blocked streams" and defer to new-table-entries instead, so that all guidance on emitting Insert Count Increment instructions is in one place. Note that the decoder has no way to know if the encoder chose not to risk blocked streams, so I find this phrase confusing to start with.
Assume a decoder receives the following data: A: the first dynamic table insertion, B: the second dynamic table insertion, C: the third dynamic table insertion, X: a header block with required insert count of one. A, B, and C are guaranteed to be received in this order, because they are all on the encoder stream. X arrives on a request stream, so its order relative to the insertions is not guaranteed. Consider the orders A, B, X, C, and A, X, B, C. Suppose the decoder sends a Header Acknowledgement of the header block as soon as it is received (all necessary entries are available by that time). This Header Acknowledgement instruction increases the encoder's Known Received Count by one. Suppose that after receiving C, the decoder decides to send an Insert Count Increment instruction. According to URL, "The value of the Increment is the total number of dynamic table insertions and duplications processed by the decoder since the last time it sent a Header Acknowledgement instruction that increased the Known Received Count (see Section ) or an Insert Count Increment instruction." In the first case, one insertion (C) was processed since the last Header Acknowledgment instruction that increased the Known Received Count was sent. In the second case, two insertions (B and C). I'm ready to address this issue with a PR but waiting for review of issue 2942 which would conflict.
Ah, it needs to explicitly reference the Required Insert Count from the header block that was acked, rather than "since the last time". I think this is editorial, since that's the intent of the text.
I agree that it's editorial. In I'm already proposing to mandate both the encoder and the decoder to keep track of the Known Received Count. Then Insert Count Increment could simply increase Known Received Count, without any references to previously acked header blocks if any etc.
Fixed by

tracks the Known Received Count in order to identify which dynamic table entries can be referenced without potentially blocking a stream.  The decoder tracks the Known Received Count in order to be  <ins> able to send Insert Count Increment instructions. A Header Acknowledgement instruction (header-acknowledgement) implies that the decoder has received all dynamic table state necessary to process corresponding the header block.  If the Required Insert Count of the acknowledged header block is greater than the current Known Received Count, Known Received Count is updated to the value of the Required Insert Count. An Insert Count Increment instruction insert-count-increment increases the Known Received Count by its Increment parameter.  See new-table-entries for guidance. </ins> 2.2. Insert Count has already been acknowledged, that MUST be treated as a connection error of type "HTTP_QPACK_DECODER_STREAM_ERROR". <ins> The Header Acknowledgement instruction might increase the Known Received Count, see known-received-count. </ins> 4.4.2. When a stream is reset or reading is abandoned, the decoder emits a The Insert Count Increment instruction begins with the '00' two-bit pattern, followed by the Increment encoded as a 6-bit prefix integer.  <ins> This instruction increases the Known Received Count (see known- received-count) by the value of the Increment parameter.  The decoder should send an Increment value that increases the Known Received Count to the total number of dynamic table insertions and duplications processed so far. </ins> An encoder that receives an Increment field equal to zero, or one that increases the Known Received Count beyond what the encoder has


9.4. The capacity available on the new path might not be the same as the <del> old path.  Packets sent on the old path SHOULD NOT contribute to </del>  congestion control or RTT estimation for the new path. On confirming a peer's ownership of its new address, an endpoint MUST

The intent here was that either (a) you only ever have a singe CC context and you reset it for each path, or (b) you have a CC context for each path, in which case each context is strongly tied to a path and you don't need to reset anything (except when it's been a while and the previous values may no longer be valid, etc.). We require that everyone does at least (a), while not preventing folks who want to do extra work for a potential performance gain from doing (b). Packets can contribute to the CC/RTT estimation for the old path if it's still around and you're willing to maintain that state, which is why this (mistakenly) was a SHOULD.
This looks good. I'm calling it editorial. However, it changes normative language, so I want an opinion on that from at least NAME or NAME before merging (other opinions are also highly valued).
+1 that we should require this behavior. We know that implementers will try out tricks here if they want to, and there's no enforcement to stop that from happening.
I agree that this seems generally editorial, but either way I think this is ready to go?
This is awkward, because the issue of assumptions about the path appears spread over multiple places. Section 9.4 of draft-ietf-quic-transport : The capacity available on the new path might not be the same as the old path. Packets sent on the old path SHOULD NOT contribute to congestion control or RTT estimation for the new path. GF: That would be fine IF the new path were a separate CC context, which I am not yet clear whether it is, read on: — Section 9.4 of draft-ietf-quic-transport : On confirming a peer's ownership of its new address, an endpoint SHOULD immediately reset the congestion controller and round-trip time estimator for the new path. GF: Why not MUST? - because I think this is a new path, I think it can only be should if something can confirm this is the same set of network-devices and classifiers on the path. I don't know how you would know that, and I don't like the guess. — draft-ietf-quic-transport : For instance, a change in the client's port number is likely indicative of a rebinding in a middlebox and not a complete change in path. GF: So. maybe this is the guess, and I don't believe it! In a world of ECMP and other methods, this needs to be treated as a potential change of path. — Section 9.4 of draft-ietf-quic-transport: While multiple paths might be used during connection migration, a single congestion control context and a single loss recovery context (as described in [QUIC-RECOVERY]) may be adequate. GF: Well it could if the paths WERE known to be identical, but how would an endpoint guarantee that? Section 9.2 of QUIC transport seems to suggest the CC is reset for the new path, which I would agree with, but which part of QUIC recovery says that? — draft-ietf-quic-recovery: For instance, an endpoint might delay switching to a new congestion control context until it is confirmed that an old path is no longer needed (such as the case in Section 9.3.3). GF: This seems underspecified and novel to me. I think it may b quite OK to continue using the old path and the old CC, for a while. Same question: is this implying the two paths share a single CC and is there some real mechanism to detect that they are really the same set of network devices on path?
1) SHOULD NOT -> MUST NOT Suggestion: "New paths have new congestion control contexts, implementations can reset the existing congestion control context." 2) In keeping with the first resolution, it's a new CC, and whether a new CC is created or the existing one is reset is an implementation decision. 3) Instead of saying it's the same path, say that the path parameters(RTT and CC state) are likely to be similar. 4) This is resolved with the resolution to 1, we can decide where the implementation note should live. 5) (In Section 9.4 of transport) This is really "A path can be validated, but not switched to immediately, particularly in the presence of a potential attack. When you switch, you need to use a new(or reset) congestion controller."
For 5, we need to say a bit more about the fact that the attack is that when a sender receives data on a new path causing it to potentially reset its CC state immediately if it switches immediately.
For 1,2,4: I think this was always the intent -- some implementations don't want to have two CC contexts so they can reset it and start with zero assumptions about the new path. Implementations that are okay with having more than one will make a new one and use that on the new path, switching back to the old one if they use the old path and the information is still relevant (i.e. it's been <1 RTT and you decided the new path wasn't actually better -- pick some threshold I don't have a strong preference). 3: Yes, it's not necessarily exactly the same path. An endpoint that wants to put in extra effort in determining which paths are equivalent to make a smarter decision can do so while the default behavior is a complete reset every time (after confirming the migration). This also impacts (5) around some of the attacks. 5: I agree with NAME we should be a bit more explicit about preventing an off-path attacker from being able to cause continuous resets as you go back and forth between paths, that's a good case where you would want to keep CC state around (if you're being told to swap multiple times per-RTT you've got other issues, too, which are mostly addressed but might need some more text, likely in transport).
Discussed in Cupertino. NAME to propose a PR, current belief is that it will be Editorial (classifying as such); if not, we'll reclassify.
We've summarized some discussion for these above, but to go through the resolution plan for concrete changes for each one: The intent here was that either (a) you only ever have a singe CC context and you reset it for each path, or (b) you have a CC context for each path, in which case each context is strongly tied to a path and you don't need to reset anything. We require that everyone does at least (a), while not preventing folks who want to do extra work for a potential performance gain from doing (b). Addressing this in a PR: They can contribute to the CC/RTT estimation for the old path if it's still around and you're willing to maintain that state, which is why this (mistakenly) was a SHOULD. Yes, this should be a MUST, because the CC/RTT for that path has to be reset for a new address, either by making a new one for the new path and keeping the old one around for the old path, or by resetting your single one. This is now a MUST in the current editor's copy. A very good point! This is now: This seems to address the need for caution here. This is intended to say that you aren't required to maintain additional per-path state, it's adequate to reset it for each path. In recovery, the section entitled "Handshakes and New Paths" discusses initial values to use on a new path. In general, recovery seems to be an instance of CC/LR, so if transport instructed you to toss out the old one and make a new one, you'd be using initial values, etc. as specified in recovery. Transport does require that you reset CC/RTT a few paragraphs above (as noted in your previous comment): I don't think we're trying to specify a new mechanism to determine equality of paths across a network. The intent here is to recommend that an endpoint be a little bit cautious about throwing out its state before it knows it's really going to be sticking with the new path.

9.4. The capacity available on the new path might not be the same as the  <ins> old path.  Packets sent on the old path MUST NOT contribute to </ins> congestion control or RTT estimation for the new path. On confirming a peer's ownership of its new address, an endpoint MUST


4.1.2. <del> In this document, the TLS handshake is considered confirmed at an endpoint when the following two conditions are met: the handshake is complete, and the endpoint has received an acknowledgment for a packet sent with 1-RTT keys.  This second condition can be implemented by recording the lowest packet number sent with 1-RTT keys, and the highest value of the Largest Acknowledged field in any received 1-RTT ACK frame: once the latter is higher than or equal to the former, the handshake is confirmed. </del>  4.1.3. 4.10.2. <del> An endpoint MUST NOT discard its handshake keys until the TLS handshake is confirmed (handshake-confirmed).  An endpoint SHOULD discard its handshake keys as soon as it has confirmed the handshake. Most application protocols will send data after the handshake, resulting in acknowledgements that allow both endpoints to discard their handshake keys promptly.  Endpoints that do not have reason to send immediately after completing the handshake MAY send ack- eliciting frames, such as PING, which will cause the handshake to be confirmed when they are acknowledged. </del>  4.10.3.

I actually like this better than . It is simpler. NAME and I discussed the point that this solution might cause endpoints to lose some RTT and congestion signals since not all ACKs need to be received, and we believe that is ok. It might be useful to recommend that endpoints bundle ACKs when sending initial and handshake packets to allow the peer to get RTT signals as early and frequently as possible at the start of the connection, but that can be done separately.
Do we need a concept of handshake fully confirmed server side once HSD has been ACK'ed? For example to say that path migration cannot start before handshake is really complete and likewise key updates.
I like the simplicity of this proposal. The main downside I see is that it's possible for a server to never send HANDSHAKEDONE. We could solve that however, by saying: the server MUST send HANDSHAKEDONE in all its 1-RTT packets once its handshake is complete and until one of them is ACKed if the client receives an ACK for a 1-RTT packet it sent and has not seen HANDSHAKEDONE, it MUST close the connection with a PROTOCOLVIOLATION
I don't think that we need clients to be policing server behaviour. Especially if it requires creating abnormal sending policies for frames.
I tend to agree with NAME here. I do not think we should make the protocol complex just for enforcing the correct behavior. Regarding clients using the receipt of an ACK for a 1-RTT packet as the alternative signal, I agree that that'd work, but I am not sure if we need to write it down. Writing it down (or mandating it) gives less incentive for the server to send HANDSHAKE_DONE.
PS. The approach proposed in this PR has the following two properties: sending one-way signal from to the client, when the server reaches handshake complete use a frame as the conveyor of the signal It's good to see emerging consensus (on the first point), but it seems to me that some are concerned about the second point. Specifically, I see suggestions to use a continuous signal (URL), or an ACK as a signal too (see URL). If we are in fact unhappy with using a frame, then I might suggest using Key Update as the conveyor of the one-way signal. We can require the server to initiate a Key Update when the handshake is complete and to repeatedly send PINGs until it receives an ACK for one of them. That'd give us an continuous signal, and also removes the need for an alternative signal. And also the need to have a different frame.
NAME I think that 1-RTT packets could be sent from the server before the handshake is confirmed in which case the HSD frame cannot be sent. Or if that is not the case, then any 1-RTT packet could be used as a HSD signal and we wouldn't need the frame. But , I think it is a good idea to keep sending HSD frames in every packet until done because if the HSD frame is lost, there could be unnecessary delays. As to client policing, that wouldn't work due to the above because of loss and reordering.
While I am not too keen on NAME key update because it conflates issues, it has the one benefit that it uses a header bit for a continous signal.
I also had this thought, but flipping the key update bit doesn't force something to be sent until it's acknowledged, which this frame does. We could do both if we thought it was very important for every packet to have the signal, but I'm not convinced it's worthwhile.
The part I care about here is that we disallow some protocol features (such as migration) until the handshake is confirmed. So I want to make sure the server cannot prevent the client from confirming the handshake. If we explicitly write that the client receiving an ACK for a 1-RTT packet can treat that like receipt of HANDSHAKE_DONE, then I'm fine with this.
Looks great!
NAME see URL
Catching up on old PR's: The text says that the client can consider the handshake done when receiving a handshake done frame. Such a frame can only be sent in 1-RTT and the server can only send it when it completes the handshake. However, a MITM could inject a handshake done packet earlier in the flow. Even if the client is required to reject early handshake done frames, it might be a good idea to make it explicit that a handshake done packet MUST NOT be trusted before 1-RTT.
MITM or not, the client will terminate the connection with a Protocol Violation error if it receives a Handshake Done frame before sending the last handshake frame. We spent time debugging that during the Zurich interop.
At the moment, each endpoint confirms the handshake by it's own. That means that, a client might confirm the handshake and initiate a migration and that migration might complete while the server is transmitting Handshake packets, in case the 1-RTT acks sent by the client is repeatedly lost. This is a possibility, as we do not require 1-RTT acks to be bundled with packets carrying PATHCHALLENGE or PATHRESPONSE frames. However, we do not specify if the path used for sending Handshake packets migrate or continue to use the old path. In case it is the former, we also need to specify how the server fills in the SCID field of the Handshake packets it sends. Credit: NAME
I think there is a key principle we've previously agreed upon. The 5-tuple doesn't change during the handshake. That means we should not send Handshake packets down the new path.
NAME I agree. Assuming that is the case, we need to define a mechanism that allows the server to drop Handshake keys and the path before migration completes, or even better before the sends the first packet in response to a migration attempt. Otherwise, the server would be required to have a special-case for handle the original path. Proposed solution is .
I think this comes back to a lot of what NAME was bringing up during the interim -- there are other things that rely on having a explicit signal for the handshake being entirely complete, and this is one of them. Having a well-defined moment where we're entirely done and can move on from Handshake packets would make this a lot nicer.
NAME this appears to be addressed by , like the others in this cluster. Is that right?
NAME Yes. That's correct.
There's a loss pattern that QUIC cannot recover from: Assume client and server perform a handshake up to the point just after the server received the ClientFinished message. No packet loss happens so far. Both peers have now completed (but not yet confirmed) the handshake. Right after sending the ClientFinished, the client sends a 1-RTT packet (e.g. containing application data). This packet is lost. The server sends an acknowledgement for the ClientFinished (in a Handshake packet), and this packet is lost as well. Furthermore, the server also sends a 1-RTT packet (e.g. containing a session ticket). This packet is successfully received and acknowledged by the client. At this point the endpoint are in the following state: Since it received an acknowledgement for a packet sent with 1-RTT keys, the server now confirmed the handshake, and drops the Handshake keys. On the other hand, the client is still waiting for an acknowledgement for the ClientFinished, and retransmits it. It will however never receive an acknowledgement, since the server dropped the keys already.
Sounds like receiving a 1-RTT packet on the client side might need to be another way to confirm the handshake.
A server can send a 1-RTT packet before even completing the handshake, so a client receiving one doesn't mean anything. In this scenario, the client will continue retransmitting the last cryptographic handshake flight. That sounds terrible. But if it ever has cause to send 1-RTT data (as it does in Marten's example, which I'm not sure is relevant), then it will retransmit that data on PTO. When that is eventually acknowledged, that will confirm the handshake. The more troublesome scenario is where the client sends nothing, only acknowledgments. In that case the client will have a complete handshake that cannot be confirmed. Would it be enough to say that "unless an endpoint sends some ack-eliciting data in 1-RTT packets, the handshake might remain unconfirmed indefinitely" and leave it at that?
NAME Agreed. IIRC, the assumption in the design team was that the client would have something to send. +1. We may hint that "a client MAY send a PING packet to confirm the handshake."
NAME Unfortunately, that wouldn't work. As long as the client has outstanding crypto data, it will only retransmit the crypto packets, see URL NAME Agreed, although I'd prefer a SHOULD here.
FWIW, what I had in mind was something like "once the handshake is complete, endpoints SHOULD send ack-eliciting packets to confirm the handshake. If there is no application data to be sent, such packets can contain PING frames."
No I think the state needs to progress. Resources might be tied up and timeouts might be in place to weed out bad handshakes before a longer or indefinite idle timeout. I would say the client MUST send at least one ack-eliciting 1-RTT packet once it is able to do so, and within, say, 1 PTO.
If I understand the issue correctly, the problem is that server drops handshake key too early before client gets ack for its last handshake flight. Server can ack any client Short packet without client last Handshake flight. That means that sending any ack-eliciting 1RTT packet from client does not fix this issue. Coalescing missing Handshake and Short packet would suffer MTU overflow as disscused in PR. I think server has to wait for client's confirmation of handshake before confirming handshake. It might be good idea to reconsider explicit signal again?
From the : We could change this MAY to a SHOULD and it'll resolve this issue.
I'm sorry, but I'm having trouble following the initial example. You said that the client sent a 1-RTT packet and it got lost. In this case, it will continue to retransmit it and will eventually get an ACK, at which point the handshake will be confirmed and it will drop the keys and stop retransmitting ClientFinished. And as David indicates above, if the client is not sending at all, then it can just send ack-eliciting frames.
Unfortunately, it doesn't. As I described in the initial example, and in URL, the client won't retransmit 1-RTT data until the handshake is completed.
NAME well in that case, the source of the problem is the retransmission algorithm, not this. Why does it make sense to not retransmit 1-RTT data when there is an outstanding CFIN? Just consider the simple case where the client's second flight (CFIN + some data) is lost. When the PTO expires, you should resend all of that.
NAME The client could have sent an arbitrary amount of 1-RTT data, so you wouldn't want to retransmit all of this. As discussed on the PR (), this can have some weird interaction with the congestion controller as well.
Sure, you would, however, want to retransmit some of it
Technically, 1RTT packets can be sent on the PTO when there is unacked Handshake data, but you're correct that if there are two MSS or more of Handshake data, the client would repeatedly retransmit that data instead of sending any 1RTT packets as written today. I think MT's PR is heading in the right direction. NAME do you have an alternate suggestion of how to deal with this situation?
Discussed in Cupertino. With the change in and , we believe this deadlock issue is addressed. (also, NAME has a pending edit).
NAME My point is URL
Has anyone thought about revisiting implicit ACK's in handshake? It appears to me that many problems occur because of waiting for an ACK that doesn't happen?
IIUC, we agreed today in Cupertino that forbidding handshake key discarding () is the solution (at least for the time being), based on the assumption that it works. I am starting to wonder if it works as expected. Consider the case where the client migrates to a new path once the handshake is confirmed. That is in fact the recommended behavior when server-preferred address is used. But IIUC, the existing transport logic is designed based on the assumption that path migration happens only after the exchange of Handshake packets have ceased. I think I have found at least one issue: when the client intentionally migrates to a new path while still having unacknowledged handshake data, how does it choose the new client CID when sending the Handshake packet? Current design assumes that the server to pick the client CID from the pool of the CIDs it has received with the NEWCONNECTIONID frames. That works for 1-RTT packets, because 1-RTT packet does not have the Source CID field. But when using the long header packets, the sender choose the Source CID (the source being the client in this particular case). What is the solution here?
Regarding the way on discarding the Handshake keys, I think the failures the WG have made until now are that we have been looking for a clever method in trying to infer when a Handshake key can be dropped, and/or using 1-RTT packet to tell the peer that the peer can drop the handshake keys (the problem here is that the peer might continue retransmitting Handshake packets if the endpoint ceases to send 1-RTT packets). Based on the discussion in Cupertino, I think that if we are to drop the Handshake keys, we should adopt a safer approach. I'd argue that there exists a class of approaches that can be considered safe. That is to require an endpoint to retain the Handshake keys until the peer sends an explicit signal instructing the endpoint to drop the Handshake keys. Assuming that my observation in is correct, we'd also need to adjust the transition to the handshake confirmed state to when an endpoint sends and receives that message. That signal can be a new frame, a header bit, or a KeyUpdate.
What if each peer sends a required PING every 1/2 RTT until it sees a 1-RTT packet from peer or it times out using an aggressive version of PTO? Or something? And preferably replace handshake ACK's with implicit ACK's if possible.
Or better a each peer sends a handshake-PING (HSPING) such the a received HSPING indicates that the peer is still not happy. This differs from ACK's in that it is unsolicited and therefore cannot hang. EDIT: the details needs to be worked out - when to send HSPING on HS PN space and when to send on 1-RTT PN space, and when to stop. But I think this should stop deadlocks and eventually confirm the handshake. Ordinary 1-RTT can start before HSPINGS stop, but HS is not complete until HSPING's are no longer required to be sent. After that point the peer can go into a mode where HS keys can be dropped, keys updated, and paths migrated. Also the connection handler can use an optimized versions for post handshake logic.
NAME it sounds like you are recommending something like the previous RETIRE_KEYS proposal?
I would definitely prefer something like that over never getting rid of the handshake keys.
I still prefer an implicit signal, and can think we can get it to work. But I would much prefer an explicit frame to keeping it forever.
Why not simply prohibit migration until all handshake packets have been ACKed. There is no ambiguity here, as the server is forbidden from initiating migration.
When the handshake is confirmed, all the Handshake data is known to be delivered and Handshake recovery state can be discarded. Previously, this was the moment Handshake keys were discarded, so the recovery doc relied on text about key discard to discard recovery state. But now that the Handshake keys are staying around longer than the Handshake confirmed point, I'd suggest we add a paragraph to section 5.5(URL) like: "When the handshake has been confirmed, all data sent in Handshake packet is known to be delivered and all recovery state should be discarded." To be clear, this suggestion is not a hack. We should do this if we're going to keep Handshake keys past Handshake Confirmed(which every solution I believe requires?), regardless of how long or why. I'll note that the server can do this upon receipt of any 1-RTT packet, but waiting for an ACK isn't likely to delay dropping Handshake state significantly and is consistent between client and server.
NAME The problem here is that even in the case where an endpoint (e.g., server) has received all the handshake transcript and also the ACKs for all the ack-eliciting Handshake packets it has sent, the other side (e.g., client) might have still some data outstanding (due to ACKs being lost). NAME (Assuming that your presumption here is that the path for the Handshake packets do not change when the client migrates) It's not only the the receive-side state of the Handshake packets that you need to retain forever, but also the state related to that path (e.g., socket). I am not sure we'd prefer that. This might include CIDs that are retired.
NAME That might be the case (honestly I do not remember the details of the RETIRES_KEYS proposal), though I think I'm inclined to suggesting using a continuous signal (i.e., a Key Update or burning an unused header bit). The reasons being i) using a continuous signal allows us to abandon the Handshake keys earlier ii) we do not need to define a new signal that needs to be retransmitted in the transport draft, or rely on a particular behavior defined in the recovery draft that is not generally considered as necessary for a recovery logic (i.e. PTO "MUST" trigger a PING when there is no data to send).
I remember thinking RETIRE_KEYS was extraneous given all the implicit signals. But it seems that screwy-but-legal behavior seems to make those implicit signals risky. So let's stop being cute and just use the signal.
In response to NAME "when the client intentionally migrates to a new path while still having unacknowledged handshake data, how does it choose the new client CID when sending the Handshake packet?" I'm arguing the client should never have unacknowledged Handshake data when it migrates, because it waits for handshake confirmation to migrate and at that point, all Handshake data should be discarded. There is a possibility the server does not believe the handshake is confirmed but the client does. In that case, if the client didn't keep listening on the old path when it migrated AND the server didn't send anything in 1-RTT, there would be a problem. But don't we require both client and server to validate the path in this case, which would mean both would achieve handshake confirmation on the new path, if they had not done so previously?
NAME Yes. This is the case I am pointing out. I am not sure if there is a guarantee that handshake confirmation would be obtained before the path is validated, because the path might become validated while all the packets carrying 1-RTT ACKs get dropped. And at that point, the endpoints might cease sending 1-RTT packets to each other.
What NAME points out is a problem. Keeping HS keys forever prevents the deadlock, but it does so by admitting that HS packets can be sent forever. This means after migration as well. That is something we don't consider in the spec. NAME I don't think you're right about this: Handshake data is discarded when the handshake keys are dropped, not when confirmation is reached (see . Now with handshake keys forever, the client can keep retransmitting forever. And as NAME notes, the server can surely send keep retransmitting as well since if it hasn't reached confirmation yet. There are a few ways to curb this... but I'm strongly inclined towards an explicit signal from both sides indicating handshake confirmation, after which migration can be initiated.
There is a much larger issue at stake here. The currently published proofs for the TLS 1.3 handshake do not allow the server to process 1-RTT packets until the client finished has been verified. It does not mean there is an attack, because of the defensive design of TLS 1.3 which implicitly authenticates the transcript in the transport secret derivation. However, this is still a weaker argument than checking the finish, particularly with respect to downgrade attacks. If a server supports a transport ciphersuite with broken integrity (e.g. someone discovers a way to forge CCM8 tags), then clients of this server that support strong ciphersuites may still be attacked. There is also a clear risk with client authentication. If a server is configured to require a client certificate, but QUIC gives to the application 1-RTT packets before the client finished flight has been received, the application may incorrectly believe that this data has passed the client authentication check. A reliable way to prevent such issues is to mandate that every 1-RTT packet sent by the client before the client finished frame is acknowledged by the server must include in the same datagram a copy of the finished frame (in the form of a separate handshake packet, before the short 1-RTT packet). The overhead is limited because finished messages are short, so even with the overhead of the long packet headers and AEAD tag, less than 100 bytes are used in the datagram (leaving the rest for the 1-RTT packet). If this is done, it is possible to enforce that a server must have checked the client finished before processing 1-RTT packets from the client. Adding some separate signal for handshake confirmation is a terrible idea - in TLS, receiving the peer's finished message IS the the explicit handshake confirmation signal (which is why CCS is removed in 1.3). Restoring a transport-level signal is a clear step backwards and must be avoided.
Hi NAME since you're describing a "much larger issue" that I agree should be discussed, could you please open a separate issue? This issue is about a specific deadlock that can happen during the handshake, not about the security properties of the handshake.
This was closed erroneously... apparently we're not quite fully zen with github's AI yet.
Discussed in Singapore; CfC .
I like this. The HANDSHAKE_DONE has a clear point in time where it must be transmitted and retransmitted, and the client explicitly has something to send (ACKs) driving the handshake forward in a timely manner. Using the alternative key update proposal might work, but it couples somewhat separate problems tightly - e.g. if one parts will change in future versions. A done signal is a clean way to communicate this.Re-reviewing, since there have been a lot of commits since I reviewed before. Still looks good.Thanks for making changes, I support this.Ship it!LGTM

4.1.2.  <ins> In this document, the TLS handshake is considered confirmed at the server when the handshake completes.  At the client, the handshake is considered confirmed when a HANDSHAKE_DONE frame is received. A client MAY consider the handshake to be confirmed when it receives an acknowledgement for a 1-RTT packet.  This can be implemented by recording the lowest packet number sent with 1-RTT keys, and comparing it to the Largest Acknowledged field in any received 1-RTT ACK frame: once the latter is greater than or equal to the former, the handshake is confirmed. </ins> 4.1.3. 4.10.2.  <ins> An endpoint MUST discard its handshake keys when the TLS handshake is confirmed (handshake-confirmed).  The server MUST send a HANDSHAKE_DONE frame as soon as it completes the handshake. </ins> 4.10.3.


containing a random payload on the path to be validated. An endpoint MAY send multiple PATH_CHALLENGE frames to guard against <del> packet loss, however an endpoint SHOULD NOT send multiple </del>  PATH_CHALLENGE frames in a single packet.  An endpoint SHOULD NOT send a PATH_CHALLENGE more frequently than it would an Initial packet, ensuring that connection migration is no more load on a new

I really appreciate all of these PRs. It's easy to miss these things with larger changes.

containing a random payload on the path to be validated. An endpoint MAY send multiple PATH_CHALLENGE frames to guard against  <ins> packet loss.  However, an endpoint SHOULD NOT send multiple </ins> PATH_CHALLENGE frames in a single packet.  An endpoint SHOULD NOT send a PATH_CHALLENGE more frequently than it would an Initial packet, ensuring that connection migration is no more load on a new


the token. A server can also use a Retry packet to defer the state and <del> processing costs of connection establishment.  By giving the client a different connection ID to use, a server can cause the connection to be routed to a server instance with more resources available for new connections. </del>  A flow showing the use of a Retry packet is shown in fig-retry. The server includes a connection ID of its choice in the Source Connection ID field.  This value MUST not be equal to the Destination <del> Connection ID field of the packet sent by the client.  The client MUST use this connection ID in the Destination Connection ID of subsequent packets that it sends. </del>  A server MAY send Retry packets in response to Initial and 0-RTT packets.  A server can either discard or buffer 0-RTT packets that it

Two changes here: better explanation of why, and a validation requirement on clients.
From section 17.2.5: URL "This value MUST not be equal to the Destination Connection ID field of the packet sent by the client." This was originally added when multiple Retrys were supported. I don't think it's still necessary, but if it is, I'd like to add some rationale, because it's not obvious to me why this restriction is necessary. Past slides from Martin: URL
If I understand correctly, it's still necessary to prevent a MITM from performing a Retry (and then removing the token from the second Initial).
Fair point. Is that an 'attack' that we're concerned about? That sounds like NAME ideal DDoS device, actually.
Generally, I believe the goal is to prevent all unauthorized tampering with the handshake. If it happens, it should be detected and the handshake fails. I agree with this philosophy even though it does make the Retry scenario a bit trickier to offload. Also, even if that was allowed, designing a DDoS device around it would be tricky still. You'd have to handle the case where the peer only pads their packet up to 1200 bytes including the token, so if you removed it, the backend server wouldn't accept it.
Good point, it seems like we can fix this with an editorial PR to clarify the risk of not changing CID on Retry.
NAME the retry token is part of the initial encryption associated data, so if you remove it you'll need to recompute the crypto anyway. NAME can you elaborate on what the consequences of this attack are? It sounds like the attacker can close the connection by sending a bad retry. How is that different from having the attacker send a bad retry that changes the CID and contains a bogus retry token?
Q: Should the retry token be part of the TLS transcript? That might prevent a whole class of issues?
NAME The transport parameter ensures that part of the retry token is in the TLS transcript. If the retry token is protected by authenticated encryption (which it should) then transitively we already have tamper protection for the retry token. The issue here is that if the retry token is tampered with, then the connection is closed. But I don't see how changing the server connection ID during retry changes that though (as long as not changing the CID still requires sending the transport parameter).
NAME I think one worry was that if a middle box had the ability to change the CID using Retry, then they could more easily attack packet protection. NAME could probably add more here.
NAME NAME I think you are referring to (which is a general statement), with one of the possible attacks being URL
NAME I'm not sure I understand the attack, if the middlebox has access to the server's retry token key then it can do bad things, but I don't think that's worth protecting against.
NAME Yeah I think that the issues being described in the provided links has been fixed by introducing the ODCID TP (and therefore requiring the retry token key to be shared).
Thanks NAME . Given that, does anyone see a reason to require changing the CID with retry?
Consider the case where: Client sends Initial packet 0, no token, DCID A Client times out the Initial packet and sends Initial packet 1, no token, DCID A Client receives a Retry with ODCID = SCID = A (which is what you're proposing to permit) Client sends Initial packet 2, with token from Retry, DCID A Client receives a Retry with ODCID = SCID = A. Is this Retry a response to packet 1 from a stateless server with a high RTT, or a prohibited second Retry in response to packet 2? If packet 2 carries a different DCID, the client can tell by inspecting the ODCID field in the Retry packet. If packet 2 uses the same DCID as packets 0-1, it can't tell. Requiring a change allows the client to determine that the server isn't misbehaving in this case.
Thanks NAME , that makes sense. I'm not 100% sure detecting this class of bad behavior is worth the added requirement on the protocol though.
We had other reasons why this was critical, but we've changed other requirements in the spec that eliminated them. (For example, when the client was permitted to change the ClientHello following a Retry, you had to know which Initial the server wound up responding to or the handshake would fail.)
NAME can this be closed with no action?
NAME If we decide to not change the current requirement to change the CID, this issue should track adding editorial text explaining why we require changing it.
I'm happy for this to be marked as editorial and then we can explain why we require changing it.
Another reason, I think: By requiring it to change, the ODCID becomes something that the server can include in the TPs to ensure than an on-path attacker didn't inject a Retry before the Initial reached the server. It proves that the sender of the Retry was, if not the server itself, affiliated with it. Otherwise, the ODCID is potentially still in the client's Initial for the server to echo back without coordination.
By not allowing it to change you have a stronger assurance in that the server has to have a key. In other words, it strengthens the cooperation requirement. Having just implemented Retry (yeah, it is like that...) I don't see any advantage either side of this from an implementation perspective. Either way, the server either holds state or puts the odcid in the token. The client implementation is very slightly easier and more efficient if the key doesn't change.
NAME does that mean you're advocating for this to be a design issue again?
I won't do that if others don't think that, but it seems that at least NAME thinks it's worth doing.
In issue I was advocating for keeping the initial key the same (even across a Retry that changes the CID), but I don't have a preference for whether the CID MUST vs MAY vs MUST NOT change in a Retry.
OK, I think that I'm getting my wires crossed. I think that changing CID is valuable because it then forces the server to demonstrate continuity. It's keeping the keys constant (per ) that has the added value.
Thanks for the PR!

the token. A server can also use a Retry packet to defer the state and  <ins> processing costs of connection establishment.  Requiring the server to provide a different connection ID, along with the original_connection_id transport parameter defined in transport- parameter-definitions, forces the server to demonstrate that it, or an entity it cooperates with, received the original Initial packet from the client.  Providing a different connection ID also grants a server some control over how subsequent packets are routed.  This can be used to direct connections to a different server instance. </ins> A flow showing the use of a Retry packet is shown in fig-retry. The server includes a connection ID of its choice in the Source Connection ID field.  This value MUST not be equal to the Destination  <ins> Connection ID field of the packet sent by the client.  A client MUST discard a Retry packet that contains a Source Connection ID field that is identical to the Destination Connection ID field of its Initial packet.  The client MUST use this connection ID in the Destination Connection ID field of subsequent packets that it sends. </ins> A server MAY send Retry packets in response to Initial and 0-RTT packets.  A server can either discard or buffer 0-RTT packets that it


5.4. <del> A Retry or Version Negotiation packet causes a client to send another Initial packet, effectively restarting the connection process and resetting congestion control and loss recovery state, including resetting any pending timers.  Either packet indicates that the Initial was received but not processed.  Neither packet can be treated as an acknowledgment for the Initial. The client MAY however compute an RTT estimate to the server as the time period from when the first Initial was sent to when a Retry or a </del>  Version Negotiation packet is received.  The client MAY use this <del> value to seed the RTT estimator for a subsequent connection attempt to the server. </del>  5.5.

This section was well out of date. Also, this covers what needs to be retained across this transition better. Basically, the only thing that needs to go is the congestion and loss recovery state. The rest can stay; in fact, the crypto handshake has to stay.
Follow-up to . As NAME suggests, we might want to add text to the transport draft that enumerates what is reset: all state except for connection IDs, packet numbers and, optionally, an RTT measurement.
Thanks, nice update!

5.4.  <ins> A Retry packet causes a client to send another Initial packet, effectively restarting the connection process.  A Retry packet indicates that the Initial was received, but not processed.  A Retry packet MUST NOT be treated as an acknowledgment. Clients that receive a Retry packet reset congestion control and loss recovery state, including resetting any pending timers.  Other connection state, in particular cryptographic handshake messages, is retained; see Section 17.2.5 of QUIC-TRANSPORT. The client MAY compute an RTT estimate to the server as the time period from when the first Initial was sent to when a Retry or a </ins> Version Negotiation packet is received.  The client MAY use this  <ins> value in place of its default for the initial RTT estimate. </ins> 5.5.