available and unused connection IDs.  Endpoints store received connection IDs for future use and advertise the number of connection IDs they are willing to store with the active_connection_id_limit <del> transport parameter.  An endpoint SHOULD NOT provide more connection IDs than the peer's limit. An endpoint SHOULD supply a new connection ID when it receives a packet with a previously unused connection ID or when the peer retires one, unless providing the new connection ID would exceed the peer's limit.  An endpoint MAY limit the frequency or the total number of connection IDs issued for each connection to avoid the risk of running out of connection IDs; see reset-token. </del>  An endpoint that initiates migration and requires non-zero-length connection IDs SHOULD ensure that the pool of connection IDs An endpoint can cause its peer to retire connection IDs by sending a NEW_CONNECTION_ID frame with an increased Retire Prior To field. <del> Upon receipt, the peer MUST retire the corresponding connection IDs and send corresponding RETIRE_CONNECTION_ID frames.  Failing to retire the connection IDs within approximately one PTO can cause packets to be delayed, lost, or cause the original endpoint to send a stateless reset in response to a connection ID it can no longer route </del>  correctly. An endpoint MAY discard a connection ID for which retirement has been The payload of an Initial packet includes a CRYPTO frame (or frames) containing a cryptographic handshake message, ACK frames, or both. <del> PADDING and CONNECTION_CLOSE frames are also permitted.  An endpoint that receives an Initial packet containing other frames can either discard the packet as spurious or treat it as a connection error. </del>  The first packet sent by a client always includes a CRYPTO frame that contains the start or all of the first cryptographic handshake 0. The payload of this packet contains CRYPTO frames and could contain <del> PADDING, or ACK frames.  Handshake packets MAY contain </del>  CONNECTION_CLOSE frames.  Endpoints MUST treat receipt of Handshake packets with other frames as a connection error. length of the Connection ID field. The maximum number of connection IDs from the peer that an <del> endpoint is willing to store.  This value includes only connection IDs sent in NEW_CONNECTION_ID frames.  If this parameter is absent, a default of 0 is assumed. </del>  If present, transport parameters that set initial flow control limits (initial_max_stream_data_bidi_local, for different connection IDs, the endpoint MAY treat that receipt as a connection error of type PROTOCOL_VIOLATION. <del> The Retire Prior To field is a request for the peer to retire all connection IDs with a sequence number less than the specified value. This includes the initial and preferred_address transport parameter connection IDs.  The peer SHOULD retire the corresponding connection IDs and send the corresponding RETIRE_CONNECTION_ID frames in a timely manner. The Retire Prior To field MUST be less than or equal to the Sequence Number field.  Receiving a value greater than the Sequence Number MUST be treated as a connection error of type FRAME_ENCODING_ERROR. </del>  Once a sender indicates a Retire Prior To value, smaller values sent in subsequent NEW_CONNECTION_ID frames have no effect.  A receiver mandatory, was present though it is forbidden, or is otherwise in error.  An endpoint detected an error with protocol compliance that was not covered by more specific error codes.

NAME NAME As discussed in I added a minimum value of 2 for the TP, since values below this don't make any sense. Having an invalid default value seems wrong to me, so I also made 2 the default value for the TP. People might disagree if this a reasonable value.
I'm seeing some people asking for changes, but I am happy with Marten's responses to those. I'm going to mark the associated issues as .
In , we state that: I think that these are correct. However, we also state: I think this is incorrect. This should instead state that "Connection IDs that are unused and not retired are considered active, ..." That said, I think the name is confusing, as it is awkward to argue that the connection ID that is currently used is "active." Maybe we should rename the parameter to something like "spareconnectionid_limit."
Isn't it impossible to agree on whether a given connection ID was used? In particular, connection IDs sent on unusable paths won't be received by the issuer to allow them to know that they a no longer unused.
I hastily reused the term "unused" from the other text quoted above. So please interpret "unused" as "until receiving a packet with a previously unused connection ID."
FWIW, the alternative approach (that could better match people's intuition) would be to retain the definition of "active connection IDs" as connection IDs that are issued and not retired are considered active, and change the normative texts. Actually, I think that this might be a better choice, due to the following two reasons: Current text requires you to issue new CIDs under two conditions; i.e., when receiving a packet carrying a previously unused CID, or when receiving RETIRECID. We can get rid of the former. Current design has a potential DoS vector that could lead to memory exhaustion on the CID issuer. This is because the number of CIDs that the current text recommends an endpoint to retain is activeconnectionidlimit + numberofCIDsinuse. The number of CIDs in use is something that the consumer of CIDs controls. Changing the number of CIDs to be retained to just the value of activeconnectionid_limit eliminates that attack vector.
NAME I agree, that was my understanding of the spec, since this is what the definition of the TP implies to me: I think it might be worth considering to slightly change the definition of the TP though, such that it includes the CID used during the handshake (and the one provided in the SPA). Otherwise, when the peer provides the requested number N of CIDs, you actually have to store N+1 CIDs, until you retire the CID used during the handshake. Furthermore, on the peer's side, after having provided N connection IDs, it must take care of NOT issuing a replacement for CID 0 when it is retired (whereas all other CIDs need to be replaced).
The implication, of course, is that 0 => infinite, but we don't actually say that anywhere. (This part might be editorial; it omits to say what we all know it means.) 1 is also a mostly-invalid value, since you can't avoid exceeding that limit without the peer dropping to zero CIDs in the interim and therefore stalling the connection for an RTT + any recovery time needed. Also, if the server uses , it has provided two CIDs before it knows the client's limit. This is fine, really, so long as the peer SHOULD NOT exceed the limit; if you send (or default to) a stupid value, the peer will ignore you and all is well. But if we take 's suggestion that exceeding the limit be a MUST NOT, the default needs to be infinite and values less than 2 need to be prohibited.
(Design issue please.) There is a lower point on the idle_timeout, which is . Having 0 imply is awkward, so I think that Mike's suggestion is the best outcome.
NAME I am literally not sure if this is the case. As I've pointed out in URL, the normative language suggests that activeconnectionidlimit actually means the number of "spare" CIDs that should be available, though the definition of "active connection ID" is defined differently. If we are to consider the normative language as being correct, it would be totally valid to set activeconnectionidlimit to 1 (i.e. I want one spare CID!), or 0 (i.e. I do not want any).
The definition of the transport parameter says: So sending a value of 0 (or, as it currently stands, omitting the TP), is a valid thing to do. It's an endpoint declaring that it will never, under no circumstances, change the connection ID, but to keep on using the connection ID provided during the handshake. Of course this also implies that connection migration is effectively disabled. We might want to decide to make this illegal though. Since the introduction of the Retire Prior To field on the NEWCONNECTIONID frame, implementations have the option to retire connection IDs in case their infrastructure requires that. A peer sending a value of 0 would prevent those implementations from rotating their connection ID mapping, which seems like an awkward special case. As NAME points out, sending a value of 1 doesn't make any sense, since you can't switch to a new CID without stalling the connection first. Therefore, I agree with NAME suggestion to forbid values below 2. I'm undecided if we should define a default value, and if so, what that value should be.
Discussed in Singapore; a bit more discussion and then CfC.
The parameters communicates the maximum number of connection IDs sent in NEWCONNECTIONID frames that an endpoint is willing to store. This value is only advisory though, there's nothing stopping an implementation from storing more CIDs than this limit, and the peer is allowed to send more CIDs than this limit: We don't specify any behavior in the case the peer provides more CIDs than this limit, but since retiring a CID asks the peer to provide a new CID, it seems like the reasonable thing to do would be to silently drop the CID on the floor: This means that a peer cannot fulfill its duty of retiring those discarded connection IDs when receiving a NEWCONNECTIONID frame with Retire Prior To set: This, however, is not guaranteed, either. A peer might decide to send a RETIRECONNECTIONID frame for a sequence number that it already forgot. Furthermore, depending on how implementations decide with all these uncertainties, one could end up in a situation where an endpoint only has a single active CID available, but its peer assumes that it is still keeping track of CIDs and thus won't provide any new CIDs. In this situation, the endpoint won't be able to initiate or respond to a connection migration any more, leading to a failure of the connection.
That was the previous recommendation if the peer provided too many CIDs and you didn't want to maintain any more, and I think that still applies if the peer doesn't respect the TP. For context, this came up in several issues, the TP came in via , with the being that it would be advisory only (hence the SHOULD). PR actually made the change which included that SHOULD, and also resolved the concerns from . helped to clarify some rate-limiting around how quickly you should cycle through CIDs. All that is to say, if you want to use the limit and explicitly communicate it with the TP, then you SHOULD do that. Everyone SHOULD do that. If not, or if your peer doesn't respect it, you can always dump those extra CIDs on the floor, with all the potential for running out that comes from taking that action. In this case, you end up maintaining as many as you wanted, probably the same value that you sent in the TP that the other side ignored... If/when the peer sends RetirePriorTo and requests that you retire them, you retire the ones you haven't yet retired and still know about. Generally, that means that you get a replacement for those, so now you still have CIDs. (Side note: The place this gets messy is if the peer suddenly decides to respect the value you put in the TP and doesn't replace them. This has always been a potential consequence of dropping them, though.) For the ones that you discarded, the peer should discard those 3PTO after it sent RetirePriorTo. From : So the peer that requested retirement can discard any state about them, and you're back to having everyone on the same page about how many are outstanding. That generally seems sufficient, although it would be great to cover another scenario or add some text that could be added to explain this better? If the issuing endpoint doesn't use RetirePriorTo then you're fine holding as many as you wanted to maintain forever. If they do use RetirePriorTo, then you're reset back to a good state. And if you ever get RetirePriorTo indicating an old value, you're also happy since you don't need to do anything.
Dropping connection IDs worked out fine before we introduced the Retire Prior To field. We added that field in , which was merged in July (about half a year after our discussion in Tokyo and the claryfing PRs). That's not what the spec says (but maybe what the spec intends to say?). If this was the only issue, we could probably easily resolve this by changing the wording to something like "MUST retire the corresponding connection IDs that it still remembers" or so. That might or might not happen. It all depends on the peer's assumption of what you did: Does it assume that you dropped connection IDs (if so, which ones?)? Does it assume that you actually enforced the limit that you announced in the TP by dropping all CIDs in excess of it? The situation gets even worse when you assume that a RETIRECONNECTIONID frame was in flight just when the endpoint provided the new CIDs. In this situation, there's no race-condition-free view of how many CIDs the peer was actually storing, so it's not possible to know how many of the new CIDs were actually stored and how many of them might have been dropped.
I think the model that NAME has described is accurate. At the same time, it seems alarming to me that we have failed to incorporate that model into the text. Specifically, as NAME points out, we have the following text: and FWIW also: that both assume that all connections IDs are expected to be retired. But as NAME points out, when the issuer provides more than what recommends, those connection IDs are not expected to be retired. I'd like to point out that the way we control the active CIDs sticks out from other "crediting schemes". For all other crediting schemes (i.e. MAXDATA, MAXSTREAMS, MAXSTREAMDATA), there is a hard limit. It is a protocol violation when the sender exceeds the allowed maximum. It is only for this activeconnectionidlimit - RETIRECONNECTIONID pair that we do not enforce the sender adhering the limit. Considering these aspects, and the fact that implementors are complaining about the complexity caused by "SHOULD NOT," I think it's worth considering changing the "SHOULD NOT" of "an endpoint SHOULD NOT provide more connection IDs than the peer’s limit" to "MUST NOT." The counterargument might be that it's an additional complexity; but I doubt if it's that important, compared to the importance of resolving the confusion that we have now. As NAME have stated, the issuer is excepted to stick to providing N active CIDs, where N is a constant throughout the lifetime of a connection. No other approach works. So the complexity that is to be added by changing the "SHOULD NOT" to "MUST NOT" is that the issuer would be required to set N to no greater than the value of "activeconnectionid_limit" provided by the peer. I think we can pay that cost for buying a consistent design (that avoids the confusion).
It seems like the problem we're facing here is that 3 things can happen with a new CID: It is stored for later use. It is retired via a RETIRECONNECTIONID frame. It is silently dropped. For the endpoint providing the CIDs, 1. and 3. are indistinguishable, which might lead to a mismatching view of how many CIDs are actually active. In the extreme case, this mismatch might mean that an endpoint believes that its peer has enough CIDs available, while it actually doesn't have any CIDs besides the one it is currently using, which would then make it impossible to migrate. At the very least, the indistinguishability between 1. and 3. leads to some non-trivial reasoning about what the peer might or might not have done, if you provide more CIDs than the peer advertised at any point in the connection. I agree with NAME that changing the "SHOULD NOT" to a "MUST NOT" would lead to a significant simplification here, since it would eliminate 3. from the list. With that change, a newly provided CID is active until it is retired. Furthermore, I don't think that there's any additional complexity here. On the receiver side, you'll need some logic to protect against a peer that is sending too many CIDs anyway. On the sender side, limiting the number to a constant value seems a lot easier than reasoning about which CIDs might have been dropped.
Changing to "MUST NOT" SGTM
I also agree that changing to a "MUST NOT" seems reasonable.
I think a MUST NOT is reasonable; there was considerable pushback in Tokyo for reasons I don't remember clearly. I don't think you can get out of sync provided the TP is at least 2. The other possibility, as an implementation, is to track which IDs you've retired/forgotten. Obviously, the point of not keeping all the CIDs is to reduce the memory consumption and this still requires keeping some memory. But it's worth pointing out that you don't actually need to have the CID in hand in order to promise you'll never use it again. For that matter, a naive implementation that wants to save memory at the cost of bytes could retire every CID sequence number up to RPT upon receipt of an increase RPT value, whether it has previously retired it or not.
I definitely agree that it could cut down a lot of complexity to change from SHOULD NOT -> MUST NOT, although we probably should check the minutes from Tokyo about why we didn't want that at the time. NAME for: I can see where it might be irritating to not know a precise count here, but I'm not sure the two sides actually need to be in sync about that. NAME point about being able to retire CIDs that you've forgotten is well taken, although it does run the risk that you'll get a replacement (although maybe you drop that too?). I think NAME argument that changing to a MUST NOT would bring us in line with the other crediting schemes makes a lot of sense, and it would bring us to a much nicer interaction model -- you ask for whatever you want, the peer gives that many to you. (Can they still give you fewer? I think the answer has to be yes, if it's costly to generate and maintain, then the issuer should control that.) I think it would drastically simplify if we made this MUST NOT. Happy to send a PR for that. Do we want a protocol violation if exceeded? We will likely need too along with this.
Another potential pitfall of "MUST NOT": Assume you've filled the peer's quota, but now need to revoke all prior CIDs and start a new generation. You have to send at least one NCID to carry the RPT and give the client something in the new generation of CIDs. Depending on the client's implementation, the limit might be exceeded during the processing of the frame. This can be addressed if the client takes actions in this order: Retire all unretired CIDs <= RPT, queuing the RCID frames for transmission Add the new CID to the connection Send the queued RCID frames using the new CID During this transition, though, there are zero active CIDs on the connection. A more savvy implementation might retire all but one of the old CIDs (so data flow never gets stalled), install the new CID, then retire the last old one. Of course, that gets trickier in a multipath world; you have to make sure that the one you keep is okay for the currently-active path. What the client can't do is add the new CID first, check the limit, then process any retirements; in that order, the limit gets breached.
There definitely was a reason we avoided making this a strict requirement, and I think this played into it, but this is the benefit of having RPT delivered in the same frame that delivers a new one. Perhaps modify the text to indicate that the error is if the limit is exceeded after retiring any in RPT? Or even some non-normative text along the lines of “If RPT retires all outstanding CIDS, immediately start sending new packets with the new CID and make sure you only check the limit after retiring any indicated in RPT”? On one hand, it’s kind of an annoying thing to keep track of. But on the other, making it a hard requirement does decently simplify a lot of other scenarios. That said, we should probably go dig up the other reasons we avoided this the first time around.
The aren't super informative.... NAME was against it being an error, and NAME thought it needed to be a hard requirement if present. Nothing else specific about who opposed it or why. I really wish we audio-recorded these meetings. This is why I try to transcribe the discussion rather than just minuting outcomes, chair comments notwithstanding.
Ah, thanks for looking that up! In that case, probably worth thinking through if there are other situations like this, but even if so I'm inclined towards the proposed change, albeit with additional text to cover the edge cases/provide implementation guidance.
NAME IIRC, some mildly opposed to the addition of activeconnectionid_limit because it's an unnecessary complexity, arguing that the model could be simple as: issue-some initially, then issue more as the peer uses the CIDs. After the discussion, the middle ground that we agreed was to make it an advisory settings. Note that we made this decision prior to the addition of Retire Prior To. But now, with the introduction of Retire Prior To, we have another way of retiring CIDs. I think that the simple model is no longer unsustainable.
NAME That matches what I remember from the discussion in Tokyo. I think we actually need normative text along the lines that NAME suggests. I don't think it's a problem that during the time you process a NEWCONNECTIONID frame the number of active connection IDs might temporarily reach 0, if we assume that these frames are processed atomically. And I can't really think of a reason why any implementation might want split processing of the Retire Prior To field and adding the new CID. I updated with a suggestion in 8354804a2a30580fd1b7deadb19f3baf3ad68fd8. WDYT?
I'm going to contradict me-from-yesterday a little, or at least complicate the point. The reason we have the 1-RTO window is that some implementations might not be able to actually retire CIDs instantly (packets are queued in hardware offloads, you want to avoid racing in-flight packets against the packet containing the RCID, etc.). If we require the retirement to be done as part of processing the frame, processing the frame can take up to an RTO. What happens to other frames in the packet? Does this packet get ACK'd if you're not done with this process yet? Plus, you can't actually send the RCID frames until you've got the new CID unless you have a race with one more RCID frame in a subsequent packet. This can get sticky very quickly. If I had an implementation where I couldn't retire CIDs right away, I would probably tag them for retirement upon receipt of this frame and say those that are tagged don't count toward the limit. But that's not entirely compliant with the text in your commit, and also assumes I have some headroom I'm not telling the peer about, because I have to remember all of them temporarily.
I agree that this would be a reasonable thing to do in this case. Clearly, if you have such an implementation, it's a highly optimized one that uses async processing and / or hardware offloads. I don't think it's too much too ask for that such an implementation takes appropriate steps to bridge the time it takes to retire a CID. Do you have any suggestion how to improve the text in the PR without getting too much into implementation details?
Perhaps to say that the peer MUST NOT consider the connection IDs it has been asked to retire when calculating whether the limit has been exceeded, even if they haven't actually retired them yet? That gives implementations the latitude to hang onto them for a bit if needed, but keeps you from going over the limit even so. Then it's the implementation's business whether it wants to defer adding the CID (and block processing of this frame), add it and exceed the limit temporarily, retire the old CIDs on the spot, or something else.
Right, I think the issues about packets that are already enqueued, etc. are more for the person requesting retirement of the CID to know that some packets might still show up (although potentially not after the ACK for the NCID frame that had RPT if they're all in the same queue? Not sure I'd want to rely on that ordering even before looking at the network). The endpoint that is retiring the CID can simply stop sending new packets with that CID and be fine. And leaving it up to the implementation but making a note that it's not a problem to retire/replace right up to the limit sounds good to me.
Proxying from the PR in regards to enforcing MUST error if the limit is exceeded: NAME NAME NAME NAME NAME
Having discussed this offline with NAME and NAME I now understand the new model that you are proposing. I'm fine with it. I'll leave my comments on the PR.
Having chatted about this with NAME and NAME I'm ok with this set of changes. A few suggestions.

available and unused connection IDs.  Endpoints store received connection IDs for future use and advertise the number of connection IDs they are willing to store with the active_connection_id_limit  <ins> transport parameter.  An endpoint MUST NOT provide more connection IDs than the peer's limit.  An endpoint that receives more connection IDs than its advertised active_connection_id_limit MUST close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. An endpoint SHOULD supply a new connection ID when the peer retires a connection ID.  If an endpoint provided fewer connection IDs than the peer's active_connection_id_limit, it MAY supply a new connection ID when it receives a packet with a previously unused connection ID.  An endpoint MAY limit the frequency or the total number of connection IDs issued for each connection to avoid the risk of running out of connection IDs; see reset-token. </ins> An endpoint that initiates migration and requires non-zero-length connection IDs SHOULD ensure that the pool of connection IDs An endpoint can cause its peer to retire connection IDs by sending a NEW_CONNECTION_ID frame with an increased Retire Prior To field.  <ins> Upon receipt, the peer MUST first retire the corresponding connection IDs using RETIRE_CONNECTION_ID frames and then add the newly provided connection ID to the set of active connection IDs.  Failure to retire the connection IDs within approximately one PTO can cause packets to be delayed, lost, or cause the original endpoint to send a stateless reset in response to a connection ID it can no longer route </ins> correctly. An endpoint MAY discard a connection ID for which retirement has been The payload of an Initial packet includes a CRYPTO frame (or frames) containing a cryptographic handshake message, ACK frames, or both.  <ins> PING, PADDING, and CONNECTION_CLOSE frames are also permitted.  An endpoint that receives an Initial packet containing other frames can either discard the packet as spurious or treat it as a connection error. </ins> The first packet sent by a client always includes a CRYPTO frame that contains the start or all of the first cryptographic handshake 0. The payload of this packet contains CRYPTO frames and could contain  <ins> PING, PADDING, or ACK frames.  Handshake packets MAY contain </ins> CONNECTION_CLOSE frames.  Endpoints MUST treat receipt of Handshake packets with other frames as a connection error. length of the Connection ID field. The maximum number of connection IDs from the peer that an  <ins> endpoint is willing to store.  This value includes the connection ID received during the handshake, that received in the preferred_address transport parameter, and those received in NEW_CONNECTION_ID frames.  Unless a zero-length connection ID is being used, the value of the active_connection_id_limit parameter MUST be no less than 2.  If this transport parameter is absent, a default of 2 is assumed.  When a zero-length connection ID is being used, the active_connection_id_limit parameter MUST NOT be sent. </ins> If present, transport parameters that set initial flow control limits (initial_max_stream_data_bidi_local, for different connection IDs, the endpoint MAY treat that receipt as a connection error of type PROTOCOL_VIOLATION.  <ins> The Retire Prior To field counts connection IDs established during connection setup and the preferred_address transport parameter (see retiring-cids).  The Retire Prior To field MUST be less than or equal to the Sequence Number field.  Receiving a value greater than the Sequence Number MUST be treated as a connection error of type FRAME_ENCODING_ERROR. </ins> Once a sender indicates a Retire Prior To value, smaller values sent in subsequent NEW_CONNECTION_ID frames have no effect.  A receiver mandatory, was present though it is forbidden, or is otherwise in error. <ins> The number of connection IDs provided by the peer exceeds the advertised active_connection_id_limit. </ins> An endpoint detected an error with protocol compliance that was not covered by more specific error codes.


An endpoint can cause its peer to retire connection IDs by sending a NEW_CONNECTION_ID frame with an increased Retire Prior To field. Upon receipt, the peer MUST retire the corresponding connection IDs <del> and send corresponding RETIRE_CONNECTION_ID frames.  Failing to retire the connection IDs within approximately one PTO can cause packets to be delayed, lost, or cause the original endpoint to send a stateless reset in response to a connection ID it can no longer route correctly. </del>  An endpoint MAY discard a connection ID for which retirement has been requested once an interval of no less than 3 PTO has elapsed since an for different connection IDs, the endpoint MAY treat that receipt as a connection error of type PROTOCOL_VIOLATION. <del> The Retire Prior To field is a request for the peer to retire all connection IDs with a sequence number less than the specified value. This includes the initial and preferred_address transport parameter connection IDs.  The peer SHOULD retire the corresponding connection IDs and send the corresponding RETIRE_CONNECTION_ID frames in a timely manner. The Retire Prior To field MUST be less than or equal to the Sequence Number field.  Receiving a value greater than the Sequence Number MUST be treated as a connection error of type FRAME_ENCODING_ERROR. </del>  Once a sender indicates a Retire Prior To value, smaller values sent in subsequent NEW_CONNECTION_ID frames have no effect.  A receiver

Consolidate Retire Prior To instructions in the section that discusses retiring CIDs and clean up the frame description. Left the operational requirements about the frame and its values with the frame, but all of the mechanism for requesting retirement of CIDs is now in one section.
In the section talking about Connection IDs (section 5.1.2) it says: In the description of the NEWCONNECTIONID frame (section 19.15) it says: So it's not clear if you just SHOULD or if you MUST send the RETIRECONNECTIONID frame. As an editorial remark, I find the existing text a bit hard to understand, since it's spread over these two sections. Maybe it would be easier to keep the frame description relatively short, and move more of the logic into the section that describes how CIDs are used and retired.
I think what we concluded in previous discussion was that you MUST retire them, and SHOULD do so in a timely manner. The suggested reorganization isn't a bad suggestion either.
Based on this feedback, this can probably be handled as an editorial matter. I would be happy to receive a PR that reconciled this with .
Definitely agree that this is editorial, we just missed this duplicate requirement in the original change. Removed that duplication and cleaned up the frame description a bit.

An endpoint can cause its peer to retire connection IDs by sending a NEW_CONNECTION_ID frame with an increased Retire Prior To field. Upon receipt, the peer MUST retire the corresponding connection IDs  <ins> using RETIRE_CONNECTION_ID frames.  Failure to retire the connection IDs within approximately one PTO can cause packets to be delayed, lost, or cause the original endpoint to send a stateless reset in response to a connection ID it can no longer route correctly. </ins> An endpoint MAY discard a connection ID for which retirement has been requested once an interval of no less than 3 PTO has elapsed since an for different connection IDs, the endpoint MAY treat that receipt as a connection error of type PROTOCOL_VIOLATION.  <ins> The Retire Prior To field counts connection IDs established during connection setup and the preferred_address transport parameter (see retiring-cids).  The Retire Prior To field MUST be less than or equal to the Sequence Number field.  Receiving a value greater than the Sequence Number MUST be treated as a connection error of type FRAME_ENCODING_ERROR. </ins> Once a sender indicates a Retire Prior To value, smaller values sent in subsequent NEW_CONNECTION_ID frames have no effect.  A receiver


components MUST be negotiated before being used.  For example, an extension that changes the layout of the HEADERS frame cannot be used until the peer has given a positive signal that this is acceptable. <del> In this case, it could also be necessary to coordinate when the revised layout comes into effect. </del>  This document doesn't mandate a specific method for negotiating the use of an extension but notes that a setting (settings-parameters)

Simply notes that coordination is hard in the face of SETTINGS, so it's likely to be better to replace than modify.
Resolution of was to have no design change, but consider a "here be dragons" warning in the extension section paralleling what was added to HTTP/3 in . (The reasons, and therefore the guidance, are different at the transport layer -- we can't directly steal the text.)
Unlike HTTP/2, HTTP/3 has no indication when the peer has received and processed SETTINGS. This means that any extension which redefines a basic protocol element (e.g. HEADERS uses something other than QPACK or uses a different static table; PRIORITY frames should be interpreted differently) has to define its own coordination mechanism -- and that coordination mechanism will be unwieldy (see ). would give the necessary ordering for server settings -- they are effective for all server data -- but would only make sense if one side considered the other's SETTINGS prior to sending its own. The server cannot do this in the current scheme or in for 1-RTT connections. Should we simply remove the misleading suggestions that elements of the mapping can be redefined, pointing people instead to creating replacement frames (HEADERS2, PRIORITYEX, etc.)? Or is the verbiage here that you're on your own to coordinate that switch sufficient hard-hat warning?
Thanks for creating this issue. I don't think using extension frames completely solves the problem because of the risks of changing the core behaviour so dramatically without endpoint coordination. For example, a client that sends requests only using HEADERS2 would need to wait for a HEADERS2 response before deciding that the extension is supported? That's a pretty big gamble to make. Given the challenge to design a solution and the reality that we don't need one right now (but that we are chartered to allow H2-like extension mechanisms) I'd be inclined to say remove the specific example and summarise that these components mean frames, unidirectional stream types, request/response message exchanges etc.
I think what a client would do is send HEADERS until it sees the SETTINGS frame and discovers it can use HEADERS2 from now on, including in 0-RTT in the future. No gamble involved, but the server has to continue to support the "old" elements as well.
Yes, that is how I would do it. The idea would be that once you advertise support for HEADERS2, you could receive one at any time, but you might still receive the old version for an indefinite period. You probably shouldn't even rely on seeing an ACK for the SETTINGS frame bits.
Your extension could define a frame that simply means, "I pinky swear I won't ever use the old frames again." But then you have all the same cross-stream coordination issues.

components MUST be negotiated before being used.  For example, an extension that changes the layout of the HEADERS frame cannot be used until the peer has given a positive signal that this is acceptable.  <ins> Coordinating when such a revised layout comes into effect could prove complex.  As such, allocating new identifiers for new definitions of existing protocol elements is likely to be more effective. </ins> This document doesn't mandate a specific method for negotiating the use of an extension but notes that a setting (settings-parameters)


entire request if the response does not depend on any portion of the request that has not been sent and received.  When the server does not need to receive the remainder of the request, it MAY abort <del> 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. </del>  4.1.1. The client's stream terminated without containing a fully-formed request. <del> The remainder of the client's request is not needed to produce a response.  For use in STOP_SENDING only. </del> The connection established in response to a CONNECT request was reset or abnormally closed.

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....)
I think a normal transport assumption is that a change of path implies a (potential) loss of congestion state, and that the endpoint should reset the path CC parameters. Transport-ID: “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.” • Does the "reset" require setting the CC to the initial window value for a connection? I think so. • Why not MUST? Transport-ID: “An endpoint MUST NOT return to the send rate used for the previous path unless it is reasonably sure that the previous send rate is valid for the new path. “ This could therefore be seen as a “significant” departure from conventional approaches considered safe - and extends this to “reasonably sure”. • Is there a rationale for extending this to “reasonably sure”, because this seems bolder than permitted for a new TCP connection. How do people quantify reasonably sure, and why not just in this case grow from the Initial window?
Yes. We should articulate this better though. To allow for pre-existing knowledge, such as stored values. I think this can be changed to say "unless an endpoint has other knowledge, it MUST reset" (or some such). Fair point. The intent here again was to allow pre-existing knowledge to be used, but I think the wording can be changed to reflect that more explicitly.
Discussed in London: NAME to provide a PR
Re-opening issue. This needs a bit more work.

entire request if the response does not depend on any portion of the request that has not been sent and received.  When the server does not need to receive the remainder of the request, it MAY abort  <ins> reading the request stream, send a complete response, and cleanly close the sending part of the stream.  The error code H3_NO_ERROR SHOULD be used when requesting that the client stop sending on the request 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. The client's stream terminated without containing a fully-formed request.  The connection established in response to a CONNECT request was reset or abnormally closed.


2.1. <del> The HTTP/3 specification is split into seven parts.  The document begins with a detailed overview of the connection lifecycle and key concepts: </del>  Connection Setup and Management (connection-setup) covers how an HTTP/3 endpoint is discovered and a connection is established.

I count eight including the additional resources or six not including them. But what I'm most concerned about is that Sections 1 and 2 are also part of the HTTP/3 specification.

2.1.  <ins> The following sections provide a detailed overview of the connection lifecycle and key concepts: </ins> Connection Setup and Management (connection-setup) covers how an HTTP/3 endpoint is discovered and a connection is established.


of the connection's RTT.  An endpoint computes the following three values: the minimum value observed over the lifetime of the connection (min_rtt), an exponentially-weighted moving average <del> (smoothed_rtt), and the variance in the observed RTT samples (rttvar). </del>  4.1. 4.3. smoothed_rtt is an exponentially-weighted moving average of an <del> endpoint's RTT samples, and rttvar is the endpoint's estimated variance in the RTT samples. </del>  The calculation of smoothed_rtt uses path latency after adjusting RTT samples for ACK delays.  For packets sent in the ApplicationData Implementations MAY experiment with absolute thresholds, thresholds from previous connections, adaptive thresholds, or including RTT <del> variance.  Smaller thresholds reduce reordering resilience and </del>  increase spurious retransmissions, and larger thresholds increase loss detection delay. The PTO period is the amount of time that a sender ought to wait for an acknowledgement of a sent packet.  This time period includes the <del> estimated network roundtrip-time (smoothed_rtt), the variance in the </del>  estimate (4*rttvar), and max_ack_delay, to account for the maximum time by which a receiver might delay sending an acknowledgement. When the PTO is armed for Initial or Handshake packet number spaces,

It's a mean deviation. This has confused at least one reader.
True-ish, but nor is smoothed_rtt a mean. And now you have a variable with a name that bears no semblance to the concept you use to describe it. You could instead play apologist and explain that this isn't a variance in the statistical sense, but variance in a purely colloquial sense.
Section 2 of RFC2988 says "To compute the current RTO, a TCP sender maintains two state variables, SRTT (smoothed round-trip time) and RTTVAR (round-trip time variation)." It does subsequently use the term variance in one spot, but given the original definition is a better one, I'd rather use variation than variance. RFC6298 does the exact same thing, and defines RTTVAR as "round-trip time variation".
In both sentences, minrtt and smoothedrtt are described in technically accurate terms, so I think it makes sense to describe rttvar as a mean deviation, because that's the most technically accurate term. I also understand the need for consistency, so if you have suggestions on how to improve that without removing the technical description, it'd be appreciated.

of the connection's RTT.  An endpoint computes the following three values: the minimum value observed over the lifetime of the connection (min_rtt), an exponentially-weighted moving average  <ins> (smoothed_rtt), and the mean deviation (referred to as "variation" in the rest of this document) in the observed RTT samples (rttvar). </ins> 4.1. 4.3. smoothed_rtt is an exponentially-weighted moving average of an  <ins> endpoint's RTT samples, and rttvar is the variation in the RTT samples, estimated using a mean variation. </ins> The calculation of smoothed_rtt uses path latency after adjusting RTT samples for ACK delays.  For packets sent in the ApplicationData Implementations MAY experiment with absolute thresholds, thresholds from previous connections, adaptive thresholds, or including RTT  <ins> variation.  Smaller thresholds reduce reordering resilience and </ins> increase spurious retransmissions, and larger thresholds increase loss detection delay. The PTO period is the amount of time that a sender ought to wait for an acknowledgement of a sent packet.  This time period includes the  <ins> estimated network roundtrip-time (smoothed_rtt), the variation in the </ins> estimate (4*rttvar), and max_ack_delay, to account for the maximum time by which a receiver might delay sending an acknowledgement. When the PTO is armed for Initial or Handshake packet number spaces,


endpoint before the final TLS handshake messages are received.  A client will be unable to decrypt 1-RTT packets from the server, whereas a server will be able to decrypt 1-RTT packets from the <del> client. </del>  Even though 1-RTT keys are available to a server after receiving the first handshake messages from a client, it is missing assurances on Any received 0-RTT data that the server responds to might be due to a replay attack. <del> Therefore, the server's use of 1-RTT keys is limited before the handshake is complete.  A server MUST NOT process data from incoming 1-RTT protected packets before the TLS handshake is complete. Because sending acknowledgments indicates that all frames in a packet have been processed, a server cannot send acknowledgments for 1-RTT packets until the TLS handshake is complete.  Received packets protected with 1-RTT keys MAY be stored and later decrypted and used once the handshake is complete. </del>  The requirement for the server to wait for the client Finished message creates a dependency on that message being delivered.  A

We really REALLY mean it. The effect of has been to shame some implementations into the right behaviour, but on reviewing this section, it really was unambiguous. This tweaks the text slightly and adds a cautionary note, but it is otherwise unchanged other than a few editorial tweaks.
I know this has been discussed before - but reading the text as it is I wonder: the text mixes several different terms, seeming arbitrarily: decryption of 1-RTT, processing of 1-RTT, and reading 1-RTT packets, and not using the 1-RTT keys at all. I can see how packets MUST not be processed ahead of time, but I don't see why the keys cannot be used to qualify the packets. In fact the text says you can store 1-RTT packets until ready, but how do you know they are indeed 1-RTT packets if you cannot use the keys in some form? There is a related concern about timing attacks if you do not both decrypt and verify but if that is not topic here, and if it is a concern it should probably be mentioned or referenced.
In it's becoming clear that some QUIC server implementations are using their 1RTT receive keys before getting CFIN, which is not supposed to happen. IMO, there are going to be plenty of hacky QUIC implementations but only a handful of TLS implementations, especially TLS implementations that have the special APIs required for QUIC. This is the narrow waist where we should enforce good behavior. Figure 5 of quic-tls shows that we shouldn't deliver the keys that early, but there's no normative language. I will file a PR shortly for quic-tls that imposes this requirement, so that QUIC implementations can't possibly mess this up.
I like that this class of problem is naturally assumed to require some sort of protocol change. As I said on , maybe those implementations can fix their bug. is a pragmatic sort of fix that would make this an editorial issue. I'd like that, but it sounds like there is a strong desire to go all nuclear on this. We should discuss that, or its spectre will haunt us.
I think it's worth noting this, though I tend to agree with NAME that we cannot write this down using a normative language because that is a requirement of TLS. That said, I actually wonder if we can have a section discussing Implementation Pitfalls. TLS 1.3 has an appendix dedicated to that (see ), and it has been extremely helpful to me. I think it would be nice to have something like that in the QUIC drafts.
I'm not sure of the rules here, but this requirement is on the QUIC-specific interfaces. People already have to modify TLS to comply with quic-tls.
IIUC, This requirement comes from the TLS handshake protocol defined in section 4 of RFC 8446 (specifically, ). TLS 1.3, DTLS 1.3, and QUIC all share the same handshake protocol.
Wouldn't this prevent the server from sending 0.5 RTT responses to 0-RTT?
Or, well, sending 0.5 RTT at all, not just for 0-RTT.
NAME Regardless of if we should use the normative language in QUIC-TLS, I think that RFC 8446, Section 4.4.4 clarifies what the requirements are, i.e.: do not use the 1-RTT keys until the Finished is received with the exception of 0-RTT and also with the exception of 0.5-RTT, while keeping in mind that the peer's identity or liveness has not been asserted.
NAME yeah, that makes sense thanks.
NAME No, just the receive keys.
NAME I understand that the key applicability is a general property of TLS1.3; however QUIC is the only one where the secret is actually consumed outside of TLS. quic-tls defines this interface and we have the right to impose constraints on it.
NAME As said, it is not only QUIC. DTLS 1.3 also uses the TLS 1.3 handshake protocol while having a different packetization layer ("records" in TLS term). That said, I am fine with letting the editors of the QUIC-TLS draft decide.
Calling this editorial, since it looks like it can be addressed without adding requirements.
Closing now that we have merged .
In TLS 1.3, 1-RTT secrets can be derived once the server finished is computed, when the handshake is half-complete. This allows the server to send application data without waiting for the client finished flight (a feature sometimes called 0.5-RTT in the TLS WG). This is safe because checking the peer's finished message is only required for receiving, and the finished must be received before any application traffic when using the TLS record layer. In QUIC, the server may receive 1-RTT packets before the client finished. This can happen because of packet loss, or an active adversary deliberately dropping handshake packets from the client, or because the client is malicious and does not send its finished flight at all. As far as I understand, the current QUIC spec allows the server to process the 1-RTT packets and hand them to the application without waiting for the client finished. Doing this falls outside the scope of what is currently proved secure about the TLS handshake, and it leads to multiple problems: Deadlock when the network drops the client finished flight: see Potential to bypass client authentication requirements: if a server is configured with required client certificate authentication during the handshake (e.g. for access control to a service), a client may send the request before the client finished flight. If QUIC passes the request to the application, it may incorrectly believe that the client has been authenticated. Potential for downgrade attacks: the client finished is used to explicitly authenticate the negotiation of algorithms in the handshake. Due to its defensive design, TLS 1.3 also implicitly authenticates the transcript in the transport secret derivation. However, the implicit authentication only holds for strong AEAD algorithms, whereas the explicit authentication relies on strong hash algorithms (and/or signature, depending on the mode). A reliable way to prevent all of these 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 packet (the same handshake packet may be sent in multiple datagrams). 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 short 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 a new transport-level signal for handshake confirmation (as proposed in ) is a bad 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). Instead, QUIC should simply ensure that the signal has been received. It's only an issue on the server side, because clients cannot decrypt 1-RTT packets without processing the server finished.
While this is true, I wonder if we have the issues you point out, because we prohibit the use of such packets until the server receives Client Finished. states that a server MUST NOT process data from incoming 1-RTT protected packets before the TLS handshake is complete, where "handshake complete" is defined as when the TLS stack has both sent a Finished message and verified the peer’s Finished message ().
Yes, I agree that this spec is correct in that respect. However, it is clear that several implementations are not implementing this restriction correctly, which is understandable given that it requires server to actively delay processing buffered packets (i.e. use memory resources). I think it is much better to enforce the correct behavior by requiring that the finished is repeated in every datagram until the handshake is confirmed by the server, because the server is guaranteed that it does not need a logic for received data that cannot be passed to the application.
This recommendation has been made a few times, but we have thus far resisted it on the basis of it not working with client authentication. The suggestion that the server might offer proof that it didn't do something bad is new. If I were inclined to take that suggestion seriously, I would instead say that the server should use an exporter to generate something. You could even see how this might make its way into the proposed HANDSHAKE_DONE frame. But I don't think that this needs this level of mechanism. This is something that servers just need to do on their own. Just like having a good source of randomness for their key generation, the consequences of failure here are that they might be open to attack. I have no problem with test suites that purposefully "drop" critical packets to reveal misbehaving servers. That might be better than adding mechanism. That said, the server can just withhold ACK frames until it sees the whole handshake, even if it processes the entire handshake. A server could answer requests from 1-RTT until clients start looking for that happening. Ultimately, we can't stop the server from performing processing if it offers no reliable proof of that happening. And I don't think that it's worth trying.
To help decide whether this is likely to be a problem, I decided to run an experiment. I wrote a very simple patch to OpenSSL to disable sending a finished: I checked with to check that the patch works as expected. Then, I tried to connect to various public h3-23 endpoints listed on URL using the ngtcp2 client built with my patched OpenSSL. The point of the experiment is to check which servers would acknowledge the short packets that I sent. Note that there can be false negatives in this experiment if the server is passing the 1-RTT data to the application but withholding the ACKS (this is insecure because the application may make unsafe side effects based on the received data, e.g. a POST/DELETE request). Two servers on the list clearly implement a wrong behavior: URL straight up acknowledged the 1-RTT packet in a short packethttp3-URL acknowledges the client 1-RTT packet, but does that in an handshake packet (!) It seems very likely that some implementations will ignore this requirement in practice (and understandably so, as buffering the 1-RTT packets is expensive), to the point that I think it is worth making the correct behavior verifiable by the client (as it somewhat exposes the client in the downgrade case, although a complete exploit seems unlikely with a signature, and difficult with a PSK binder)
In hindsight, the keyschedule for TLS 1.3 might have derived clientapplicationtrafficsecret0 from the same handshake context as resumptionmastersecret. Maybe in TLS 1.4? ;)
Yes... the reason it can't (currently) is because finished keys are derived from traffic secrets (which is also a mistake, especially when different transports use separate traffic secrets). Without changing the 1.3 key schedule QUIC could use the exporter MS to derive the client 1-RTT traffic secret.
NAME Thank you for sharing the results. This is very interesting. FWIW, I do not think QUIC stacks using picotls would have the issue, as picotls hands out the 1-RTT read key only after processing ClientFinished... but I can see that the API might vary between the TLS stacks. Though, if we are to have a protocol design that enforces the server to wait for CF, just bundling CF in every 1-RTT packet is not enough. I'd argue that CF should include some bits that is unpredictable by the server.
Like NAME our TLS doesn't even deliver the receive keys until it gets Finished. Rather than mess with the spec or rely on various QUIC implementations to do it right, I think it's best to fix the various TLS implementations (or, more specifically, their QUIC-facing secret export API) to do this correctly. There are relatively few of these and it's a problem specific to TLS 1.3. The ability to coalesce sent packets is optional and the spec goes to great pains to not require sending them at any point, which has caused much suffering for NAME Moreover, I am not at all convinced that coalescing Finished solves the problem.
This might be receptive to an interop-level test. Dropping Handshake packets from the client should lead to a situation where the client provides ample temptation for the server to break this rule.
NAME How does this help? Can't the exporter MS derived at the same time as the traffic secrets?
My bad for leading you astray there. I forgot that only the resumption secret is spun off after the client Finished. Yes, an exporter doesn't help. (As an aside, I'm fairly made exporters that way to allow them to be used with QUIC. Bit of a fail there in retrospect.)
Sorry, for some reason I was convinced that exporter master includes client finished, but after double checking, this is only the case of resumption master (honestly I think this is wrong because exporter are used for binding authentication and they pretty much fail at binding client authentication. I would suggest this is fixed in future TLS).
I agree, coalescing Finished is just a way to avoid server being blocked. The proper fix to the problem is to ensure that the client traffic secret for 1-RTT is derived from a transcript that includes client finished, as Subodh suggests. Unfortunately, only the TLS resumption master secret includes it.
Would it be unreasonable to derive the client's 1-RTT send key from the resumption master secret then? I guess one downside of this approach is that we'd be using different key derivation paths for server and client, since the server needs to be able to send 0.5-RTT data.
I'd advise against doing anything like that. A change to the key schedule is very disruptive.
Is it disruptive because we'd all have to update our implementations (I imagine it would just be one more HKDF call a QUIC stack would perform when the TLS stacks hands it the 1-RTT keys, wouldn't it?), or are there any cryptographic reasons not to do this? I'm just trying to understand the issue here better, not advocating for one way or the other at this point.
My feeble crypto brain says it would be fine, but it feels like enough of a deviation from TLS1.3 to need a formal security analysis.
Figure 5 of quic-tls is pretty clear on what should happen, but I think that section could use some normative text.
If we did something like this, it would likely trigger completely new analysis of the protocol. The thing that concerns me is that it would reduce or eliminate key separation between resumption keys and traffic keys, which is a concern. We'd be better off creating a new "secret" with the same transcript being input to its expansion. But that smells a lot like defining TLS 1.4 to me.
NAME it's disruptive also because of possible ongoing hardware design. That said, if it is important enough to fix ...
This is on purpose btw. We process the 1-RTT packet (as in, we decrypt it), but do not process the frames it contains until the handshake is complete. The reasoning behind this was to avoid having to buffer potentially invalid 1-RTT packets.
If you don't look at the frames but acknowledge the packets when do you stop buffering them? When the server is out of memory?
There's a limit on the number of packets buffered, something like "initial congestion window"-amount of packets.
NAME the transport spec says you MUST not acknowledge a packet until it's been processed. Given you're limiting the number of buffered packets, I don't see any obvious gain from trying to decrypt them before buffering them. The buffer will be cleared when the handshake completes no matter what.
Yeah, but that doesn't mean I should still buffer junk packets if I can avoid it....
As for the not ackng, sure, but that's a separate problem.
Agree it's off topic, but it's still very interesting for security research. I would never have expected implementations to do this and the security implications for stream integrity can be very surprising in some scenarios (such as compromised PSK), so thanks for clarifying that.
NAME can you point me where the draft says that? I can't seem to be able to find it in URL
It's the first sentence in 13.1: "A packet MUST NOT be acknowledged until packet protection has been successfully removed and all frames contained in the packet have been processed."
Sure, you can't ACK or process a packet before the key space is valid, but you can buffer them. There is not point in buffering garbage. Forcing keys to only be available after the key space is confirmed amounts to babysitting and there are plenty other things any endpoint can do wrong. Receiving an early ACK is ofcourse a protocol violation.
In interesting aside: how do you deal with max ack delay when you buffer packets that cannot be processed yet?
This feels like a great idea for Quic V2. Instead of just having 0-RTT and 1-RTT packets, also define 0.5-RTT packets that the sender can emit before the handshake is complete. (Or maybe call them sender side 0-RTT.) Use different key derivations for 0.5-RTT and for 1-RTT, and make the 1RTT key dependent on some nonce carried with the client finished message. Add parameters somewhere (TLS or Quic TP) explaining whether the client will accept 0.5RTT packets or discard them. But no, not today please, not when we are finishing V1. For V1, we should just stick with the spec we have.
Discussed in Singapore; .
I agree that this solves the issue IF implementations adhere to the spec. A change of the key schedule that would make it impossible to use 1-RTT keys before the completion of the handshake would still be my preferred solution of this issue. I'm not sure if this isn't out of scope though.Looks good. I like the idea of having normative text (MUST NOT) for 1-RTT packets, because that is a QUIC-specific requirement.

endpoint before the final TLS handshake messages are received.  A client will be unable to decrypt 1-RTT packets from the server, whereas a server will be able to decrypt 1-RTT packets from the  <ins> client.  Endpoints in either role MUST NOT decrypt 1-RTT packets from their peer prior to completing the handshake. </ins> Even though 1-RTT keys are available to a server after receiving the first handshake messages from a client, it is missing assurances on Any received 0-RTT data that the server responds to might be due to a replay attack.  <ins> Therefore, the server's use of 1-RTT keys MUST be limited to sending data before the handshake is complete.  A server MUST NOT process incoming 1-RTT protected packets before the TLS handshake is complete.  Because sending acknowledgments indicates that all frames in a packet have been processed, a server cannot send acknowledgments for 1-RTT packets until the TLS handshake is complete.  Received packets protected with 1-RTT keys MAY be stored and later decrypted and used once the handshake is complete. TLS implementations might provide all 1-RTT secrets prior to handshake completion.  Even where QUIC implementations have 1-RTT read keys, those keys cannot be used prior to completing the handshake. </ins> The requirement for the server to wait for the client Finished message creates a dependency on that message being delivered.  A


URL now we know how many holes it takes to fill the Albert Hall. This section has existed for a long time as "TBD". No longer. It also takes the opportunity to explain a little bit about what 0-RTT is for.
Retry is there, but at one level higher up (confirms that endpoints are willing to communicate ({{validate-handshake}})).
So we have little diagrams for send and receive stream states. Any chance this section could include a state diagram and transitions for the overall connection state?
There's a lot of text in the transport draft that assumes you know what 0-RTT / early data are. The term "early data" occurs only once, and doesn't even appear until describing the 0-RTT packet type. The introductory sections should probably contain some high-level description of Early Data, then point off to TLS for more detailed considerations. Purely editorial -- nothing new needs to be specified, but an uninitiated reader will need some context. This could possibly be part of .
I didn't see any other TBDs in the draft -transport doc or an existing issue, so opening this for tracking purposes.
Whoops!
I noticed you didn't write anything about Retry. Was that intentional?

5.3.  <ins> A QUIC connection is a stateful interaction between a client and server, the primary purpose of which is to support the exchange of data by an application protocol.  Streams (streams) are the primary means by which an application protocol exchanges information. Each connection starts with a handshake phase, during which client and server establish a shared secret using the cryptographic handshake protocol QUIC-TLS and negotiate the application protocol. The handshake (handshake) confirms that both endpoints are willing to communicate (validate-handshake) and establishes parameters for the connection (transport-parameters). An application protocol can also operate in a limited fashion during the handshake phase.  0-RTT allows application messages to be sent by a client before receiving any messages from the server.  However, 0-RTT lacks certain key security guarantees.  In particular, there is no protection against replay attacks in 0-RTT; see QUIC-TLS. Separately, a server can also send application data to a client before it receives the final cryptographic handshake messages that allow it to confirm the identity and liveness of the client.  These capabilities allow an application protocol to offer the option to trade some security guarantees for reduced latency. The use of connection IDs (connection-id) allows connections to migrate to a new network path, both as a direct choice of an endpoint and when forced by a change in a middlebox.  migration describes mitigations for the security and privacy issues associated with migration. For connections that are no longer needed or desired, there are several ways for a client and server to terminate a connection (termination). </ins> 5.4.


RESET_STREAM frame (frame-reset-stream) with an appropriate error code to terminate just the affected stream. <del> RESET_STREAM MUST be instigated by the protocol using QUIC. RESET_STREAM carries an application error code.  Only the application protocol is able to cause a stream to be terminated.  A local instance of the application protocol uses a direct API call and a remote instance uses the STOP_SENDING frame, which triggers an </del>  automatic RESET_STREAM. <del> Resetting a stream without knowledge of the application protocol could cause the protocol to enter an unrecoverable state. Application protocols might require certain streams to be reliably delivered in order to guarantee consistent state between endpoints. </del> Application protocols SHOULD define rules for handling streams that are prematurely cancelled by either endpoint.

This cribs Lucas' proposed tweak and makes a few other tiny adjustments.
The transport currently says It feels more correct to say I'll prepare a PR if my understanding and suggestion is correct.
It's a subtle point, but it seems OK. I do find the use of a possessive in that phrasing abhorrent though.
I made a stupid typo, whoops. Open to alternative phrasing. "Pulling the rug" comes to mind...
You're changing the meaning here, and I think the existing phrasing is correct. If you don't understand the application protocol, you don't know which streams are safe to reset and which will be connection-fatal. Telling the application protocol that you reset the stream doesn't make it any less fatal if you reset the wrong stream.
So looking at the paragraph in entirety I agree with NAME : directly or through the receipt of a STOPSENDING frame from a peer. RESETSTREAM carries an application error code. Resetting a stream without knowledge of the application protocol could cause the protocol to enter an unrecoverable state. Application protocols might require certain streams to be reliably delivered in order to guarantee consistent state between endpoints. Is the intention to describe why " RESET_STREAM MUST be instigated by the protocol"? The words following the normative text are a bit wooly/weasely. I think this paragraph slightly falls victim to the problem that you raised on URL but from the other side of the coin. Nowhere else do we use the phrase "protocol using QUIC" - it sounds a bit bcp56(bis).
Yes -- QUIC doesn't unilaterally reset streams, because it doesn't have the context to do so safely. The application layer on one of the peers has to do that, even if it does so via a STOP_SENDING transmitted by QUIC.
Small commentslgtm

RESET_STREAM frame (frame-reset-stream) with an appropriate error code to terminate just the affected stream.  <ins> Resetting a stream without the involvement of the application protocol could cause the application protocol to enter an unrecoverable state.  RESET_STREAM MUST only be instigated by the application protocol that uses QUIC. RESET_STREAM carries an application error code, for which the semantics are defined by the application protocol.  Only the application protocol is able to cause a stream to be terminated.  A local instance of the application protocol uses a direct API call and a remote instance uses the STOP_SENDING frame, which triggers an </ins> automatic RESET_STREAM.  Application protocols SHOULD define rules for handling streams that are prematurely cancelled by either endpoint.


the frame.  These frames are explained in more detail in frame- formats.  An endpoint MUST treat the receipt of a frame of unknown type as a connection error of type FRAME_ENCODING_ERROR.

It's still slightly odd where we have this setup between TLS and transport, but it seems like replicating the information might be enough.
Would it make sense to add PN space columns to the table of frames with section references, with an X where a frame is applicable? Transport section 12.4 URL
Looks good to me, and thank you for adding this column. Seeing frames like PATH_CHALLENGE/RESPONSE in 0RTT packets is strange, but clearly allowed by the spec. I don't see a reason to changing this, but it's why I think this column is useful.

the frame.  These frames are explained in more detail in frame- formats. <ins> The "Packets" column in frame-types does not form part of the IANA registry (see iana-frames).  This column summarizes the types of packets that each frame type can appear in, indicated as up to four characters indicating: Initial (packet-initial) Handshake (packet-handshake) 0-RTT (packet-0rtt) 1-RTT (short-header) A CONNECTION_CLOSE frame of type 0x1c can appear in Initial, Handshake, and 1-RTT packets, whereas a CONNECTION_CLOSE of type 0x1d can only appear in a 1-RTT packet. Section 4 of QUIC-TLS provides more detail about these restrictions. Note that all frames can appear in 1-RTT packets. </ins> An endpoint MUST treat the receipt of a frame of unknown type as a connection error of type FRAME_ENCODING_ERROR.


QUIC requires that the cryptographic handshake provide authenticated protocol negotiation.  TLS uses Application Layer Protocol <del> Negotiation (ALPN) RFC7301 to select an application protocol.  Unless </del>  another mechanism is used for agreeing on an application protocol, endpoints MUST use ALPN for this purpose.  When using ALPN, endpoints MUST immediately close a connection (see Section 10.3 in QUIC- TRANSPORT) if an application protocol is not negotiated with a no_application_protocol TLS alert (QUIC error code 0x178, see tls- <del> errors).  While RFC7301 only specifies that servers use this alert, QUIC clients MUST also use it to terminate a connection when ALPN </del>  negotiation fails. <del> An application-layer protocol MAY restrict the QUIC versions that it can operate over.  Servers MUST select an application protocol compatible with the QUIC version that the client has selected.  If the server cannot select a compatible combination of application protocol and QUIC version, it MUST abort the connection.  A client MUST abort a connection if the server picks an application protocol incompatible with the protocol version being used. </del>  8.2.

The old text used the non-specific "abort" here, but we can point to the right error code.
What does "abort" mean? Perhaps change this to "drop QUIC connection state"? Originally posted by NAME in URL
LGTM modulo the preference below.

QUIC requires that the cryptographic handshake provide authenticated protocol negotiation.  TLS uses Application Layer Protocol  <ins> Negotiation (ALPN) ALPN to select an application protocol.  Unless </ins> another mechanism is used for agreeing on an application protocol, endpoints MUST use ALPN for this purpose.  When using ALPN, endpoints MUST immediately close a connection (see Section 10.3 in QUIC- TRANSPORT) if an application protocol is not negotiated with a no_application_protocol TLS alert (QUIC error code 0x178, see tls-  <ins> errors).  While ALPN only specifies that servers use this alert, QUIC clients MUST also use it to terminate a connection when ALPN </ins> negotiation fails.  <ins> An application protocol MAY restrict the QUIC versions that it can operate over.  Servers MUST select an application protocol compatible with the QUIC version that the client has selected.  The server MUST treat the inability to select a compatible application protocol as a connection error of type 0x178 (no_application_protocol).  Similarly, a client MUST treat the selection of an incompatible application protocol by a server as a connection error of type 0x178. </ins> 8.2.


An endpoint MAY send data to an unvalidated peer address, but it MUST protect against potential attacks as described in address-spoofing and on-path-spoofing.  An endpoint MAY skip validation of a peer <del> address if that address has been seen recently. </del>  An endpoint only changes the address that it sends packets to in response to the highest-numbered non-probing packet.  This ensures

This is really just an observation, because there is no real way we can insist on people doing this sort of thing, but being aware of the performance implication of spoofed migrations might motivate solutions.
I think the sections that describe connection migration attacks should mention that, when the endpoint detects a spurious migration, it should revert back to using the same CC state and ECN state as before. Otherwise, an attacker could degrade the performance of a connection.
Perhaps the tag should be recovery, not transport.
I can see it going either way. NAME what do you think?
I think the text is going to be in transport, so that's probably the right tag
As long as this doesn't lead to confusion (for example: we recently tightened up the MUST around resetting state) for folks when reading this, I think it's really valuable to actually state this in the draft rather than hint at it throughout -- looks good!

An endpoint MAY send data to an unvalidated peer address, but it MUST protect against potential attacks as described in address-spoofing and on-path-spoofing.  An endpoint MAY skip validation of a peer  <ins> address if that address has been seen recently.  In particular, if an endpoint returns to a previously-validated path after detecting some form of spurious migration, skipping address validation and restoring loss detection and congestion state can reduce the performance impact of the attack. </ins> An endpoint only changes the address that it sends packets to in response to the highest-numbered non-probing packet.  This ensures


data for a terminated connection is to use the stateless reset process (stateless-reset). <del> An endpoint that receives an invalid CONNECTION_CLOSE frame MUST NOT signal the existence of the error to its peer. </del> 11.2. If an application-level error affects a single stream, but otherwise

If we get one, rely on the back-off that we require endpoints to implement when they enter the closing state. No need for any additional special processing rules (as mentioned in ).
Section 11.1 says: "An endpoint that receives an invalid CONNECTIONCLOSE frame MUST NOT signal the existence of the error to its peer." Editorially, it is not clear to me whether I am to suppress the error code in the CONNECTIONCLOSE frame or the error I generate when I receive an invalid frame. Moreover, I am not sure what constitutes an invalid CONNECTION_CLOSE frame. The words don't occur elsewhere. Section 19.19 doesn't define any fatal errors for this type. I can imagine a Reason Length that overruns the packet, or perhaps an undefined error code? A bogus frame type? We should probably define those in 19.19. Perhaps this sentence is an artifact from before we were throwing away Initial keys agressively?
If you suppress the error, does it matter which error you do not send?
I'm not sure what the intent of the sentence is. Do I send PROTOCOL_VIOLATION but not the error code in the frame? Do I send nothing?
As I read it, you should not send anything if you see CONNECTION_CLOSE. As to the motivation, I'm not quite sure, but if the peer closes but does it badly, chances are that anything you send just results in stateless resets. Maybe there can also be some malicious intend, especially in early handshake, but I don't think that is the reason.
I think this is addressing the injected packet case -- if you ignore an injected CONNECTION_CLOSE, you shouldn't send the real peer anything about it, or you've just allowed an attacker to inject authenticated traffic in the connection.
This is more about the looping case: sending CONNECTIONCLOSE in response to CONNECTIONCLOSE. But it is badly worded.
From the current text in 10.3 After sending a CONNECTIONCLOSE frame, endpoints immediately enter the closing state. During the closing period, an endpoint that sends a CONNECTIONCLOSE frame SHOULD respond to any packet that it receives with another packet containing a CONNECTION_CLOSE frame. To minimize the state that an endpoint maintains for a closing connection, endpoints MAY send the exact same packet. This will be a loop since the same invalid packet will be sent again by the sender ....
I'm going to request that this be re-assessed. When looking into Roni's comment, I realize that we aren't really clear enough about this invalid case and some (small amount of) discussion might be necessary to confirm that this change is OK.

data for a terminated connection is to use the stateless reset process (stateless-reset).  11.2. If an application-level error affects a single stream, but otherwise


6.1. <del> If a path has been verified to support ECN, QUIC treats a Congestion Experienced codepoint in the IP header as a signal of congestion. This document specifies an endpoint's response when its peer receives packets with the Congestion Experienced codepoint.  As discussed in RFC8311, endpoints are permitted to experiment with other response functions. </del>  6.2.

And note that 8311 is an update.
(Broken out of .) >A receiver maintains a cumulative sum of It would have helped me, had this explained what was intended by “control violations” - I think it means to detect when the sender sends more than permitted by the flow control limit. I think the control part comes in a later sentence? (Is this measured against the largest advertised offset, or the current offset? … given what is written about reducing the advertised value?)
one suggestion, but I'm approving

6.1.  <ins> If a path has been verified to support ECN RFC3168 RFC8311, QUIC treats a Congestion Experienced(CE) codepoint in the IP header as a signal of congestion.  This document specifies an endpoint's response when its peer receives packets with the Congestion Experienced codepoint. </ins> 6.2.


6. <del> QUIC's congestion control is based on TCP NewReno RFC6582.  NewReno is a congestion window based congestion control.  QUIC specifies the congestion window in bytes rather than packets due to finer control and the ease of appropriate byte counting RFC3465. QUIC hosts MUST NOT send packets if they would increase bytes_in_flight (defined in vars-of-interest) beyond the available congestion window, unless the packet is a probe packet sent after a PTO timer expires, as described in pto. Implementations MAY use other congestion control algorithms, such as Cubic RFC8312, and endpoints MAY use different algorithms from one another.  The signals QUIC provides for congestion control are generic and are designed to support different algorithms. </del>  6.1.

Also changes NewReno to Reno because of pedantry (not from Gorry).
I think this is design, so it's in limbo at the moment. Also, I need to re-read RFC8085 and ensure that MUST is viable.
Yup, the issue this address should be marked design.
The proposed commit says: "If an endpoint uses a different controller than that specified in this document, it MUST conform to the congestion control guidelines specified in {{!RFC8085}}." RFC 8085's section 3.1 appears to be the salient piece here, along with its reference to RFC 2914. A more direct pointer might be useful. I also note that you have a MUST pointer to what amounts to a set of guidelines which are all SHOULD or RECOMMENDED, which is a level lower. The result is likely to be a little less strict than the naive reader might believe.
If there are MUSTs we believe are important from 8085 and apply to QUIC and are not already MUSTs in other portions of the QUIC specs, I'd suggest we move those explicitly into this document and then either non-normatively reference 8085 or use a SHOULD for the rest of the document?
I'd always believed the intent was for the design for QUIC to fall within the scope of BCP145, which provides the check-list of SHOULD and MUST requirements for UDP-based protocol mechanisms. Moving blocks of requirements language and surrounding context that relate to QUIC from BCP145, seems like creating a lot of work, with very little positive outcomes. Citing this as normative to an implementation seems like it avoids much of this, and also much reduces the need for detailed checking in last call. Is there a reason to "move" to the QUIC document itself?
I suspect that Ian is looking to create a stronger requirement for congestion control than BCP145 has. BCP145 is full of SHOULD, with no MUST.
RFC8085 has basically only SHOULDs because it tries to cover all kind of cases of UDP-based protocols. So if we can be more specific for QUIC that would be good. However, QUIC is also meant as a general purpose protocol so I'm not sure we can. I think it is most important to have a pointer to RFC8085. Not sure if any other requirements can be made (other than don't break the Internet) unfortunately...
Rabbit-hole warning. There isn't a real set of requirements anywhere for congestion control articulated any better than what's in 8085. Gorry and I looked through existing documents, realized that we're lacking here, leading to Gorry's . I don't think it makes sense to move text out of 8085 here. The entire point of a normative reference is so we don't do that. Is there any specific text that you have in mind, NAME The MUST here is so that an implementer is required to consider the UDP guidelines in 8085, but each one of those SHOULDs is evaluated individually. Simply saying SHOULD conform to 8085 is quite different than that.
Jana, can I read your reply to Ian as "I agree, but there are no MUSTs we want to include, since the SHOULDs is BCP145 are the best we can do"? If so, maybe the right thing to do is close this issue with just the more precise pointer. I do believe, however, it would then be better to recast the sentence to make the pronounce/antecedent clearer. For example: If an endpoint uses a different controller than that specified in this document, the chosen controller MUST conform to the congestion control guidelines specified in Section 3.1 of {{!RFC8085}}. There are other ways to phrase it, of course. That would alleviate my concern that a naive reader might believe that they could roll their own behavior; that's particularly worrying when the very first item in 3.1 allows this: If an application or protocol chooses not to use a congestion- controlled transport protocol, it SHOULD control the rate at which it sends UDP datagrams to a destination host, in order to fulfill the requirements of [RFC2914]. It's all too easy to read that as allowing simple packet-pacing to suffice for QUIC here. If the reader knows that they have to pick a known congestion controller, we'll be better off.
Thanks, Ted, I take your point and your suggestion is a good one. I've reworded it as you suggested.
Thanks for the suggestion Ted. To confirm, by citing the section you're normatively citing only that section, not the entire doc. My concern is that 8085 is fairly long, and a good bit of it doesn't really pertain to QUIC.
The docs call out that the design is based on (new)Reno. That is good and suitable for a PS. I have no problem here. However, there are several places where the text is very permissive "MAY..." that suggests the PS can implement this or anything else. This combination seems awkward for a PS. I'd hoped that we could allow experimentation with new CC's but say SHOULD use an IETF-specified congestion control method, or something text close to that, and change the MAYs into something more helpful. I think this is an easy piece of text to get correct.
Please check appendices, e.g. " Constants used in loss recovery are based on a combination of RFCs, papers, and common practice. Some may need to be changed or negotiated in order to better suit a variety of environments." that final sentence needs thought.
If this goes into late-stage, it should be design.
I would like to suggest that RFC8085 is normative. This RFC was written as BCP for UDP usage, and QUIC implementations need to comply with that or describe the difference and reach consensus about why it differs.
I see RFC 8085 in the list of normative references already. Was there something in particular that you think we should be explicitly depending on?
...Or, I realize now, does this comment apply to the recovery document?
This was intended for the recovery doc, but maybe the fact transport already normatively references 8085 makes this decision easier?
Ahh, the comment still applies then. What specific items in RFC 8085 would this need to depend on? We don't just add normative dependencies without that.
Yes, this comment targets recovery. I think the editors can propose text referring to the draft - there is no intended change to the technical spec.
I'm sorry, I perhaps wasn't clear. This is not stated as an issue. "This document doesn't cite X" isn't an issue. Is there something specific that is in RFC 8085 that is needed to address an issue?
I suspect what I have in PR 3248 (URL) ought to address this. I think Gorry wanted to have normative reference for congestion control conformance.
Yes, and WFM - see single comment on the PR.
If this goes into late-stage, it should be design.

6.  <ins> This document specifies a Reno congestion controller for QUIC RFC6582. The signals QUIC provides for congestion control are generic and are designed to support different algorithms.  Endpoints can unilaterally choose a different algorithm to use, such as Cubic RFC8312. If an endpoint uses a different controller than that specified in this document, the chosen controller MUST conform to the congestion control guidelines specified in Section 3.1 of RFC8085. The algorithm in this document specifies and uses the controller's congestion window in bytes. An endpoint MUST NOT send a packet if it would cause bytes_in_flight (see vars-of-interest) to be larger than the congestion window, unless the packet is sent on a PTO timer expiration (see pto). </ins> 6.1.


2. Like HPACK, QPACK uses two tables for associating header fields to <del> indices.  The static table (see _table-static) is predefined and contains common header fields (some of them with an empty value). The dynamic table (see _table-dynamic) is built up over the course of the connection and can be used by the encoder to index header fields in the encoded header lists. </del>  QPACK defines unidirectional streams for sending instructions from encoder to decoder and vice versa. QPACK defines two settings which are included in the HTTP/3 SETTINGS frame. <del> The default value is zero.  See _table-dynamic for usage.  This is the equivalent of the SETTINGS_HEADER_TABLE_SIZE from HTTP/2. </del>  The default value is zero.  See blocked-streams.

xml2rfc v3 format prohibits the use of these, which will cause problems. This makes the anchors longer, so feel free to go with something else.
Had to merge it, but feel free to fix anchors.

2. Like HPACK, QPACK uses two tables for associating header fields to  <ins> indices.  The static table (see header-table-static) is predefined and contains common header fields (some of them with an empty value). The dynamic table (see header-table-dynamic) is built up over the course of the connection and can be used by the encoder to index header fields in the encoded header lists. </ins> QPACK defines unidirectional streams for sending instructions from encoder to decoder and vice versa. QPACK defines two settings which are included in the HTTP/3 SETTINGS frame.  <ins> The default value is zero.  See header-table-dynamic for usage. This is the equivalent of the SETTINGS_HEADER_TABLE_SIZE from HTTP/2. </ins> The default value is zero.  See blocked-streams.