compatible with the QUIC version that the client has selected.  If the server cannot select a compatible combination of application protocol and QUIC version, it MUST abort the connection.  A client <del> MUST abort a connection if the server picks an incompatible combination of QUIC version and ALPN identifier. </del>  8.2.

The server doesn't pick a version. The server only picks the application protocol from what the client offered, and it might do that based on the QUIC version that the client picked.
NAME That depends on how you read RFC 7301, doesn't it? Section 3.1 says: I interpret "suitable" as "one of the options that the client proposed", but unfortunately that RFC is not very specific.
That is confusing, but it doesn't mean that the server doesn't pick an ALPN. We do require that it select an ALPN value, and even if it picks one that the client doesn't offer (whether legitimate or not), that doesn't invalidate this text.
No, I was suggesting restoring the sentence that was there before, but with alternate wording to address NAME concern. Now the server MUST abort if it can't pick a proper combination, and the client MUST abort if the server fails to abide by that requirement.
I think removing the sentence entirely is the wrong approach.OK, I see now. Yes, this WFM.

compatible with the QUIC version that the client has selected.  If the server cannot select a compatible combination of application protocol and QUIC version, it MUST abort the connection.  A client  <ins> MUST abort a connection if the server picks an application protocol incompatible with the protocol version being used. </ins> 8.2.


When the crypto retransmission timer is active, the probe timer (pto) is not active. <del> 6.2.1. A Retry or Version Negotiation packet causes a client to send another Initial packet, effectively restarting the connection process and resetting congestion control and loss recovery state, including resetting any pending timers.  Either packet indicates that the Initial was received but not processed.  Neither packet can be treated as an acknowledgment for the Initial. The client MAY however compute an RTT estimate to the server as the time period from when the first Initial was sent to when a Retry or a Version Negotiation packet is received.  The client MAY use this value to seed the RTT estimator for a subsequent connection attempt to the server. </del> 6.3. A Probe Timeout (PTO) triggers a probe packet when ack-eliciting data 6.4.  When packet protection keys are discarded (see Section 4.9 of QUIC- TLS), all packets that were sent with those keys can no longer be acknowledged because their acknowledgements cannot be processed might be destroyed sooner, as soon as handshake keys are available (see Section 4.10 of QUIC-TLS). <del> 6.5. </del>  The majority of constants were derived from best common practices among widely deployed TCP implementations on the internet.

Moves the "Retry and Version Negotiation" out of the "Crypto Retransmission Timeout" section in preparation for a redo of

When the crypto retransmission timer is active, the probe timer (pto) is not active.  6.3. A Probe Timeout (PTO) triggers a probe packet when ack-eliciting data 6.4. <ins> A Retry or Version Negotiation packet causes a client to send another Initial packet, effectively restarting the connection process and resetting congestion control and loss recovery state, including resetting any pending timers.  Either packet indicates that the Initial was received but not processed.  Neither packet can be treated as an acknowledgment for the Initial. The client MAY however compute an RTT estimate to the server as the time period from when the first Initial was sent to when a Retry or a Version Negotiation packet is received.  The client MAY use this value to seed the RTT estimator for a subsequent connection attempt to the server. 6.5. </ins> When packet protection keys are discarded (see Section 4.9 of QUIC- TLS), all packets that were sent with those keys can no longer be acknowledged because their acknowledgements cannot be processed might be destroyed sooner, as soon as handshake keys are available (see Section 4.10 of QUIC-TLS).  <ins> 6.6. </ins> The majority of constants were derived from best common practices among widely deployed TCP implementations on the internet.


If a promised server push is not needed by the client, the client SHOULD send a CANCEL_PUSH frame.  If the push stream is already open or opens after sending the CANCEL_PUSH frame, a QUIC STOP_SENDING <del> frame with an appropriate error code can also be used (e.g., HTTP_PUSH_REFUSED, HTTP_PUSH_ALREADY_IN_CACHE; see errors).  This </del>  asks the server not to transfer additional data and indicates that it will be discarded upon receipt. A client-only setting was sent by a server, or a server-only setting by a client. <del> The server has attempted to push content which the client will not accept on this connection. </del>  An internal error has occurred in the HTTP stack. <del> The server has attempted to push content which the client has cached. </del>  <del> The request or its response is cancelled. </del>  The client's stream terminated without containing a fully-formed request.

My intention was to keep PRs somewhat atomic and fragment the error code space in each PR that separately removes or consolidates an error code. Once complete, I would then defrag it on a PR that let's people weigh in on which error code should have which number. (It also simplifies dependencies which having branches off branches) Happy to do something different if you want.
RFC 7540 section 8.2.2 states: receive the pushed response from the server or if the server takes too long to begin sending the promised response, the client can send a RSTSTREAM frame, using either the CANCEL or REFUSEDSTREAM code and referencing the pushed stream's identifier. HTTP/3 changed push a bit and introduced two error codes: HTTPPUSHREFUSED and HTTPPUSHALREADYINCACHE. These is extra fidelity on top of HTTP/2, clearly articulating if the pushed item is in cache or not. This may be seen as useful, however there are some practical downsides: The CANCEL_PUSH frame exists and is the first RECOMMENDED action for declining a push. This does not expose the reason In actuality, server push caching is murky, which may affect the amount of confidence an operator could put into analyising these error codes. I think it is simpler, more self-consistent, and more HTTP/2-consistent to have one error code for server push. I'll prepare a PR for this.
I agree that we can remove HTTPPUSHALREADYINCACHE. In addition, I think we might also want to remove CANCELPUSH in favor of using REQUESTCANCELLED. IIUC, the semantics of CANCELPUSH can be (or can be) exactly the same as REQUESTCANCELLED after the client has sent the entire request.
Clarification, do you mean: replace the HTTPPUSHREFUSED error with HTTPREQUESTCANCELLED error? remove CANCELPUSH frame altogether and replace the function by sending a STOPSENDING with HTTPREQUESTCANCELLED error? I don't think option 2 is possible, the purpose of CANCEL_PUSH frame is to stop pushes before any stream is created - at that point there is no stream ID to reference.
Also, option 1 then deviates from HTTP/2 that had "REFUSED_STREAM" and "CANCEL" error codes/
Apologies. I meant option 1. REFUSEDSTREAM (of HTTP/2) maps to HTTPREQUESTREJECTED (of HTTP/3). CANCEL (of HTTP/2) maps to HTTPREQUESTCANCELLED (of HTTP/3). Therefore, it is safe to combine HTTPPUSHREFUSED to HTTPREQUESTCANCELLED. HTTPPUSHREFUSED error is used after the server processes the request, therefore the semantics is same as CANCELL, not REFUSEDSTREAM.
Thanks for clarifying, I agree with this and will incorporate it into this PR.
Define terms for the operations which an application can cause a transport to take, and for the events of which the application can be notified by the transport. This should in the process. This issue should: Eliminate the term "trigger" when talking about QUIC frames Try not to mention specific QUIC frames at all Remove "send a QUIC RESETSTREAM" from 7.2.4 Remove "sends a STOPSENDING with HTTPREQUESTCANCELLED, a server MAY send the error code HTTPREQUESTREJECTED in the corresponding RESET_STREAM" from 4.1.2 Reword references to sending values in particular transport parameters I believe those essential functions are: On the connection: Open a connection, specifying certain properties that translate to transport parameters Receive incoming connections, which will possess certain properties based on the peer's transport parameters Participate in deciding whether to accept 0-RTT based on embedded data Provide data to be embedded in resumption tokens Cooperate to control keep-alive, either by sending PINGs directly or by telling the transport that the connection can't be allowed to time out Immediate close On the stream: Read data Write data, understanding at what point flow control credit has been committed Close the sending part Request closure of the receiving part The events we depend on are connection closure and stream closure. This is largely editorial, since it's mostly defining terminology, but we'll also likely be mandating that QUIC stacks expose these features, so that's normative/design. (Obviously the 0-RTT ones will be "if implemented" requirements.)

If a promised server push is not needed by the client, the client SHOULD send a CANCEL_PUSH frame.  If the push stream is already open or opens after sending the CANCEL_PUSH frame, a QUIC STOP_SENDING  <ins> frame with an error code of HTTP_REQUEST_CANCELLED can be used.  This </ins> asks the server not to transfer additional data and indicates that it will be discarded upon receipt. A client-only setting was sent by a server, or a server-only setting by a client.  <ins> This code is reserved and has no meaning. </ins> An internal error has occurred in the HTTP stack.  <ins> This code is reserved and has no meaning. </ins>  <ins> The request or its response (including pushed response) is cancelled. </ins> The client's stream terminated without containing a fully-formed request.


server will not have discarded the state.  Clients MUST NOT send the "SETTINGS_NUM_"S" setting; receipt of this setting by a server MUST be treated as a connection error of type <del> "HTTP_WRONG_SETTING_DIRECTION". </del>  Client-controlled placeholders are identified by an ID between zero and one less than the number of placeholders the server has Parameters MUST NOT occur more than once in the SETTINGS frame.  A receiver MAY treat the presence of the same parameter more than once <del> as a connection error of type HTTP_MALFORMED_FRAME. </del>  The payload of a SETTINGS frame consists of zero or more parameters. Each parameter consists of a setting identifier and a value, both No error.  This is used when the connection or stream needs to be closed, but there is no error to signal. <del> A client-only setting was sent by a server, or a server-only setting by a client. </del>  This code is reserved and has no meaning. A server rejected a request without performing any application processing. <del> Peer violated protocol requirements in a way which doesn't match a more specific error code, or endpoint declines to use the more specific error code. </del>  An error in a specific frame type.  If the frame type is "0xfe" or less, the type is included as the last byte of the error code.

