requests rejected by the server as though they had never been sent at all, thereby allowing them to be retried later on a new connection. Servers MUST NOT use the HTTP_REQUEST_REJECTED error code for <del> requests which were partially or fully processed.  When a client sends a STOP_SENDING with HTTP_REQUEST_CANCELLED, a server MAY indicate the error code HTTP_REQUEST_REJECTED in the corresponding RESET_STREAM if no processing was performed.  Clients MUST NOT reset streams with the HTTP_REQUEST_REJECTED error code except in response to a QUIC STOP_SENDING frame. </del>  If a stream is cancelled after receiving a complete response, the client MAY ignore the cancellation and use the response.  However, if The server has attempted to push content which the client has cached. <del> The client no longer needs the requested data. </del>  The client's stream terminated without containing a fully-formed request.

I think this is editorial, as servers were not previously prohibited from using this error code. This merely suggests when they might do so.
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.
When a reverse proxy forwards a HTTP response to the client, sometimes it sees the upstream server terminate the response early. For example, a HTTP/1.1 connection being closed prior to transmitting number of bytes specified by the field. Or a chunked response being closed abruptly. The only way of delegating such a condition to the client in HTTP/2 was to send a RSTSTREAM frame with . However, use of the error code is awkward in this case, considering the fact that the error did not happen within the communication between the endpoints using the particular HTTP/2 connection. I think it makes sense to have an error code that better designates the case. One way to address the issue in H3 would be to change to , and let the server use the error code (as an argument of the RESET_STREAM frame) to indicate that the response has been "cancelled". Originally proposed in URL
What if you do not want to admit to being a proxy? (who does?)
HTTPMESSAGECANCELLED would simply mean that the transfer of the response is cancelled, regardless of the server being an origin or a reverse proxy.
I can get behind this. As long as we're not looking for a byte-by-byte reproduction of the bytes the intermediary received (as was once requested and rejected, I think at the Seattle interim), then it makes sense to signal that the message was killed.
This is basically an expansion of the CANCELLED/REJECTED codes to allow a server to do either. WFM.
NAME Yes. And thank you for the PR.

requests rejected by the server as though they had never been sent at all, thereby allowing them to be retried later on a new connection. Servers MUST NOT use the HTTP_REQUEST_REJECTED error code for  <ins> requests which were partially or fully processed.  When a server abandons a response after partial processing, it SHOULD abort its response stream with the error code HTTP_REQUEST_CANCELLED. When a client sends a STOP_SENDING with HTTP_REQUEST_CANCELLED, a server MAY send the error code HTTP_REQUEST_REJECTED in the corresponding RESET_STREAM if no processing was performed.  Clients MUST NOT reset streams with the HTTP_REQUEST_REJECTED error code except in response to a QUIC STOP_SENDING frame that contains the same code. </ins> If a stream is cancelled after receiving a complete response, the client MAY ignore the cancellation and use the response.  However, if The server has attempted to push content which the client has cached.  <ins> The request or its response is cancelled. </ins> The client's stream terminated without containing a fully-formed request.


implementer.  An implementation MUST ensure that the length of a frame exactly matches the length of the fields it contains.  10. 10.1.

