value for the subsequent ACK Range using the following formula: If any computed packet number is negative, an endpoint MUST generate <del> a connection error of type FRAME_ENCODING_ERROR indicating an error in an ACK frame. </del>  19.3.2. implies that each encryption level is treated as a separate CRYPTO stream of data.  Unlike STREAM frames, which include a Stream ID indicating to which stream the data belongs, the CRYPTO frame carries data for a single stream per encryption level.  The stream does not have an explicit delivered on a stream - the sum of the offset and data length - cannot exceed 2^62-1, as it is not possible to provide flow control credit for that data.  Receipt of a frame that exceeds this limit <del> will be treated as a connection error of type FLOW_CONTROL_ERROR. </del>  19.9. A count of the cumulative number of streams of the corresponding type that can be opened over the lifetime of the connection.  Loss or reordering can cause a MAX_STREAMS frame to be received which states a lower stream limit than an endpoint has previously received. STREAMS_BLOCKED frames contain the following fields: A variable-length integer indicating the stream limit at the time <del> the frame was sent. </del>  19.15. An 8-bit unsigned integer containing the length of the connection ID.  Values less than 1 and greater than 20 are invalid and MUST <del> be treated as a connection error of type PROTOCOL_VIOLATION. </del>  A connection ID of the specified length. The Retire Prior To field MUST be less than or equal to the Sequence Number field.  Receiving a value greater than the Sequence Number <del> MUST be treated as a connection error of type PROTOCOL_VIOLATION. </del>  Once a sender indicates a Retire Prior To value, smaller values sent in subsequent NEW_CONNECTION_ID frames have no effect.  A receiver The sequence number specified in a RETIRE_CONNECTION_ID frame MUST NOT refer to the Destination Connection ID field of the packet in which the frame is contained.  The peer MAY treat this as a <del> connection error of type PROTOCOL_VIOLATION. </del>  An endpoint cannot send this frame if it was provided with a zero- length connection ID by its peer.  An endpoint that provides a zero-