This in a different way by subtituting the error code and not changing the requirements related to NUM_"S.
HTTPWRONGSETTINGDIRECTION presently applies to exactly one case, a client that sends NUM"S. Declassifying this as an error seems like a sensible option. This frees up error code 0x1, which we can move HTTPGENERALPROTOCOL_ERROR to (making HTTP/3 more similar to HTTP/2). My initial preference is to state that servers MUST ignore this value if present. I'll prepare a PR to that effect.
I forgot to mention that NAME made an observation that SETTINGS errors are presently also covered by MALFORMEDFRAME, which is also on the chopping block. An alternative solution is therefore to create an that covers all the ways that a SETTINGS frames could go wrong.
Thank you for opening the issues. While I agree that we do not need an error code specific to SETTINGS going in the wrong direction, I am not sure if declassifying it as an error is the right option. Not that I have a strong opinion, but IIRC our consensus is to be more strict against misbehaving peers in HTTP/3 compared to HTTP/2. MALFORMEDFRAME or INVALIDSETTINGS sounds like a more conservative fix to me.
Thanks NAME after suggesting this I changed my mind. I made to implement the change as you and Mike suggest. The nice side effect is that it takes one step away from MALFORMED_FRAME.
The HTTP draft doesn't have any SETTINGS parameters with a maximum value, but QPACK does. I don't see any text in hpack sec. 4.2.5.1 or qpack sec 5 that explains what to do if we receive a parameter that exceeds the maximum parameter.
Good observation. This is another case where MALFORMEDFRAME is not appropriate. I might reset the peer-initiated stream with HTTPLIMITEXCEEDED, which would prompt the other end to close the connection HTTPCLOSEDCRITICALSTREAM. Note that this changes how the error code is currently defined. An alternative is just CONNECTIONCLOSE with HTTPGENERALPROTOCOLERROR. ... or We could invoke the YAEC label :) Whatever we decide, I think it needs documenting.
Technically, you can't reset a peer-initiated stream, just STOP_SENDING.
That's an astute observation and something I've missed. Perhaps I'm missing it but the spec doesn't say how to handle a STOPSENDING received on a control stream. Should this peer issue a RESETSTREAM in response (causing the other peer to blow up) or should it just close the connection itself?
My understanding is that STOPSENDING is an act of closing a stream rather than an advisory signal. Based on that, I'd argue that receipt of STOPSENDING on a critical stream should trigger a HTTPCLOSEDCRITICAL_STREAM connection-level error.
I don’t think that STOP_SENDING terminates a stream per se, but an application can choose to interpret it as such, so HTTP/3 can make that call.
To be precise, STOPSENDING "typically indicates that the receiving application is no longer reading data it receives from the stream, but it is not a guarantee that incoming data will be ignored" (). Because there is a chance of it indicating that the peer is no longer reading, STOPSENDING received on a critical stream ought to trigger a HTTPCLOSEDCRITICAL_STREAM error.
I'd be happy with MUST close connection in response
Discussed in London; generalize to a "SETTINGS error" which covers multiple ways you can screw up the SETTINGS frame. This is one of them.
Resolved by and .
Looks good!

server will not have discarded the state.  Clients MUST NOT send the "SETTINGS_NUM_"S" setting; receipt of this setting by a server MUST be treated as a connection error of type  <ins> "HTTP_SETTINGS_ERROR". </ins> Client-controlled placeholders are identified by an ID between zero and one less than the number of placeholders the server has Parameters MUST NOT occur more than once in the SETTINGS frame.  A receiver MAY treat the presence of the same parameter more than once  <ins> as a connection error of type HTTP_SETTINGS_ERROR. </ins> The payload of a SETTINGS frame consists of zero or more parameters. Each parameter consists of a setting identifier and a value, both No error.  This is used when the connection or stream needs to be closed, but there is no error to signal.  <ins> Peer violated protocol requirements in a way which doesn't match a more specific error code, or endpoint declines to use the more specific error code. </ins> This code is reserved and has no meaning. A server rejected a request without performing any application processing.  <ins> An endpoint detected an error in the payload of a SETTINGS frame: a duplicate setting was detected, a client-only setting was sent by a server, or a server-only setting by a client. </ins> An error in a specific frame type.  If the frame type is "0xfe" or less, the type is included as the last byte of the error code.


server can promise.  A server SHOULD use Push IDs sequentially, starting at 0.  A client MUST treat receipt of a push stream with a Push ID that is greater than the maximum Push ID as a connection <del> error of type HTTP_LIMIT_EXCEEDED. </del>  The header of the request message is carried by a PUSH_PROMISE frame (see frame-push-promise) on the request stream which generated the Each Push ID MUST only be used once in a push stream header.  If a push stream header includes a Push ID that was used in another push stream header, the client MUST treat this as a connection error of <del> type HTTP_DUPLICATE_PUSH. </del>  6.2.3. 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 a connection <del> error of type HTTP_LIMIT_EXCEEDED. </del>  A PRIORITY frame received on any stream other than the control stream MUST be treated as a connection error of type HTTP_WRONG_STREAM. See QPACK for more details. A server MUST NOT use a Push ID that is larger than the client has <del> provided in a MAX_PUSH_ID frame (frame-max-push-id) and MUST NOT use the same Push ID in multiple PUSH_PROMISE frames.  A client MUST treat receipt of a PUSH_PROMISE that contains a larger Push ID than the client has advertised or a Push ID which has already been promised as a connection error of type HTTP_MALFORMED_FRAME. </del>  If a PUSH_PROMISE frame is received on the control stream, the client MUST respond with a connection error (errors) of type (see frame-push-promise).  A MAX_PUSH_ID frame cannot reduce the maximum Push ID; receipt of a MAX_PUSH_ID that contains a smaller value than previously received MUST be treated as a connection error <del> of type HTTP_MALFORMED_FRAME. </del>  7.2.9. Push ID that is larger than the client has provided in a MAX_PUSH_ID frame (frame-max-push-id).  A client MUST treat receipt of a DUPLICATE_PUSH that contains a larger Push ID than the client has <del> advertised as a connection error of type HTTP_MALFORMED_FRAME. </del>  This frame allows the server to use the same server push in response to multiple concurrent requests.  Referencing the same server push A frame was received on a stream where it is not permitted. <del> A Stream ID, Push ID, or Placeholder ID greater than the current maximum for that identifier was referenced. </del>  <del> A Push ID was referenced in two different stream headers. </del>  A unidirectional stream header contained an unknown stream type.

Implements suggestions from
Updated to replace HTTPLIMITEXCEEDED and HTTPPUSHIDERROR with the single error HTTPID_ERROR.
There are presently a few cases where the Push ID can be used incorrectly. The table shows current error codes, proposes replacing the HTTPDUPLICATEPUSH with an HTTPPUSHIDERROR code, and replaces use of HTTPMALFORMED frame. Note, on I suggested a slightly different solution to replace HTTPMALFORMED FRAME, but I think this is a better path.
To me it seems that having an error code for specific part of push (i.e. ID of push-related information being incorrect) a bit too detailed. I think it sticks out, because we do not have things like "IDERROR, STREAMIDERROR. Also, using PUSHIDERROR in some cases while using MALFORMEDFRAME in others seems confusing. I think we might want to consider doing either of the two: Use PUSHIDERROR (or a generic error code for push) for all the push-related errors. This makes sense if we consider push as an optional feature of HTTP/3 Introduce IDERROR that captures all the limit and ID errors.
Thanks NAME The proposal is a bit of a compromise, so I agree with some of the points that you make. I like your idea of replacing HTTPLIMITEXCEEDED with HTTPIDERROR. I think it solves the issue in the most general way and doesn't change the protocol mechanics. What follows is the response I made first, before coming to that agreement :) Right. The difference here is that H3 frames only reference the stream and placeholder ID spaces, they don't control them. So, if the peer references an ID that doesn't exist then respond with HTTPLIMITEXCEEDED. Multiple references to the same ID is a little different, its ok to reuse placeholder ID (as long as the above condition holds, and you create a semantically valid PRIORITY frame). The transport spec discusses reuse of stream IDs at that layer; in H3 we only use stream ID for PRIORITY and GOAWAY - this has similar restrictions. However, server push is a little different because it's ID space is managed by the MAXPUSHID frame. The three errors that use HTTPPUSHIDERROR are unique to push. However, if we modified HTTPLIMITEXCEEDED to cover exceeding limits and incorrect ID usage (call it HTTPID_ERROR) I think that would work.
I updated the PR with the above, so that we can see what it looks like.
I'm not totally sold on this, but I'm having trouble articulating what it is that doesn't flow for me. I suspect that means I don't actually disagree, I just need some time to wrap my head around it.
We merged so I'm closing this as completed.

