QUIC allows the application to abruptly terminate (reset) individual streams or the entire connection when an error is encountered.  These are referred to as "stream errors" or "connection errors" and are <del> described in more detail in QUIC-TRANSPORT.  An endpoint MAY choose to treat a stream error as a connection error. </del>  Because new error codes can be defined without negotiation (see extensions), use of an error code in an unexpected context or receipt

This is the mildest way to Cautionary text in the permission to upgrade stream errors to connection errors. Purely editorial.
In HTTP/2, it has been easy for a proxy to forward upstream errors to client. When the upstream abruptly closes a H1 connection (i.e., closed before serving the correct number of bytes as suggested by the content-length header, or before trailers when chunked encoding is used), the HTTP/2 terminator could forward all the content that it received from upstream, then send an RSTSTREAM frame. Because HTTP/2 uses TCP, which guarantees the transmission order, the client receives the bytes that the origin has sent, before receiving a reset. In contrast, HTTP/3 does not have such guarantee. When a HTTP/3 proxy resets a request stream mid-response, the QUIC stack is allowed to cease transmitting data. Depending on when the HTTP/3 proxy receives input from upstream, and depending on how the responses are prioritized, there is fair chance that the HTTP/3 proxy would be resetting the request stream with HTTPREQUEST_CANCELED, without sending any response at all. Are we fine with this behavior change? We could argue that the root cause is the upstream failing to send the correct response. But the change of the failure mode might be an issue, because it would worsen the user-experience when the upstream misbehaves. For example, when an upstream server abruptly terminates the transfer of a JPEG image, a user connected through an HTTP/2 proxy would receive a partial image, whereas a user connected through an HTTP/3 proxy is more likely to see nothing (due to the reasons stated above). Related to:
PS. Note that HTTP expects intermediaries to forward these errors. If an intermediary erases this error, downstream might cache and reuse the corrupt response.
If the TCP connection is closed with a reset, there's no guarantee how many bytes the intermediary has read from the kernel buffer before the reset is received, so I don't think the behavior is likely to be that different. If there's an error at the HTTP layer, then I think there would commonly be a change in what the client sees, but that seems like a buggy backend, so I'm not sure it's worth attempting to deal with more gracefully?
NAME point is right: there's no guarantee what the proxy has read out of a connection that has received a TCP RST. Additionally, there's no guarantee that the H1 sender's pending data has been sent out of its TCP buffers yet either. NAME Do you think think this is somehow different?
NAME NAME Thank you for your responses. This is an HTTP issue, and it is NOT related to TCP resets. HTTP/1.1 does not distinguish between TCP FIN and RST, but has it's own framing; see . HTTP/2 does not (need to) distinguish between the two, it relies on HTTP/2 frames to see if an HTTP message has been transferred completely. My point is that a HTTP/1.1 or HTTP/2 intermediary acting as a "tunnel" (as defined in ), is able to forward unexpected termination of an HTTP message that it received from upstream to downstream. In HTTP/1.1, what a tunnel can do is abruptly terminate a chunked encoding (by closing the connection normally). In HTTP/2, a tunnel sends a RST_STREAM. In either case, the close signal never overtakes the partial payload. I am not sure what others do, but at least H2O have been strict about forwarding partial payload and error close as they were received from upstream; see URL The intention is to provide the same response to the user even when a server running behind returns a corrupt response. But in HTTP/3, because a stream reset can overtake the transmission of application data, it becomes very hard to act as a tunnel in such a way. The worst case is as follows: consider the case where an origin always returns an HTTP response (containing an image) that terminates abruptly (at the HTTP/1.1 or HTTP/2 layer). Note that the delivery of partial, progressive JPEG image might not have any negative effects on user experience. Or the origin might in fact be returning an entire image, but failing to terminate the chunked encoding correctly. When the connection between the tunnel and the client is HTTP/1.1 or HTTP/2, the tunnel can (and likely will) forward that partial response. But when the connection between the tunnel and the client is HTTP/3, it is very hard for the tunnel to deliver the partial response before the stream reset. Assuming that the partial response is of low priority, it is likely that the client would see nothing (rather than an image), as the stream reset signal would precede delivery of any stream data.
One possible solution to this issue is to allow a tunnel to intentionally send a malformed response to downstream, rather than resetting the stream. I've created that specifies such behavior.
I don't think that this is a good response to this condition. It creates a situation where an intermediary purposefully violates the protocol. If the connection is broken, then let it be broken. The intermediary can forward precisely what it received, ensure that that data was received, then reset the stream if it truly cares about fidelity. Note that the intermediary might also have to deal with gaps toward the tail of such a broken stream as well, and then they can't propagate that information with the same accuracy. We shouldn't permit a condition that the recipient can't distinguish from badness. The result of a change like this is to force clients to accept this junk as something other than a connection-level error. That means we can't catch badness that looks similar to this. For this particular kind of behaviour, I would expect connection errors in most implementations. I realize that HTTP/1.1 implementations do this all the time and that CDNs often have to patch up these situations, but this isn't patching it up, it's propagating the badness to the rest of the system. You are right the the badness is sometimes constrained. As you say, the trailing part of a progressive image often can be lost with no visible impact. This is an area where browsers might need to be tolerant of resets and retain at least what has already been displayed. You might want to be able to rely on that happening too, so maybe this is an area in which we pursue solutions in other areas (like Fetch).
NAME The problem here is that it is complex to ensure that the data was received before transmitting a reset. That's something that the QUIC transport does not have a concept, and I'm also not sure if it can be implemented in the HTTP/3 stack, because it is uncertain if we can except QUIC stacks to expose a signal indicating if data has been acked. We might argue that in-order delivery of stream resets was a feature of HTTP/2, and that we need it in HTTP/3. As you state, I think we'd better define a way to patch up these situations, otherwise people might suffer from worse user experience when using HTTP/3. Am I correct in assuming that your distaste is against the solution proposed in rather than against this issue itself? If so, would you be more open to using a HTTP/3 frame for indicating abrupt termination?
Look, I recognize the difficulty here, but I don't think that this problem suggests anything other than a (maybe) a new stream reset error code. I realize that pushes for widening of the HTTP/3-to-transport interface, but I can't see an alternative that does not create horrible externalities for this use case. Important as it might be for CDNs and the web, I don't think that it is a good idea to have the rest of the system bear the cost of dealing with the problem. I am very much opposed to the proposal in for previously-stated reasons. Enabling in-order delivery of stream resets would be a massive disruption to the protocol, even if we only did it at the HTTP/3 layer. I don't concretely know how to do that, but it implies a significant shift (or bifurcation) in the stream model if we did it at the transport layer. Adding in-order resets at the application layer for HTTP/3 is not trivial either. A frame on the same stream is not going to work because it prevents an intermediary from forwarding without reframing at that layer. Otherwise, they would not be able to guarantee that they can send any frame on the stream. That requires a big shift in how people approach building of intermediaries. Something on a control stream might work, but then you have the potential for ambiguity about placement relative to the target stream unless you effectively replicate the RESET_STREAM design at the application layer. That would make me very uncomfortable. It seems to me that you can address this without changing the protocol, even if it requires some awkward crossing of protocol layers. That's my preference.
NAME Thank you for sharing your views. I am not sure if it is correct to frame this issue as specific to CDNs, I might argue that it is a problem for intermediaries in general. I agree that we shouldn't try to introduce in-order delivery of stream resets at the transport layer. If we are to introduce in-order delivery, that should be done in HTTP/3. I think this is what I disagree. I think that many if not most of the intermediaries to be doing reframing. In case of HTTP/2, intermediaries should have been doing reframing by itself, otherwise it cannot prioritize the responses, I'd assume that proxies would be doing the same for HTTP/3 (though I might be wrong). If that assumption is correct, the fix would be as trivial as introducing a HTTP/3 frame indicating the error, which is sent on the request stream. It's ugly to have two ways of signaling error (one at QUIC layer and one at HTTP layer), but it'd help us provide same expectations across different versions of HTTP.
PS. Or, if we are to consider H3 tunnels that do not do reframing as within the scope of the specification, I think we need to have something like . Consider the case of a H3-H3 intermediary that parses the request, and binds HTTP requests to different origins, transmitting the response as-is without dealing with frames. Such a tunnel would work as expected, assuming that QPACK dynamic table is not used. In this type of design, a stream-level error (i.e. a stream closed normally while transmitting an incomplete H3 frame) does not indicate a connection-level error. Therefore, we need to state that.
I think I agree with NAME here. Adding in-order stream reset sounds extremely complicated at either the HTTP/3 or QUIC layers. The particular use case here does not seem compelling enough to warrant this complexity.
NAME As stated above, if it ought to be a complicated mechanism at the HTTP/3 layer depends on the how the HTTP/3 tunnel frames the response. As we've discussed previously in , an HTTP/3 intermediaries can and will have different framing strategies. When the strategy employed by an intermediary is to create a new DATA frame every time it emits something, sending a HTTP frame to indicate an error after sending all the HTTP response it has is trivial. All that said, I think that we might not need a new frame after all. We already allow a HTTP tunnel to send a malformed response (see ). That tunnel might be coalescing HTTP messages going to different endpoints. That means that an endpoint should not handle a stream that contains invalid DATA frames as a connection-level issue. I've changed to be a clarification of this requirement, which has been implicit until now. WDYT?
Ah! This sounds much better. No additional complexity in HTTP/3 or QUIC? SGTM.
Thanks, NAME -- this re-framing (pun alert!) helps quite a bit. What you now have in the PR makes sense. Do you mind changing the title and description of this issue to reflect that?
NAME Changed the title. Though I'm not sure if you like the new one. I think the only difference is that we are now talking about errors in the headers too. NAME Yes. That true for what we have now in . Though, unfortunately, I now realize that we have the : When a stream terminates cleanly, if the last frame on the stream was truncated, this MUST be treated as a connection error of type H3FRAMEERROR._ If we consider that a tunnel should be allowed to send DATA frames (or HTTP/1 chunks) that it receives from upstream as is, forwarding them frame-by-frame to downstream, and expect things to work, then we need change this provision to so that incomplete (DATA) frames at the end of the stream would be considered as a malformed response instead of a connection-level error. I would prefer making such a change, though I would anticipate hearing objections. Admittedly, it is an unnecessary complexity for clients, and for server deployments who control the origin. To this end, I wonder what other proxy vendors think. NAME NAME WDYT?
I wasn't following this discussion so closely, so IIUC the problem is that the stream cannot end abruptly or else there are no guarantess from QUIC layer that the DATA will arrive at the HTTP client. Closing cleanly would ensure DATA is delivered but triggers h3-24 section 7.1. To invoke the problem, an intermediary would have needed to committed a DATA frame of length , to stream offset , then attempt to fin the stream at offset , which is less than . I can see several scenarios that this might occur in an intermediary and it would be quite unfortunate to cause a connection close. Since request streams are the only core streams that can be cleanly closed without blowing up the connection I think the middle ground might be to state something like: an endpoint that receives truncated leading HEADERS or PUSH_PROMISE should treat it as a connection error. Truncation of DATA and non-leading HEADERS is ok. Extension frames or streams need to consider truncation and specify the behaviour.
This is not true, or at least not universal. It's entirely legitimate for a TCP stack to dump anything that's in buffers (unread by the application) and immediately surface the TCP RST. The reset can and does overtake partial payload in places. That's not my understanding of that paragraph. It says that if an intermediary receives a malformed response, it MUST NOT forward it and MUST treat receipt of the malformed response as a stream error. This is unclear in two ways we need to correct: First, it doesn't say whether the specified error should be sent upstream or downstream. I believe this intends to say it resets the upstream stream that sent it a malformed response. Second, assuming the first answer, it doesn't specify what to send downstream instead of forwarding the malformed response. I would propose that we need YAEC to signal an aborted response upstream. The suggestion that we send as much as we can, then cleanly terminate the stream disturbs me. I'm inclined to take a hard line here and keep the text that intermediaries shouldn't be forwarding known broken responses. That way lies consistency bugs. You forward data as long as it's valid; if it becomes invalid, then you forward a RESET_STREAM that indicates the upstream is no longer talking sense.
Ah, I see -- you're keying off the language that says an intermediary might not process the message and therefore might not notice that the message is malformed. I agree that an intermediary that deliberately doesn't check for message validity wouldn't trigger this requirement for things like missing pseudo-headers. I'm less sanguine about saying the tunnel could silently pass through things like truncated frames, unknown frame types, etc.
NAME NAME Thank you for sharing your thoughts. I can see the hesitation against requiring endpoints (clients) to consider partial frame at the end of stream as stream-level errors (rather than connection-level problem). Maybe what we want to discuss first is: Q1. Do intermediary developers (especially of those that talk to servers owned by different organizational entities) want to deliver the partial response that they've received from upstream to downstream, before tearing down the stream? Q2. Do we need a mechanism for signaling such behavior? That mechanism can be a partial frame at the end of the stream, or could be a new HTTP/3 frame. Or if the answer to this question is no, then, people wanting to do Q1 need to send the partial response body, wait for all the ACKs, then send a stream reset. My answer to Q1 is a strong yes, and Q2 is a weaker yes. I'd prefer having a simple signaling mechanism (partly because I think everybody should do Q1), but for myself, I can resort to the complex way of delaying the emission of stream reset.
It seems to me that we don't need to specify what tunnels ought to do but recognize what tunnels might do. The way I read it, Kazuho's proposed recommendation makes sense when a tunnel passes a partial response through, as H2O does and others might as well.
I am a yes for Q1. For Q2, if we wanted to pursue another frame type I don't (yet?) understand how it would work, won't the usefulness of the frame be subject to the size of DATA frames that people chose to use?
With TCP, typically the reset will arrive after the payload, so the typically the partial payload can be processed. How about using a similar recommendation for QUIC/H3: You CAN/SHOULD send all the data before transmitting the reset, but there's no need to guarantee it all arrives before sending the reset? If an implementation really wants to guarantee the payload arrives before the stream is reset, it can, but this seems like best effort. I don't want to add a new mechanism for this, but I agree it'd be nice to avoid the H3 connection being torn down in this case. I do think we should scope this case a bit tighter and be more specific than the current PR, since it currently says any incorrectly formatted response MUST NOT cause a connection close and I'm realizing how wide that might be.
I think there's a larger idea here: We took a series of PRs to make any protocol misbehavior connection-fatal. However, when there's an intermediary, it's not necessarily clear whether the misbehavior is on the part of the intermediary or the origin. And it's not necessarily clear to the client whether there's an intermediary, so it generally will have to assume one or the other all the time. The takeaway for me is that we overreached -- if the trigger for an error could be attributed to the origin rather than the intermediary, then the error should not be connection-fatal on the client-intermediary connection. If there are things in this class that we believe need to be connection-fatal anyway, the corollary is that the intermediary MUST check for and sanitize these things.
NAME I agree to your comments, with the only exception that I do not think we have "overreached." In my view, HTTP/2 is underspecified in sense that it allows any stream error to be promoted to a connection error, even though as we agree, a stream error that might have originated from an origin cannot be promoted to a connection error. I think it is an improvement in HTTP/3, to be more explicit about the distinction between the two types of errors. Therefore, even though I am not sure if I like the complexity of defining the type of error for every possible scenario, I'd prefer trying to do that until we fail now that we've crossed the bridge.
I think that we might disagree on this point. Even if the spec didn't expressly include this permission, you can always reduce this to a question of local policy. That is, my policy could be to cease communicating with a peer that fails to follow the spec. The effect of such a policy has been beneficial in that it provides some incentive for new implementations to follow the spec. The same policy has also been bad in those cases where this was overzealously implemented: if we can't used new frame types without explicit negotiation, that's not a great state to be in. I don't think "cannot" is agreed. We might agree on the outcome of propagating the error, but I personally don't think that we should externalize these effects even in this way. We might highlight the possibility of problems, but if an intermediary is forwarding streams without looking at their contents, it is still ultimately responsible for the effect of their contents. If clients decide to hold the intermediary responsible for a failing at an origin server that is invisible to them, that's still the intermediary's problem. I'm going to suggest that we acknowledge that this is tricky and then move on. I don't want to create another application of the Robustness Principle before we've even shipped the protocol.
NAME I might argue that we have had that principle. by default allows a client to coalesce requests going to multiple origins (assuming that the DNS lookup and the provided certificate allows that), then let the server opt-out (by sending 421). and talk about coalescing requests going to multiple origins. It is my understanding that we have collectively thought that coalescing connections when possible is beneficial for privacy and performance reasons (prevents leak of SNI, better network utilization by not having multiple congestion controllers between the same endpoints). Saying that a response from one origin might disrupt responses from other origins that are coalesced on one HTTP connection will be a wind against these previous attempts. I am not necessarily against creating a new connection per each origin (or set of origins owned by single entity). The privacy benefit will be covered by , and the performance reason can be covered by something like . However, as I have stated, I think we need to take a principled approach on when coalescing should (or should not) happen. This includes reconsidering if it is a good idea to allow the client to coalesce by default, as sending 421 requires a round-trip.
I think coalescing is still a good thing. I don't expect this issue to have much impact on that in practice.
Taking a look at the text, I still think the right principle here is that errors which might have been tunneled from an origin shouldn't be required to be stream errors. Looking at the text, here are the things that might fall into this category: "Malformed" requests or responses are already stream errors Other frame types on CONNECT streams are connection errors, but with a CONNECT stream, the tunneled content is the DATA payload. This could go either way. Frames that don't have the right size/content are connection errors, including final frames on streams that close cleanly. Control frames on non-control streams are connection errors. Does this principle hold for the opposite direction? Should servers consider garbage on streams as stream errors, because the "client" might actually be a proxy for several clients? It's probably the simplest to keep things symmetrical. I've put as an alternative solution to this.
I think that this issue discusses two orthogonal problems: This issue was originally about how an intermediary can close a HTTP message that it has been forwarding, when that intermediary recognizes that the message being forwarded is malformed (e.g., when upstream closes the request stream mid-frame, when the amount of body provided does not match the value of content-length response header), at the same time forwarding all the content that it has received from upstream. The only approach that works is delaying the transmission of stream resets until receiving ACKs for all the data it has sent on that stream. H2 does not have the first problem because a stream reset can never overtake the delivery of other frames that have already been sent. The scope of an error that happens on the request stream (or the push stream). At the moment, some errors (i.e. invalid frames) are handled at connection-level, while others (i.e. malformed response) are stream-level. This is a departure from the design that we have in H2, that localizes stream-level issues to stream-level errors. Note that H2 and H3 both allow endpoints to escalate stream-level errors to connection-level. It might be a good idea to split this issue in two; both and are attempts to address the second problem, but not the first problem. Regarding the second problem, there are three paths: a) status quo b) go back to what H2 does - c) require those errors to be stream-level, forbidding promotion to connection-level - As stated in , my preference goes to , as it maximizes the chance of requests being coalesced. Unless we forbid the promotion of stream-level errors to connection level (or without stating that such promotion might have negative effects), CDNs cannot allow coalescing of HTTP requests that go to different origins, as one misbehaving origin might obstruct the exchanges between the client and other origins that share the same connection. While this is not an issue for all the server deployments, it would be a backlash against our previous efforts (e.g., ORIGIN frame, secondary certificates) to maximize the chance of requests going to different origins getting coalesced, as there would be less interest in implementing these extensions from CDNs.
I agree that these are separate problems; for the first problem, I'm strongly inclined toward the status quo. If you want to ensure delivery of partial content, there is a workaround; for the default case, "play stupid games, win stupid prizes" seems like a reasonable point in the design space. I'm willing to add cautionary text around the promotion permission, but I think an outright prohibition is both unwise and unenforceable. Implementers will do what they will do, and when there's garbage coming from the peer, it's a totally sensible design to close aggressively. Frankly, I'd say CDNs shouldn't be shuffling raw frames anyway; they should be shuttling content, at the very least, and mostly have to anyway for HPACK/QPACK to work (unless everything is literal). At that point, we're really only talking about malformed headers.
I agree with NAME completely.
NAME It is true that implementations would do whatever they do. To that end, I think it is correct to argue that there is no difference between a MUST NOT and a cautionary text with a clarification on what might happen. We'd have the same design as that in HTTP/2, and having clarification on the side-effects would be an improvement. I also agree that CDNs shouldn't be shuffling raw frames anyway. That's not something on table. The original issue, for which I am seeing complexity, is about transmitting an error at the same time forwarding all the bytes up to where the error was detected. As previously stated, doing that has become much more complicated in HTTP/3 compared to HTTP/2. That said, it's not impossible, and if others implementing proxies are happy with living with such an workaround (or do not care about the problem), I can concede with that workaround. Re malformed headers, I am starting to wonder the intent. Which of the following two are we suggesting? headers considered malformed in the HTTP/3 specification headers consider malformed by the specifications that define those headers If it's the latter, it would be impossible for an HTTP/3 intermediary to detect all the malformed headers (consider the case of a new HTTP extension defining a authentication header - a type of header field that is forbidden from being sent as part of a trailer). Maybe it's a good idea to clarify that it is the former, assuming that that is the intent.
"Malformed" is defined by . If a specific header is not used properly, that's an error at the semantic layer; the server might send you a 400, but that's a successful request/response as far as HTTP/3 is concerned. adds the advice without any change to what is a stream/connection error, meaning that malformed messages are stream errors and the rest remain connection errors.
NAME Yeah my point is that "prohibited header fields" is ambiguous. I've opened .
Note: editorial fix; issue hadn't been triaged as Design. However, NAME / NAME / NAME if you'd like to run the process on not handling the case of reliable delivery prior to a RESET_STREAM, please re-open.
Thank you for working on the PR. I agree that this level of warning is sufficient. Client makes the final decision on if a new request should be sent on an existing connection, therefore it is responsible for the outcome of promoting a stream-level error to connection level.