Editors discussed this and could do either. This is likely a nuisance topic for a meeting. Editors' preference is for NAME to decide this. NAME can you either back out the "or" on the flow control thing, or add an "or" to the stream limit frame descriptions?
FWIW, I think that the "or" needs to cover the following four frames: CRYPTO, STREAMS, MAXSTREAMS, STREAMSBLOCKED.
NAME did you want to fix the examples listed in URL or shall I?
NAME NAME I added text for CRYPTO, MAXSTREAMS and STREAMSBLOCKED frames, however: for CRYPTO frames, there's no flow control. It would be awkward to use a FLOWCONTROLERROR here, so I opted for just using a FRAMEENCODINGERROR, for MAXSTREAMS frames FRAMEENCODINGERROR is the only applicable error. We do have a STREAMLIMITERROR, but this error is described as "Too many streams opened", and MAXSTREAMS doesn't open any streams by itself.
Those seem like reasonable conclusions to me. I'm happy taking this. Now I just need to ensure that the changelog is updated.
NAME I'm not sure if I follow the logic here. If there is no flow control, then I'd presume that exceeding 262 bytes is totally fine, and therefore that that the receiver should not be required (i.e. MUST) to send an error.
NAME There's no flow control in the sense that you don't advertise a window and you don't update any window.
NAME Are you suggesting that the CRYPTO stream has implicit limit of 262 bytes that can be transferred? I think my question is how you would justify it. My claim is that it's an unnecessary complexity.
NAME That's what's written in the spec. I was not happy with our decision from Tokio to not communicate a limit for the data that can be sent on a CRYPTO stream, and have peers just drop any frames sent on that stream in the future instead. But that's what the working group decided to do.
NAME I think you are conflating two different issues. is an issue about the amount of data buffered, while this issue is about the maximum offset of data an endpoint can ultimately represent. Let me more practically explain why "MUST respond with a FRAMEENCODINGERROR" for a CRYPTO stream is a problem. The reason we have allowed the use of FLOWCONTROLERROR for STREAM frames is because that is an error that can be caught by the stream-level receive buffer (and it's logic), rather than the frame decoder. Stating that the error code MUST be FRAMEENCODINGERROR for CRYPTO streams in this particular case goes against that agreement. That logic that we used for STREAM frames can be equally applied to CRYPTO streams. The error code can either be CRYPTOBUFFEREXCEEDED or FLOWCONTROLERROR. I don't mind either. But requiring FRAMEENCODINGERROR is different, because it could require the stream-level logic to respond either with a stream-level error code (CRYPTOBUFFEREXCEEDED or FLOWCONTROLERROR) and frame-level error code (FRAMEDECODINGERROR) based on the input. As can be seen, this requirement to distinguish between the two cases is the same as the one we argue for the STREAM frame case.
NAME You're right. I missed that we have a CRYPTOBUFFEREXCEEDED error. I'll prepare a follow-up PR later today. Unfortunately, it looks like it won't make it into -24 any more though.
In the context of deciding what code to use for badly formatted frames: Originally posted by NAME in URL I tend to agree with this. Let's go through and try to apply some sort of consistency. Maybe even try to be principled about it.
How does this principle sound: If you can detect an error while parsing the frame, and without consulting any connection state, it should be a FRAMEENCODINGERROR. For everything that happens after frame parsing, we use the more concrete error types (or PROTOCOL_VIOLATION).
+1. To rephrase, as the name suggests, FRAMEENCODINGERROR indicates a syntactic (i.e., encoding) error. But nothing else.
I'd like to see a PR, but that seems like a good principle.
NAME I prepared a PR, just waiting for to get merged to apply the principle there.
Can we call this editorial?
It changes functional requirements about what error codes to send, so not really.
Discussed in Cupertino. Will be consensus-called.
I agree with NAME that I would prefer FLOWCONTROLERROR since it is a bit more specific. Otherwise looks fine to me.

value for the subsequent ACK Range using the following formula: If any computed packet number is negative, an endpoint MUST generate  <ins> a connection error of type FRAME_ENCODING_ERROR. </ins> 19.3.2. implies that each encryption level is treated as a separate CRYPTO stream of data. <ins> The largest offset delivered on a stream - the sum of the offset and data length - cannot exceed 2^62-1.  Receipt of a frame that exceeds this limit MUST be treated as a connection error of type FRAME_ENCODING_ERROR. </ins> Unlike STREAM frames, which include a Stream ID indicating to which stream the data belongs, the CRYPTO frame carries data for a single stream per encryption level.  The stream does not have an explicit delivered on a stream - the sum of the offset and data length - cannot exceed 2^62-1, as it is not possible to provide flow control credit for that data.  Receipt of a frame that exceeds this limit  <ins> MUST be treated as a connection error of type FRAME_ENCODING_ERROR or FLOW_CONTROL_ERROR. </ins> 19.9. A count of the cumulative number of streams of the corresponding type that can be opened over the lifetime of the connection. <ins> Stream IDs cannot exceed 2^62-1, as it is not possible to encode stream IDs larger than this value.  Receipt of a frame that permits opening of a stream larger than this limit MUST be treated as a FRAME_ENCODING_ERROR. </ins> Loss or reordering can cause a MAX_STREAMS frame to be received which states a lower stream limit than an endpoint has previously received. STREAMS_BLOCKED frames contain the following fields: A variable-length integer indicating the stream limit at the time  <ins> the frame was sent.  Stream IDs cannot exceed 2^62-1, as it is not possible to encode stream IDs larger than this value.  Receipt of a frame that encodes a larger stream ID MUST be treated as a STREAM_LIMIT_ERROR or a FRAME_ENCODING_ERROR. </ins> 19.15. An 8-bit unsigned integer containing the length of the connection ID.  Values less than 1 and greater than 20 are invalid and MUST  <ins> be treated as a connection error of type FRAME_ENCODING_ERROR. </ins> A connection ID of the specified length. The Retire Prior To field MUST be less than or equal to the Sequence Number field.  Receiving a value greater than the Sequence Number  <ins> MUST be treated as a connection error of type FRAME_ENCODING_ERROR. </ins> Once a sender indicates a Retire Prior To value, smaller values sent in subsequent NEW_CONNECTION_ID frames have no effect.  A receiver The sequence number specified in a RETIRE_CONNECTION_ID frame MUST NOT refer to the Destination Connection ID field of the packet in which the frame is contained.  The peer MAY treat this as a  <ins> connection error of type FRAME_ENCODING_ERROR. </ins> An endpoint cannot send this frame if it was provided with a zero- length connection ID by its peer.  An endpoint that provides a zero-


A QUIC server starts the process by providing TLS with the client's handshake bytes. <del> At any given time, the TLS stack at an endpoint will have a current sending encryption level and receiving encryption level.  Each encryption level is associated with a different flow of bytes, which is reliably transmitted to the peer in CRYPTO frames.  When TLS provides handshake bytes to be sent, they are appended to the current flow and any packet that includes the CRYPTO frame is protected using keys from the corresponding encryption level. </del>  QUIC takes the unprotected content of TLS handshake records as the content of CRYPTO frames.  TLS record protection is not used by QUIC. QUIC assembles CRYPTO frames into QUIC packets, which are protected using QUIC packet protection.  When an endpoint receives a QUIC packet containing a CRYPTO frame from the network, it proceeds as follows: Section 4.2.10 of TLS13).  It then sends the application data in 0-RTT packets. <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 accepts 0-RTT by sending an early_data extension in the

This is a point NAME makes in , but it probably pays to make it more clearly. This was possible in earlier iterations of the draft, so it's worth stamping out residual mentions.
Section 4.5 of quic-tls: 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. As there is no way to express TLS application data (no record headers!) I believe there is no way for the proposed error condition to occur. I propose we strike the paragraph, though we might mention that this condition is impossible for clarity.

A QUIC server starts the process by providing TLS with the client's handshake bytes.  <ins> At any time, the TLS stack at an endpoint will have a current sending encryption level and receiving encryption level.  Each encryption level is associated with a different flow of bytes, which is reliably transmitted to the peer in CRYPTO frames.  When TLS provides handshake bytes to be sent, they are appended to the current flow and any packet that includes the CRYPTO frame is protected using keys from the corresponding encryption level. </ins> QUIC takes the unprotected content of TLS handshake records as the content of CRYPTO frames.  TLS record protection is not used by QUIC. QUIC assembles CRYPTO frames into QUIC packets, which are protected using QUIC packet protection. <ins> QUIC is only capable of conveying TLS handshake records in CRYPTO frames.  TLS alerts are turned into QUIC CONNECTION_CLOSE error codes; see tls-errors.  TLS application data and other message types cannot be carried by QUIC at any encryption level and is an error if they are received from the TLS stack. </ins> When an endpoint receives a QUIC packet containing a CRYPTO frame from the network, it proceeds as follows: Section 4.2.10 of TLS13).  It then sends the application data in 0-RTT packets.  4.6. A server accepts 0-RTT by sending an early_data extension in the


to authenticate when requested.  The requirements for client authentication vary based on application protocol and deployment. <del> A server MUST NOT use post-handshake client authentication (see Section 4.6.2 of TLS13). </del>  4.5.

Add short explanation and reference to explain why QUIC-TLS bans the use of post-handshake client authentication.
draft-tls section 4.4 (Peer Authentication) states: This sounds like there are security implications, but what they are is unclear. As a reader of the document, this makes me doubt my understanding of the protocol. If we don't have a good justification for banning this, we shouldn't be banning it. (This is me reopening since that was closed for being a question.)
Is the text unclear, or are you asking to re-enable post-handshake authentication?
At least I am curious about the reasoning behind this, not that I am against it.
NAME The best way to understand the rationale is to try to work through how a particular protocol might use this feature. Read , look at the design and discussion and see the sorts of problems it tries to address. In that case, some of the problems are with server-authentication, which you can safely ignore, but the general class of problems related to concurrency and how that relates to the status of authentication produce a fairly complex design. Now I might concede that it is possible to build a protocol that uses this TLS feature well, but I would still hold that are a far superior design.
NAME Thanks for the explantion and pointers. On exported authenticators I guess there is some overlap with OAuth2, Fido, and temporary web service tokens once you get past the handshake.
NAME if we have a good reason (which it sounds like we do), then this issue tracks adding that reason to the draft.
Now that NAME has clearly written this up, perhaps we can resolve this issue by adding a reference to URL in draft-ietf-quic-tls. If that sounds good to the editors I can write a PR.
I'd prefer more direct text. You get references to HTTP/2 doc and that in turn references TLS 1.2 with compression rules etc. that needs to be filtered out.
NAME : that sounds like a good way forward.
I'm going to say that this is editorial-enough to just merge. MUST NOT implies PROTOCOL_VIOLATION, so the new normative language is fine as editorial. I'll give others a chance to disagree though.

to authenticate when requested.  The requirements for client authentication vary based on application protocol and deployment.  <ins> A server MUST NOT use post-handshake client authentication (as defined in Section 4.6.2 of TLS13), because the multiplexing offered by QUIC prevents clients from correlating the certificate request with the application-level event that triggered it (see HTTP2-TLS13). More specifically, servers MUST NOT send post-handshake TLS CertificateRequest messages and clients MUST treat receipt of such messages as a connection error of type PROTOCOL_VIOLATION. </ins> 4.5.


A QUIC server starts the process by providing TLS with the client's handshake bytes. <del> At any given time, the TLS stack at an endpoint will have a current sending encryption level and receiving encryption level.  Each encryption level is associated with a different flow of bytes, which is reliably transmitted to the peer in CRYPTO frames.  When TLS provides handshake bytes to be sent, they are appended to the current flow and any packet that includes the CRYPTO frame is protected using keys from the corresponding encryption level. </del>  QUIC takes the unprotected content of TLS handshake records as the content of CRYPTO frames.  TLS record protection is not used by QUIC. QUIC assembles CRYPTO frames into QUIC packets, which are protected using QUIC packet protection.  When an endpoint receives a QUIC packet containing a CRYPTO frame from the network, it proceeds as follows: 4.3. <del> QUIC requires that the first Initial packet from a client contain an entire cryptographic handshake message, which for TLS is the ClientHello.  Though a packet larger than 1200 bytes might be supported by the path, a client improves the likelihood that a packet is accepted if it ensures that the first ClientHello message is small enough to stay within this limit. </del>  QUIC packet and framing add at least 36 bytes of overhead to the ClientHello message.  That overhead increases if the client chooses a For servers, in addition to connection IDs and tokens, the size of TLS session tickets can have an effect on a client's ability to <del> connect.  Minimizing the size of these values increases the probability that they can be successfully used by a client. A client is not required to fit the ClientHello that it sends in response to a HelloRetryRequest message into a single UDP datagram. </del>  The TLS implementation does not need to ensure that the ClientHello is sufficiently large.  QUIC PADDING frames are added to increase the 4.5. <del> In order to be usable for 0-RTT, TLS MUST provide a NewSessionTicket message that contains the "early_data" extension with a max_early_data_size of 0xffffffff; the amount of data which the client can send in 0-RTT is controlled by the "initial_max_data" transport parameter supplied by the server.  A client MUST treat receipt of a NewSessionTicket that contains an "early_data" extension with any other value as a connection error of type PROTOCOL_VIOLATION. </del>  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. <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 accepts 0-RTT by sending an early_data extension in the 9.3. <del> Header protection relies on the packet protection AEAD being a pseudorandom function (PRF), which is not a property that AEAD algorithms guarantee.  Therefore, no strong assurances about the general security of this mechanism can be shown in the general case. The AEAD algorithms described in this document are assumed to be PRFs. The header protection algorithms defined in this document take the form: This construction is secure against chosen plaintext attacks (IND- CPA) IMC. </del>  Use of the same key and ciphertext sample more than once risks compromising header protection.  Protecting two different headers

The restriction for ClientHello to fit in a single UDP datagram was added back when the retry mechanism was more tightly coupled to the TLS crypto. At this point, the restriction doesn't get us much, and is preventing us from running some post-quantum experiments.
This proposed changes imposes some new, nontrivial requirements on the server side. As a server, would I be required to support multi-packet client initials? Do we really need this in V1?
I share NAME concern: The pain in the ass would be quite real. Is post-quantum crypto in scope?
NAME I'd point out that endpoints are already required to support muti-packet client Initials when the TLS handshake uses HRR. Therefore, IMO the PR does not add new threats.
HRR is not stateless (at the QUIC layer) though, correct? The client's first packet still must completely contain ALPN and (optionally) SNI. That information is used by WinQuic to decide initially if the connection should be accepted and more server resources commit to processing the connection. Adding another point to the QUIC layer to decide if Retry is needed is additional work.
NAME I might argue that this PR does not prevent you from using the ALPN and SNI in the first packet as a way to "skip" Retry. In that hack, the difference between the status quo and this PR is that an incomplete CH in the first packet becomes a signal to send a Retry rather than an error. I do not think it adds complexity. Considering the fact that we can remove other complexities due to the 1-packet restriction, this PR is a good simplification.
I'm not arguing that this PR would require loads more code, but I definitely don't agree that it's a "good simplification" of the current design. This adds additional attack surfaces that must be analyzed and implementations ensure they have adequate defense. Again, I ask is this absolutely required for V1? What chartered requirements for QUIC deem this change necessary for V1?
At the moment, a QUIC stack is required to indicate to the TLS stack that the CH should be constructed in a way that invokes HRR (by omitting the keyshare extension), if the CH is otherwise going to not fit in one packet. This requirement is not only complex but also imposes one additional roundtrip (when the rule is invoked). Regarding the charter, we are tasked to create a protocol that minimizes connection establishment latency, while having "security and privacy properties that are at least as good as a stack composed of TLS 1.3 using TCP." We already have post-quantum extensions proposed for TLS 1.3, it is my understanding that Chrome is already doing experiments. They require use of CH that do not fit in single packet, which, without resolving this issue, imposes complexity and one extra round-trip. Considering these aspects, I might argue that we are encouraged_ by the charter to resolve the problem :-)
Regardless of post-quantum and HRR, I agree with NAME that it is problematic to commit multiple packets before any kind of validation. If that is already the case due to HRR, I hope there is a way to avoid that without relying on client generated content. Otherwise the server is wide open to slow loris connection attacks. As to post-quantum - fine to have an experimental setting - in a real post-quantum world, perhaps min MTU can be larger?
draft-ietf-quic-tls says "QUIC requires that the first Initial packet from a client contain an entire cryptographic handshake message, which for TLS is the ClientHello." My (possibly incorrect) understanding is this was needed so the server can, in the old QUIC/TLS embedding, statelessly send a HelloRetryRequest without having to statefully assemble a ClientHello out of multiple packets. In the new QUIC/TLS embedding, this concern no longer applies because the stateless rejects are entirely external to TLS. Thus, should we simply delete most of the #clienthello-size section? This avoids a number of awkward pitfalls because the ClientHello size is a function of many factors, with both client and server considerations that change over time. Perhaps we add a new TLS extension or key share type and some SNIs work but larger SNIs were closer to the limit and this brings them over. Or perhaps the server's NewSessionTicket is large[] and breaks subsequent connections from some client. Perhaps that server tested it against one client, but another client has a larger baseline ClientHello and then it breaks. Finally, post-quantum key exchange candidates tend to come with . The current text prevents QUIC from experimenting with or deploying such schemes. This is particularly fun because QUIC clients typically also speak TLS over TCP, so the security of connections to a server is bound by the weaker of TLS over QUIC and TCP. [] Client certificates are often inserted into the ticket, so now we have a client-specific limit being influenced by a server-specific implementation decision which is, in turn, dependent on client/CA-controlled size of client certificate. Additionally, draft-ietf-quic-transport#zerortt-parameters implies servers may wish to remember QUIC transport parameters in the ticket.
This seems like a reasonable change to me. I don't anticipate this creating any new issues with recovery/etc, though it might exacerbate some of the existing ones.
I believe a main motivation for a single packet is that it avoids binding a lot of state on the server which can be a significant DoS vector. If anything we can expect certificates to get smaller with more widespread use of EC25519. As to post quantum, lets make another QUIC version in about 30-60 years or so.
As NAME says, I believe there was a concern about state at the server for connections that have not yet been established, if it receives only part of a ClientHello. I don't think this is a debilitating concern however. I would like to air this for a little while though ... I remember us using the assumption that the ClientHello is a single packet a few times, and I'm not sure if we did document them all.
I agree we should not hurry on this. From a text perspective, it's a simple change. I'd like the chairs(NAME NAME ) to briefly bring this up at the next QUIC session to ensure we can be aware of any potential pitfalls before making this change.
Could you elaborate on this? There are other client messages than the initial ClientHello, which would all require state at the server for unestablished connections. There is certainly a need to statelessly do something with unestablished connections, but there's a point where the server transitions to being stateful. In the old TLS/QUIC embedding, yes, that point was at the first ClientHello and you needed the entire ClientHello to produce a HelloRetryRequest+cookie, which was the mechanism for statelessly bouncing a connection. That's no longer the case in the new embedding, as far as I can tell. The retry packets don't actually depend on the full ClientHello or interact with the TLS handshake at all. It should work equally well if that initial packet has a full ClientHello or half of one. This time estimate is not consistent with what people are already doing in this space. URL
I'm not sufficiently deep into the handshake to detail this - but you are correct that there is less dependency on the TLS engine than what went before. I'm not sure when ClientCerts land in this process - I know they can't always fit in one piece but all else being equal, it simplifies bad content defence a lot to have a consistent starting point.
If we did allow for multiple packets for the ClientHello, I'd still want to retain the ability to decide whether or not to accept the incoming connection from every one of those initial packets. This would mean things like ALPN and SNI would need to be duplicated in each packet. I wouldn't want to accept a design where an attacker forces the server to buffer packets while it wait for the necessary information just to decided if it can accept the connection. IMO, this sounds like a v2 feature. Does this prevent us from shipping v1?
So if a client is forced to split the ClientHello into two datagrams, you can use Retry. I don't think that this is crazy. The only thing we know to be true is that the restriction is annoying.
My mistake: late stage process means that only chairs mark things as design. This is a design change.
I agree with NAME . We should remove the restrictions and add text explaining that servers can either buffer or send retry. I can write the PR if it helps.
Thanks David. We'll probably want to raise this on the list, but I'd greatly appreciate a PR. It should be mostly deleting text, but some advice about how to handle multi-packet startups is probably wise, because it isn't a great situation to be dealing with.
I observed that the size of ticket OpenSSL issues if client certificate is used becomes quite large and it does not fit into a single QUIC packet.
NSS does the same because it puts the entire certificate (if not the certification path) into the ticket.
BoringSSL as well.
If we do a change like that, how do we test it?
Discussed in Cupertino. PR exists . NAME assigned.
NAME to add a test to the interop matrix
I just defined the test matrix option Q for Quantum Readiness.
The text of this PR looks good. I share the concern about whether we want to support multi-packet ClientHello messages, though the discussion (which should be on the issue, not the PR) seems fairly convincing that we'll need to do this at some point if we don't do it now.LGTM