Separately from this, I note that Security Considerations is becoming more of a jumbled bag of waving at various concerns. Some organization and expanded text around each topic might be warranted.
Can you add a sub-heading(##) for each of these considerations, like in the transport draft? Originally posted by NAME in URL
Seems mostly right. Though I would prefer something that simply highlighted the fact that a remote address is not expected to be stable over the lifetime of a connection. Therefore, decisions that use an address as input need to either keep up to date, or accept the consequences of not doing so. I would also point out that this is true even if migration is disabled.The text looks great. I really appreciate the fact that we have a recommendation on what IP address we should associate with logging (and address-based authentication).

implementer.  An implementation MUST ensure that the length of a frame exactly matches the length of the fields it contains. <ins> Certain HTTP implementations use the client address for logging or access-control purposes.  Since a QUIC client's address might change during a connection (and future versions might support simultaneous use of multiple addresses), such implementations will need to either actively retrieve the client's current address or addresses when they are relevant or explicitly accept that the original address might change. </ins> 10. 10.1.


stateless reset allows a peer to more quickly identify when a connection becomes unusable. <del> Packets that are matched to an existing connection, but for which the endpoint cannot remove packet protection, are discarded. </del>  Invalid packets without packet protection, such as Initial, Retry, or Version Negotiation, MAY be discarded.  An endpoint MUST generate a

by discarding packets which are "inconsistent with the state of the connection." Previously, this was only a failure to remove packet protection; incorrect version is added as another example inconsistency. Note that this phrasing doesn't rule out upward-VN like , since receiving an expected upgrade is not inconsistent with your local state.
I'm wondering if there is a difference between failed decryption and version number. If the versions differ, it doesn't really make sense to try to read a subsequent coalesced packet, unless you want to do trial and error since the concepts of coalescing and length fields are not defined in the general case. Even if you understand the version, two version can not (or should not?) exist in the same datagram. If packet protection fails on the same version, it might just be a timing issue, or the (to me) dreaded path probe with prefixed knowingly invalid handshake.
That's an interesting question. The Length field is version-specific. If you include it, you can have no expectation that a peer which doesn't speak that version will even be able to find anything further in the datagram -- as far as it knows, the remainder of the datagram is that connection. But that actually has a potential use. On receipt, if the logic first attempts to chunk up the datagram and the subsequent packet is the correct version for the connection, then acknowledging that second coalesced packet implies that you support the first packet's version.

stateless reset allows a peer to more quickly identify when a connection becomes unusable.  <ins> Packets that are matched to an existing connection are discarded if the packets are inconsistent with the state of that connection.  For example, packets are discarded if they indicate a different protocol version than that of the connection, or if the removal of packet protection is unsuccessful once the expected keys are available. </ins> Invalid packets without packet protection, such as Initial, Retry, or Version Negotiation, MAY be discarded.  An endpoint MUST generate a


fields, each of which has a fixed index over time.  Its entries are defined in static-table.  Note the QPACK static table is indexed from 0, whereas the HPACK static table is indexed from 1. an absolute index greater than or equal to the Base, the representation starts with the '0000' four-bit pattern.  The following bit, 'N', indicates whether an intermediary is permitted to add this header to the dynamic header table on subsequent hops.  When the 'N' bit is set, the encoded header MUST

A couple of clarifications that can be useful to people who are looking at *PACK for the first time like myself. Not sure about the normative language though. I can reword it if necessary.
Let's see how handles it: We could simply add similar verbiage to Sections and 4.5.5. of the QPACK draft instead.
NAME I can add something like that, but TBH I don't think that the HPACK wording would have helped me, so I'd rather keep my clarification (that says "ignore value") as well.
This wording seems fine.

fields, each of which has a fixed index over time.  Its entries are defined in static-table. <ins> All entries in the static table have a name and a value.  However, values can be empty (that is, have a length of 0). </ins> Note the QPACK static table is indexed from 0, whereas the HPACK static table is indexed from 1. an absolute index greater than or equal to the Base, the representation starts with the '0000' four-bit pattern. <ins> Only the header field name stored in the static or dynamic table is used.  Any header field value MUST be ignored. </ins> The following bit, 'N', indicates whether an intermediary is permitted to add this header to the dynamic header table on subsequent hops.  When the 'N' bit is set, the encoded header MUST


of the TLS handshake messages being integrated into the calculation of secrets, the inclusion of the QUIC transport parameters extension ensures that handshake and 1-RTT keys are not the same as those that <del> might be produced by a server running TLS over TCP.  However, 0-RTT keys only include the ClientHello message and might therefore use the same secrets.  To avoid the possibility of cross-protocol key synchronization, additional measures are provided to improve key separation. </del>  The QUIC packet protection keys and IVs are derived using a different label than the equivalent keys in TLS. To preserve this separation, a new version of QUIC SHOULD define new labels for key derivation for packet protection key and IV, plus the <del> header protection keys. </del>  <del> The initial secrets also use a key that is specific to the negotiated QUIC version.  New QUIC versions SHOULD define a new salt value used in calculating initial secrets. </del>  10.

This includes the suggestion from NAME to remove the 0-RTT text. Plus some text explaining the choice of label.

of the TLS handshake messages being integrated into the calculation of secrets, the inclusion of the QUIC transport parameters extension ensures that handshake and 1-RTT keys are not the same as those that  <ins> might be produced by a server running TLS over TCP.  To avoid the possibility of cross-protocol key synchronization, additional measures are provided to improve key separation. </ins> The QUIC packet protection keys and IVs are derived using a different label than the equivalent keys in TLS. To preserve this separation, a new version of QUIC SHOULD define new labels for key derivation for packet protection key and IV, plus the  <ins> header protection keys.  This version of QUIC uses the string "quic". Other versions can use a version-specific label in place of that string. </ins>  <ins> The initial secrets use a key that is specific to the negotiated QUIC version.  New QUIC versions SHOULD define a new salt value used in calculating initial secrets. </ins> 10.


size for every QUIC version it supports. The client populates the Source Connection ID field with a value of <del> its choosing and sets the SCIL field to match. </del>  The Destination Connection ID field in the server's Initial packet contains a connection ID that is chosen by the recipient of the On 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.  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. </del>  A client MUST only change the value it sends in the Destination Connection ID in response to the first packet of each type it

For and NAME
Thanks
Note that section 17.2.3. 0-RTT does not mention 0-RTT CID but the following section 7.2.4. Handshake Packet does mention Handshake CID's. In existing draft.
LGTM, given the incorporated changes.

size for every QUIC version it supports. The client populates the Source Connection ID field with a value of  <ins> its choosing and sets the SCIL field to indicate the length. The first flight of 0-RTT packets use the same Destination and Source Connection ID values as the client's first Initial. </ins> The Destination Connection ID field in the server's Initial packet contains a connection ID that is chosen by the recipient of the On 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 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. </ins> A client MUST only change the value it sends in the Destination Connection ID in response to the first packet of each type it


original_connection_id transport parameter if it sent a Retry packet. <del> The idle timeout is a value in seconds that is encoded as an </del>  integer, see (idle-timeout).  If this parameter is absent or zero then the idle timeout is disabled.

This is fine, but I think that we want chairs to call it.
This falls under the new process, so it's upto the chairs.
Based on the discussion in I think this has rough consensus. Tagging as such and reassigning.
Slow down, we're not in the new process yet. And, in either case, consensus needs to be determined on-list, not in issues.
Currently, the t is in units of seconds. This seems to be too restrictive and not very future proof. Additionally, from an API perspective, most other timers are settable on the millisecond scale, so it creates a kind of ugly discrepancy between all the different timers settable by the app if just one of them has units of seconds. I propose we change the units from seconds to milliseconds. The change both for implementations and spec should be minimal, and it makes things (timers) a little more consistent.
This seems sensible to me both for consistency and I can imagine cases when one would want to specify a fraction(ie: 1/2 of a second).
Agreed. A second is an eternity for a intra-datacenter connection for example. For future proofing all time units should be at least msec IMO.
Don't we want to specify a minimum value now?
No, I don't think so. Even before, if you had a greater than 1 sec RTT (somehow...) if you set an idle timeout of 1 second it would cause you problems. I think bottom line you be smart about your idle timeout values. If you set too small a value you can unnecessarily kill your own connection.
As NAME says, actual idle timeout depends on RTT, and that’s what we have adopted in .
This SGTM.
I support this
I think this has rough consensus. Tagging as such and reassigning.
LG, but I don't know what process this falls under. I'll let NAME and NAME decide.

original_connection_id transport parameter if it sent a Retry packet.  <ins> The idle timeout is a value in milliseconds that is encoded as an </ins> integer, see (idle-timeout).  If this parameter is absent or zero then the idle timeout is disabled.


acknowledgement is received, a new RTT is computed and the timer SHOULD be set for twice the newly computed smoothed RTT. <del> When crypto packets are sent, the sender MUST set a timer for the crypto timeout period.  Upon timeout, the sender MUST retransmit all </del>  unacknowledged CRYPTO data if possible. Until the server has validated the client's address on the path, the

From reading the text, it was not clear that the timer is set based on the last (and not the first) crypto packet sent.
LGTM. I've reworded a bit.

acknowledgement is received, a new RTT is computed and the timer SHOULD be set for twice the newly computed smoothed RTT.  <ins> When a crypto packet is sent, the sender MUST set a timer for the crypto timeout period.  This timer MUST be updated when a new crypto packet is sent.  Upon timeout, the sender MUST retransmit all </ins> unacknowledged CRYPTO data if possible. Until the server has validated the client's address on the path, the


connection is in the draining state. An endpoint MAY transition from the closing period to the draining <del> period if it can confirm that its peer is also closing or draining. Receiving a CONNECTION_CLOSE frame is sufficient confirmation, as is receiving a stateless reset.  The draining period SHOULD end when the closing period would have ended.  In other words, the endpoint can use the same end time, but cease retransmission of the closing packet. </del>  Disposing of connection state prior to the end of the closing or draining period could cause delayed or reordered packets to be

This doesn't seem implementable without a list of all the things that were sufficient.
That is the list. If you think it should be reworded, I'm happy to take a PR.

connection is in the draining state. An endpoint MAY transition from the closing period to the draining  <ins> period if it receives a CONNECTION_CLOSE frame or stateless reset, both of which indicate that the peer is also closing or draining. The draining period SHOULD end when the closing period would have ended.  In other words, the endpoint can use the same end time, but cease retransmission of the closing packet. </ins> Disposing of connection state prior to the end of the closing or draining period could cause delayed or reordered packets to be


7.3.1. <del> A client that attempts to send 0-RTT data MUST remember the transport parameters used by the server.  The transport parameters that the server advertises during connection establishment apply to all connections that are resumed using the keying material established during that handshake.  Remembered transport parameters apply to the new connection until the handshake completes and new transport parameters from the server can be provided. A server can remember the transport parameters that it advertised, or store an integrity-protected copy of the values in the ticket and recover the information when accepting 0-RTT data.  A server uses the transport parameters in determining whether to accept 0-RTT data. A server MAY accept 0-RTT and subsequently provide different values for transport parameters for use in the new connection.  If 0-RTT data is accepted by the server, the server MUST NOT reduce any limits or alter any values that might be violated by the client with its 0-RTT data.  In particular, a server that accepts 0-RTT data MUST NOT set values for the following parameters (transport-parameter- definitions) that are smaller than the remembered value of those parameters. </del>  initial_max_data initial_max_stream_data_bidi_remote, or initial_max_streams_uni and initial_max_stream_data_uni. <del> The value of the server's previous preferred_address MUST NOT be used when establishing a new connection; rather, the client should wait to observe the server's new preferred_address value in the handshake. </del> A server MUST either reject 0-RTT data or abort a handshake if the implied values for transport parameters cannot be supported.

but there are some value judgments here. I believe the new layout is much clearer for implementers. I made maxpacketsize optional for reasons that I think are self-evident. I thought saving maxackdelay might be useful for clients setting loss timers on their 0-RTT stuff. Everything else is required or forbidden. Per NAME suggestion, I put all the new TPs in the required category. I also specified behavior when the EE msg omits a TP. I thought it would be simpler for servers that spit out a standard set of TPs to do things that way.
NAME : Are you planning to incorporate feedback into the PR?
Yes, sorry for the delay.
Thanks for doing this Martin. I will raise this on the list so that people are clear about this. Technically, there isn't a change here, but it certainly looks like it if you don't understand what is going on.
Sec 7.3.1 of quic-transport says: A client that attempts to send 0-RTT data MUST remember the transport parameters used by the server. The transport parameters that the server advertises during connection establishment apply to all connections that are resumed using the keying material established during that handshake. Remembered transport parameters apply to the new connection until the handshake completes and new transport parameters from the server can be provided. Later on, the section makes it clear that this is really about the flow control parameters, which makes a lot of sense, but here I am storing all this data to be compliant. I propose that we change the MUST above to refer to only the 6 FC parameters. This reduces the stuff that clients have to store. going through each: originalconnectionid: clearly useless idletimeout: useless in handshake statelessresettoken: clearly useless maxpacketsize: could be useful, but meh ackdelayexponent: not useful before we get the server Handshake maxackdelay: same disablemigration: same preferred_address: same
It's not just the flow control parameters -- it's also support for extensions in TPs yet-to-be-defined. So while I would be amenable to reorienting this issue to create an exception for the ones you've marked as useless, I think this is the correct default rule.
good point. I'm happy to write a PR as you describe if others agree.
I agree that a good number of those are useless. Possibly we should explicitly state which params need to be stored and extensions should be required to state whether they need to be stored when they're defined?
How is anyone going to know if you don't remember these values? Just remember the ones you know you need to remember.
A conformance test that deliberately lowers them after accepting 0-RTT and checks whether the client chokes.
You can't lower preferred addresses, disable migration, etc.. Basically, we should allow all those that NAME points out make no sense to remember to change. That's the real fix here.
This makes sense, and the list is good.
Editors: please don't close the issue before consensus is declared.
For context, the PR was marked editorial but the issue it auto-closed was marked design. Comment from the PR discussion: The PR changes the text from "MUST remember everything" to "MUST NOT remember the things that don't make sense to remember and you weren't actually remembering anyway, but MUST remember everything else". That is, there's no functional change to real implementations, but the normative requirements in the text didn't match what was actually possible to implement. Martin's right -- this is really an editorial change, despite touching normative text.
A few suggestions, but I like this. Maybe I will do the tabulation thing later.One editorial suggestion.Looks reasonable to me. I'm not opposed to NAME suggestion that lets future transport parameters decide whether they need to be remembered or not, either.

7.3.1.  <ins> Both endpoints store the value of the server transport parameters from a connection and apply them to any 0-RTT packets that are sent in subsequent connections to that peer, except for transport parameters that are explicitly excluded.  Remembered transport parameters apply to the new connection until the handshake completes and the client starts sending 1-RTT packets.  Once the handshake completes, the client uses the transport parameters established in the handshake. The definition of new transport parameters (new-transport-parameters) MUST specify whether they MUST, MAY, or MUST NOT be stored for 0-RTT. A client need not store a transport parameter it cannot process. A client MUST NOT use remembered values for the following parameters: original_connection_id, preferred_address, stateless_reset_token, and ack_delay_exponent.  The client MUST use the server's new values in the handshake instead, and absent new values from the server, the default value. A client that attempts to send 0-RTT data MUST remember all other transport parameters used by the server.  The server can remember these transport parameters, or store an integrity-protected copy of the values in the ticket and recover the information when accepting 0-RTT data.  A server uses the transport parameters in determining whether to accept 0-RTT data. If 0-RTT data is accepted by the server, the server MUST NOT reduce any limits or alter any values that might be violated by the client with its 0-RTT data.  In particular, a server that accepts 0-RTT data MUST NOT set values for the following parameters (transport- parameter-definitions) that are smaller than the remembered value of the parameters. </ins> initial_max_data initial_max_stream_data_bidi_remote, or initial_max_streams_uni and initial_max_stream_data_uni.  A server MUST either reject 0-RTT data or abort a handshake if the implied values for transport parameters cannot be supported.


server, can cause a situation in which the server cannot send when the client has no data to send and the anti-amplification limit is reached.  In order to avoid this causing a handshake deadlock, <del> clients SHOULD send a packet upon a handshake timeout, as described in QUIC-RECOVERY.  If the client has no data to retransmit and does not have Handshake keys, it SHOULD send an Initial packet in a UDP datagram of at least 1200 bytes.  If the client has Handshake keys, it SHOULD send a Handshake packet. </del>  A server might wish to validate the client address before starting the cryptographic handshake.  QUIC uses a token in the Initial packet Upon receiving the client's Initial packet, the server can request address validation by sending a Retry packet (packet-retry) containing a token.  This token MUST be repeated by the client in all <del> Initial packets it sends after it receives the Retry packet.  In response to processing an Initial containing a token, a server can either abort the connection or permit it to proceed. </del>  As long as it is not possible for an attacker to generate a valid token for its own address (see token-integrity) and the client is future connections.  The client includes this token in Initial packets to provide address validation in a future connection.  The client MUST include the token in all Initial packets it sends, unless <del> a Retry replaces the token with a newer token.  The client MUST NOT use 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 different connections; validating the port is therefore unlikely to be successful. <del> A token SHOULD be constructed to be easily distinguishable from tokens that are sent in Retry packets as they are carried in the same field. </del>  If the client has a token received in a NEW_TOKEN frame on a previous connection to what it believes to be the same server, it SHOULD token in a previous connection using the NEW_TOKEN frame, and if the server has lost state, it might be unable to validate the token at all, leading to connection failure if the packet is <del> discarded.  A server MAY encode tokens provided with NEW_TOKEN </del>  frames and Retry packets differently, and validate the latter more strictly.

Although not super-important, the biggest decision in here is that the packet number of the response to a RETRY is 1. Based on interop experience, I believe this is the semi-consensus of implementers.
Should there be normative text mandating that the packet number be increased?
I think we need a new issue on that. The information about this is scattered around issuing CID's, path validation, retransmission of Initial. It would be good with a clear statement of when the packet number starts counting. I also believe that there is somewhere text to the effect that a CID cannot have the same PN twice, but that does not apply to Retry.

server, can cause a situation in which the server cannot send when the client has no data to send and the anti-amplification limit is reached.  In order to avoid this causing a handshake deadlock,  <ins> clients SHOULD send a packet upon a crypto retransmission timeout, as described in QUIC-RECOVERY.  If the client has no data to retransmit and does not have Handshake keys, it SHOULD send an Initial packet in a UDP datagram of at least 1200 bytes.  If the client has Handshake keys, it SHOULD send a Handshake packet. </ins> A server might wish to validate the client address before starting the cryptographic handshake.  QUIC uses a token in the Initial packet Upon receiving the client's Initial packet, the server can request address validation by sending a Retry packet (packet-retry) containing a token.  This token MUST be repeated by the client in all  <ins> Initial packets it sends for that connection after it receives the Retry packet.  In response to processing an Initial containing a token, a server can either abort the connection or permit it to proceed. </ins> As long as it is not possible for an attacker to generate a valid token for its own address (see token-integrity) and the client is future connections.  The client includes this token in Initial packets to provide address validation in a future connection.  The client MUST include the token in all Initial packets it sends, unless  <ins> a Retry or NEW_TOKEN frame replaces the token with a newer one.  The client MUST NOT use 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, there might be some time between when the token is created and when the token is different connections; validating the port is therefore unlikely to be successful.  <ins> A token SHOULD be constructed for the server to easily distinguish it from tokens that are sent in Retry packets as they are carried in the same field. </ins> If the client has a token received in a NEW_TOKEN frame on a previous connection to what it believes to be the same server, it SHOULD token in a previous connection using the NEW_TOKEN frame, and if the server has lost state, it might be unable to validate the token at all, leading to connection failure if the packet is  <ins> discarded.  A server SHOULD encode tokens provided with NEW_TOKEN </ins> frames and Retry packets differently, and validate the latter more strictly.


4.2.3. <del> The PRIORITY (type=0x02) frame specifies the client-advised priority </del>  of a request, server push or placeholder. A PRIORITY frame identifies an element to prioritize, and an element 4.2.6. <del> The PUSH_PROMISE frame (type=0x05) is used to carry a promised request header set from server to client on a request stream, as in HTTP/2. </del>  The payload consists of:

Simply making this consistent through the document by doing the minimal change and removing leading 0s. Happy to add them if we prefer.
Works for me.

4.2.3.  <ins> The PRIORITY (type=0x2) frame specifies the client-advised priority </ins> of a request, server push or placeholder. A PRIORITY frame identifies an element to prioritize, and an element 4.2.6.  <ins> The PUSH_PROMISE frame (type=0x5) is used to carry a promised request header set from server to client on a request stream, as in HTTP/2. </ins> The payload consists of:


Packet numbers are limited to this range because they need to be representable in whole in the Largest Acknowledged field of an ACK frame (frame-ack).  When present in a long or short header however, <del> packet numbers are reduced and encoded in 1 to 4 bytes, see packet- </del>  encoding). <del> Version Negotiation (packet-version) and Retry packet-retry packets </del>  do not include a packet number. Packet numbers are divided into 3 spaces in QUIC: <del> Initial space: All Initial packets packet-initial are in this </del>  space. <del> Handshake space: All Handshake packets packet-handshake are in </del>  this space. Application data space: All 0-RTT and 1-RTT encrypted packets <del> packet-protected are in this space. </del>  As described in QUIC-TLS, each packet type uses different protection keys. types. A QUIC endpoint MUST NOT reuse a packet number within the same packet <del> number space in one connection (that is, under the same cryptographic keys).  If the packet number for sending reaches 2^62 - 1, the sender MUST close the connection without sending a CONNECTION_CLOSE frame or any further packets; an endpoint MAY send a Stateless Reset (stateless-reset) in response to further packets that it receives. </del>  A receiver MUST discard a newly unprotected packet unless it is certain that it has not processed another packet with the same packet