QUIC allows the application to abruptly terminate (reset) individual streams or the entire connection when an error is encountered.  These are referred to as "stream errors" or "connection errors" and are  <ins> described in more detail in QUIC-TRANSPORT. An endpoint MAY choose to treat a stream error as a connection error under certain circumstances.  Implementations need to consider the impact on outstanding requests before making this choice. </ins> Because new error codes can be defined without negotiation (see extensions), use of an error code in an unexpected context or receipt


the peer. During the handshake, packets with the long header (long-header) are <del> used to establish the connection ID that each endpoint uses.  Each </del>  endpoint uses the Source Connection ID field to specify the connection ID that is used in the Destination Connection ID field of packets being sent to them.  Upon receiving a packet, each endpoint sets the Destination Connection ID it sends to match the value of the <del> Source Connection ID that they receive. </del>  When an Initial packet is sent by a client that has not previously <del> received an Initial or Retry packet from the server, it populates the Destination Connection ID field with an unpredictable value.  This MUST be at least 8 bytes in length.  Until a packet is received from the server, the client MUST use the same value unless it abandons the connection attempt and starts a new one.  The initial Destination Connection ID is used to determine packet protection keys for Initial packets. </del>  The client populates the Source Connection ID field with a value of its choosing and sets the SCID Len field to indicate the length. <del> The first flight of 0-RTT packets use the same Destination and Source Connection ID values as the client's first Initial. </del>  Upon first receiving an Initial or Retry packet from the server, the client uses the Source Connection ID supplied by the server as the <del> Destination Connection ID for subsequent packets, including any subsequent 0-RTT packets.  That means that a client might change the Destination Connection ID twice during connection establishment, once in response to a Retry and once in response to the first Initial packet from the server.  Once a client has received an Initial packet from the server, it MUST discard any packet it receives with a different Source Connection ID. A client MUST only change the value it sends in the Destination Connection ID in response to the first packet of each type it receives from the server (Retry or Initial); a server MUST set its value based on the Initial packet.  Any additional changes are not permitted; if subsequent packets of those types include a different Source Connection ID, they MUST be discarded.  This avoids problems that might arise from stateless processing of multiple Initial packets producing different connection IDs. The connection ID can change over the lifetime of a connection, especially in response to connection migration (migration); see issue-cid for details. </del>  7.3.