A QUIC server starts the process by providing TLS with the client's handshake bytes.  <ins> At any time, the TLS stack at an endpoint will have a current sending encryption level and receiving encryption level.  Each encryption level is associated with a different flow of bytes, which is reliably transmitted to the peer in CRYPTO frames.  When TLS provides handshake bytes to be sent, they are appended to the current flow and any packet that includes the CRYPTO frame is protected using keys from the corresponding encryption level. </ins> QUIC takes the unprotected content of TLS handshake records as the content of CRYPTO frames.  TLS record protection is not used by QUIC. QUIC assembles CRYPTO frames into QUIC packets, which are protected using QUIC packet protection. <ins> QUIC is only capable of conveying TLS handshake records in CRYPTO frames.  TLS alerts are turned into QUIC CONNECTION_CLOSE error codes; see tls-errors.  TLS application data and other message types cannot be carried by QUIC at any encryption level and is an error if they are received from the TLS stack. </ins> When an endpoint receives a QUIC packet containing a CRYPTO frame from the network, it proceeds as follows: 4.3.  <ins> The first Initial packet from a client contains the start or all of its first cryptographic handshake message, which for TLS is the ClientHello.  Servers might need to parse the entire ClientHello (e.g., to access extensions such as Server Name Identification (SNI) or Application Layer Protocol Negotiation (ALPN)) in order to decide whether to accept the new incoming QUIC connection.  If the ClientHello spans multiple Initial packets, such servers would need to buffer the first received fragments, which could consume excessive resources if the client's address has not yet been validated.  To avoid this, servers MAY use the Retry feature (see Section 8.1 of QUIC-TRANSPORT) to only buffer partial ClientHello messages from clients with a validated address. </ins> QUIC packet and framing add at least 36 bytes of overhead to the ClientHello message.  That overhead increases if the client chooses a For servers, in addition to connection IDs and tokens, the size of TLS session tickets can have an effect on a client's ability to  <ins> connect efficiently.  Minimizing the size of these values increases the probability that clients can use them and still fit their ClientHello message in their first Initial packet. </ins> The TLS implementation does not need to ensure that the ClientHello is sufficiently large.  QUIC PADDING frames are added to increase the 4.5.  <ins> To communicate their willingness to process 0-RTT data, servers send a NewSessionTicket message that contains the "early_data" extension with a max_early_data_size of 0xffffffff; the amount of data which the client can send in 0-RTT is controlled by the "initial_max_data" transport parameter supplied by the server.  Servers MUST NOT send the "early_data" extension with a max_early_data_size set to any value other than 0xffffffff.  A client MUST treat receipt of a NewSessionTicket that contains an "early_data" extension 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.  4.6. A server accepts 0-RTT by sending an early_data extension in the 9.3.  <ins> NAN analyzes authenticated encryption algorithms which provide nonce privacy, referred to as "Hide Nonce" (HN) transforms.  The general header protection construction in this document is one of those algorithms (HN1).  Header protection uses the output of the packet protection AEAD to derive "sample", and then encrypts the header field using a pseudorandom function (PRF) as follows: The header protection variants in this document use a pseudorandom permutation (PRP) in place of a generic PRF.  However, since all PRPs are also PRFs IMC, these variants do not deviate from the HN1 construction. As "hp_key" is distinct from the packet protection key, it follows that header protection achieves AE2 security as defined in NAN and therefore guarantees privacy of "field", the protected packet header. Future header protection variants based on this construction MUST use a PRF to ensure equivalent security guarantees. </ins> Use of the same key and ciphertext sample more than once risks compromising header protection.  Protecting two different headers


stream.  Once an endpoint sends a RESET_STREAM frame, no further STREAM frames are needed. <del> The most recent set of acknowledgments are sent in ACK frames.  An ACK frame SHOULD contain all unacknowledged acknowledgments, as described in sending-acknowledgements. </del>  Cancellation of stream transmission, as carried in a RESET_STREAM frame, is sent until acknowledged or until all stream data is