The deleted parenthetical is not accurate, and is unnecessary: 0-RTT and 1-RTT with various key phases share a PN space without sharing keys. I prefer the MUST NOT in coalescing, but if there are objections I can revert it.
Thanks Martin.
All good apart from the MUST NOT. I'm with Martin -- everything else looks fine.

Packet numbers are limited to this range because they need to be representable in whole in the Largest Acknowledged field of an ACK frame (frame-ack).  When present in a long or short header however,  <ins> packet numbers are reduced and encoded in 1 to 4 bytes (see packet- </ins> encoding).  <ins> Version Negotiation (packet-version) and Retry (packet-retry) packets </ins> do not include a packet number. Packet numbers are divided into 3 spaces in QUIC:  <ins> Initial space: All Initial packets (packet-initial) are in this </ins> space.  <ins> Handshake space: All Handshake packets (packet-handshake) are in </ins> this space. Application data space: All 0-RTT and 1-RTT encrypted packets  <ins> (packet-protected) are in this space. </ins> As described in QUIC-TLS, each packet type uses different protection keys. types. A QUIC endpoint MUST NOT reuse a packet number within the same packet  <ins> number space in one connection.  If the packet number for sending reaches 2^62 - 1, the sender MUST close the connection without sending a CONNECTION_CLOSE frame or any further packets; an endpoint MAY send a Stateless Reset (stateless-reset) in response to further packets that it receives. </ins> A receiver MUST discard a newly unprotected packet unless it is certain that it has not processed another packet with the same packet