In thinking about , I started this editorial PR to clarify language in the section on negotiating connection IDs. I suspect much of it was left over from before we had the DCID / SCID split, and also before the new handshake was established. Addresses .
I would like to merge this PR as the corresponding issue has consensus, but it has outstanding issues. NAME would you mind? One thing to add is that touches some of this text. I'll need to rebase that I guess.
I've taken in the comments, and revised this PR based on the resolution to .
We're currently not specifying which DCID is supposed to be used on retransmissions of Handshake packets. A client might receive a NEWCONNECTIONID frame in the first couple of 1-RTT packets it receives, before receiving either a HANDSHAKEDONE or an acknowledgement for a 1-RTT packet is sent itself. Therefore, it's still running loss recovery on the Handshake packets it sent out earlier. If there's an acknowledgement outstanding for one of those Handshake packets, the client will have to retransmit that packet. Which DCID is it supposed to use on the retransmission? Is it the DCID that was used on the original packet? Or is it the CID provided in the NEWCONNECTIONID frame? Note that the NEWCONNECTION_ID frame might already have requested the retiring of the CID used during the Handshake.
As I can see, there are three options: a) let the receiver of NCID to send Handshake packets using the new CIDs b) require (or recommend) receiver of NCID to continue using the original DCID until the handshake is confirmed c) require (or recommend) the sender to withhold the emission NCID (with a non-zero RTP) to until it sees an ACK for handshake confirmation I prefer (c) (or maybe (b)), because (a) has corner cases even though it might seem easy. Consider the case where a server sends TP.preferredaddress then as the first packet, sends the NCID frame with RTP set to 2. If we adopt (a), this would mean that the client would have only one CID that it can use (the one being embedded in the NCID frame) even though it needs two (one for the original path, and another for the path specified by TP.preferredaddress). As you can see, the design of the NCID has been based on the assumption that providing one new CID alongside a retirement request guarantees that the receiver would have enough active CIDs. But in this particular case, that is not the case. Based on this observation, and based on my assumption that a non-zero RTP would be sent by only some of the endpoints (the necessity of CID retirement is questionable for HTTP/3, the scope of QUIC v1, as the HTTP connections are typcially short-lived and can most likely be gracefully terminated to induce a new connection when retirement of CIDs is necessary), my preference goes to (c): an endpoint that uses RTP should cover the cost.
NAME (I think you mean RPT, not RTP) Thank you for the analysis. I prefer (c) as well, except that I do not want to condition it on non-zero RPT, to keep things simpler. Otherwise we're still left with what to do at the receiver. Ensuring that the handshake confirmation is acked means that there are no HS packets pending.
I'll also note that this issue was found through a resilience test we've built for the interop runner -- high loss handshake test. We're excited to see how everyone's handshake machinery performs under 50% loss :-)
At the time of this writing, ngtcp2 does (b).
NAME for you problem with (a) isn't that just a general problem with having multiple paths and then getting a NEWCONNECTIONID with RPT for all previous CIDs? You end up in a scenario where you only have 1 CID (the new one) and multiple paths that just had all their CIDs retired. If you can get away with using that one CID for the active path, you can still make forward progress. If not, the connection dies.
I believe we specify (b) already: "A client MUST only change the value it sends in the Destination Connection ID in response to the first packet of each type it receives from the server (Retry or Initial); a server MUST set its value based on the Initial packet. Any additional changes are not permitted; if subsequent packets of those types include a different Source Connection ID, they MUST be discarded. This avoids problems that might arise from stateless processing of multiple Initial packets producing different connection IDs." URL
NAME that is just indicating when a client should change DCID in response to receiving a Initial/Retry packet with a new CID. I don't think anything restricts either side from sending NEWCONNECTIONID frames in their first 1-RTT packets and their peers immediately using them for any packets (including handshake) they send. Personally, I don't have a problem with (a). I think that no matter what receivers will have to handle the multiple paths but single CID scenario, so I would prefer not to special case the handshake. I also don't think we have handshake confirmation problems because an endpoint should only send a NEWCONNECTIONID out when it's able to accept it. Then, if the peer can decrypt the packet with the new CID, I see no reason why it shouldn't be allowed to use it immediately.
NAME my reading of the text I quoted is that you cannot use any DCIDs in long header packets that weren't either the original client DCID/SCID or from a server Initial or Retry.
NAME There's a fourth option: d) require that the sender withholds the NCID frame until it has confirmed the handshake. As soon as an endpoint confirms the handshake, it drops the Handshake keys (I'm assuming here). As a result, the endpoint obviously doesn't care about Handshake packets any longer (and especially doesn't care which DCID was used to send the packet). Therefore, there's no problem if the receiver of the NCID frame immediately retires the connection ID used during the handshake, even if the the NCID and the HANDSHAKE_DONE frame were reordered.
NAME The part about what to do with DCID changes is underspecified. From the part of the spec you quote -- "Any additional changes are not permitted; if subsequent packets of those types include a different Source Connection ID, they MUST be discarded." -- it's not clear what these additional changes are limited to. I think the intent was for all long-form packets, but that's certainly not clear here. In any case, the problem with that approach is that the client will have to carefully construct HS packets with the old DCID while sending everything else with the new DCID.
NAME : (d) works too, but an endpoint still has to decide which DCID to use for sending HS packets in the case that the HANDSHAKE_DONE frame is lost. I'd rather not leave that unspecified.
"of those types" => Retry or Initial, since that's what it was talking about. That's trying to avoid the case where the server statelessly generates a new Retry (or Initial, in the rare case that would be stateless) with a second SCID upon receiving a retransmission or duplication of the client's Initial. But it doesn't say anything about the server issuing new CIDs via an NCID frame in 1-RTT before the handshake is confirmed. I think (a) or (b) would be fine; the server is clearly intending to accept the other CID if it issued it, but could conceivably use separate logic for long-header versus short-header routing.
Can someone explain why using the new connection ID is a problem?
NAME I think that that is a good way at looking into the issue. I agree that the endpoints can start using the new CIDs during handshake, if the peer sends NCID frames. The only problem that I can think of is related to retirement (see URL). When the server provides TP.preferred_address, and also decides to retire the CIDs immediately after sending all the handshake transcript, there's going to be a very small time window in which it has to send two CIDs (one for the current path and another for the path using the preferred address). Unless the QUIC stack allows bundling of two NCID frames when building a QUIC packet, the client might end up not having sufficient amount of CIDs, in which case it might terminate the connection. It is my view that this is very different from the ordinary case of retiring CIDs, in which case there would be enough time window to guarantee that a handful number of new generation CIDs are delivered before the older ones are delivered (cc NAME That said, I now tend to think that the most we might have to do is the following two: clarify that CIDs received using NCID frames can be used for Handshake packets have a cautionary text stating that a server needs to make sure that the client has enough number of new generation CIDs when it retires an older generation of CIDs
NAME Wouldn't we be able to solve this by using my proposed option d?
Artificial constraints on when the frame can be sent would be terrible to enforce and would introduce a performance cost for using NEWCONNECTIONID. In a great many cases, the 0.5RTT from the server is idle, which makes it a great way to get things like NEWCONNECTIONID sent without taking capacity from real work. Asking the server to defer sending would mean that NEWCONNECTIONID could compete with HTTP responses and the like. I see no reason not to allow use of connection IDs when they are available. They apply to the connection as a whole. You will note that we allow changing them with the Initial/Retry mini-protocol in ways that don't correlate with other connection-level events, so this isn't any different in that regard. Forcing the connection ID state to synchronize with other state changes seems more likely to complicate things than help. The caution NAME mentions is always true. Whenever an endpoint is forced to retire connection IDs, a short supply is always a risk to the connection.
I think QUIC allows more than 1 NCID frames in a single QUIC packet. Now my preference is (a). If server utilizes retire-prior-to, it should know the consequences and should provide sufficient backup connection IDs. Otherwise, it would be misconfiguration of server software.
NAME The problem is that the client might end up receiving only some of the NCID frames, unless the server always sends (and retransmits) the set of NCID frames as a whole. But anyways, I agree that (a) is probably fine. That is because it is trivial to design a server that never requests CID retirement during the handshake. All you need to do is start issuing the new generation of CIDs at least N seconds before sending NCID frames that asks for the retirement of the previous generation, where N is your handshake timeout. Knowing that this design is why I initially preferred (c), not realizing that it works equally well with (a). But know that I understand that, I prefer (a) as it is simpler.
Given a server has to sent NCID to have the CID change, I'm fine with (a), but I thought it was not what we'd previously agreed to and specified, so I believed (b) was the status quo.
I don't think this is a serious problem, because if the server doesn't want the long header DCID to change, it can wait before sending NEWCONNECTIONID. That said, we do have an issue here in that different readers of the draft had different interpretations of what was allowed. All that to say I don't much care if this is allowed or prohibited, but we should at least make an editorial change to clarify things.
I agree with NAME here. Any alternative is ok, but the draft should say whether servers can expect that the client might switch DCID and whether the clients can expect the server to send NCID w/ RPT>0 before the handshake is complete.
Let's keep it simple: no migration before Handshake Done, period. That would include prohibit NCID in 0-RTT Also state that implementations MAY drop any packet received with NCID different from original before Handshake Done.
NAME The problem is not that simple. Consider the case where the server receives ClientFinished and sends two 1-RTT packets, the first packet containing HANDSHAKE_DONE, and the second packet containing an NCID with RPT set to 1. If the first 1-RTT packet gets lost, the client receives a request to retire the connection ID before it confirms the handshake.
I think I'd prefer a design that meets the two requirements stated below: A server should not be required to withhold NCID frames until it receives an ACK for HANDSHAKEDONE. A client should be allowed to use the original CID until the handshake is confirmed. The intention is to minimize additional complexity to existing (and future) QUIC stacks. One solution that meets these requirements is to state that a NCID frame confirms the handshake, exactly the same way as HANDSHAKEDONE frame does. That would be a trivial change to any of the QUIC stacks, and there would be no ambiguity regarding the state changes.
Discussed in ZRH. Proposed resolution is to clarify that issuing a CID means being prepared to seeing it used at any time. Additional Editorial issue on packetization issues.
Editorial PR on packetization challenges with retransmitted frames is .
Ugh, editorial changes for a design issue. Reopened for consensus calling.
This was fixed by . is related to this issue, but that simply clarifies things. To keep things sane, let's just close this when is merged.