server can promise.  A server SHOULD use Push IDs sequentially, starting at 0.  A client MUST treat receipt of a push stream with a Push ID that is greater than the maximum Push ID as a connection  <ins> error of type HTTP_ID_ERROR. </ins> The header of the request message is carried by a PUSH_PROMISE frame (see frame-push-promise) on the request stream which generated the Each Push ID MUST only be used once in a push stream header.  If a push stream header includes a Push ID that was used in another push stream header, the client MUST treat this as a connection error of  <ins> type HTTP_ID_ERROR. </ins> 6.2.3. 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 a connection  <ins> error of type HTTP_ID_ERROR. </ins> A PRIORITY frame received on any stream other than the control stream MUST be treated as a connection error of type HTTP_WRONG_STREAM. See QPACK for more details. A server MUST NOT use a Push ID that is larger than the client has  <ins> provided in a MAX_PUSH_ID frame (frame-max-push-id).  A client MUST treat receipt of a PUSH_PROMISE frame that contains a larger Push ID than the client has advertised as a connection error of HTTP_ID_ERROR. A server MUST NOT use the same Push ID in multiple PUSH_PROMISE frames.  A client MUST treat receipt of a Push ID which has already been promised as a connection error of type HTTP_ID_ERROR. </ins> If a PUSH_PROMISE frame is received on the control stream, the client MUST respond with a connection error (errors) of type (see frame-push-promise).  A MAX_PUSH_ID frame cannot reduce the maximum Push ID; receipt of a MAX_PUSH_ID that contains a smaller value than previously received MUST be treated as a connection error  <ins> of type HTTP_ID_ERROR. </ins> 7.2.9. Push ID that is larger than the client has provided in a MAX_PUSH_ID frame (frame-max-push-id).  A client MUST treat receipt of a DUPLICATE_PUSH that contains a larger Push ID than the client has  <ins> advertised as a connection error of type HTTP_ID_ERROR. </ins> This frame allows the server to use the same server push in response to multiple concurrent requests.  Referencing the same server push A frame was received on a stream where it is not permitted.  <ins> A Stream ID, Push ID, or Placeholder ID was used incorrectly, such as exceeding a limit, reducing a limit, or being reused. </ins>  <ins> N/A </ins> A unidirectional stream header contained an unknown stream type.


HTTP/3 does not use server-initiated bidirectional streams, though an extension could define a use for these streams.  Clients MUST treat receipt of a server-initiated bidirectional stream as a connection <del> error of type HTTP_GENERAL_PROTOCOL_ERROR unless such an extension has been negotiated. </del>  6.2. by the recipient, the remainder of the stream cannot be consumed as the semantics are unknown.  Recipients of unknown stream types MAY trigger a QUIC STOP_SENDING frame with an error code of <del> HTTP_UNKNOWN_STREAM_TYPE, but MUST NOT consider such streams to be a connection error of any kind. </del>  Implementations MAY send stream types before knowing whether the peer supports them.  However, stream types which could modify the state or HTTP_MISSING_SETTINGS.  Only one control stream per peer is permitted; receipt of a second stream which claims to be a control stream MUST be treated as a connection error of type <del> HTTP_WRONG_STREAM_COUNT.  The sender MUST NOT close the control </del>  stream, and the receiver MUST NOT request that the sender close the control stream.  If either control stream is closed at any point, this MUST be treated as a connection error of type Only servers can push; if a server receives a client-initiated push stream, this MUST be treated as a connection error of type <del> HTTP_WRONG_STREAM_DIRECTION. </del>  Each Push ID MUST only be used once in a push stream header.  If a push stream header includes a Push ID that was used in another push N/A <del> A unidirectional stream header contained an unknown stream type. </del>  <del> A unidirectional stream type was used more times than is permitted by that type. </del>  A stream required by the connection was closed or reset. <del> A unidirectional stream type was used by a peer which is not permitted to do so. </del>  The remainder of the client's request is not needed to produce a response.  For use in STOP_SENDING only.

There are presently several kinds of very similar error when creating streams: HTTPUNKNOWNSTREAMTYPE MAY be used to tell a peer to STOPSENDING on a stream type that is unknown by the local peer. It is not a connection error. HTTPWRONGSTREAMCOUNT MUST be used if a peer tries to open more than one critical stream (control, QPACK). Connection error. HTTPWRONGSTREAMDIRECTION MUST be used if a client tries to open a push stream. More generally this could be used if a peer attempts to open a stream type that is not allowed to, but push is the only example today. Connection error. The suggestion is to rationalize these into a single error code - HTTPSTREAMCREATIONERROR - that can be used in STOPSENDING or CLOSECONNECTION. The impact of merging COUNT and DIRECTION is minimal because implementations fundamentally have to open streams correctly in order to interop. PS. The eagle eyes will notice I omitted the HTTPDUPLICATE_PUSH creation error. This is on the chopping block in . I class it differently because it is about parsing information after the stream type.
Should a client respond with STREAMCREATIONERROR when it sees the server opening a bidirectional stream? At the moment the designated error code is HTTPGENERALPROTOCOLERROR, but STREAMCREATIONERROR might be a better fit. Or if we are not going to use STREAMCREATIONERROR for server-initiated bidirectional streams, I think we should better rename it to UNIDIRECTIONALSTREAMCREATIONERROR to avoid confusion.
This relates to some earlier discussion on URL and its now-merged PR URL We only recently added text to specify the use of HTTPGENERALPROTOCOLERROR in this case, I think it fits to modify this to STREAMCREATION_ERROR.
Changed in URL

HTTP/3 does not use server-initiated bidirectional streams, though an extension could define a use for these streams.  Clients MUST treat receipt of a server-initiated bidirectional stream as a connection  <ins> error of type HTTP_STREAM_CREATION_ERROR unless such an extension has been negotiated. </ins> 6.2. by the recipient, the remainder of the stream cannot be consumed as the semantics are unknown.  Recipients of unknown stream types MAY trigger a QUIC STOP_SENDING frame with an error code of  <ins> HTTP_STREAM_CREATION_ERROR, but MUST NOT consider such streams to be a connection error of any kind. </ins> Implementations MAY send stream types before knowing whether the peer supports them.  However, stream types which could modify the state or HTTP_MISSING_SETTINGS.  Only one control stream per peer is permitted; receipt of a second stream which claims to be a control stream MUST be treated as a connection error of type  <ins> HTTP_STREAM_CREATION_ERROR.  The sender MUST NOT close the control </ins> stream, and the receiver MUST NOT request that the sender close the control stream.  If either control stream is closed at any point, this MUST be treated as a connection error of type Only servers can push; if a server receives a client-initiated push stream, this MUST be treated as a connection error of type  <ins> HTTP_STREAM_CREATION_ERROR. </ins> Each Push ID MUST only be used once in a push stream header.  If a push stream header includes a Push ID that was used in another push N/A  <ins> The endpoint detected that its peer created a stream that it will not accept. </ins>  <ins> N/A </ins> A stream required by the connection was closed or reset.  <ins> N/A </ins> The remainder of the client's request is not needed to produce a response.  For use in STOP_SENDING only.


immediate-close). Endpoints MUST NOT exceed the limit set by their peer.  An endpoint <del> 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 </del>  (error-handling). Once a receiver advertises a stream limit using the MAX_STREAMS

This aligns section 4.5 with section 19.11's text: "An endpoint MUST terminate a connection with a STREAMLIMITERROR error if a peer opens more streams than was permitted."
I'm going to recommend calling this editorial on the basis that it reconciles this text with another MUST-level requirement. But I will leave the issue open for the chairs to make a design/editorial assessment and any resulting consensus call.
In most places in QUIC, if an error is clearly avoidable(ie: only a buggy implementation would do so), then we advise closing the connection with an error, not just closing the stream with an error. In section 4.5 of transport(URL) it says: "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 STREAMLIMITERROR" Came up in discussion of
As I commented in the updated PR description, section 19.11 already says this: "An endpoint MUST terminate a connection with a STREAMLIMITERROR error if a peer opens more streams than was permitted."
Reopening to keep this visible.
is effectively the proposal, correct?
Yes. It was editorial. So I merged it. But then this issue really isn't editorial.
Yes, I opened this issue, then found that text in section 19.11 in the doc said exceeding MAXSTREAMID was a connection error, so the PR ended up aligning the two sections.
In HTTP/3 Appendix A.4 it states : : HTTPREQUESTREJECTED (in {{http-error-codes}}) is used to indicate that a request was not processed. Otherwise, not applicable because QUIC handles stream management. A STREAMIDERROR at the QUIC layer is used for streams that are improperly opened. But STREAMIDERROR was removed in transport draft 17.
Since URL there are different error codes that the transport might use depending on error conditions (maybe STREAMSTATEERROR or maybe STREAMLIMITERROR). I'm inclined to simply omit the sentence.

immediate-close). Endpoints MUST NOT exceed the limit set by their peer.  An endpoint  <ins> that receives a frame with a stream ID exceeding the limit it has sent MUST treat this as a connection error of type STREAM_LIMIT_ERROR </ins> (error-handling). Once a receiver advertises a stream limit using the MAX_STREAMS


data on this stream.  Either frame might arrive before a STREAM or STREAM_DATA_BLOCKED frame if packets are lost or reordered. <del> Before creating a stream, all streams of the same type with lower- </del>  numbered stream IDs MUST be created.  This ensures that the creation order for streams is consistent on both endpoints.