Negotiation (ALPN) RFC7301 to select an application protocol.  Unless another mechanism is used for agreeing on an application protocol, endpoints MUST use ALPN for this purpose.  When using ALPN, endpoints <del> MUST abort a connection if an application protocol is not negotiated. </del>  An application-layer protocol MAY restrict the QUIC versions that it can operate over.  Servers MUST select an application protocol

Just so you don't have to read the whole RFC7301.
Yeah I was thinking about that. I wonder if it would make sense for the client to simply send PROTOCOL_VIOLATION instead of a TLS alert.
NAME I added a clarification to allow clients to send noapplicationnegotiated as well. Should I send an email to the mailing list for discussion now that the new process is in effect?
Unfortunately yes. This is a design change so it will need an issue as well. I like it and we should do it, but can you add the error code we would use in CONNECTION_CLOSE to the text?
NAME ok, added error code, created issue () and sent email to the list.
NAME thanks, suggestions accepted.
PR added a requirement for end-points to terminate a connection if ALPN negotiation fails, but doesn't specify an error code for this situation. RFC7301 defines the alert that must be used by TLS servers, so QUIC servers should probably use that as well. As for clients, RFC7301 doesn't specify a behaviour, but it also doesn't forbid use of in clients, and it seems better if QUIC clients use the same error code as the servers. Proposed wording change is in .
My :+1: goes to making the change, though I would like to confirm that it wouldn't be illegal for a client to close the connection with error after it sends ClientFinished. I ask this because depending on the design of the TLS stack, the QUIC stack might not be able to see the result of ALPN before the TLS stack generates a ClientFinished (note that EE.ALPN and ServerFinished might arrive in one packet). It's my understanding that the intent of the issue is to not allow the client to send any application data on ALPN failure. Assuming that's true, sending the alert after ClientFinished should be fine.
Yes, I think that would be fine. I don't think that we would be able to guarantee that the alert goes out before sending ClientFinished either. It's certainly possible, but I can see how it might be tricky.
This was fixed with .
a few nits