the peer. During the handshake, packets with the long header (long-header) are  <ins> used to establish the connection IDs in each direction.  Each </ins> endpoint uses the Source Connection ID field to specify the connection ID that is used in the Destination Connection ID field of packets being sent to them.  Upon receiving a packet, each endpoint sets the Destination Connection ID it sends to match the value of the  <ins> Source Connection ID that it receives. </ins> When an Initial packet is sent by a client that has not previously  <ins> received an Initial or Retry packet from the server, the client populates the Destination Connection ID field with an unpredictable value.  This Destination Connection ID MUST be at least 8 bytes in length.  Until a packet is received from the server, the client MUST use the same Destination Connection ID value on all packets in this connection.  This Destination Connection ID is used to determine packet protection keys for Initial packets. </ins> The client populates the Source Connection ID field with a value of its choosing and sets the SCID Len field to indicate the length.  <ins> The first flight of 0-RTT packets use the same Destination Connection ID and Source Connection ID values as the client's first Initial packet. </ins> Upon first receiving an Initial or Retry packet from the server, the client uses the Source Connection ID supplied by the server as the  <ins> Destination Connection ID for subsequent packets, including all subsequent 0-RTT packets.  This means that a client might have to change the connection ID it sets in the Destination Connection ID field twice during connection establishment: once in response to a Retry, and once in response to an Initial packet from the server. Once a client has received an Initial packet from the server, it MUST discard any subsequent packet it receives with a different Source Connection ID. A client MUST change the Destination Connection ID it uses for sending packets in response to only the first received Initial or Retry packet.  A server MUST set the Destination Connection ID it uses for sending packets based on the first received Initial packet. Any further changes to the Destination Connection ID are only permitted if the values are taken from any received NEW_CONNECTION_ID frames; if subsequent Initial packets include a different Source Connection ID, they MUST be discarded.  This avoids unpredictable outcomes that might otherwise result from stateless processing of multiple Initial packets with different Source Connection IDs. The Destination Connection ID that an endpoint sends can change over the lifetime of a connection, especially in response to connection migration (migration); see issue-cid for details. </ins> 7.3.


13.2.5. An endpoint measures the delays intentionally introduced between when <del> an ack-eliciting packet is received and the corresponding acknowledgment is sent.  The endpoint encodes this delay for the largest acknowledged packet in the Ack Delay field of an ACK frame (see frame-ack).  This allows the receiver of the ACK to adjust for any intentional delays, which is important for getting a better estimate of the path RTT when acknowledgments are delayed.  A packet might be held in the OS kernel or elsewhere on the host before being processed.  An endpoint MUST NOT include delays that is does not control when populating the Ack Delay field in an ACK frame. </del>  13.2.6.

From Timo Völker's comment on the QUIC mailing list. There might be a better way to state this, but I think this is more accurate.

13.2.5. An endpoint measures the delays intentionally introduced between when  <ins> the packet with the largest packet number is received and an acknowledgment is sent.  The endpoint encodes this delay in the Ack Delay field of an ACK frame (see frame-ack).  This allows the receiver of the ACK to adjust for any intentional delays, which is important for getting a better estimate of the path RTT when acknowledgments are delayed.  A packet might be held in the OS kernel or elsewhere on the host before being processed.  An endpoint MUST NOT include delays that it does not control when populating the Ack Delay field in an ACK frame. </ins> 13.2.6.


(i.e. 2^62-4 in case of QUIC version 1).  This GOAWAY frame signals to the client that shutdown is imminent and that initiating further requests is prohibited.  After allowing time for any in-flight <del> requests (at least one round-trip time), the server would send another GOAWAY frame with an updated last Stream ID.  This ensures that a connection can be cleanly shut down without causing requests to fail. </del>  Once all accepted requests have been processed, the server can permit the connection to become idle, or MAY initiate an immediate closure

using mostly the text proposed in that issue.
On NAME proposes a change to GOAWAY that reduces the exposure of the QUIC API to HTTP/3. The paragraph in question also suggests behaviour that relies on exposure of QUIC transport information: . This seems to be the only occurence of round-trip time in the HTTP/3 document. I think we can editorialize ourselves out of this. For some HTTP/3 implementations, the RTT could be readily accessible, for others maybe not.
The other issue of the last two sentences (quoted below) is that they do not suggest how an endpoint should choose last Stream ID of the second GOAWAY frame.
Regarding how we should change, maybe something like below would be sufficient. My observation is that we can drop "at least one round-trip time" because the text that directly precede that implies that already. "After allowing time for any in-flight requests to reach the server, the server MAY send another GOAWAY frame that carries the greatest request stream ID it has seen. This ensures that a connection can be cleanly shut down without losing requests."
The text implies a little bit of interaction with the QUIC Stream ID space. Because we had to change GOAWAY to signal the first unprocessed Stream ID, the HTTP layer needs to be able to get "the next" stream ID from QUIC in a given space. But as to the general issue, I don't object to removing the specific dependency; anyone reading should be able to conclude that you need to allow an RTT for the requests to arrive, and if they have access to that value, they'll use it.
Seems alright.Thanks!

(i.e. 2^62-4 in case of QUIC version 1).  This GOAWAY frame signals to the client that shutdown is imminent and that initiating further requests is prohibited.  After allowing time for any in-flight  <ins> requests to reach the server, the server can send another GOAWAY frame indicating which requests it will accept before the end of the connection.  This ensures that a connection can be cleanly shut down without causing requests to fail. </ins> Once all accepted requests have been processed, the server can permit the connection to become idle, or MAY initiate an immediate closure