The phrase, , is semantically equal to This is not correct since all streams don't create a (new) stream. Moreover, by definition, a stream is created by sending data[1] or STREAM frames implicitly create a stream[2]. This indicates a stream itself doesn't create another stream. [1] URL [2] URL
Looks good. Just a minor comment.

data on this stream.  Either frame might arrive before a STREAM or STREAM_DATA_BLOCKED frame if packets are lost or reordered.  <ins> Before a stream is created, all streams of the same type with lower- </ins> numbered stream IDs MUST be created.  This ensures that the creation order for streams is consistent on both endpoints.


protection for all 0-RTT packets because the same key and nonce could be used to protect different content. <del> Receiving a Retry packet, especially a Retry that changes the connection ID used for subsequent packets, indicates a strong possibility that 0-RTT packets could be lost.  A client only receives acknowledgments for its 0-RTT packets once the handshake is complete. Consequently, a server might expect 0-RTT packets to start with a packet number of 0.  Therefore, in determining the length of the packet number encoding for 0-RTT packets, a client MUST assume that all packets up to the current packet number are in flight, starting from a packet number of 0.  Thus, 0-RTT packets could need to use a longer packet number encoding. </del>  A client MUST NOT send 0-RTT packets once it starts processing 1-RTT packets from the server.  This means that 0-RTT packets cannot

Retry always changes the connection ID.
NAME The paragraph 2 before this one says exactly that.
Ah... I missed that. Thanks for pointing it out NAME

protection for all 0-RTT packets because the same key and nonce could be used to protect different content.  <ins> A client only receives acknowledgments for its 0-RTT packets once the handshake is complete.  Consequently, a server might expect 0-RTT packets to start with a packet number of 0.  Therefore, in determining the length of the packet number encoding for 0-RTT packets, a client MUST assume that all packets up to the current packet number are in flight, starting from a packet number of 0. Thus, 0-RTT packets could need to use a longer packet number encoding. </ins> A client MUST NOT send 0-RTT packets once it starts processing 1-RTT packets from the server.  This means that 0-RTT packets cannot


If the packet is a 0-RTT packet, the server MAY buffer a limited number of these packets in anticipation of a late-arriving Initial <del> Packet.  Clients are forbidden from sending Handshake packets prior to receiving a server response, so servers SHOULD ignore any such </del>  packets. Servers MUST drop incoming packets under all other circumstances.

Clients aren't forbidden from sending a Handshake packet before receiving the ServerHello, they just don't have the keys to do so.
LGTM

If the packet is a 0-RTT packet, the server MAY buffer a limited number of these packets in anticipation of a late-arriving Initial  <ins> packet.  Clients are not able to send Handshake packets prior to receiving a server response, so servers SHOULD ignore any such </ins> packets. Servers MUST drop incoming packets under all other circumstances.


with any other value as a connection error of type PROTOCOL_VIOLATION.  Early data within the TLS connection MUST NOT be used.  As it is for other TLS application data, a server MUST treat receiving early data on the TLS connection as a connection error of type 4.6. <del> A server rejects 0-RTT by rejecting 0-RTT at the TLS layer.  This also prevents QUIC from sending 0-RTT data.  A server will always reject 0-RTT if it sends a TLS HelloRetryRequest. </del>  When 0-RTT is rejected, all connection characteristics that the client assumed might be incorrect.  This includes the choice of and the EncryptedExtensions messages during the handshake.  Endpoints MUST send the quic_transport_parameters extension; endpoints that receive ClientHello or EncryptedExtensions messages without the <del> quic_transport_parameters extension MUST terminate the TLS handshake with a fatal missing_extension alert (an error of 0x16d). </del>  While the transport parameters are technically available prior to the completion of the handshake, they cannot be fully trusted until the Disabling 0-RTT entirely is the most effective defense against replay attack. <del> QUIC extensions MUST describe how replay attacks affects their </del>  operation, or prohibit their use in 0-RTT.  Application protocols MUST either prohibit the use of extensions that carry application semantics in 0-RTT or provide replay mitigation strategies. this could allow a malicious peer to exhaust processing capacity without consequence. <del> QUIC prohibits the sending of empty "STREAM" frames unless they are marked with the FIN bit.  This prevents "STREAM" frames from being sent that only waste effort. </del> While there are legitimate uses for some redundant packets, implementations SHOULD track redundant packets and treat excessive volumes of any non-productive packets as indicative of an attack.

Of the TLS doc says: A server rejects 0-RTT by rejecting 0-RTT at the TLS layer. This also prevents QUIC from sending 0-RTT data. However, this doesn't explicitly say that it prevents QUIC from receiving 0-RTT data. I suspect this is implied, but it might be worth clarifying. I could imagine a server rejecting 0-RTT data, but attempting to ACK the client's 0-RTT packets in order to deliver a congestion signal to the client. I think this would be a rather terrible idea, so it would be nice to explicitly prohibit it.
Throughout transport, there is the idea that you MUST not acknowledge packets you can't fully process, so this should be against the spec. But it may be worth noting explicitly, probably in transport.
Doing it in transport would make sense. Or perhaps in TLS by noting that if 0-RTT is rejected, 0-RTT keys must not be generated.
NAME I just added text about that to my PR that's targeting .
SG NAME If so, can you update the CL description to indicate what issues it closes?
I'm going so recommend that this be triaged as a design issue, even if it might seem trivial. The prohibition in is the right response.
The spec says RFC 8446 says about rejecting 0-RTT: This doesn't really apply to QUIC, since we don't need a connection that requires us to read the bytes in order. The QUIC equivalent would be to drop all packets of packet type 0-RTT.
It is my understanding the existence of early_data extension in the EncryptedExtensions message indicates if the server has accepted 0-RTT (or not). Merely dropping the packets is not sufficient. This is because the parameters of a connection depends on if 0-RTT has been received; for example, in case of HTTP/3, until the client receives a SETTINGS frame, the settings of previous connection is applied when 0-RTT is accepted, whereas the default settings defined by the specification is applied when 0-RTT is rejected.
PS. What I think we need to clarify is that "use and acceptance" of 0-RTT is communicated the same way as TLS 1.3, by using the earlydata extension. That includes earlydata extension sent in ClientHello, EncryptedExtensions, NewSessionTicket.
Thanks NAME That confirms my interpretation of how accepting and rejecting is supposed to work. I’ll prepare a PR later.

with any other value as a connection error of type PROTOCOL_VIOLATION. <ins> A client that wishes to send 0-RTT packets uses the "early_data" extension in the ClientHello message of a subsequent handshake (see Section 4.2.10 of TLS13).  It then sends the application data in 0-RTT packets. </ins> Early data within the TLS connection MUST NOT be used.  As it is for other TLS application data, a server MUST treat receiving early data on the TLS connection as a connection error of type 4.6.  <ins> A server accepts 0-RTT by sending an early_data extension in the EncryptedExtensions (see Section 4.2.10 of TLS13).  The server then processes and acknowledges the 0-RTT packets that it receives. A server rejects 0-RTT by sending the EncryptedExtensions without an early_data extension.  A server will always reject 0-RTT if it sends a TLS HelloRetryRequest.  When rejecting 0-RTT, a server MUST NOT process any 0-RTT packets, even if it could.  When 0-RTT was rejected, a client SHOULD treat receipt of an acknowledgement for a 0-RTT packet as a connection error of type PROTOCOL_VIOLATION, if it is able to detect the condition. </ins> When 0-RTT is rejected, all connection characteristics that the client assumed might be incorrect.  This includes the choice of and the EncryptedExtensions messages during the handshake.  Endpoints MUST send the quic_transport_parameters extension; endpoints that receive ClientHello or EncryptedExtensions messages without the  <ins> quic_transport_parameters extension MUST close the connection with an error of type 0x16d (equivalent to a fatal TLS missing_extension alert, see tls-errors). </ins> While the transport parameters are technically available prior to the completion of the handshake, they cannot be fully trusted until the Disabling 0-RTT entirely is the most effective defense against replay attack.  <ins> QUIC extensions MUST describe how replay attacks affect their </ins> operation, or prohibit their use in 0-RTT.  Application protocols MUST either prohibit the use of extensions that carry application semantics in 0-RTT or provide replay mitigation strategies. this could allow a malicious peer to exhaust processing capacity without consequence.  While there are legitimate uses for some redundant packets, implementations SHOULD track redundant packets and treat excessive volumes of any non-productive packets as indicative of an attack.


with any other value as a connection error of type PROTOCOL_VIOLATION. <del> Early data within the TLS connection MUST NOT be used.  As it is for other TLS application data, a server MUST treat receiving early data on the TLS connection as a connection error of type PROTOCOL_VIOLATION. </del> 4.6. A server rejects 0-RTT by rejecting 0-RTT at the TLS layer.  This

As of -12, TLS records are not used in QUIC (TLS handshake messages are directly transmitted instead), and therefore it is impossible in QUIC to transmit early data using the machinery provided by TLS.

with any other value as a connection error of type PROTOCOL_VIOLATION.  4.6. A server rejects 0-RTT by rejecting 0-RTT at the TLS layer.  This