Negotiation (ALPN) RFC7301 to select an application protocol.  Unless another mechanism is used for agreeing on an application protocol, endpoints MUST use ALPN for this purpose.  When using ALPN, endpoints  <ins> MUST immediately close a connection (see Section 10.3 in QUIC- TRANSPORT) if an application protocol is not negotiated with a no_application_protocol TLS alert (QUIC error code 0x178, see tls- errors).  While RFC7301 only specifies that servers use this alert, QUIC clients MUST also use it to terminate a connection when ALPN negotiation fails. </ins> An application-layer protocol MAY restrict the QUIC versions that it can operate over.  Servers MUST select an application protocol


MAX_STREAMS frames (frame-max-streams).  Separate limits apply to unidirectional and bidirectional streams.  Endpoints MUST NOT exceed the limit set by their peer.  An endpoint that receives a STREAM frame with a stream ID exceeding the limit it has sent MUST treat this as a stream error of type STREAM_LIMIT_ERROR

This LGTM, but it will need chair approval eventually.
This is also applies to the transport parameters, doesn’t it?
Good point, I think it does.
I now mention the transport parameters and incorporated a version of Martin's suggested text. PTAL.
2^60 is too large as well, as 2^62 cannot be expressed as a variable-length integer, either.
I believe 2^60 means stream IDs of that type with Stream IDs less than 2^62 can be opened, but those equal and greater cannot. ie: MAX_STREAMS of 1 means a single stream can be opened, and that stream will have an ID less than 1 * 2^2 = 4.
NAME I see. You're right -- thanks.
Currently, stream IDs are as high as 2^62-1. The MAXSTREAMS frame specifies a limit as a varint that has a max value of 2^62-1 that translates to a Stream ID of 4 * maxstreams + initialstreamidfortype. This is larger than the max expressible stream ID, so we should specify what happens in this case. I'm suggesting that we allow all streams to be opened and MUST NOT close the connection in PR
Good idea.
I don't have a strong opinion, but this seems like a bug at the peer. I would be inclined to specify that an endpoint MUST NOT advertise max streams that are too large, and close the connection (with PROTOCOL_VIOLATION) if a value is received that is too large.
I'd also lean towards closing the connection (probably with a TRANSPORTPARAMETERERROR, not a PROTOCOL_VIOLATION though). Interestingly, we have the following definition for packet numbers but no such thing for stream IDs, although this is implied by the way we encode them.
I'm happy to close the connection as well, as long as the behavior is well-defined.
On PR , NAME noted that this logic should apply to the equivalent transport param as well.
Wordsmithing, but content is fine.