It can result in an inflated RTT, as well as being a waste of bytes.
Even though a sender is encouraged to assemble frames containing up-to-date information every time it sends a packet, it is not forbidden to retransmit copies of frames from lost packets. A receiver MUST accept packets containing an outdated frame, such as a MAX_DATA frame carrying a smaller maximum data than one found in an older packet. URL If a packet containing an ACK is lost and the payload is retransmitted with a new packet number AND no ACKs are received prior to the retransmitted ACK frame arriving, the peer will calculate and RTT sample that is 2 RTTs + loss recovery delay. As such, I'd suggest we add a warning that retransmitting ACKs can cause RTT inflation and is heavily discouraged. There may be other issues with retransmitting ACKs, but this is the first that comes to mind.

stream.  Once an endpoint sends a RESET_STREAM frame, no further STREAM frames are needed.  <ins> ACK frames carry the most recent set of acknowledgements and the Ack Delay from the largest acknowledged packet, as described in sending-acknowledgements.  Delaying the transmission of packets containing ACK frames or sending old ACK frames can cause the peer to generate an inflated RTT sample or unnecessarily disable ECN. </ins> Cancellation of stream transmission, as carried in a RESET_STREAM frame, is sent until acknowledged or until all stream data is


can be mounted using spoofed source addresses.  In determining this limit, servers only count the size of successfully processed packets. <del> Clients MUST ensure that UDP datagrams containing only Initial packets are sized to at least 1200 bytes, adding padding to packets </del>  in the datagram as necessary.  Sending padded datagrams ensures that the server is not overly constrained by the amplification restriction.

I was a bit unsure if the text meant 1200 or 1192 byte payloads. I think this is clearer. by removing the "only"
Not directly related to the change which is editorial, but why is it that 1200 only applies to Initial? Isn't it possible to coalesce Initial with random other packets and thereby work around this limit?
NAME I agree that the wording currently implies if a client bundles a 0-RTT packet with an Initial in a datagram, then it's ok for it to be less than 1200 bytes, which I don't believe is what we want?
NAME NAME I agree with the concern, and have opened (before reading your comments here). I think this PR can be kept an editorial issue while needs to be a design issue.
At the moment, states, quote: "Clients MUST ensure that UDP datagrams containing only Initial packets are sized to at least 1200 bytes, adding padding to packets in the datagram as necessary." I think this is incorrect and that "only" should be dropped, as it excludes a UDP datagram that contains an Initial and a 0-RTT packet. Pointed out by NAME in .
Technically a design change. I think that it should be any packets that contain Initial and don't contain Handshake or 1-RTT packets. At least, that's what I implemented.
I'll note that I think there are other requirements formulated in other sections. Perhaps define a term such a minimal initial datagram size and have the requirement in one place rather than sprinkling 1200 bytes in multiple places where it can be difficult to get the finer points (such as UDP vs QUIC packets and with or without header size). For example section 14 in transport:
NAME What you implemented should be fine. However, Initial + 0-RTT coalesced in a datagram should be padded I believe and I don't think we're clear on that today.
It might be simpler to require every UDP packet that contains an Initial be padded to at least 1200? I'm worried that only requiring that of Initial if there is no Handshake/1-RTT present might be tricky on the receiver: what do I do if I receive a non-padded Initial with Handshake but I can't decrypt the Handshake data?
NAME What we are talking here is a requirement specific to clients, and I am not sure if a client has a chance of sending Initial and Handshake packets at the same time. Quoting of the TLS draft, "a client MUST discard Initial keys when it first sends a Handshake packet." And even if there is a possibility of a client sending Initial and Handshake at the same moment, I do not think requiring the client to pad a packet that contains only an Initial is correct, as coalescing is an optional behavior to the send side. It would mean that clients coalescing the QUIC packets would send a non-padded datagram, while clients not coalescing would send a padded datagram. That seems like an odd behavior.
And yet another issue gets sucked into the wormhole that is the key discard mess. I guess that we will have to make a final determination based on the outcome of that stuff.
On consideration, I think that the requirement is simple and we don't have to stress about interactions with discarding keys. If the packet contains an Initial, it's possible that the Initial packet is the only packet that can be used by a server. Therefore, the datagram should be padded to 1200, regardless of whatever else is included. So we strike the "only" and we're good. See 6cfcbe26385f17072ccd330aea619f65bf3bdcb4.
I wonder - if 0-RTT does not have huge certificates, would it not make sense to permit small packets in this case, provided the auth token validates. But in other cases 1200 bytes must be provided. But since 0-RTT might fail, this could then require a retransmission. Resource constrained devices might be prefer the latter.
NAME Regardless of 0-RTT, for bandwidth constrained deployments, it makes sense for a client to always send a small packet and let the server validate the path. That said, the chartered goal of V1 is to reduce the connection establishment latency, and we have explicitly decided to require clients to send full-sized packets until the path is validated. I do not think we should revisit that design. Maybe we can in v2.
I do not believe that this is necessarily correct. The reason is that we have not yet nailed down Initial key discard and in at least one version, an Initial ACK would appear in the client's second flight, but that need not be padded.
I think the specification should mandate padding of server initial packets as well. When I was traveling a couple weeks ago, I was on a network where, when using IPv6, it allowed 1200-byte UDP packets through from client to server but blackholed them from server to client. Since our implementation pads initials in both directions, we were able to detect this failure during the handshake and fall back to TCP/TLS. If the server didn't pad its initials, we would have succeeded at the handshake but never gotten an HTTP response.
This definitely seems like a design issue. The purpose of the padding rule is solely to limit amplification, not to establish a minimum MTU. If you want some sort of MTU rule, that should be discussed separately.
This issue is marked as design. Are you suggesting I should open a separate one?
NAME I believe it is dual purpose: From the current transport draft: >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 Initial packet and/or by coalescing the Initial packet (see Section 12.2). 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 Section 8.
Yeah, that's dicta as far as I am concerned. I don't think we ever had consensus that we were going to enforce MTU here.
The insight I had here was this: If you are sending Initial, it is possible that this is the only packet in the datagram that can be processed by your peer. If that is the case, then a client needs to ensure that it pads the datagram as though no other packets were present. You can infer from the fact that you have Handshake keys that the server is able to process Handshake packets, and then extend that to say that if you include Handshake packets then the server won't need padding. But then you can extend that inference to saying that there is no need to send any Initial packets (as the current draft does; currently, you are not required to keep Initial keys once you are sending with Handshake keys). Either way, I figure that it is better to keep things simple and say "if Initial, pad". In those cases where padding ends up being excessive, it's only excessive for a short while.
I don't know where you get this from. It's total datagram size that matters, not the contents of the rest of the datagram (which is why the neqo padding strategy works). Anyway, the algorithm you propose seems fine, but it's not necessary, and therefore there's no need to specify it in the spec.
This was my thought too.
I'm not seeing a conflict between these statements. If the packet contains an Initial, the total datagram size must be at least 1200 bytes. You can achieve this by padding any packet in the payload; I don't think anyone is advocating for requiring the padding be in a particular spot.
I don't understand what "pads the datagram as though no other packets were present" is supposed to mean, then. The way neqo does this is that it puts the packets in the datagram and then pads the rest, without any regard to what keys the server might have.
Imprecise wording on my part. "pads the datagram as though no other packets were present" might be better as "as though no other packets can be processed by the server". Let's keep in mind that the proposed changes is just striking "only" in "Clients MUST ensure that UDP datagrams containing Initial packets have UDP payloads of at least 1200 bytes, [...]". I agree that this will result in larger packets than are ideal in a couple of cases. But the ACK-only Initial packets a client sent are most likely coalesced with other packets outside of cases where the padding is genuinely required.
I'm sorry, but I still don't see what benefit this change provides. Perhaps this would be easier next week.
The change makes it clear that if you send Initial+0-RTT then you should still pad to 1200. That's all. Previously, you would not have been required to pad, which would have been bad. Most of the debate and discussion is about the knock-on consequences of applying the broader rule, which I don't think are that important. And yes, the whole "when do you discard Initial keys" thing does confound things a little.
Discussed in Cupertino. The word "only" is to be removed from the respective sentence. Moving to Editorial.
This is much clearer, thanks!

can be mounted using spoofed source addresses.  In determining this limit, servers only count the size of successfully processed packets.  <ins> Clients MUST ensure that UDP datagrams containing Initial packets have UDP payloads of at least 1200 bytes, adding padding to packets </ins> in the datagram as necessary.  Sending padded datagrams ensures that the server is not overly constrained by the amplification restriction.


eliciting packet.  This recommendation is in keeping with standard practice for TCP RFC5681. <del> A receiver's delayed acknowledgment timer SHOULD NOT exceed the current RTT estimate or the value it indicates in the "max_ack_delay" transport parameter.  This ensures an acknowledgment is sent at least once per RTT when packets needing acknowledgement are received.  The sender can use the receiver's "max_ack_delay" value in determining timeouts for timer-based retransmission. </del>  In order to assist loss detection at the sender, an endpoint SHOULD send an ACK frame immediately on receiving an ack-eliciting packet determine whether an immediate or delayed acknowledgement should be generated after processing incoming packets. <del> Acknowledgements of packets carrying CRYPTO frames SHOULD be minimally delayed, to complete the handshake with minimal latency. Delaying them by a small amount, such as the local timer granularity, allows the endpoint to bundle any data sent in response with the ACK frame.  ACK frames SHOULD be sent immediately when the crypto stack indicates all data for that packet number space has been received. </del> Packets containing PADDING frames are considered to be in flight for congestion control purposes QUIC-RECOVERY.  Sending only PADDING frames might cause the sender to become limited by the congestion processed.  An endpoint MUST NOT include delays that is does not control when populating the Ack Delay field in an ACK frame. <del> An endpoint MUST NOT excessively delay acknowledgements of ack- eliciting packets.  An endpoint commits to a maximum delay using the max_ack_delay transport parameter; see transport-parameter- definitions.  max_ack_delay declares an explicit contract: an endpoint promises to never delay acknowledgments of an ack-eliciting packet by more than the indicated value.  If it does, any excess accrues to the RTT estimate and could result in delayed retransmissions from the peer.  For Initial and Handshake packets, a max_ack_delay of 0 is used. </del> 13.2.6. ACK frames MUST only be carried in a packet that has the same packet