The payload of a UDP datagram carrying the first Initial packet MUST be expanded to at least 1200 bytes, by adding PADDING frames to the <del> Initial packet and/or by combining the Initial packet with a 0-RTT packet (see packet-coalesce).  Sending a UDP datagram of this size ensures that the network path supports a reasonable Maximum Transmission Unit (MTU), and helps reduce the amplitude of amplification attacks caused by server responses toward an unverified client address; see address-validation. </del>  The datagram containing the first Initial packet from a client MAY exceed 1200 bytes if the client believes that the Path Maximum

It doesn't matter what an Initial is coalesced with. It could be a 0-RTT packet, it could be an (invalid) Handshake packet, or the bytes after the Initial packet could be completely random. The only thing that matters is that the resulting UDP datagram has at least 1200 bytes.
CI failure isn't my fault. The faulty commit seems to be bbe2b80d490c39c1d5900a5f00b4bbe11a29c4df.

The payload of a UDP datagram carrying the first Initial packet MUST be expanded to at least 1200 bytes, by adding PADDING frames to the  <ins> Initial packet and/or by coalescing the Initial packet (see packet- coalesce).  Sending a UDP datagram of this size ensures that the network path supports a reasonable Maximum Transmission Unit (MTU), and helps reduce the amplitude of amplification attacks caused by server responses toward an unverified client address; see address- validation. </ins> The datagram containing the first Initial packet from a client MAY exceed 1200 bytes if the client believes that the Path Maximum


1-RTT protected packets. After a client receives a Retry packet, 0-RTT packets are likely to <del> have been lost or discarded by the server.  A client MAY attempt to resend data in 0-RTT packets after it sends a new Initial packet. </del>  A client MUST NOT reset the packet number it uses for 0-RTT packets, since the keys used to protect 0-RTT packets will not change as a