The header of the request message is carried by a PUSH_PROMISE frame (see frame-push-promise) on the request stream which generated the <del> push.  This allows the server push to be associated with a client request.  Promised requests MUST conform to the requirements in </del>  Section 8.2 of HTTP2. <del> The same server push can be associated with additional client requests using a DUPLICATE_PUSH frame (see frame-duplicate-push). </del>  Ordering of a PUSH_PROMISE or DUPLICATE_PUSH in relation to certain parts of the response is important.  The server SHOULD send

I decided to keep this pretty simple and leave the use or lack thereof unspecified, since we already know there's variation there. I was hoping to avoid creating conflicts with , but it made more sense to pull the association references into the same paragraph, which that PR also touches. Sorry, NAME Editorial-ish -- explicitly states a MAY that was previously implicit.
IIUC, since this is editorial it be merged before Ryan's PR that removes DUPLICATEPUSH in order to solve the design issue . Once merged, Ryan will need to remove the sentence and the paragraph will still stand correct?
Instead of just removing the sentence, probably replace it with a sentence about sending multiple PUSH_PROMISES to associate with multiple streams, but basically.
NAME and I had an enlightening discussion on , which highlighted a philosophical difference in our view of how push works, the filters through which we read RFC 7540, and the purpose of PUSHPROMISE on multiple streams. It might be worth expanding the text in HTTP/3 to give the reader the sense of both viewpoints, especially since disagreement between server and client implementations might have some sharp edges. RFC 7540 says: This "association" is only leveraged one other place in H2 directly; the push response is dependent on the associated stream for priority purposes. What does it mean to be "associated"? One implementation path ignores it totally, keeping a global cache of pending pushes. In this viewpoint, the purpose of PUSHPROMISE on multiple streams is to make sure that some PUSHPROMISE arrives before a reference to a resource that's being pushed, so the client knows it's already coming and can suppress requests. Subsequent PUSHPROMISEs are entirely superfluous. Another view says that pushes are tied to requests which are related, and might not be applicable to a request for that resource made in a different context (different navigations, different clients of a proxy, etc.); this is especially true if the response is not cacheable. In this viewpoint, the purpose of PUSHPROMISE/DUPLICATEPUSH is to signal which request contexts the pushed response is applicable to. These views are mostly identical if the response is cacheable or has appropriate Vary headers set, but they're subtly different views of the system.
Yeah, this works for me.Other than conflicting with (/me shakes his fist!) this looks good to me. I appreciate the neutrality around the interpretation of association.

The header of the request message is carried by a PUSH_PROMISE frame (see frame-push-promise) on the request stream which generated the  <ins> push.  Promised requests MUST conform to the requirements in </ins> Section 8.2 of HTTP2.  <ins> Each pushed response is associated with one or more client requests. The push is associated with the request stream on which the PUSH_PROMISE frame was received.  The same server push can be associated with additional client requests using a DUPLICATE_PUSH frame (see frame-duplicate-push).  These associations do not affect the operation of the protocol, but MAY be used by user agents when deciding how to use pushed resources. </ins> Ordering of a PUSH_PROMISE or DUPLICATE_PUSH in relation to certain parts of the response is important.  The server SHOULD send


MUST NOT consider these streams to have any meaning upon receipt. The payload and length of the stream are selected in any manner the <del> implementation chooses. </del>  7. The requested operation cannot be served over HTTP/3.  The peer should retry over HTTP/1.1.  9. HTTP/3 permits extension of the protocol.  Within the limitations The entries in the iana-error-table are registered by this document.  11.2.4. This document establishes a registry for HTTP/3 unidirectional stream