Also removes obsolete text about acknowledging CRYPTO frames quickly, now that Initial and Handshake packet spaces have a maxackdelay of 0.
Sorry I'm late here but I think it would still be good to mention that congestion control usually is supposed to work on a RTT granularity and if you send less ACKs than one per RTT that can have an impact on cc. I don't think this has to be a normative requirement given you have normative requirements on maxackdelay and a reasonable default value but it would still be good to mention this dependency as cc is done on sender and ACKing on receiver and therefore it might not always that obvious.
The spec gives no reason (that I can see) as to why an ACK frame must be sent at least once per round trip (assuming there are ack-eliciting packets to acknowledge), effectively overwriting the ACK delay logic when the RTT is very small. If the RTT is smaller than the platform's timer granularity, this isn't really even possible. I propose to remove this requirement. If the WG instead feels that we should keep this requirement, then there should be a reasoning for it; and probably an update to the PTO calculation as well.
As I commented on the PR that this came up in, given we have an explicit maxackdelay that's now communicated, removing the 1 ACK per RTT requirement seems wise. TCP has no such recommendation and I'm unaware of any indications or proposals that it should have such a property.
NAME point that the RTT can be smaller than the platform's granularity is a good one. I agree that this text ought to be removed.
This is changing text in a SHOULD statement, so I believe it should be marked as design.
LGTM

eliciting packet.  This recommendation is in keeping with standard practice for TCP RFC5681.  <ins> An endpoint MUST NOT excessively delay acknowledgements of ack- eliciting packets.  An endpoint commits to a maximum delay using the max_ack_delay transport parameter; see transport-parameter- definitions.  max_ack_delay declares an explicit contract: an endpoint promises to never delay acknowledgments of an ack-eliciting packet by more than the indicated value.  If it does, any excess accrues to the RTT estimate and could result in delayed retransmissions from the peer.  For Initial and Handshake packets, a max_ack_delay of 0 is used.  The sender uses the receiver's "max_ack_delay" value in determining timeouts for timer-based retransmission, as detailed in Section 5.2.1 of QUIC-RECOVERY. </ins> In order to assist loss detection at the sender, an endpoint SHOULD send an ACK frame immediately on receiving an ack-eliciting packet determine whether an immediate or delayed acknowledgement should be generated after processing incoming packets.  Packets containing PADDING frames are considered to be in flight for congestion control purposes QUIC-RECOVERY.  Sending only PADDING frames might cause the sender to become limited by the congestion processed.  An endpoint MUST NOT include delays that is does not control when populating the Ack Delay field in an ACK frame.  13.2.6. ACK frames MUST only be carried in a packet that has the same packet


might be destroyed sooner, as soon as handshake keys are available (see Section 4.9.1 of QUIC-TLS). <del> 5.6. The majority of constants were derived from best common practices among widely deployed TCP implementations on the internet. Exceptions follow. A shorter delayed ack time of 25ms was chosen because longer delayed acks can delay loss recovery and for the small number of connections where less than packet per 25ms is delivered, acking every packet is beneficial to congestion control and loss recovery. </del> 6. QUIC's congestion control is based on TCP NewReno RFC6582.  NewReno

The recommendation for a 25ms delayed ack time is in transport, not recovery, so remove it from recovery. Should this paragraph be moved to transport?
I think that it's fine to just cut this.
This paragraph didn't seem to relate to anything else in the document: Is it left-over from a previous version?
Thanks for pointing this out. The intent was to explain why 25ms was chosen, given it's smaller than most TCP implementations default delayed ack time. There were more exceptions before, but now there's only the one, which makes the preceding paragraph awkward as well. "The majority of constants were derived from best common practices among widely deployed TCP implementations on the internet. Exceptions follow."
I realized that the 25ms recommendation is in transport, so this is just out of place now.
Ah, I see, I remember I went looking for it in transport, but I think I looked for "delayed ack" and didn't come up with anything fitting. I've got no concrete suggestion how to resolve it but moving the notice to transport in some form would probably make sense.

might be destroyed sooner, as soon as handshake keys are available (see Section 4.9.1 of QUIC-TLS).  6. QUIC's congestion control is based on TCP NewReno RFC6582.  NewReno


If packets sent prior to the largest acknowledged packet cannot yet be declared lost, then a timer SHOULD be set for the remaining time. <del> Using max(SRTT, latest_RTT) protects from the two following cases: </del>  <del> the latest RTT sample is lower than the SRTT, perhaps due to reordering where the acknowledgement encountered a shorter path; </del>  <del> the latest RTT sample is higher than the SRTT, perhaps due to a sustained increase in the actual RTT, but the smoothed SRTT has not yet caught up. </del>  The RECOMMENDED time threshold (kTimeThreshold), expressed as a round-trip time multiplier, is 9/8. This document does not specify a pacer, but it is RECOMMENDED that a sender pace sending of all in-flight packets based on input from the congestion controller.  For example, a pacer might distribute the <del> congestion window over the SRTT when used with a window-based </del>  controller, and a pacer might use the rate estimate of a rate-based controller.

In section "Time Threshold", is used for the first time. I guess that would reference the same value before called ? If that's the case they should be named the same. Further down in the same paragraph "smoothed SRTT" is used which is even more confusing. SRTT is used a few more times further down.
There was a cleanup a while ago to ensure definitions such as SRTT happened before use, but that might have been lost in recovery to transport migration?
:+1:

If packets sent prior to the largest acknowledged packet cannot yet be declared lost, then a timer SHOULD be set for the remaining time.  <ins> Using max(smoothed_rtt, latest_rtt) protects from the two following cases: </ins>  <ins> the latest RTT sample is lower than the smoothed RTT, perhaps due to reordering where the acknowledgement encountered a shorter path; </ins>  <ins> the latest RTT sample is higher than the smoothed RTT, perhaps due to a sustained increase in the actual RTT, but the smoothed RTT has not yet caught up. </ins> The RECOMMENDED time threshold (kTimeThreshold), expressed as a round-trip time multiplier, is 9/8. This document does not specify a pacer, but it is RECOMMENDED that a sender pace sending of all in-flight packets based on input from the congestion controller.  For example, a pacer might distribute the  <ins> congestion window over the smoothed RTT when used with a window-based </ins> controller, and a pacer might use the rate estimate of a rate-based controller.


5.3.1.  When a PTO timer expires, a sender MUST send at least one ack- eliciting packet as a probe, unless there is no data available to send.  An endpoint MAY send up to two full-sized datagrams containing Sending different payloads each time reduces the chances of spurious retransmission. <del> 5.3.2. </del>  Delivery or loss of packets in flight is established when an ACK frame is received that newly acknowledges one or more packets.