In URL, NAME points out that after a Retry, all 0-RTT packets sent in the first flight are still valid, although they will probably have been dropped by the server (unless there's significant reordering). That means that a client SHOULD retransmit all 0-RTT packets in the second flight. We now have two different scenarios: In the case of a HelloRetryRequest, the client is prohibited from sending 0-RTT, and has to cancel retransmissions for all outstanding 0-RTT packets. In the case of Retry, the 0-RTT packets from the first flight are declared lost (without any effect on the cwnd though) and retransmitted. Considering that Retry adds a roundtrip anyway (and thereby limits the usefulness of 0-RTT), and that we only expect it to be used by a server that's under attack (which makes it likely to refuse 0-RTT later in the handshake), does it really make sense to send 0-RTT in that case?
NAME I am not sure if I agree with the observation. The transport draft states that a server MAY send Retry packets in response to Initial and 0-RTT packets. A server can either discard or buffer 0-RTT packets that it receives (). Notice that the server is allowed to "buffer" 0-RTT packets. I am not sure if I like this ambiguity (because I think most if not all of us will be dropping 0-RTT packets that carry the original DCID when sending a Retry), but IMO the draft is clear.
NAME thanks for pointing that out. My point is that we should change the draft (i.e. this is a design issue, not an editorial issue). The reason a server (or a LB) performs a Retry is that it either has reasons to suspect that the client isn't reachable at the 2-tuple it sent the packet from, or that it is under load, and either uses the Retry to redirect traffic to a different backend server, or just to buy itself some time Under both circumstances, a server wouldn't want to allocate additional state for buffering 0-RTT packets. I'm therefore suggesting to change the draft such that: A server that performs a Retry MUST discard 0-RTT packets. A client MUST NOT use 0-RTT after a Retry was performed. Does that make sense to you?
Yeah, I can envision a server that considers buffering some number of packets to be cheaper than doing a TLS handshake. For such servers, delaying the TLS handshake until client proves it's existence but buffering the 0-RTT packets in the meantime would make sense. That said, I'd be fine with this, assuming that the discarded 0-RTT packets are the ones that carry the original DCID. I would prefer not having this requirement. Once the client is known to exist, a server needs to communicate with the client. Use of 0-RTT helps reduce the server load, because when used, average lifetime of connections becomes 1-RTT less. That leads us to having less concurrent connections.
When our implementation does Retry, it's because we've hit a memory pressure limit and we want to protect ourselves from attack. If a client supplies us with a valid token, we will accept 0-RTT still, but we won't buffer it from the previous Initial (without a token). So, if the client gets a Retry and still wants to do 0-RTT, they need to retransmit it, with the new CID, in a new packet (with a new packet number). I don't see why that design should be prevented.
Ok, I see that there's value in doing 0-RTT after a Retry, and furthermore, there seem to be good reasons (prevention of tampering by middle boxes) to disallow 0-RTT after a HelloRetryRequest in TLS 1.3, as NAME pointed out OOB. Since we can assume that most (all?) server implementation will drop 0-RTT packets when sending a Retry, the only reasonable thing to do for a client is to treat all 0-RTT packets as lost and retransmit them. Considering that, it would make sense to require servers to drop all Retry packets from the first flight, as NAME suggests.
NAME Thank you for suggesting a path forward. I think my weak preference goes to something slightly different. I think we are in agreement that most if not all the servers would not buffer the 0-RTT packets carrying the original DCIDs when they send Retrys. It makes sense to acknowledge the fact in the specification and suggest clients to retransmit 0-RTT data it has already sent. What I am not sure is if there is a reason to forbid servers from buffering such 0-RTT packets. From client's point of view, sending 0-RTT data once per connection is an optimization. Retransmitting 0-RTT data when the server sends a Retry is further optimization. If we think that way, servers that buffer 0-RTT packets when sending a Retry has increased chance of utilizing 0-RTT data. Compared to that, there is no reason to mandate servers to drop 0-RTT packets. To summarize, I think all we need to clarify is that servers responding with Retries are likely to discard 0-RTT packets that carry the original DCIDs and therefore that retransmitting 0-RTT data makes sense. But nothing more.
What if client takes action on Retry, and the old 0-RTT state lingers - wouldn't that make servers more vulnerable to DDoS attacks from packet sniffers?
This also bears on . If the DCID doesn't change, then the server won't be able to differentiate the first flight of 0-RTT packets, which you're proposing to drop, from the second flight of 0-RTT packets which it accepts. The current text says that the server MAY buffer and process them later; you're saying that servers probably won't. I think that's fine, but some might -- hence, I favor NAME suggestion to retain the permission to buffer but the caution to the client that the packets were quite possibly lost/dropped. In terms of actual spec change, I think that means this text in : ...becomes a SHOULD instead of a MAY.
It seems like you have sorted this out. I have no problem with using "SHOULD" over "MAY" here. Note that the reason we have this distinction is logical separation of the mechanism of Retry and 0-RTT acceptance. Retry strictly precedes the connection attempt. No point in blocking 0-RTT if you have to Retry. HelloRetryRequest is different in that certain characteristics it might cause to reset affect whether the 0-RTT works. Retry is required to use the same ClientHello, so there is no need to have the two interact; you can treat a Retry almost as a loss event for the first Initial and everything works fine.

1-RTT protected packets. After a client receives a Retry packet, 0-RTT packets are likely to  <ins> have been lost or discarded by the server.  A client SHOULD attempt to resend data in 0-RTT packets after it sends a new Initial packet. </ins> A client MUST NOT reset the packet number it uses for 0-RTT packets, since the keys used to protect 0-RTT packets will not change as a


streams to dedicate resources to, the implementation SHOULD use the information provided by the application.  3. This section describes streams in terms of their send or receive 3.5. <del> If an endpoint is no longer interested in the data it is receiving on a stream, it MAY send a STOP_SENDING frame identifying that stream to prompt closure of the stream in the opposite direction.  This typically indicates that the receiving application is no longer reading data it receives from the stream, but it is not a guarantee that incoming data will be ignored. </del>  STREAM frames received after sending STOP_SENDING are still counted toward connection and stream flow control, even though these frames TBD.  6. Version negotiation ensures that client and server agree to a QUIC

This consists of two related commits: Defines the minimal set of operations which a QUIC implementation MUST expose to the application, introducing terms for them which don't rely on the specific wire formatting Changes HTTP/3 to use these terms instead of referring to specific QUIC frames Note that this doesn't remove all references to QUIC frames, but the remaining references are explanatory -- they describe what will happen at the transport layer after taking a particular action, rather than being part of the normative text. (But if you find one of these you think needs to go, please include that in this review.) This is a design, because exposing this functionality is mandatory. I'm not sure how you would previously have created a usable QUIC implementation without these operations, but you weren't previously required to have them all.
NAME and NAME I think this is mergeable from an HTTP standpoint. Since it's technically a requirements change (requiring that functionality be exposed), it probably needs to be marked for a consensus call when you think it's ready.
Define terms for the operations which an application can cause a transport to take, and for the events of which the application can be notified by the transport. This should in the process. This issue should: Eliminate the term "trigger" when talking about QUIC frames Try not to mention specific QUIC frames at all Remove "send a QUIC RESETSTREAM" from 7.2.4 Remove "sends a STOPSENDING with HTTPREQUESTCANCELLED, a server MAY send the error code HTTPREQUESTREJECTED in the corresponding RESET_STREAM" from 4.1.2 Reword references to sending values in particular transport parameters I believe those essential functions are: On the connection: Open a connection, specifying certain properties that translate to transport parameters Receive incoming connections, which will possess certain properties based on the peer's transport parameters Participate in deciding whether to accept 0-RTT based on embedded data Provide data to be embedded in resumption tokens Cooperate to control keep-alive, either by sending PINGs directly or by telling the transport that the connection can't be allowed to time out Immediate close On the stream: Read data Write data, understanding at what point flow control credit has been committed Close the sending part Request closure of the receiving part The events we depend on are connection closure and stream closure. This is largely editorial, since it's mostly defining terminology, but we'll also likely be mandating that QUIC stacks expose these features, so that's normative/design. (Obviously the 0-RTT ones will be "if implemented" requirements.)
Some small suggestions, but looks good overall.

streams to dedicate resources to, the implementation SHOULD use the information provided by the application. <ins> 2.4. There are certain operations which an application MUST be able to perform when interacting with QUIC streams.  This document does not specify an API, but any implementation of this version of QUIC MUST expose the ability to perform the operations described in this section on a QUIC stream. On the sending part of a stream, application protocols need to be able to: write data, understanding when stream flow control credit (data- flow-control) has successfully been reserved to send the written data end the stream (clean termination), resulting in a STREAM frame (frame-stream) with the FIN bit set; and reset the stream (abrupt termination), resulting in a RESET_STREAM frame (frame-reset-stream), even if the stream was already ended. On the receiving part of a stream, application protocols need to be able to: read data abort reading of the stream and request closure, possibly resulting in a STOP_SENDING frame (frame-stop-sending) Applications also need to be informed of state changes on streams, including when the peer has opened or reset a stream, when a peer aborts reading on a stream, when new data is available, and when data can or cannot be written to the stream due to flow control. </ins> 3. This section describes streams in terms of their send or receive 3.5.  <ins> If an application is no longer interested in the data it is receiving on a stream, it can abort reading the stream and specify an application error code. If the stream is in the "Recv or "Size Known" states, the transport SHOULD signal this by sending a STOP_SENDING frame to prompt closure of the stream in the opposite direction.  This typically indicates that the receiving application is no longer reading data it receives from the stream, but it is not a guarantee that incoming data will be ignored. </ins> STREAM frames received after sending STOP_SENDING are still counted toward connection and stream flow control, even though these frames TBD. <ins> 5.4. There are certain operations which an application MUST be able to perform when interacting with the QUIC transport.  This document does not specify an API, but any implementation of this version of QUIC MUST expose the ability to perform the operations described in this section on a QUIC connection. When implementing the client role, applications need to be able to: open a connection, which begins the exchange described in handshake; enable 0-RTT; and be informed when 0-RTT has been accepted or rejected by a server. When implementing the server role, applications need to be able to: listen for incoming connections, which prepares for the exchange described in handshake; if Early Data is supported, embed application-controlled data in the TLS resumption ticket sent to the client; and if Early Data is supported, retrieve application-controlled data from the client's resumption ticket and enable rejecting Early Data based on that information. In either role, applications need to be able to: configure minimum values for the initial number of permitted streams of each type, as communicated in the transport parameters (transport-parameters); control resource allocation of various types, including flow control and the number of permitted streams of each type; identify whether the handshake has completed successfully or is still ongoing keep a connection from silently closing, either by generating PING frames (frame-ping) or by requesting that the transport send additional frames before the idle timeout expires (idle-timeout); and immediately close (immediate-close) the connection. </ins> 6. Version negotiation ensures that client and server agree to a QUIC


cease retransmission of the closing packet. Disposing of connection state prior to the end of the closing or <del> draining period could cause delayed or reordered packets to be handled poorly.  Endpoints that have some alternative means to ensure that late-arriving packets on the connection do not create QUIC state, such as those that are able to close the UDP socket, MAY use an abbreviated draining period which can allow for faster resource recovery.  Servers that retain an open socket for accepting new connections SHOULD NOT exit the closing or draining period early. </del>  Once the closing or draining period has ended, an endpoint SHOULD discard all connection state.  This results in new packets on the

FWIW, the state that was implied was the creation of a new connection. That's not realistic in QUICv1 however, so this is good.
I'm not following why in general this would create state. If the handshake has occurred > MSL ago, and you just refuse to create state on short packets with unknown CIDs s, why isn't that enough?
This was written when there was some uncertainty about what packets might look like. I think that we can truncate this sentence. Basically, you can drop state earlier, but it will mean that you won't be able to attribute any laggardly packets properly.

cease retransmission of the closing packet. Disposing of connection state prior to the end of the closing or  <ins> draining period could cause delayed or reordered packets to generate an unnecessary stateless reset.  Endpoints that have some alternative means to ensure that late-arriving packets on the connection do not induce a response, such as those that are able to close the UDP socket, MAY use an abbreviated draining period which can allow for faster resource recovery.  Servers that retain an open socket for accepting new connections SHOULD NOT exit the closing or draining period early. </ins> Once the closing or draining period has ended, an endpoint SHOULD discard all connection state.  This results in new packets on the


the message header (see RFC7230, Section 3.2), sent as a single HEADERS frame (see frame-headers), <del> the payload body (see RFC7230, Section 3.3), sent as a series of DATA frames (see frame-data), </del>  <del> optionally, one HEADERS frame containing the trailer-part, if present (see RFC7230, Section 4.1.2). </del>  A server MAY send one or more PUSH_PROMISE frames (see frame-push- promise) before, after, or interleaved with the frames of a response message.  These PUSH_PROMISE frames are not part of the response; see server-push for more details.  The HEADERS and PUSH_PROMISE frames might reference updates to the QPACK dynamic table.  While these updates are not directly part of the message exchange, they must be received and processed before the The "chunked" transfer encoding defined in Section 4.1 of RFC7230 MUST NOT be used. <del> If a DATA frame is received before a HEADERS frame on a either a request or push stream, the recipient MUST respond with a connection error of type HTTP_UNEXPECTED_FRAME (errors). Trailing header fields are carried in an additional HEADERS frame following the body.  Senders MUST send only one HEADERS frame in the trailers section; receivers MUST discard any subsequent HEADERS frames. </del> A response MAY consist of multiple messages when and only when one or 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.  An HTTP request/response exchange fully consumes a bidirectional QUIC stream.  After sending a request, a client MUST close the stream for sending.  Unless using the CONNECT method (see the-connect-method), A push stream is indicated by a stream type of "0x01", followed by the Push ID of the promise that it fulfills, encoded as a variable- length integer.  The remaining data on this stream consists of HTTP/3 <del> frames, as defined in frames, and fulfills a promised server push. Server push and Push IDs are described in server-push. </del>  Only servers can push; if a server receives a client-initiated push stream, this MUST be treated as a connection error of type

Clarify HTTP message definition. Require the connection to be closed upon receipt of frames in incorrect order. Refer to HTTP messages in push stream section. This addresses URL
Good catch. I added a paragraph to explicitly allow unknown frames to the message exchange section. I also modified the wording in the push section to be more permissive of interleaved frames ("by" instead of "consists of").
LGTM
According to URL, a bidirectional HTTP stream can have at least one HEADERS frame (exactly one if the stream is client-initiated), zero or more DATA frames, then zero or one HEADERS frame, in this order. Handling the receipt of frames that does not conform to this requirement is inconsistent: (1) If a DATA frame is received before any other frame, that's an error: (2) If the trailing HEADERS frame is followed by another HEADERS frame, that is to be discarded: (3) There is no guidance on how to handle a DATA frame after the trailing headers. I suggest that every violation to this requirement should be an error. (I do not feel strongly whether a connection error like (1) is handled right now, or a stream error. The implementation I am contributing to will promote most stream errors to connection errors regardless.) Note that (2) comes from the technical limitation that header blocks needed to be processed, which is not the case any longer with QPACK. It just seems like that when the requirement to decode the header block was removed at URL, adding the requirement to signal an error was not in scope of that commit.
I agree with NAME . If you read that requirement a certain way, then any use of trailers will blow up the connection :) I think we could rewrite this text to be clearer about when DATA frames are expected to be received.
Good points. Let's continue tightening down the screws. :-)
Closed in .
This text was already somewhat lacking by not discussing the possibility of extension frames being interleaved; stating that the push stream consists of "responses" rather than "frames" extends this shortcoming to the push stream section. I'm open to fixing this in a separate PR, if you prefer, though.

the message header (see RFC7230, Section 3.2), sent as a single HEADERS frame (see frame-headers),  <ins> optionally, the payload body, if present (see RFC7230, Section 3.3), sent as a series of DATA frames (see frame-data), </ins>  <ins> optionally, trailing headers, if present (see RFC7230, Section 4.1.2), sent as a single HEADERS frame. </ins> A server MAY send one or more PUSH_PROMISE frames (see frame-push- promise) before, after, or interleaved with the frames of a response message.  These PUSH_PROMISE frames are not part of the response; see server-push for more details. <ins> Frames of unknown types (extensions), including reserved frames (frame-grease) MAY be sent on a request or push stream before, after, or interleaved with other frames described in this section. </ins> The HEADERS and PUSH_PROMISE frames might reference updates to the QPACK dynamic table.  While these updates are not directly part of the message exchange, they must be received and processed before the The "chunked" transfer encoding defined in Section 4.1 of RFC7230 MUST NOT be used.  A response MAY consist of multiple messages when and only when one or 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. <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 type HTTP_UNEXPECTED_FRAME (errors).  In particular, a DATA frame before any HEADERS frame, or a HEADERS or DATA frame after the trailing HEADERS frame is considered invalid. </ins> An HTTP request/response exchange fully consumes a bidirectional QUIC stream.  After sending a request, a client MUST close the stream for sending.  Unless using the CONNECT method (see the-connect-method), A push stream is indicated by a stream type of "0x01", followed by the Push ID of the promise that it fulfills, encoded as a variable- length integer.  The remaining data on this stream consists of HTTP/3  <ins> frames, as defined in frames, and fulfills a promised server push by zero or more non-final HTTP responses followed by a single final HTTP response, as defined in request-response.  Server push and Push IDs are described in server-push. </ins> Only servers can push; if a server receives a client-initiated push stream, this MUST be treated as a connection error of type