Pleasantly, the same formula we've used for grease values elsewhere in HTTP/3 does not collide with any currently-assigned error code, so I've just reused it. (We'll need to be cautious about 0x119, though.) The same logic could be applied to transport error codes, but there's not much point. If you're intolerant of the way your peer closed the connection, you might erroneously... close the connection? Editorial-ish: A recommendation which reserves some error code space we haven't used; modifies the IANA instructions. Doesn't mandate or prohibit anything new.
Constraining the recommendation on grease streams, because sending any error code potentially generates an error code that has semantics to your peer of which you are unaware.
The discussion on made us conclude that the reason for doing this is that we want to safeguard our ability to define new error codes. Greasing might help with that.
Since you'd probably want want the grease to not sacrifice connections it might make sense to couple grease error codes to H3 grease streams. Also, what about greasing of transport-level error codes? Can't play the stream tricks there.
Unknown error codes are supposed to be equivalent to H3NOERROR. Perhaps a recommendation (SHOULD) to substitute a grease code with non-zero probability when you would have sent NO_ERROR? And MAY use them when terminating grease streams, sure.
Looks fine to me. I wonder how much this will end up doing anything...LG, but I think it's worth accepting Martin's suggestion.

MUST NOT consider these streams to have any meaning upon receipt. The payload and length of the stream are selected in any manner the  <ins> implementation chooses.  Implementations MAY terminate these streams cleanly, or MAY abruptly terminate them.  When terminating abruptly, the error code H3_NO_ERROR or a reserved error code (http-error- codes) SHOULD be used. </ins> 7. The requested operation cannot be served over HTTP/3.  The peer should retry over HTTP/1.1. <ins> Error codes of the format "0x1f * N + 0x21" for integer values of N are reserved to exercise the requirement that unknown error codes be treated as equivalent to H3_NO_ERROR (extensions).  Implementations SHOULD select an error code from this space with some probability when they would have sent H3_NO_ERROR. </ins> 9. HTTP/3 permits extension of the protocol.  Within the limitations The entries in the iana-error-table are registered by this document. <ins> Additionally, each code of the format "0x1f * N + 0x21" for integer values of N (that is, "0x21", "0x40", ..., through "0x3FFFFFFFFFFFFFFE") MUST NOT be assigned by IANA. </ins> 11.2.4. This document establishes a registry for HTTP/3 unidirectional stream


In order to assist loss detection at the sender, an endpoint SHOULD send an ACK frame immediately on receiving an ack-eliciting packet <del> that is out of order.  The endpoint MAY continue sending ACK frames immediately on each subsequently received packet, but the endpoint SHOULD return to acknowledging every other packet within a period of 1/8 x RTT, unless more ack-eliciting packets are received out of order.  If every subsequent ack-eliciting packet arrives out of </del>  order, then an ACK frame SHOULD be sent immediately for every received ack-eliciting packet.

Currently the transport draft says: However, since this text was written, recovery was changed to require packet AND time threshold loss detection, so the only time sending multiple immediate ACKs for a single missing packet would be beneficial is if the first ACK is lost. Also, only sending 1 immediate ACK is a lot easier to implement and reason about in my opinion than "up to 1/8 RTT".
NAME NAME NAME Can you please mark this as design since the PR will change normative text.
I think it could have other effects - it will impact the robustness to loss/congestion on the return path compared to the way TCP operates. Sending one isolated ACK here isn't the same, although you could argue that the ACK information is indeed repeated "ACK Delay" or "N packets" later. If we relax the number of packets per ACK this may need a little more thought to avoid delaying congestion detection when there is loss?
Currently in the drafts, N is 2, so there are very few cases where sending more ACKs would be beneficial. The worst case I can construct is If the first ACK is lost, then the loss detection could be delayed by the maxackdelay IF exactly one more packet was received. That seems quite acceptable and loss of ack-only packets is rare in my experience. Drafts like the ack-frequency extension do change the dynamics some, but they also increase the relative cost of sending immediate ACKs more than once. For example, instead of sending an ACK-only packet every 10 or 20 packets, a receiver could switch to sending an ACK for every packet, which could massively slow down the connection and increase load on the network. I'm going to run a production experiment with this change as well to confirm that it performs as well or better, but that may take a few months.
My point is I think this is the wrong direction. I think n=2 is unnecessarily restrictive as a default, and does have unwanted performance impacts in some places. I think the case of loss for a burst of one packet that you mention is handed by the timer. The congestion impact is minor. While I agree for n=2 the method doesn't make much difference, for n=10, it would make a difference - because I'd expect it to be Important to be able to robustly detect and respond after congestion, I'm not sure it matters if the intention is to send DupACK+k immediate ACks or to send ACKs more frequently for some proportion of a RTT.
NAME You appear to be saying that if we substantially changed the current ACK guidance, this PR might be suboptimal. Is that correct? I don't believe the current guidance is optimal in almost any circumstance, so I would really like to change it. Also, I don't believe we'll end up changing the ACK behavior substantially in the core drafts at this point, so I believe we should proceed with guidance we think works well with the existing draft without being substantially sub-optimal if a receiver does unilaterally decide to ACK less frequently.
NAME : I'd like to move this one along. I think this is generally fine, and it makes things quite a bit simplicity for the implementation, and it reduces the number of ACKs significantly when there's some reordering. If you think we should have a different recommendation, please say so. I'm marking this proposal-ready so this gets into the next consensus call run.
I think it will impact the robustness to loss/congestion on the return path compared to the way TCP operates, but the use of PTO generally mitigates this... There are cases where relying only on the PTO would delay congestion detection, such as when there is a significantly reduced path RTT at the same time as loss (we could think of scenario where this is a result of re-routing for the path). I’m also interested in a case where there is loss in both directions (which can also happen in congestion collapse). The reduction of ACK information could result in unnecessary duplicated data (because the sender does not receive ACK range info). This issue seems most severe when there are too few ACKs per RTT. It seems there is merit in ensuring there are sufficient ACKs per RTT - e.g., a rule that sends ACKs at least every (say) 1/8 RTT would seem wise. Overall, I am in favour of the proposal to change the response after loss/reorder. QUIC currently generates a lot more ACK traffic (in volume) than TCP. ACK after loss event are common with RENO overflowing a router buffer (we are looking at this now). This proposed change is likely to really help reduce ACK traffic volume on the return path. If the arguments for this seem valid, I think related issue needs to be considered again.
currently states: NAME notes that nowhere do we define precisely how a receiver might do this detection. Specifically a receiver could implement this as when a newly received packet is smaller than the largest acked, or as when the packet is larger than largest acked plus one. Doing the former would actually not help with loss detection, since the receiver would not ack immediately on a gap. We should add some text codifying the latter.
That section used to be in recovery, where we have a definition already(which is now unused), so I'd suggest we move it over. "Out-of-order Packets: : Packets that do not increase the largest received packet number for its packet number space by exactly one. Packets arrive out of order when earlier packets are lost or delayed."
Isn't this supposed to be "Packets that increase the largest received packet number ... by more than one." in this case?
Thinking about this some more, there are three possible definitions here for out-of-order: a newly received packet is greater than largest received by more than one, a newly received packet is smaller than the largest received, both (1) and (2). (1) is the obvious case where a loss happens and a subsequent packet is received. (2) is the reordering case, where an earlier packet is delivered later, and while it does not indicate loss, it does indicate that a gap was filled. It's useful to signal (2) as well immediately to prevent a sender from unnecessarily retransmitting what it believes to be a missing packet. The definition we want for "out-of-order" is the one that is most useful for determining when to send an immediate ack. The definition in the PR captures both (1) and (2), and is probably the one we want.
NAME : If you believe we ought to change the definition, please re-open, and we can continue this discussion.

In order to assist loss detection at the sender, an endpoint SHOULD send an ACK frame immediately on receiving an ack-eliciting packet  <ins> that is out of order.  The endpoint SHOULD NOT continue sending ACK frames immediately unless more ack-eliciting packets are received out of order.  If every subsequent ack-eliciting packet arrives out of </ins> order, then an ACK frame SHOULD be sent immediately for every received ack-eliciting packet.


samples, estimated using a mean variation. The calculation of smoothed_rtt uses path latency after adjusting RTT <del> samples for ACK delays.  For packets sent in the ApplicationData packet number space, a peer limits any delay in sending an acknowledgement for an ack-eliciting packet to no greater than the value it advertised in the max_ack_delay transport parameter. Consequently, when a peer reports an Ack Delay that is greater than its max_ack_delay, the delay is attributed to reasons out of the peer's control, such as scheduler latency at the peer or loss of previous ACK frames.  Any delays beyond the peer's max_ack_delay are therefore considered effectively part of path delay and incorporated into the smoothed_rtt estimate. </del>  When adjusting an RTT sample using peer-reported acknowledgement delays, an endpoint:

Notes that ack delays are right shifted by ackdelayexponent when transmitted in ACK frames.
The recovery draft currently assumes you've already applied the ack delay exponent. It should spell that out explicitly so readers of the draft don't miss it. Ideally it'd be obvious if you just read transport, but that's a long draft, so there's no harm in stating it again. From an on-list comment by Timo Völker
Lgtm

samples, estimated using a mean variation. The calculation of smoothed_rtt uses path latency after adjusting RTT  <ins> samples for acknowledgement delays.  These delays are computed using the ACK Delay field of the ACK frame as described in Section 19.3 of QUIC-TRANSPORT.  For packets sent in the ApplicationData packet number space, a peer limits any delay in sending an acknowledgement for an ack-eliciting packet to no greater than the value it advertised in the max_ack_delay transport parameter.  Consequently, when a peer reports an Ack Delay that is greater than its max_ack_delay, the delay is attributed to reasons out of the peer's control, such as scheduler latency at the peer or loss of previous ACK frames.  Any delays beyond the peer's max_ack_delay are therefore considered effectively part of path delay and incorporated into the smoothed_rtt estimate. </ins> When adjusting an RTT sample using peer-reported acknowledgement delays, an endpoint:


output of AEAD_AES_128_GCM AEAD used with the following inputs: The secret key, K, is 128 bits equal to <del> 0xf5ed4642e0e4c8d878bbbc8a828821c9. </del>  <del> The nonce, N, is 96 bits all set to zero. </del>  The plaintext, P, is empty. The associated data, A, is the contents of the Retry Pseudo- Packet, as illustrated in retry-pseudo:  The Retry Pseudo-Packet is not sent over the wire.  It is computed by taking the transmitted Retry packet, removing the Retry Integrity Tag and prepending the two following fields:

Just to set expectations, I think that I want to reserve the ability to pick new values for these. Just as there will be a new value for the Initial salt when we publish an RFC, this probably needs a new value at the same time.
Retry integrity protection () specifies the AES-GCM key and IV directly. While that works fine for people using TLS stacks that provide direct access to AES-GCM, it does not for people using TLS stacks that only provides access to TLS AEAD API. Previously, we have been consistent in using HKDF-Expand-Label in a specific way so that people could rely on the "TLS" stack generating AEAD contexts. I think we can resolve the concern by simply changing the hard-coded AES-GCM key and IV we specify in the TLS draft to a set of values that can be derived by using the TLS AEAD API.
It would be easier for me to do as NAME suggested, but the overall effort is small. I can live with either answer. Note that the additional indirection can be hard-coded out if people find they want to prime their AES directly. Of course, you don't need to run AES at all if you want to go that far.
I agree that this is not a must, even though it's about helping some of the implementations. I would highly appreciate it if you could merge the PR before -25 ships, as I think changing the default key before shipping the first draft that adopts the GCM-based Retry protection would have the least friction.
This is useful, and we should merge this. I don't think this needs to be design.
If this is the outcome, then this seems fine.I think this is useful -- we should merge this.

output of AEAD_AES_128_GCM AEAD used with the following inputs: The secret key, K, is 128 bits equal to  <ins> 0x4d32ecdb2a2133c841e4043df27d4430. </ins>  <ins> The nonce, N, is 96 bits equal to 0x4d1611d05513a552c587d575. </ins> The plaintext, P, is empty. The associated data, A, is the contents of the Retry Pseudo- Packet, as illustrated in retry-pseudo: <ins> The secret key and the nonce are values derived by calling HKDF- Expand-Label using 0x656e61e336ae9417f7f0edd8d78d461e2aa7084aba7a14c1e9f726d55709169a as the secret, with labels being "quic key" and "quic iv" (protection- keys). </ins> The Retry Pseudo-Packet is not sent over the wire.  It is computed by taking the transmitted Retry packet, removing the Retry Integrity Tag and prepending the two following fields:


An HTTP message (request or response) consists of: <del> the message header (see RFC7230, Section 3.2), sent as a single </del>  HEADERS frame (see frame-headers), <del> optionally, the payload body, if present (see RFC7230, Section 3.3), sent as a series of DATA frames (see frame-data), </del>  <del> optionally, trailing headers, if present (see RFC7230, Section 4.1.2), sent as a single HEADERS frame. </del>  A server MAY send one or more PUSH_PROMISE (see frame-push-promise) or DUPLICATE_PUSH (see frame-duplicate-push) frames before, after, or MUST NOT be used. A response MAY consist of multiple messages when and only when one or <del> more informational responses (1xx; see RFC7231, Section 6.2) precede a final response to the same request.  Non-final responses do not contain a payload body or trailers. </del>  If an endpoint receives an invalid sequence of frames on either a request or a push stream, it MUST respond with a connection error of more cookie-pairs, before compression.  If a decompressed header list contains multiple cookie header fields, these MUST be concatenated before being passed into a non-HTTP/2, non-HTTP/3 context, as <del> described in HTTP2, Section 8.1.2.5. </del>  An HTTP/3 implementation MAY impose a limit on the maximum size of the message header it will accept on an individual HTTP message.  A 4.2. <del> The pseudo-method CONNECT (RFC7231, Section 4.3.6) is primarily used with HTTP proxies to establish a TLS session with an origin server for the purposes of interacting with "https" resources.  In HTTP/1.x, CONNECT is used to convert an entire HTTP connection into a tunnel to a remote host.  In HTTP/2, the CONNECT method is used to establish a tunnel over a single HTTP/2 stream to a remote host for similar purposes. </del>  A CONNECT request in HTTP/3 functions in the same manner as in <del> HTTP/2.  The request MUST be formatted as described in HTTP2, Section 8.3.  A CONNECT request that does not conform to these restrictions is malformed (see malformed).  The request stream MUST NOT be closed at the end of the request. </del>  A proxy that supports CONNECT establishes a TCP connection (RFC0793) to the server identified in the ":authority" pseudo-header field. Once this connection is successfully established, the proxy sends a HEADERS frame containing a 2xx series status code to the client, as <del> defined in RFC7231, Section 4.3.6. </del>  All DATA frames on the stream correspond to data sent or received on the TCP connection.  Any DATA frame sent by the client is transmitted 4.3. <del> HTTP/3 does not support the HTTP Upgrade mechanism (RFC7230, Section 6.7) or 101 (Switching Protocols) informational status code (RFC7231, Section 6.2.2). </del>  4.4.

Martin flagged this on some imported text in , but others have crept in other places, mostly also as text imported from RFC7540. For future reference, (({{])), (Section d+(.d+)*) => 1

An HTTP message (request or response) consists of:  <ins> the message header (see Section 3.2 of RFC7230), sent as a single </ins> HEADERS frame (see frame-headers),  <ins> optionally, the payload body, if present (see Section 3.3 of RFC7230), sent as a series of DATA frames (see frame-data), </ins>  <ins> optionally, trailing headers, if present (see Section 4.1.2 of RFC7230), sent as a single HEADERS frame. </ins> A server MAY send one or more PUSH_PROMISE (see frame-push-promise) or DUPLICATE_PUSH (see frame-duplicate-push) frames before, after, or MUST NOT be used. A response MAY consist of multiple messages when and only when one or  <ins> more informational responses (1xx; see Section 6.2 of RFC7231) precede a final response to the same request.  Non-final responses do not contain a payload body or trailers. </ins> If an endpoint receives an invalid sequence of frames on either a request or a push stream, it MUST respond with a connection error of more cookie-pairs, before compression.  If a decompressed header list contains multiple cookie header fields, these MUST be concatenated before being passed into a non-HTTP/2, non-HTTP/3 context, as  <ins> described in Section 8.1.2.5 of HTTP2. </ins> An HTTP/3 implementation MAY impose a limit on the maximum size of the message header it will accept on an individual HTTP message.  A 4.2.  <ins> The pseudo-method CONNECT (Section 4.3.6 of RFC7231) is primarily used with HTTP proxies to establish a TLS session with an origin server for the purposes of interacting with "https" resources.  In HTTP/1.x, CONNECT is used to convert an entire HTTP connection into a tunnel to a remote host.  In HTTP/2, the CONNECT method is used to establish a tunnel over a single HTTP/2 stream to a remote host for similar purposes. </ins> A CONNECT request in HTTP/3 functions in the same manner as in  <ins> HTTP/2.  The request MUST be formatted as described in Section 8.3 of HTTP2.  A CONNECT request that does not conform to these restrictions is malformed (see malformed).  The request stream MUST NOT be closed at the end of the request. </ins> A proxy that supports CONNECT establishes a TCP connection (RFC0793) to the server identified in the ":authority" pseudo-header field. Once this connection is successfully established, the proxy sends a HEADERS frame containing a 2xx series status code to the client, as  <ins> defined in Section 4.3.6 of RFC7231. </ins> All DATA frames on the stream correspond to data sent or received on the TCP connection.  Any DATA frame sent by the client is transmitted 4.3.  <ins> HTTP/3 does not support the HTTP Upgrade mechanism (Section 6.7 of RFC7230) or 101 (Switching Protocols) informational status code (Section 6.2.2 of RFC7231). </ins> 4.4.


receiver within the maximum ack delay and are called ack-eliciting packets. <del> Packets that do not increase the largest received packet number for its packet number space by exactly one.  Packets arrive out of order when earlier packets are lost or delayed. </del> 3. All transmissions in QUIC are sent with a packet-level header, which CONNECTION_CLOSE.  These cause a recipient to send an acknowledgment (see sending-acknowledgements).  An entity that can participate in a QUIC connection by generating, receiving, and processing QUIC packets.  There are only two types of endpoint in QUIC: client and server.

From recovery to transport.
currently states: NAME notes that nowhere do we define precisely how a receiver might do this detection. Specifically a receiver could implement this as when a newly received packet is smaller than the largest acked, or as when the packet is larger than largest acked plus one. Doing the former would actually not help with loss detection, since the receiver would not ack immediately on a gap. We should add some text codifying the latter.
That section used to be in recovery, where we have a definition already(which is now unused), so I'd suggest we move it over. "Out-of-order Packets: : Packets that do not increase the largest received packet number for its packet number space by exactly one. Packets arrive out of order when earlier packets are lost or delayed."
Isn't this supposed to be "Packets that increase the largest received packet number ... by more than one." in this case?
Thinking about this some more, there are three possible definitions here for out-of-order: a newly received packet is greater than largest received by more than one, a newly received packet is smaller than the largest received, both (1) and (2). (1) is the obvious case where a loss happens and a subsequent packet is received. (2) is the reordering case, where an earlier packet is delivered later, and while it does not indicate loss, it does indicate that a gap was filled. It's useful to signal (2) as well immediately to prevent a sender from unnecessarily retransmitting what it believes to be a missing packet. The definition we want for "out-of-order" is the one that is most useful for determining when to send an immediate ack. The definition in the PR captures both (1) and (2), and is probably the one we want.
NAME : If you believe we ought to change the definition, please re-open, and we can continue this discussion.
Thank you for working on this.

receiver within the maximum ack delay and are called ack-eliciting packets.  3. All transmissions in QUIC are sent with a packet-level header, which CONNECTION_CLOSE.  These cause a recipient to send an acknowledgment (see sending-acknowledgements). <ins> A packet that does not increase the largest received packet number for its packet number space by exactly one.  A packet can arrive out of order if it is delayed or if earlier packets are lost or delayed. </ins> An entity that can participate in a QUIC connection by generating, receiving, and processing QUIC packets.  There are only two types of endpoint in QUIC: client and server.


could become blocked to the value of SETTINGS_QPACK_BLOCKED_STREAMS at all times.  If a decoder encounters more blocked streams than it promised to support, it MUST treat this as a connection error of type <del> HTTP_QPACK_DECOMPRESSION_FAILED. </del>  Note that the decoder might not become blocked on every stream which risks becoming blocked. When processing header blocks, the decoder expects the Required Insert Count to exactly match the value defined in blocked-streams. If it encounters a smaller value than expected, it MUST treat this as <del> a connection error of type HTTP_QPACK_DECOMPRESSION_FAILED; see invalid-references.  If it encounters a larger value than expected, it MAY treat this as a connection error of type HTTP_QPACK_DECOMPRESSION_FAILED. </del>  2.2.2. representation to a dynamic table entry which has already been evicted or which has an absolute index greater than or equal to the declared Required Insert Count (header-prefix), it MUST treat this as <del> a connection error of type "HTTP_QPACK_DECOMPRESSION_FAILED". </del>  If the decoder encounters a reference in an encoder instruction to a dynamic table entry which has already been evicted, it MUST treat <del> this as a connection error of type "HTTP_QPACK_ENCODER_STREAM_ERROR". </del>  3. When the decoder encounters an invalid static table index in a header block representation it MUST treat this as a connection error of type <del> "HTTP_QPACK_DECOMPRESSION_FAILED".  If this index is received on the encoder stream, this MUST be treated as a connection error of type "HTTP_QPACK_ENCODER_STREAM_ERROR". </del>  3.2. then added to the table.  It is an error if the encoder attempts to add an entry that is larger than the dynamic table capacity; the decoder MUST treat this as a connection error of type <del> "HTTP_QPACK_ENCODER_STREAM_ERROR". </del>  A new entry can reference an entry in the dynamic table that will be evicted when adding this new entry into the dynamic table. send the same non-zero value in its SETTINGS frame.  If it specifies any other value, or omits SETTINGS_QPACK_MAX_TABLE_CAPACITY from SETTINGS, the encoder must treat this as a connection error of type <del> "HTTP_QPACK_DECODER_STREAM_ERROR". </del>  For HTTP/3 servers and HTTP/3 clients when 0-RTT is not attempted or is rejected, the maximum table capacity is 0 until the encoder value of the SETTINGS_QPACK_MAX_TABLE_CAPACITY parameter (configuration) received from the decoder.  The decoder MUST treat a new dynamic table capacity value that exceeds this limit as a <del> connection error of type "HTTP_QPACK_ENCODER_STREAM_ERROR". </del>  Reducing the dynamic table capacity can cause entries to be evicted; see eviction.  This MUST NOT cause the eviction of entries which are If an encoder receives a Header Acknowledgement instruction referring to a stream on which every header block with a non-zero Required Insert Count has already been acknowledged, that MUST be treated as a <del> connection error of type "HTTP_QPACK_DECODER_STREAM_ERROR". </del>  The Header Acknowledgement instruction might increase the Known Received Count; see known-received-count. An encoder that receives an Increment field equal to zero, or one that increases the Known Received Count beyond what the encoder has sent MUST treat this as a connection error of type <del> "HTTP_QPACK_DECODER_STREAM_ERROR". </del>  4.5. algorithm such as the following.  If the decoder encounters a value of EncodedInsertCount that could not have been produced by a conformant encoder, it MUST treat this as a connection error of type <del> "HTTP_QPACK_DECOMPRESSION_FAILED". </del>  TotalNumberOfInserts is the total number of inserts into the decoder's dynamic table.

I noticed that the QPACK error codes still started with the HTTPQPACK prefix, while the HTTP document has shifted to H3 as the prefix. I shortened the QPACK prefix to simply "QPACK", though "H3QPACK" could work, too. Along the way, I noticed that backticks were sometimes, but not always, used when referring to the error code names. We haven't been in the HTTP document, so I reconciled them to not. Again, this could go the opposite way. I also noticed that the settings are "SETTINGSQPACK" except in the IANA registration where they're simply "QPACK". I didn't touch those, but am open to feedback on whether that's worth trimming/reconciling.
NAME I don't need your approval, but I kind of do need NAME
Well, you did ask :)
NAME could you review and potentially merge?
Do it. You don't need my approval to do something that is so self-evidently a good idea.QPACK_ prefix WFM

could become blocked to the value of SETTINGS_QPACK_BLOCKED_STREAMS at all times.  If a decoder encounters more blocked streams than it promised to support, it MUST treat this as a connection error of type  <ins> QPACK_DECOMPRESSION_FAILED. </ins> Note that the decoder might not become blocked on every stream which risks becoming blocked. When processing header blocks, the decoder expects the Required Insert Count to exactly match the value defined in blocked-streams. If it encounters a smaller value than expected, it MUST treat this as  <ins> a connection error of type QPACK_DECOMPRESSION_FAILED; see invalid- references.  If it encounters a larger value than expected, it MAY treat this as a connection error of type QPACK_DECOMPRESSION_FAILED. </ins> 2.2.2. representation to a dynamic table entry which has already been evicted or which has an absolute index greater than or equal to the declared Required Insert Count (header-prefix), it MUST treat this as  <ins> a connection error of type QPACK_DECOMPRESSION_FAILED. </ins> If the decoder encounters a reference in an encoder instruction to a dynamic table entry which has already been evicted, it MUST treat  <ins> this as a connection error of type QPACK_ENCODER_STREAM_ERROR. </ins> 3. When the decoder encounters an invalid static table index in a header block representation it MUST treat this as a connection error of type  <ins> QPACK_DECOMPRESSION_FAILED.  If this index is received on the encoder stream, this MUST be treated as a connection error of type QPACK_ENCODER_STREAM_ERROR. </ins> 3.2. then added to the table.  It is an error if the encoder attempts to add an entry that is larger than the dynamic table capacity; the decoder MUST treat this as a connection error of type  <ins> QPACK_ENCODER_STREAM_ERROR. </ins> A new entry can reference an entry in the dynamic table that will be evicted when adding this new entry into the dynamic table. send the same non-zero value in its SETTINGS frame.  If it specifies any other value, or omits SETTINGS_QPACK_MAX_TABLE_CAPACITY from SETTINGS, the encoder must treat this as a connection error of type  <ins> QPACK_DECODER_STREAM_ERROR. </ins> For HTTP/3 servers and HTTP/3 clients when 0-RTT is not attempted or is rejected, the maximum table capacity is 0 until the encoder value of the SETTINGS_QPACK_MAX_TABLE_CAPACITY parameter (configuration) received from the decoder.  The decoder MUST treat a new dynamic table capacity value that exceeds this limit as a  <ins> connection error of type QPACK_ENCODER_STREAM_ERROR. </ins> Reducing the dynamic table capacity can cause entries to be evicted; see eviction.  This MUST NOT cause the eviction of entries which are If an encoder receives a Header Acknowledgement instruction referring to a stream on which every header block with a non-zero Required Insert Count has already been acknowledged, that MUST be treated as a  <ins> connection error of type QPACK_DECODER_STREAM_ERROR. </ins> The Header Acknowledgement instruction might increase the Known Received Count; see known-received-count. An encoder that receives an Increment field equal to zero, or one that increases the Known Received Count beyond what the encoder has sent MUST treat this as a connection error of type  <ins> QPACK_DECODER_STREAM_ERROR. </ins> 4.5. algorithm such as the following.  If the decoder encounters a value of EncodedInsertCount that could not have been produced by a conformant encoder, it MUST treat this as a connection error of type  <ins> QPACK_DECOMPRESSION_FAILED. </ins> TotalNumberOfInserts is the total number of inserts into the decoder's dynamic table.


All QUIC packets except Version Negotiation packets use authenticated encryption with additional data (AEAD) RFC5116 to provide <del> confidentiality and integrity protection.  Retry packets use an AEAD to provide integrity protection.  Details of packet protection are found in QUIC-TLS; this section includes an overview of the process. </del>  Initial packets are protected using keys that are statically derived. This packet protection is not effective confidentiality protection.

Is "AEAD" a thing, or a descriptor? I read the original as "an AEAD [algorithm]", but this change seems to consider AEAD as a technique which can be applied.
I modeled this on the previous sentence that says (italics added): Note the absence of "an" in front of the italicized text. The "AEAD" in the second sentence is simply another way to write "authenticated encryption with additional data."

All QUIC packets except Version Negotiation packets use authenticated encryption with additional data (AEAD) RFC5116 to provide  <ins> confidentiality and integrity protection.  Retry packets use AEAD to provide integrity protection.  Details of packet protection are found in QUIC-TLS; this section includes an overview of the process. </ins> Initial packets are protected using keys that are statically derived. This packet protection is not effective confidentiality protection.


the token provided in a Retry for future connections.  Servers MAY discard any Initial packet that does not carry the expected token. <del> Unlike the token that is created for a Retry packet, there might be some time between when the token is created and when the token is subsequently used.  Thus, a token SHOULD have an expiration time, which could be either an explicit expiration time or an issued timestamp that can be used to dynamically calculate the expiration time.  A server can store the expiration time or include it in an encrypted form in the token. </del>  A token issued with NEW_TOKEN MUST NOT include information that would allow values to be linked by an on-path observer to the connection on 13.2.5. <del> An endpoint measures the delays intentionally introduced between when the packet with the largest packet number is received and an acknowledgment is sent.  The endpoint encodes this delay in the Ack Delay field of an ACK frame (see frame-ack).  This allows the </del>  receiver of the ACK to adjust for any intentional delays, which is important for getting a better estimate of the path RTT when acknowledgments are delayed.  A packet might be held in the OS kernel

Fixes issue .
The wording "between when ... and when..." or "between when ... and ..." is odd. There are two instances of this in .
Fixed by .
Not critical, perhaps, but I agree that this is less awkward.

the token provided in a Retry for future connections.  Servers MAY discard any Initial packet that does not carry the expected token.  <ins> Unlike the token that is created for a Retry packet, which is used immediately, the token sent in the NEW_TOKEN frame might be used after some period of time has passed.  Thus, a token SHOULD have an expiration time, which could be either an explicit expiration time or an issued timestamp that can be used to dynamically calculate the expiration time.  A server can store the expiration time or include it in an encrypted form in the token. </ins> A token issued with NEW_TOKEN MUST NOT include information that would allow values to be linked by an on-path observer to the connection on 13.2.5.  <ins> An endpoint measures the delays intentionally introduced between the time the packet with the largest packet number is received and the time an acknowledgment is sent.  The endpoint encodes this delay in the Ack Delay field of an ACK frame (see frame-ack).  This allows the </ins> receiver of the ACK to adjust for any intentional delays, which is important for getting a better estimate of the path RTT when acknowledgments are delayed.  A packet might be held in the OS kernel


1. <del> QUIC is a new multiplexed and secure transport atop UDP.  QUIC builds on decades of transport and security experience, and implements mechanisms that make it attractive as a modern general-purpose transport.  The QUIC protocol is described in QUIC-TRANSPORT. QUIC implements the spirit of existing TCP congestion control and </del>  loss recovery mechanisms, described in RFCs, various Internet-drafts, <del> and also those prevalent in the Linux TCP implementation.  This document describes QUIC congestion control and loss recovery, and where applicable, attributes the TCP equivalent in RFCs, Internet- drafts, academic papers, and/or TCP implementations. </del>  2.

Just a proposal but I would prefer to remove the advertising slang ("decades of transport and security experience" and "makes it attractive").
Isn't the introduction the right place for ad copy, though? It describes what the document hopes to achieve and why it's worth doing. I think some of the rewording you've done here is an improvement, but I'm not convinced we should edit out the fact that we're trying to incorporate many years of work that hasn't been universally deployable.
I'm fine with this new language.

1.  <ins> QUIC is a new multiplexed and secure transport protocol atop UDP, specified in QUIC-TRANSPORT.  This document describes congestion control and loss recovery for QUIC.  Mechanisms described in this document follow the spirit of existing TCP congestion control and </ins> loss recovery mechanisms, described in RFCs, various Internet-drafts,  <ins> or academic papers, and also those prevalent in TCP implementations. </ins> 2.


Definitions of terms that are used in this document: <del> Any packet containing only one or more ACK frame(s). Packets are considered in-flight when they have been sent and are not ACK-only, and they are not acknowledged, declared lost, or abandoned along with old keys. </del> All frames other than ACK, PADDING, and CONNECTION_CLOSE are considered ack-eliciting. receiver within the maximum ack delay and are called ack-eliciting packets.  3. All transmissions in QUIC are sent with a packet-level header, which

ACK-only was only even used in the definition part and in-flight can also be defined using ack-eliciting.
I just realised that padding is the "complicating" thing here. I still don't think we need to define "act-only" just for that but can rather be explicit about padding frames because otherwise this is easy to miss (as I did initially).
While the defined term is not used elsewhere, the phrases "Packets containing only ACK frames" and "Packets only containing ACK frames" are used. We should either eliminate the definition or explicitly use the defined term in those places.
Of Mike's options, I think that this makes sense. Fewer defined terms means lower fixed cognitive load when reading this. Less jargon = good.I re-read the other spots NAME mentioned and I believe the text is still correct, so I'm going to accept this. Thanks for the simplification.

Definitions of terms that are used in this document:  All frames other than ACK, PADDING, and CONNECTION_CLOSE are considered ack-eliciting. receiver within the maximum ack delay and are called ack-eliciting packets. <ins> Packets are considered in-flight when they are ack-eliciting or contain a PADDING frame, and they have been sent but are not acknowledged, declared lost, or abandoned along with old keys. </ins> 3. All transmissions in QUIC are sent with a packet-level header, which


An RTT sample MUST NOT be generated on receiving an ACK frame that does not newly acknowledge at least one ack-eliciting packet.  A peer <del> does not send an ACK frame on receiving only non-ack-eliciting packets, so an ACK frame that is subsequently sent can include an arbitrarily large Ack Delay field.  Ignoring such ACK frames avoids </del>  complications in subsequent smoothed_rtt and rttvar computations. A sender might generate multiple RTT samples per RTT when multiple

So here is an alternative proposal. However I also wonder why this paragraph is a MUST requirement while the previous statement (avoiding multiple samples of the same packet) is only a SHOULD? I would see it rather the other way around or both MUSTs.
This is a great improvement, thanks. One additional suggestion (for existing text) below.

An RTT sample MUST NOT be generated on receiving an ACK frame that does not newly acknowledge at least one ack-eliciting packet.  A peer  <ins> usually does not send an ACK frame when only non-ack-eliciting packets are received.  Therefore an ACK frame that contains acknowledgements for only non-ack-eliciting packets could include an arbitrarily large Ack Delay value.  Ignoring such ACK frames avoids </ins> complications in subsequent smoothed_rtt and rttvar computations. A sender might generate multiple RTT samples per RTT when multiple


When an ack-eliciting packet is transmitted, the sender schedules a timer for the PTO period as follows: <del> kGranularity, smoothed_rtt, rttvar, and max_ack_delay are defined in ld-consts-of-interest and ld-vars-of-interest. </del> 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 estimated network roundtrip-time (smoothed_rtt), the variation in the

I proposed to remove this sentence here because all these values have actually been just defined in the previous sections. If a pointer to the appendix is desired, I think that should be mentioned earlier (e.g. in the intro already) that these lists existing in the appendix.
This seems fine to me, but I'd like NAME opinion as well.

When an ack-eliciting packet is transmitted, the sender schedules a timer for the PTO period as follows:  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 estimated network roundtrip-time (smoothed_rtt), the variation in the


The life of a connection that is experiencing consecutive PTOs is limited by the endpoint's idle timeout. <del> The probe timer is not set if the time threshold time-threshold loss detection timer is set.  The time threshold loss detection timer is expected to both expire earlier than the PTO and be less likely to </del>  spuriously retransmit data. 5.3.

I think this statement should be normative. Not sure if this is a design issue or not.
I'm comfortable with this being editorial.

The life of a connection that is experiencing consecutive PTOs is limited by the endpoint's idle timeout.  <ins> The probe timer MUST NOT be set if the time threshold time-threshold loss detection timer is set.  The time threshold loss detection timer is expected to both expire earlier than the PTO and be less likely to </ins> spuriously retransmit data. 5.3.