When the second duplicate client Initial is received, the server MAY retransmit the contents of it's Initial to speed up the handshake. I'm not sure we want to document only this case, or have a larger section about optimizations to speed recovery during the handshake.
That could be an attack vector since you could send small invalid handshake packets. Not sure how efficient such an attack would be and we don't try too hard to avoid problems at this stage, as long as isn't an efficient amp attack.
I think the latest text looks great. Thanks NAME for putting it together.
After thinking it through, this is a good choice. The downside is the client might have a bad initial PTO. But without this optimization, upon receipt of another client hello I'll just send more ceritificate data that was blocked by the amplification limit, data that is probably useless if the server hello has been lost. Far better for me to send the server hello again instead.
This issue has consensus and I'm happy with the text, so I'm going to merge it and we can work on a follow-up editorial PR in
This tracks any follow-ups to
Is there any such follow-up? If not, close this?
I think this is probably OBE and we should close this.
Generally, the server's first flight (of a 1-RTT connection attempt) has a single Initial packet and the rest is made up of 1 or more Handshake packets. If the UDP datagram containing the Initial packet is lost, the server only ends up retransmitting it based off the default RTT based PTO timer. If/when the client receives the Handshake packets that it can't decrypt (because the Initial was lost) then it has a (possible) signal that the server has responded, but a packet was lost. The client could use this info to immediately retransmit its Initial. If/when the server receives this, it could use this info to retransmit its Initial.
Isn't this a special case of the EMPTY_ACK frame discussed earlier?
Yes, but the problem is the worst in this case, because the server doesn't have an RTT estimate, so it can't have an accurate PTO for retransmitting. Once it has received a single ACK, the PTO becomes a lot more accurate, and falling back to the PTO is a lot less painful.
This problem also occurs when the server receives 0-RTT packets it cannot decrypt. In that case, it would be ideal if it could do something to indicate something was received but not processed. I guess it could send a PING in an Initial packet as an alternative to EMPTY_ACK? I sent out a PR that I believe covers the most important points you mention. PTAL.
I think the problems here are: Receiving a retransmit client Initial does not necessarily indicate a loss. It could simply be the case that the actual RTT is greater than the client's initial RTT. Do we want the server to retransmit packets in that case? Note that a client might use a small initial RTT when it is connecting to a server that it has previously connected to. How the server detects a retransmission will become complicated if we adopt . Considering these aspects, I might be weakly inclined to addressing the issue using an explicit signal, defined either within v1 or an extension.
I think this falls into the category of black-holing invalid packets as they might easily come from an unwanted source.
I don't believe complicates things substantially, because if only one UDP datagram is lost, then you would expect to receive an ACK in a relatively short period(no ack delay) of time and loss recovery could function quite well.
Does this need to be standardized? Or does it fall in the range of heuristics that applications may do, but don't need to be specified.
Of course the handshake packet that you receive can be a bunch of random bytes sent by an attacker...
Discussed in Cupertino. NAME to update based on discussion.
The proposed "fix" could lead to a deadlock where the server consumes its entire congestion window without making progress. If the server receives too many Initial packets (bad RTT estimate at the client, say), then it will send lots of data without getting an ACK. As suggested, this is just an optimization, so limiting the number of times this optimization can be used might avoid that problem.
Discussed in ZRH. No change since Cupertino.
Apparently this has consensus. NAME is ready?
My only problem is that I wouldn't know how to detect duplicate CRYPTO data from a cursory read. Normally you'd feed the data to a TLS blackbox and it would not necessarily provide the information needed. The alternative seems to be to inspect CRYPTO frames, but who is to say that duplicate data is always framed the same?
NAME detecting duplicate CRYPTO data is definitely implementation dependent, so I don't want to describe that in the recovery doc.
This is definitely an improvement. Thanks for writing it up! In addition to this, should we have text that says the client should/may immediately retransmit its initial packet if it gets a handshake packet it can't decrypt?

5.3.1. <ins> When a server receives an Initial packet containing duplicate CRYPTO data, it can assume the client did not receive all of the server's CRYPTO data sent in Initial packets, or the client's estimated RTT is too small.  When a client receives Handshake or 1-RTT packets prior to obtaining Handshake keys, it may assume some or all of the server's Initial packets were lost. To speed up handshake completion under these conditions, an endpoint MAY send a packet containing unacknowledged CRYPTO data earlier than the PTO expiry, subject to address validation limits; see Section 8.1 of QUIC-TRANSPORT. Peers can also use coalesced packets to ensure that each datagram elicits at least one acknowledgement.  For example, clients can coalesce an Initial packet containing PING and PADDING frames with a 0-RTT data packet and a server can coalesce an Initial packet containing a PING frame with one or more packets in its first flight. 5.3.2. </ins> When a PTO timer expires, a sender MUST send at least one ack- eliciting packet as a probe, unless there is no data available to send.  An endpoint MAY send up to two full-sized datagrams containing Sending different payloads each time reduces the chances of spurious retransmission.  <ins> 5.3.3. </ins> Delivery or loss of packets in flight is established when an ACK frame is received that newly acknowledges one or more packets.


Some stream types are reserved (stream-grease).  Two stream types are defined in this document: control streams (control-streams) and push <del> streams (push-streams).  Other stream types can be defined by extensions to HTTP/3; see extensions for more details. </del>  The performance of HTTP/3 connections in the early phase of their lifetime is sensitive to the creation and exchange of data on

The Stream Types registry (URL) currently lists two types only (0 - control, 1 - push). While working on a dissector for the base HTTP/3 protocol, I ran across 2 and 3. It turns out these are defined in the QPACK document (URL) with 2 - QPACK Encoder Stream, 3 - QPACK Decoder Stream. Since QPACK has already been referenced multiple times from the -http document, what about mentioning (or registering) the two codepoints in the -http document and reference to the -qpack document for the definition?
We can, and as QPACK is a required component of HTTP/3, you'd probably need to support QPACK for that to work. However, this is an extensible space and your dissector would need to tolerate other types it might not recognize, or that are added by future extensions. So the fact that there are definitions in other documents not mentioned by HTTP/3 is to be expected.

Some stream types are reserved (stream-grease).  Two stream types are defined in this document: control streams (control-streams) and push  <ins> streams (push-streams).  QPACK defines two additional stream types. Other stream types can be defined by extensions to HTTP/3; see extensions for more details. </ins> The performance of HTTP/3 connections in the early phase of their lifetime is sensitive to the creation and exchange of data on


frame identifies a server push by Push ID (see frame-push-promise), encoded as a variable-length integer. <del> When a server receives this frame, it aborts sending the response for the identified server push.  If the server has not yet started to send the server push, it can use the receipt of a CANCEL_PUSH frame to avoid opening a push stream.  If the push stream has been opened by the server, the server SHOULD abruptly terminate that stream. A server can send the CANCEL_PUSH frame to indicate that it will not be fulfilling a promise prior to creation of a push stream.  Once the push stream has been created, sending CANCEL_PUSH has no effect on the state of the push stream.  The server SHOULD abruptly terminate the push stream instead. </del>  A CANCEL_PUSH frame is sent on the control stream.  Receiving a CANCEL_PUSH frame on a stream other than the control stream MUST be The CANCEL_PUSH frame carries a Push ID encoded as a variable-length integer.  The Push ID identifies the server push that is being <del> cancelled (see frame-push-promise). </del>  If the client receives a CANCEL_PUSH frame, that frame might identify <del> a Push ID that has not yet been mentioned by a PUSH_PROMISE frame. </del>  7.2.4. The MAX_PUSH_ID frame (type=0xD) is used by clients to control the number of server pushes that the server can initiate.  This sets the <del> maximum value for a Push ID that the server can use in a PUSH_PROMISE frame.  Consequently, this also limits the number of push streams that the server can initiate in addition to the limit maintained by the QUIC transport. </del>  The MAX_PUSH_ID frame is always sent on the control stream.  Receipt of a MAX_PUSH_ID frame on any other stream MUST be treated as a

Open to discussion about whether all of these are ID_ERROR or not.
I've tried to improve the readability of this section a bit; I turned Ian's suggested parallel paragraphs around to talk about why client and server would send the frame (and the corresponding responses) instead of talking about behavior on receipt. Please take a fresh look, NAME and NAME
I think there are some edge cases with the CANCELPUSH frame that we don't explain: reception of a CANCELPUSH frame with ID greater than the current push limit reception of CANCEL_PUSH with ID that has already been used
I'd also add a CANCELPUSH from the client for a PUSHID that has not been promised.
CANCEL_PUSH with a reused ID is probably indistinguishable from receiving one for a stream that's already open; you SHOULD reset the stream (which is a no-op if the stream is already full transferred).

frame identifies a server push by Push ID (see frame-push-promise), encoded as a variable-length integer.  <ins> When a client sends CANCEL_PUSH, it is indicating that it does not wish to receive the promised resource.  The server SHOULD abort sending the resource, but the mechanism to do so depends on the state of the corresponding push stream.  If the server has not yet created a push stream, it does not create one.  If the push stream is open, the server SHOULD abruptly terminate that stream.  If the push stream has already ended, the server MAY still abruptly terminate the stream or MAY take no action. When a server sends CANCEL_PUSH, it is indicating that it will not be fulfilling a promise and has not created a push stream.  The client should not expect the corresponding promise to be fulfilled. Sending CANCEL_PUSH has no direct effect on the state of existing push streams.  A server SHOULD NOT send a CANCEL_PUSH when it has already created a corresponding push stream, and a client SHOULD NOT send a CANCEL_PUSH when it has already received a corresponding push stream. </ins> A CANCEL_PUSH frame is sent on the control stream.  Receiving a CANCEL_PUSH frame on a stream other than the control stream MUST be The CANCEL_PUSH frame carries a Push ID encoded as a variable-length integer.  The Push ID identifies the server push that is being  <ins> cancelled (see frame-push-promise).  If a CANCEL_PUSH frame is received which references a Push ID greater than currently allowed on the connection, this MUST be treated as a connection error of type HTTP_ID_ERROR. </ins> If the client receives a CANCEL_PUSH frame, that frame might identify  <ins> a Push ID that has not yet been mentioned by a PUSH_PROMISE frame due to reordering.  If a server receives a CANCEL_PUSH frame for a Push ID that has not yet been mentioned by a PUSH_PROMISE frame, this MUST be treated as a connection error of type HTTP_ID_ERROR. </ins> 7.2.4. The MAX_PUSH_ID frame (type=0xD) is used by clients to control the number of server pushes that the server can initiate.  This sets the  <ins> maximum value for a Push ID that the server can use in PUSH_PROMISE and CANCEL_PUSH frames.  Consequently, this also limits the number of push streams that the server can initiate in addition to the limit maintained by the QUIC transport. </ins> The MAX_PUSH_ID frame is always sent on the control stream.  Receipt of a MAX_PUSH_ID frame on any other stream MUST be treated as a