5.2. Initial packets are protected with a secret derived from the <del> Destination Connection ID field from the client's first Initial packet of the connection.  Specifically: </del>  The hash function for HKDF when deriving initial secrets and keys is SHA-256 SHA. Initial packets even where the TLS versions offered do not include TLS 1.3. <del> test-vectors-initial contains test vectors for the initial packet encryption. </del>  The Destination Connection ID is of arbitrary length, and it could be zero length if the server sends a Retry packet with a zero- packet; the client has to rely on the exchange that included the Retry packet for that property.  5.3. The Authentication Encryption with Associated Data (AEAD) AEAD

This was implied, but not explicit previously.
I think this also needs to change the first sentence that currently says "Initial packets are protected with a secret derived from the Destination Connection ID field from the client’s first Initial packet of the connection." If we're attempting to describe the behavior that people currently have implemented (where Initial keys change on a Retry packet but nothing else), the sentence I've quoted above is false.
OBE
URL states that "Initial packets are protected with a secret derived from the Destination Connection ID field from the client’s first Initial packet of the connection." This sounds like a second Initial packet from the client (in response to a server's Retry) would still be encrypted using keys derived from the original DCID instead of a potentially new DCID. However, that section later mentions the client creating an Initial packet in response to a Retry packet and the DCID is selected by the server. These two statements should be reconciled to clarify that either the initial keys stay the same for the entire connection, or that initial keys can change. (From the discussion in , it sounds like an Initial sent in response to a Retry is still the same connection.) I think there are 3 options for behavior: The initial keys stay the same for the entire connection The initial keys for encrypting/decrypting a packet are derived from the server's CID listed in that packet The initial keys are derived from the client's first DCID, unless the server sends a Retry packet, after which the initial keys are re-derived from the server's new DCID. These options are in my preference order. My understanding for why initial keys change after a Retry is to support servers handling Retry statelessly. A server should still be able to use the original DCID for initial key derivation in this case because the ODCID is needed for transport parameters, and the retry token is sent in the clear in the Initial packet, so the server can recover it from the token. Option 2 lets the keys change, but follows a simpler principle for what the keys are. When processing a packet for a connection, the information to encrypt or decrypt it is all self-contained - there is no additional connection state to check whether a new encrypter or decrypter is needed because an Initial packet was sent/received after a Retry packet. One of the reasons why option 3 seems odd to me is that there are multiple ways the server's CID can change between Initial packets on the same connection, but Retry packets are the only case where we change the initial keys. (E.g. consider from a client's perspective sending a second ClientHello in response to a Retry packet has the initial keys change, but sending it in response to a HelloRetryRequest keeps the keys the same, even though the server likely changed its CID in the Initial packet where it sent the HRR.)
1 and 2 are the clearest contracts and I slightly prefer 1 as well. 3 is odd and very unintuitive to me.
I think 1 is probably the best. I believe it would remove the need to include the original CID in the transport parameters as well, in the Retry scenario.
NAME Maybe I am wrong, but haven't we had interop based on the understanding that the 2 is the correct way (i.e. the "S" column of the interop matrix)? IIUC, the discussion is about "cryptographic handshake message" (i.e. ClientHello transmitted as the payload of the CYRPTO frame) being consistent. Not about the key that encrypts (obfuscates) the frames. One practical issue with option 1 is that it requires the server and the issuer of the Retry token to share the format of the Retry token (as well as a secret key to extract ODICID from the token in certain designs). IIRC, our previous consensus is to permit people to install DDoS mitigation boxes that issue Retry packets, without requiring such coordination between those mitigation boxes and the servers being protected (NAME am I correct?)
My understanding is that we interoped on the key being derived from the new DCID (from the client’s perspective) on the Initial packet, so that would be 3.?
NAME Correct. It is also my understanding that we do 3 (not 2).
NAME So long as the backend server has to give the OCID in the TP, you need the shared format/key for the token. This won't change that.
This is already stated in a different section. In , it says:
Yes. Right now we have 3, though not exactly as stated. It is
Are editorial clarifications needed here?
At the minimum, the first sentence of the "Initial Secrets" section needs to be revised. I'd prefer to understand the design rationale for when initial keys change (and possibly incorporate that into the edit). Right now I don't understand the "why" of option 3.
I don't believe that we have any responsibility to document "why" for anything. In this case, my understanding is that Reset is intended to be largely stateless. If it were stateless, then the token would need to carry the original destination connection ID (or the derived key), and that would need to be extracted before the packet could be processed.
+1
Documenting "why" some choices were made is not required, but it helps readers better understand the properties of the protocol, and helps spec contributors not have the same conversations every few months. The retry token already needs to carry the original destination connection ID because the server MUST include it in the transport parameter.
This is marked as editorial, but 3 seems like the most non-obvious design choice, and it's not clear we intended to make that choice. If extracting the original CID is potentially too difficult for some, why not 2, which only uses local state to process Initial packets?
Of note, in section 2.6 of the new authenticated handshake draft(URL) from NAME and NAME on protection Initials it says: "The Destination Connection ID of the Initial packet MUST be set to the value specified by the Retry packet, however the keys for encrypting and authenticating the packet MUST continue to be the original ones." This aligns with the first option. It seems like the first option also allows us to remove a transport param, which is a nice property. Given that, is anyone actively opposed to the first option assuming someone else(ie: myself or Nick) writes a PR?
Reading NAME comment on URL, I think that the computation cost of mounting a DoS attack would change if we are to retain the original Initial secrets after Retry, as proposed in . In the current design, a server can use Retry packets to enforce clients to do equal amount of HKDF extractions, as the CIDs found in Retry packets update the Initial secrets. However, if we adopt , the clients will given the freedom to choose the Initial key regardless of if Retry is used. That might open new attack vectors. For example, a DDoS attacker might send Initial packets that carry the same DCID (or a small set of DCIDs) from multiple addresses, receive Retry tokens, then send back Initial packets carrying the correct tokens and CIDs. The clients can use the precomputed Initial secrets when sending back the Initial packets, the client. I am not sure how important this kind of attack is, but I think we need to make sure that the change of computation cost is OK.
NAME has a design-level change for this in that I think is a good idea to talk about.
One new point that came up in the discussion of is that if every packet is self-contained(ie: option 2), it would be possible for the server to send a CONNECTIONCLOSE to terminate a handshake that is doomed to failure quickly, opposed to waiting for the handshake to timeout. If my PR is accepted, the server would not be able to send a CONNECTIONCLOSE.
Discussed in Cupertino; we believe that we should clarify to option 3 (i.e., mt's ).
For the record, the two arguments that carried this were a) everyone has already implemented , and b) you can't send CONNECTION_CLOSE if you keep the keys the same. Mix to taste.
This is fine for clarifying the current state of affairs. I think we need to come to a conclusion on , however, whether that will stay the case.

5.2. Initial packets are protected with a secret derived from the  <ins> Destination Connection ID field from the client's Initial packet. Specifically: </ins> The hash function for HKDF when deriving initial secrets and keys is SHA-256 SHA. Initial packets even where the TLS versions offered do not include TLS 1.3.  <ins> The secrets used for protecting Initial packets change when a server sends a Retry packet to use the connection ID value selected by the server.  The secrets do not change when a client changes the Destination Connection ID it uses in response to an Initial packet from the server. </ins> The Destination Connection ID is of arbitrary length, and it could be zero length if the server sends a Retry packet with a zero- packet; the client has to rely on the exchange that included the Retry packet for that property. <ins> test-vectors-initial contains test vectors for the initial packet encryption. </ins> 5.3. The Authentication Encryption with Associated Data (AEAD) AEAD


An endpoint MUST NOT excessively delay acknowledgements of ack- eliciting packets.  The maximum ack delay is communicated in the <del> max_ack_delay transport parameter; see Section 18.1 of QUIC- </del>  TRANSPORT.  max_ack_delay implies an explicit contract: an endpoint promises to never delay acknowledgments of an ack-eliciting packet by more than the indicated value.  If it does, any excess accrues to the New transport parameters can be used to negotiate new protocol behavior.  An endpoint MUST ignore transport parameters that it does not support.  Absence of a transport parameter therefore disables any <del> optional protocol feature that is negotiated using the parameter. </del>  New transport parameters can be registered according to the rules in iana-transport-parameters. 18.1.  This section details the transport parameters defined in this document. The initial contents of this registry are shown in iana-tp-table.  22.2. IANA [SHALL add/has added] a registry for "QUIC Frame Types" under a