MAX_STREAMS frames (frame-max-streams).  Separate limits apply to unidirectional and bidirectional streams. <ins> If a max_streams transport parameter or MAX_STREAMS frame is received with a value greater than 2^60, this would allow a maximum stream ID that cannot be expressed as a variable-length integer (see integer- encoding).  If either is received, the connection MUST be closed immediately with a connection error of type STREAM_LIMIT_ERROR (see immediate-close). </ins> Endpoints MUST NOT exceed the limit set by their peer.  An endpoint that receives a STREAM frame with a stream ID exceeding the limit it has sent MUST treat this as a stream error of type STREAM_LIMIT_ERROR


PRIORITY frame sent on a control stream with the Prioritized Element Type set to "11" MUST be treated as a connection error of type HTTP_MALFORMED_FRAME.  A PRIORITY frame with Empty bits not set to <del> zero MUST be treated as a connection error of type </del>  HTTP_MALFORMED_FRAME. When a PRIORITY frame claims to reference a request, the associated

in the PRIORITY frame
I agree, this doesn't seem to mesh with the discussion on the issue. If nothing else, it introduces the inconsistency NAME pointed out, but the gist of the issue seems to be that a receiver MAY check that it's zero.
NAME Are you in favor of changing MUST to MAY, should I discard the PR, or have I misunderstood your comment completely?
MUST => MAY
PR kicked up a bit more controversy than expected, so I'm opening an issue. Currently he Empty bits it the PRIORITY frame MUST be zero and MUST be ignored by the receiver. In most other areas, we're killing connections for this kind of violation. Then again, HTTP/3 is a different protocol from QUIC. What do people think?
Closing the connection SGTM.
I can live with must be zero and connection error if not.
Which means drop requirement to ignore them.
I'd keep the requirement to ignore them on receipt -- why require more unnecessary checking?
That works, but then state that their value is completely meaningless and should not be interpreted as anything
NAME you want to add verbiage in addition to this?
Looking back to HTTP/2, it had this to say about in-frame PADDING in DATA frames. Padding octets MUST be set to zero when sending. A receiver is not obligated to verify padding but MAY treat non-zero padding as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. URL
That is probably OK here as well. Though the need is less acute for a 4 bit field. Padding in DATA can be arbitrarily large. Either way, the hope would be that at least a few implementations check and enforce.
I don't see how this agrees with the field requirement that empty bits are ignored Empty: A four-bit field which MUST be zero when sent and MUST be ignored on receipt.

PRIORITY frame sent on a control stream with the Prioritized Element Type set to "11" MUST be treated as a connection error of type HTTP_MALFORMED_FRAME.  A PRIORITY frame with Empty bits not set to  <ins> zero MAY be treated as a connection error of type </ins> HTTP_MALFORMED_FRAME. When a PRIORITY frame claims to reference a request, the associated


packet that is not newly acknowledged or not ack-eliciting. When RTT is calculated, the ack delay field from the ACK frame SHOULD <del> be limited to the max_ack_delay specified by the peer.  Limiting ack_delay to max_ack_delay ensures a peer specifying an extremely small max_ack_delay doesn't cause more spurious timeouts than a peer that correctly specifies max_ack_delay.  It SHOULD be subtracted from the RTT as long as the result is larger than the min_rtt.  If the result is smaller than the min_rtt, the RTT should be used, but the ack delay field should be ignored. A sender calculates both smoothed RTT (SRTT) and RTT variance (RTTVAR) similar to those specified in RFC6298, see on-ack-received. </del>  A sender takes an RTT sample when an ACK frame is received that <del> acknowledges a larger packet number than before (see on-ack- received).  A sender will take multiple RTT samples per RTT when multiple such ACK frames are received within an RTT.  When multiple samples are generated within an RTT, the smoothed RTT and RTT variance could retain inadequate history, as suggested in RFC6298. Changing these computations is currently an open research question. </del>  min_rtt is the minimum RTT measured over the connection, prior to adjusting by ack delay.  Ignoring ack delay for min RTT prevents intentional or unintentional underestimation of min RTT, which in turn prevents underestimating smoothed RTT.  6. QUIC senders use both ack information and timeouts to detect lost