header fields are defined in Section 8.1.2.3 and 8.1.2.4 of HTTP2. Pseudo-header fields are not HTTP header fields.  Endpoints MUST NOT generate pseudo-header fields other than those defined in HTTP2.  The <del> restrictions on the use of pseudo-header fields in Section 8.1.2.1 of HTTP2 also apply to HTTP/3. </del>  HTTP/3 uses QPACK header compression as described in QPACK, a variation of HPACK which allows the flexibility to avoid header-

The constraints of 8.1.2.1 apply to all requests, but I missed that there are additional requirements in 8.1.2.3 for requests and 8.1.2.4 for responses. I've broadened the statement to "the restrictions on the use of pseudo-headers in Section 8.1.2" to sweep them all up. Does anyone think we need pointers to each specific subsection? At Lucas's suggestion, I've also cross-referenced handling of malformed messages here, since those restrictions talk about what is malformed in a given context.
I'm carefully parsing the wording in quic-http sec. 4.1.1 and don't think it's right: In RFC 7540, 8.1.2.1 says This notably does NOT include the further restrictions in 8.1.2.3: I am far from an expert on HTTP headers, but I suspect it's not right that clients could send quantities of :method, :scheme, and :path other than 1. We should probably fix the section reference, though I haven't reviewed 8.1.2 carefully enough to know exactly how that should read.
Good point.
The cited HTTP/2 sections mention malformed requests and response, so we probably also want to add some text to the H3 spec that links to it's section 4.1.3 on how to deal with these.

header fields are defined in Section 8.1.2.3 and 8.1.2.4 of HTTP2. Pseudo-header fields are not HTTP header fields.  Endpoints MUST NOT generate pseudo-header fields other than those defined in HTTP2.  The  <ins> restrictions on the use of pseudo-header fields in Section 8.1.2 of HTTP2 also apply to HTTP/3.  Messages which are considered malformed under these restrictions are handled as described in malformed. </ins> HTTP/3 uses QPACK header compression as described in QPACK, a variation of HPACK which allows the flexibility to avoid header-