NAME I added IANA considerations, analogous to those in the HTTP doc for the stream type.
We have space for private transport parameters and a process for assigning new standard transport parameters. However, since we do not apply GREASE to transport parameters, we leave ourselves open to that flexibility ossifying. I'm happy to produce a PR to address this in the spec.
During interop in London we found an implementation that failed the handshake upon seeing an unknown transport parameter. I think applying GREASE to transport parameters is a great idea.
Discussed in London, NAME to prepare a PR
NAME any progress here?
I have prepared something. I can open a pr
NAME sorry I totally dropped the ball here! I had text ages ago and was circling back today finally. Thanks NAME for seeing to it.
Thanks Marten!Thanks for writing this up!Need to fix the examples, but otherwise looks good.

An endpoint MUST NOT excessively delay acknowledgements of ack- eliciting packets.  The maximum ack delay is communicated in the  <ins> max_ack_delay transport parameter; see Section 18.2 of QUIC- </ins> TRANSPORT.  max_ack_delay implies an explicit contract: an endpoint promises to never delay acknowledgments of an ack-eliciting packet by more than the indicated value.  If it does, any excess accrues to the New transport parameters can be used to negotiate new protocol behavior.  An endpoint MUST ignore transport parameters that it does not support.  Absence of a transport parameter therefore disables any  <ins> optional protocol feature that is negotiated using the parameter.  As described in transport-parameter-grease, some identifiers are reserved in order to exercise this requirement. </ins> New transport parameters can be registered according to the rules in iana-transport-parameters. 18.1. <ins> Transport parameters with an identifier of the form "31 * N + 27" for integer values of N are reserved to exercise the requirement that unknown transport parameters be ignored.  These transport parameters have no semantics, and may carry arbitrary values. 18.2. </ins> This section details the transport parameters defined in this document. The initial contents of this registry are shown in iana-tp-table. <ins> Additionally, each value of the format "31 * N + 27" for integer values of N (that is, "27", "58", "89", ...) MUST NOT be assigned by IANA. </ins> 22.2. IANA [SHALL add/has added] a registry for "QUIC Frame Types" under a


The next four bytes include a 32-bit Version field (see version). <del> The next byte contains the length in bytes of the two Connection IDs (see connection-id) that follow.  Each length is encoded as a 4-bit unsigned integer.  The length of the Destination Connection ID (DCIL) occupies the high bits of the byte and the length of the Source Connection ID (SCIL) occupies the low bits of the byte.  An encoded length of 0 indicates that the connection ID is also 0 bytes in length.  Non-zero encoded lengths are increased by 3 to get the full length of the connection ID; the final value is therefore either 0 or between 4 and 18 bytes in length (inclusive).  For example, a byte with the value 0xe0 describes a 17 byte Destination Connection ID and a zero byte Source Connection ID. The connection ID lengths are followed by two connection IDs.  The connection ID associated with the recipient of the packet (the Destination Connection ID) is followed by the connection ID associated with the sender of the packet (the Source Connection ID). </del>  The remainder of the packet contains version-specific content. This field indicates which version of QUIC is in use and determines how the rest of the protocol fields are interpreted. <del> The byte following the version contains the lengths of the two connection ID fields that follow it.  These lengths are encoded as two 4-bit unsigned integers.  The Destination Connection ID Length (DCIL) field occupies the 4 high bits of the byte and the Source Connection ID Length (SCIL) field occupies the 4 low bits of the byte.  An encoded length of 0 indicates that the connection ID is also 0 bytes in length.  Non-zero encoded lengths are increased by 3 to get the full length of the connection ID, producing a length between 4 and 18 bytes inclusive.  For example, a byte with the value 0x50 describes an 8-byte Destination Connection ID and a zero-length Source Connection ID. </del>  The Destination Connection ID field follows the DCID Len and is between 0 and 20 bytes in length. negotiating-connection-ids

I noticed this earlier today when trying to resolve conflicts relating to the connection ID change.
Suggestions accepted.
A couple of changes

The next four bytes include a 32-bit Version field (see version).  <ins> The next byte contains the length in bytes of the Destination Connection ID (see connection-id) field that follows it.  This length is encoded as an 8-bit unsigned integer.  The Destination Connection ID field follows the DCID Len field and is between 0 and 255 bytes in length. The next byte contains the length in bytes of the Source Connection ID field that follows it.  This length is encoded as a 8-bit unsigned integer.  The Source Connection ID field follows the SCID Len field and is between 0 and 255 bytes in length. </ins> The remainder of the packet contains version-specific content. This field indicates which version of QUIC is in use and determines how the rest of the protocol fields are interpreted.  <ins> The byte following the version contains the length in bytes of the Destination Connection ID field that follows it.  This length is encoded as an 8-bit unsigned integer.  In QUIC version 1, this value MUST NOT exceed 20.  Endpoints that receive a version 1 long header with a value larger than 20 MUST drop the packet.  Servers SHOULD be able to read longer connection IDs from other QUIC versions in order to properly form a version negotiation packet. </ins> The Destination Connection ID field follows the DCID Len and is between 0 and 20 bytes in length. negotiating-connection-ids


RESET_STREAM frame (frame-reset-stream) with an appropriate error code to terminate just the affected stream. <del> RESET_STREAM MUST be instigated by the protocol using QUIC, either directly or through the receipt of a STOP_SENDING frame from a peer. RESET_STREAM carries an application error code.  Resetting a stream without knowledge of the application protocol could cause the protocol to enter an unrecoverable state.  Application protocols might require certain streams to be reliably delivered in order to guarantee consistent state between endpoints. </del>  12.

This was a little woolly. Hopefully this is crisper. Basically, the application protocol is what asks for stream reset, either directly, or by sending STOP_SENDING. I added a recommendation for application protocols to define handling for prematurely cancelled streams. That is, I believe, just editorial, even though it uses 2119 language, because we already do that.
Section 11.2 of the transport draft reads: >RESETSTREAM MUST be instigated by the protocol using QUIC, either directly or through the receipt of a STOPSENDING frame from a peer. RESETSTREAM carries an application error code. Resetting a stream without knowledge of the application protocol could cause the protocol to enter an unrecoverable state. Application protocols might require certain streams to be reliably delivered in order to guarantee consistent state between endpoints. This would suggest that the transport implementation itself should not be deciding to send RESETSTREAMs on its own, rather it is a matter of the application protocol using QUIC. However, it is somewhat weaselly about STOPSENDING. My original interpretation of this text was that the implementation should expose the fact that a STOPSENDING has been received, but it is up to the application protocol to actually reset the stream. This interpretation is not consistent with section 3.5, Solicited State Transitions: I believe the only reasonable way to interpret this is that the transport itself should respond to the STOPSENDING by issuing a RESETSTREAM. Requiring that the QUIC implementation itself reset the stream seems to go against the philosophy that the application protocol should have complete control over resets. It's true that in the case of receiving a STOPSENDING, the protocol on the other side is asking for a RESETSTREAM, but the entity being solicited should be its peer protocol implementation, not the QUIC implementation. I believe we can fix this by altering the normative text requiring sending a RESETSTREAM, instead prescribing that an application protocol ought to handle STOPSENDING by eventually fulfilling the solicited reset.
I don't see any inconsistency here. This is the relevant text: This says that RESETSTREAM is only sent if the application protocol asks for it. That means either the local stack or the remote stack. As you say, the transport is required to send RESETSTREAM in response to STOP_SENDING. I can firm that up.
I am suggesting more than just a clarification change, actually. I'm disputing that it makes sense for the transport to be the one initiating the RESETSTREAM as opposed to the STOPSENDING merely being informative to the application protocol, i.e. it should be the application protocol's responsibility to respond to the STOPSENDING by resetting the stream. From an implementation standpoint, one would obviously want feedback on receiving a STOPSENDING frame. This makes it so the implementation has to make a choice when receiving the STOPSENDING. Is it supposed to reset the stream before or after delivering this information to the application protocol? This has consequences as resetting the stream before informing the application obviously effects the stream state. Is there any disadvantage to instead only requiring that the transport inform the application protocol of the receipt of the STOPSENDING, and then the application protocol is left to decide when to actually reset the relevant stream?
There are two concerns that come to mind. 1) The application can implement this incorrectly. 2) The application may be slow to process the STOP_SENDING, and in the meantime a lot of unnecessary data may be sent. Doing this at the transport layer allows immediate processing.
The application may also have delivered everything including FIN.
The application can also incorrectly implement having its stream reset without notification, as is currently allowed by the spec. The spec as written IMO encourages confusing semantics with respect to STOPSENDING, as it forces the transport to send the reset and leaves it up to the implementation to decide how and when it notifies the application. We don't need to be forceful in saying the application MUST handle it, i.e. we don't need to disallow transport implementations from responding to the STOPSENDING without application input. I am suggesting that we not require that the transport be the one to do it. I don't see an advantage to allowing the application to take control of sending the reset if it wants to. I don't see how this is a concern. For such cases either the application can still issue a reset for the stream, or the transport implementation can optionally take control of it and issue the reset itself.
It may not be in the application protocols best interest to have a forced reset in this case.

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