Also clarifies the implications of ignoring ackdelay when it would result in an RTT sample less than minrtt.
From , NAME had the following suggestion for restructuring the RTT estimation section:
Does this mean that you cap the ACK Delay field to the value from the maxackdelay transport parameter with min()? That's what the pseudocode says, but "limit" is sufficiently vague that I wasn't sure. Especially since that has the effect of feeding massively inflated RTT samples into the estimator. ACK Delay will be large if the packet contains retransmissions. Shouldn't we be throwing out these samples rather than distorting them? I mean, that was my understanding of the outcome of , and it seems like this case is rare enough that it won't starve the RTT estimator too badly. I think that this means that the RTT calculation should be done with an ACK Delay of 0.
On your first point, this was an outcome of . Yes, it is a min, as the pseudocode says. For context, when was being discussed, two things were true that aren't anymore: (i) non-ack-eliciting packets were being used for the RTT computation, and (ii) we were using max(observed ackdelays) as the maxackdelay. We've since replaced that computation with the peer's explicitly stated maxackdelay in our RTO computations. So if you do get inflated ack delays, that's going to be because acks were being lost or the receiver took too long to send an ack. In either case, we want to capture the resulting increase somewhere, since the RTO should incorporate such potential delays. In , we decided to let the smoothedRTT EWMA estimator handle it. If it's rare, it shouldn't move the EWMA by much. If it's not rare, then the EWMA will capture it, and we will appropriately end up with a larger RTO. On your second point, yes, ack delay of 0 is what is meant there. Should there be more text?
Well, if I understand the first, you are saying that RTO won't be negatively affected by this. But I'm concerned about RTT. Excluding non-ack-eliciting packets changes the odds of encountering this, but that's all. I lost track in . There were pictures of inscrutable graphs. What I think I will request here is text explaining the consequences of these tweaks. Clearer text. The text on the second just needs to be clearer.
That's fair. I think we should add text to explain the consequences.
Meta-level comments. The code and text look fine.

packet that is not newly acknowledged or not ack-eliciting. When RTT is calculated, the ack delay field from the ACK frame SHOULD  <ins> be limited to the max_ack_delay specified by the peer by using a minimum of the two values.  Limiting ack_delay to max_ack_delay ensures a peer specifying an extremely small max_ack_delay doesn't cause more spurious timeouts than a peer that correctly specifies max_ack_delay.  It SHOULD be subtracted from the RTT as long as the result is larger than the min_rtt.  If the result is smaller than the min_rtt, the RTT should be used, but the ack delay field should be ignored.  Ignoring ack_delay when it would cause an RTT sample smaller than the current min_rtt prevents a peer from manipulating RTT samples below min_rtt at the cost of inflated RTT samples in the presence of persistent delayed acks. </ins> A sender takes an RTT sample when an ACK frame is received that  <ins> acknowledges a larger packet number than before.  A sender will take multiple RTT samples per RTT when multiple such ACK frames are received within an RTT.  When multiple samples are generated within an RTT, the smoothed RTT and RTT variance could retain inadequate history, as suggested in RFC6298.  Changing these computations is currently an open research question. </ins> min_rtt is the minimum RTT measured over the connection, prior to adjusting by ack delay.  Ignoring ack delay for min RTT prevents intentional or unintentional underestimation of min RTT, which in turn prevents underestimating smoothed RTT. <ins> A sender calculates both smoothed RTT (SRTT) and RTT variance (RTTVAR) similar to those specified in RFC6298.  Note that computing smoothed_rtt does not use ack_delay for the first RTT sample, because doing so would result in a smoothed_rtt that is smaller than the min_rtt. On every newly acknowledged ack-eliciting largest acked: ~~~ latest_rtt = ack_time - send_time_of_largest_acked ~~~ First RTT sample: ~~~ min_rtt = latest_rtt smoothed_rtt = latest_rtt rttvar = latest_rtt / 2 ~~~ Subsequent RTT samples: ~~~ min_rtt = min(min_rtt, latest_rtt) ack_delay = min(ack_delay, max_ack_delay) if (ack_delay + min_rtt < latest_rtt): latest_rtt = latest_rtt - ack_delay smoothed_rtt = 7/8 * smoothed_rtt + 1/8 * rtt rttvar_sample = abs(smoothed_rtt - latest_rtt) rttvar = 3/4 * rttvar + 1/4 * rttvar_sample ~~~ </ins> 6. QUIC senders use both ack information and timeouts to detect lost


control stream MUST be treated as a connection error of type HTTP_WRONG_STREAM. <del> PRIORITY frames received by a client MUST be treated as a stream </del>  error of type HTTP_UNEXPECTED_FRAME. 4.2.4.