When the decoder receives a header block with a Required Insert Count greater than its own Insert Count, the stream cannot be processed <del> immediately, and is considered "blocked" (see {blocked-decoding}). </del>  The decoder specifies an upper bound on the number of streams which can be blocked using the SETTINGS_QPACK_BLOCKED_STREAMS setting (see throughout.  This string format includes optional Huffman encoding. HPACK defines string literals to begin on a byte boundary.  They <del> begin with a single flag (indicating whether the string is Huffman- coded), followed by the Length encoded as a 7-bit prefix integer, and finally Length bytes of data.  When Huffman encoding is enabled, the Huffman table from Appendix B of RFC7541 is used without modification. </del>  This document expands the definition of string literals and permits them to begin other than on a byte boundary.  An "N-bit prefix string An encoder adds an entry to the dynamic table where the header field name matches the header field name of an entry stored in the static or the dynamic table using an instruction that starts with the '1' <del> one-bit pattern.  The second ("S") bit indicates whether the </del>  reference is to the static or dynamic table.  The 6-bit prefix integer (see prefixed-integers) that follows is used to locate the <del> table entry for the header name.  When S=1, the number represents the static table index; when S=0, the number is the relative index of the </del>  entry in the dynamic table. The header name reference is followed by the header field value Each header block is prefixed with two integers.  The Required Insert Count is encoded as an integer with an 8-bit prefix after the encoding described in ric).  The Base is encoded as sign-and-modulus <del> integer, using a single sign bit and a value with a 7-bit prefix (see base). </del>  These two values are followed by representations for compressed headers. described in relative-indexing. To save space, the Base is encoded relative to the Insert Count using <del> a one-bit sign and the "Delta Base" value.  A sign bit of 0 indicates that the Base is greater than or equal to the value of the Insert Count; the value of Delta Base is added to the Insert Count to </del>  determine the value of the Base.  A sign bit of 1 indicates that the Base is less than the Insert Count.  That is: If the entry is in the static table, or in the dynamic table with an absolute index less than the Base, this representation starts with <del> the '1' 1-bit pattern, followed by the "S" bit indicating whether the </del>  reference is into the static or dynamic table.  The 6-bit prefix integer (see prefixed-integers) that follows is used to locate the <del> table entry for the header field.  When S=1, the number represents the static table index; when S=0, the number is the relative index of </del>  the entry in the dynamic table. 4.5.3. for protecting header field values that are not to be put at risk by compressing them (see security-considerations for more details). <del> The fourth ("S") bit indicates whether the reference is to the static </del>  or dynamic table.  The 4-bit prefix integer (see prefixed-integers) that follows is used to locate the table entry for the header name. <del> When S=1, the number represents the static table index; when S=0, the </del>  number is the relative index of the entry in the dynamic table. Only the header field name is taken from the dynamic table entry; the

Renamed S bit for static vs dynamic table to T bit (for Table) Replaced backticks with single quotes around individual bits (previously, about half used single, half used double) Added explicit references to bit letter indicators where they were missing (e.g., for 'H') Fixed broken reference to "blocked-decoding" section
As for 'B' vs , transport doc just uses (B). Maybe we should follow suit, bui I can do in a separate pass.

When the decoder receives a header block with a Required Insert Count greater than its own Insert Count, the stream cannot be processed  <ins> immediately, and is considered "blocked" (see blocked-decoding). </ins> The decoder specifies an upper bound on the number of streams which can be blocked using the SETTINGS_QPACK_BLOCKED_STREAMS setting (see throughout.  This string format includes optional Huffman encoding. HPACK defines string literals to begin on a byte boundary.  They  <ins> begin with a single bit flag, denoted as 'H' in this document (indicating whether the string is Huffman-coded), followed by the Length encoded as a 7-bit prefix integer, and finally Length bytes of data.  When Huffman encoding is enabled, the Huffman table from Appendix B of RFC7541 is used without modification. </ins> This document expands the definition of string literals and permits them to begin other than on a byte boundary.  An "N-bit prefix string An encoder adds an entry to the dynamic table where the header field name matches the header field name of an entry stored in the static or the dynamic table using an instruction that starts with the '1'  <ins> one-bit pattern.  The second ('T') bit indicates whether the </ins> reference is to the static or dynamic table.  The 6-bit prefix integer (see prefixed-integers) that follows is used to locate the  <ins> table entry for the header name.  When T=1, the number represents the static table index; when T=0, the number is the relative index of the </ins> entry in the dynamic table. The header name reference is followed by the header field value Each header block is prefixed with two integers.  The Required Insert Count is encoded as an integer with an 8-bit prefix after the encoding described in ric).  The Base is encoded as sign-and-modulus  <ins> integer, using a single sign bit ('S') and a value with a 7-bit prefix (see base). </ins> These two values are followed by representations for compressed headers. described in relative-indexing. To save space, the Base is encoded relative to the Insert Count using  <ins> a one-bit sign ('S') and the "Delta Base" value.  A sign bit of 0 indicates that the Base is greater than or equal to the value of the Insert Count; the value of Delta Base is added to the Insert Count to </ins> determine the value of the Base.  A sign bit of 1 indicates that the Base is less than the Insert Count.  That is: If the entry is in the static table, or in the dynamic table with an absolute index less than the Base, this representation starts with  <ins> the '1' 1-bit pattern, followed by the 'T' bit indicating whether the </ins> reference is into the static or dynamic table.  The 6-bit prefix integer (see prefixed-integers) that follows is used to locate the  <ins> table entry for the header field.  When T=1, the number represents the static table index; when T=0, the number is the relative index of </ins> the entry in the dynamic table. 4.5.3. for protecting header field values that are not to be put at risk by compressing them (see security-considerations for more details).  <ins> The fourth ('T') bit indicates whether the reference is to the static </ins> or dynamic table.  The 4-bit prefix integer (see prefixed-integers) that follows is used to locate the table entry for the header name.  <ins> When T=1, the number represents the static table index; when T=0, the </ins> number is the relative index of the entry in the dynamic table. Only the header field name is taken from the dynamic table entry; the


activity correlated by any entity other than their peer, so different connection IDs are used when sending from different local addresses, as discussed in connection-id.  For this to be effective endpoints <del> need to ensure that connections IDs they provide cannot be linked by </del>  any other entity. At any time, endpoints MAY change the Destination Connection ID they use a CONNECTION_CLOSE frame if it has sufficient state to do so. To support this process, a token is sent by endpoints.  The token is <del> carried in the NEW_CONNECTION_ID frame sent by either peer, and servers can specify the stateless_reset_token transport parameter during the handshake (clients cannot because their transport parameters don't have confidentiality protection).  This value is protected by encryption, so only client and server know this value. Tokens are invalidated when their associated connection ID is retired via a RETIRE_CONNECTION_ID frame (frame-retire-connection-id). </del>  An endpoint that receives packets that it cannot process sends a packet in the following layout: packet either cannot be associated with a connection, cannot be decrypted, or is marked as a duplicate packet.  The endpoint MUST then compare the last 16 bytes of the packet with all Stateless Reset <del> Tokens that are associated with connection IDs that the endpoint recently used to send packets from the IP address and port on which the datagram is received.  This includes Stateless Reset Tokens from </del>  NEW_CONNECTION_ID frames and the server's transport parameters.  An endpoint MUST NOT check for any Stateless Reset Tokens associated with connection IDs it has not used or for connection IDs that have that is reset by revealing the Stateless Reset Token MUST NOT be reused for new connections at nodes that share a static key. <del> The same Stateless Reset Token MAY be used for multiple connection IDs on the same connection.  However, reuse of a Stateless Reset Token might expose an endpoint to denial of service if associated connection IDs are forgotten while the associated token is still active at a peer.  An endpoint MUST ensure that packets with Destination Connection ID field values that correspond to a reused Stateless Reset Token are attributed to the same connection as long as the Stateless Reset Token is still usable, even when the connection ID has been retired.  Otherwise, an attacker might be able to send a packet with a retired connection ID and cause the endpoint to produce a Stateless Reset that it can use to disrupt the connection, just as with the attacks in reset-oracle. </del>  Note that Stateless Reset packets do not have any cryptographic protection. 13.2.1.  An ACK frame SHOULD be generated for at least every second ack- eliciting packet.  This recommendation is in keeping with standard practice for TCP RFC5681. <del> A receiver's delayed acknowledgment timer SHOULD NOT exceed the current RTT estimate or the value it indicates in the "max_ack_delay" transport parameter.  This ensures an acknowledgment is sent at least once per RTT when packets needing acknowledgement are received.  The sender can use the receiver's "max_ack_delay" value in determining timeouts for timer-based retransmission. </del> In order to assist loss detection at the sender, an endpoint SHOULD send an ACK frame immediately on receiving an ack-eliciting packet that is out of order.  The endpoint MAY continue sending ACK frames immediately on each subsequently received packet, but the endpoint <del> SHOULD return to acknowledging every other packet after a period of </del>  1/8 x RTT, unless more ack-eliciting packets are received out of order.  If every subsequent ack-eliciting packet arrives out of order, then an ACK frame SHOULD be sent immediately for every determine whether an immediate or delayed acknowledgement should be generated after processing incoming packets. <del> Acknowledgements of packets carrying CRYPTO frames SHOULD be minimally delayed, to complete the handshake with minimal latency. Delaying them by a small amount, such as the local timer granularity, allows the endpoint to bundle any data sent in response with the ACK frame.  ACK frames SHOULD be sent immediately when the crypto stack indicates all data for that packet number space has been received. </del> Packets containing PADDING frames are considered to be in flight for congestion control purposes QUIC-RECOVERY.  Sending only PADDING frames might cause the sender to become limited by the congestion <del> controller (as described in QUIC-RECOVERY) with no acknowledgments forthcoming from the receiver.  Therefore, a sender SHOULD ensure that other frames are sent in addition to PADDING frames to elicit acknowledgments from the receiver. </del>  An endpoint that is only sending ACK frames will not receive acknowledgments from its peer unless those acknowledgements are processed.  An endpoint MUST NOT include delays that is does not control when populating the Ack Delay field in an ACK frame. <del> An endpoint MUST NOT excessively delay acknowledgements of ack- eliciting packets.  An endpoint commits to a maximum delay using the max_ack_delay transport parameter; see transport-parameter- definitions.  max_ack_delay declares an explicit contract: an endpoint promises to never delay acknowledgments of an ack-eliciting packet by more than the indicated value.  If it does, any excess accrues to the RTT estimate and could result in delayed retransmissions from the peer.  For Initial and Handshake packets, a max_ack_delay of 0 is used. </del> 13.2.6. ACK frames MUST only be carried in a packet that has the same packet

The editors assumed this was true, but I think it's worth stating this as a MUST to ensure clarity.
I'm a bit worried this misses the point. Ack-eliciting packets should be acknowledged yes, but so should non-ack-eliciting packets. By stating that ack-eliciting packets should be ACK'ed at least once, it is implied that this is not the case for other packets. This may be correct but not necessarily clear. For example for long transmissions where the peer does nothing but sending ACKs, not all of those ACKs will necessarily have to be ACK'ed. But what is the guiding principle for that? And, since you in this case are sending lots of ACK eliciting packets, you can easily ACK all the peers ACK's. So when would you not want to do that? A special case that could deadlock - e.g. too many ACKs to fit in a packet?
Also, when ACKs packets don't fit in packets, this would also hold for ACK'ing ack-eliciting packets. So you could end up having to not ACK'ing some packets and induce loss. Requiring an ACK for all packets would be problematic. I'm not sure we want that. The result could be very hard to implement while the practical benefit would be small: sending multiple ACK packets with different content due to overflow.
I just synced to master and now this seems to be an editorial change, at least to me. That being said, PTAL at the text and tell me if there are any other changes I should make.
It appears that the spec doesn't actually require sending ACKs for every ACK-eliciting packet. This could lead to a perverse implementation just not ACKing things with obvious problems.
Discussed in Cupertino. Add normative language to prescribe what should be ACKed. Reclassifying as Editorial (adds normative language, but we have consensus on that).
Generally looks good, but I don't think we can have an absolute requirement that every ack-eliciting packet is acknowledged.

activity correlated by any entity other than their peer, so different connection IDs are used when sending from different local addresses, as discussed in connection-id.  For this to be effective endpoints  <ins> need to ensure that connection IDs they provide cannot be linked by </ins> any other entity. At any time, endpoints MAY change the Destination Connection ID they use a CONNECTION_CLOSE frame if it has sufficient state to do so. To support this process, a token is sent by endpoints.  The token is  <ins> carried in the Stateless Reset Token field of a NEW_CONNECTION_ID frame.  Servers can also specify a stateless_reset_token transport parameter during the handshake that applies to the connection ID that it selected during the handshake; clients cannot use this transport parameter because their transport parameters don't have confidentiality protection.  These tokens are protected by encryption, so only client and server know their value.  Tokens are invalidated when their associated connection ID is retired via a RETIRE_CONNECTION_ID frame (frame-retire-connection-id). </ins> An endpoint that receives packets that it cannot process sends a packet in the following layout: packet either cannot be associated with a connection, cannot be decrypted, or is marked as a duplicate packet.  The endpoint MUST then compare the last 16 bytes of the packet with all Stateless Reset  <ins> Tokens corresponding to active connection IDs that the endpoint has used for sending packets to the IP address and port on which the datagram is received.  This includes Stateless Reset Tokens from </ins> NEW_CONNECTION_ID frames and the server's transport parameters.  An endpoint MUST NOT check for any Stateless Reset Tokens associated with connection IDs it has not used or for connection IDs that have that is reset by revealing the Stateless Reset Token MUST NOT be reused for new connections at nodes that share a static key.  <ins> The same Stateless Reset Token MUST NOT be used for multiple connection IDs.  Endpoints are not required to compare new values against all previous values, but a duplicate value MAY be treated as a connection error of type PROTOCOL_VIOLATION. </ins> Note that Stateless Reset packets do not have any cryptographic protection. 13.2.1. <ins> Every packet SHOULD be acknowledged at least once, and ack-eliciting packets MUST be acknowledged at least once within the maximum ack delay.  An endpoint communicates its maximum delay using the max_ack_delay transport parameter; see transport-parameter- definitions.  max_ack_delay declares an explicit contract: an endpoint promises to never intentionally delay acknowledgments of an ack-eliciting packet by more than the indicated value.  If it does, any excess accrues to the RTT estimate and could result in spurious or delayed retransmissions from the peer.  For Initial and Handshake packets, a max_ack_delay of 0 is used.  The sender uses the receiver's "max_ack_delay" value in determining timeouts for timer- based retransmission, as detailed in Section 5.2.1 of QUIC-RECOVERY. </ins> An ACK frame SHOULD be generated for at least every second ack- eliciting packet.  This recommendation is in keeping with standard practice for TCP RFC5681.  In order to assist loss detection at the sender, an endpoint SHOULD send an ACK frame immediately on receiving an ack-eliciting packet that is out of order.  The endpoint MAY continue sending ACK frames immediately on each subsequently received packet, but the endpoint  <ins> SHOULD return to acknowledging every other packet within a period of </ins> 1/8 x RTT, unless more ack-eliciting packets are received out of order.  If every subsequent ack-eliciting packet arrives out of order, then an ACK frame SHOULD be sent immediately for every determine whether an immediate or delayed acknowledgement should be generated after processing incoming packets.  Packets containing PADDING frames are considered to be in flight for congestion control purposes QUIC-RECOVERY.  Sending only PADDING frames might cause the sender to become limited by the congestion  <ins> controller with no acknowledgments forthcoming from the receiver. Therefore, a sender SHOULD ensure that other frames are sent in addition to PADDING frames to elicit acknowledgments from the receiver. </ins> An endpoint that is only sending ACK frames will not receive acknowledgments from its peer unless those acknowledgements are processed.  An endpoint MUST NOT include delays that is does not control when populating the Ack Delay field in an ACK frame.  13.2.6. ACK frames MUST only be carried in a packet that has the same packet