Has this been the only case where a protocol violation resulted in a stream error? Or should we check if there are similar cases and file PRs as necessary? I ask this, because IIUC at least some of us considered the proposal as a change of design principle, to stop localizing such errors, and I wonder if the change made in this PR is a tip of an iceberg.
I haven't comprehensively reviewed the draft, but having just implemented the HTTP/3 frame parser, I can say that other UNEXPECTED_FRAME instances are connection errors. Furthermore, PRIORITY frames can arrive on the control stream. If there were a stream error there it would have to take down the connection anyway.
NAME that's a good question. Previously, the philosophy has been that connection errors are ones which screw up the state so badly you can't trust the connection to proceed correctly. It's a reasonable, but not bulletproof, argument that a peer which doesn't obey the protocol has the same implication -- you can't trust the connection. If you see other protocol violations which are stream-local errors, please file issues and we'll discuss.
For consistency with PR and on-list discussion, there are multiple other places in the draft an error that indicates an incorrect implementation is treated as a stream error, not a connection error. "Only servers can push; if a server receives a client-initiated push stream, this MUST be treated as a stream error of type HTTPWRONGSTREAMDIRECTION." "A PRIORITY frame received after other frames on a request stream MUST be treated as a stream error of type HTTPUNEXPECTEDFRAME." Unclear: "A PRIORITY frame that references a non-existent Push ID, a Placeholder ID greater than the server's limit, or a Stream ID the client is not yet permitted to open MUST be treated as an HTTPLIMITEXCEEDED error." "A CANCELPUSH frame is sent on the control stream. Receiving a CANCELPUSH frame on a stream other than the control stream MUST be treated as a stream error of type HTTPWRONGSTREAM." Possibly should be a connection error: "A PRIORITY frame sent on a request stream with the Prioritized Element Type set to any value other than "11" or which expresses a dependency on a request with a greater Stream ID than the current stream MUST be treated as a stream error of type HTTPMALFORMEDFRAME." In the appendix: " SETTINGSMAXCONCURRENTSTREAMS: QUIC controls the largest open Stream ID as part of its flow control logic. Specifying SETTINGSMAXCONCURRENTSTREAMS in the SETTINGS frame is an error. SETTINGSINITIALWINDOWSIZE: QUIC requires both stream and connection flow control window sizes to be specified in the initial transport handshake. Specifying SETTINGSINITIALWINDOWSIZE in the SETTINGS frame is an error. SETTINGSMAXFRAMESIZE: This setting has no equivalent in HTTP/3. Specifying it in the SETTINGS frame is an error."
I'm ok with such changes but wonder if we should also add some further prose to the document that: highlights HTTP/3 difference to HTTP/2 i.e. "all errors are always connection errors" captures some of the guidance mentioned elsewhere like "identifying the stream on which a problem frame was delivered may be useful in error analysis, endpoints might want to include the stream ID in the reason phrase"
NAME I'd forgotten about the settings appendix. This reads weird. Those settings are not formally defined in core HTTP/3 by name, so I wonder if the intention is that it is a "philosophical error". The code points are reserved, so we could suggest connection error on receipt. But we also have reserved grease code points that must be ignored.
I don't believe all errors are connection errors, even after Kazuho's PR, but I believe we could make that true with some changes. For example, when too large a header is received, it's currently a stream error because it's not required you communicate your max header size, and if you don't, the peer can't expected to know the limit.
The default value of SETTINGSMAXHEADERLISTSIZE is unlimited. Would you expect a peer to generate an excessive load error on a stream with the default?
The current text is: "encountering a larger message header SHOULD be treated as a stream error of type "HTTPEXCESSIVELOAD". If an implementation wishes to advise its peer of this limit, it can be conveyed as a number of bytes in the "SETTINGSMAXHEADERLISTSIZE" parameter." To me, the issue here is that a peer can have a limit, but is not required to advertise that limit. If we required that if a peer has a limit, it must advertise it and enforce it, then closing the connection makes sense to me. Given it's really easy to advertise it and it's within encryption, I don't see any reason not to make that change, but it's a slightly larger change than the other cases, where we're changing obviously avoidable errors to connection errors.
Happy to spin off another issue. See Section 5.1.1 for usage. Is the intent here "unlimited, but not really and I may reject at any time without sharing my limit". How is a peer that broke the rule expected to recover from such an error?
How about "an endpoint encountering a header list larger than the value advertised using SETTINGSMAXHEADERLISTSIZE parameter MUST close the connection with a connection error." I do not think we should mandate the use of SETTINGSMAXHEADERLISTSIZE when imposing a limit on the header list size. This is because the maximum can be different between requests. For a proxy, the maximum size is actually unknown, because each origin would have different maximums. PS. Though such a change would mandate the sender to respect SETTINGSMAXHEADERLISTSIZE (it's not required now).
Let me correct my self. This is wrong. By not forbidding responding with a connection error, we have been effectively mandating the sender to respect the settings parameter. Therefore, I think we can clarify as an editorial change that a client MUST adhere the settings parameter, and then exceeding a limit becomes a protocol violation, which should be treated as a connection error. PS. The text covering this is tentatively pushed to . PTAL.
Your text SGTM. Since HTTP/3 is not yet at a 'higher bar', it doesn't matter too much as long as Mike agrees as well.
From an implementation point of view, being able to classify an error as either a connection error or a stream error would simplify error reporting since we'd be able to classify an error as either a connection error or a stream error without having to include extra information with the error. As an example, at the moment, when returning a error from a function, I have to include whether the is a stream error or a connection error since it can be both. If every error could be classified as either a connection error or a stream error, I wouldn't have to include this extra information since a error would always be a connection error.
NAME in keeping with HTTP/2, an endpoint can always decide to treat a stream error as a connection error. However, you're correct that some (and after the associated PR, most) errors will always be connection errors, and your implementation could choose to always treat the others as stream errors. Just make sure there's no issue if someone else decides to treat it as a connection error.
Fixed in , which should have closed this.
the very end of Section 4.2.3 of quic-http says: PRIORITY frames received by a client MUST be treated as a stream error of type HTTPUNEXPECTEDFRAME. Elsewhere, this kind of thing is a connection error. Making this a stream error seems problematic; if otherwise valid, if this goes out on the control stream a stream error may bring everything down anyway? Here is the mailing list thread: URL I believe the consensus is to make it a conn error; PR to follow. the very end of Section 4.2.3 of quic-http says: PRIORITY frames received by a client MUST be treated as a stream error of type HTTPUNEXPECTEDFRAME. Elsewhere, this kind of thing is a connection error. Making this a stream error seems problematic; if otherwise valid, if this goes out on the control stream a stream error may bring everything down anyway? Should this be a connection error, or am I missing something?

control stream MUST be treated as a connection error of type HTTP_WRONG_STREAM.  <ins> PRIORITY frames received by a client MUST be treated as a connection </ins> error of type HTTP_UNEXPECTED_FRAME. 4.2.4.


<del> QPACK: Header Compression for HTTP over QUIC </del>  draft-ietf-quic-qpack-latest Abstract

It seems appropriate given that the term is used elsewhere in the text. However, I am sorry if there is a specific reason for keeping "HTTP over QUIC" here.
This matches HPACK (Header Compression for HTTP/2), so sure. I guess if we use vanilla QPACK for HTTP/4+ people will figure it out. circleci failed but the error appears unrelated.
Thanks.

<ins> QPACK: Header Compression for HTTP/3 </ins> draft-ietf-quic-qpack-latest Abstract