Question:
As of , the client MUST use the same handshake message.
NAME That's quite complicated, and I'm not sure if we've chosen the right design here. I'd like to keep this PR editorial though. I opened to discuss this.
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.
So, the effect of not changing the handshake message is that all previously-sent 0-RTT data is still valid, but has potentially/probably been lost rather than queued. Clients obviously MUST NOT reuse the packet number for different data, but in their not-resetting, they also can't change the data that they've already sent in those packets. They'll need to retransmit it, because otherwise there's a potential DoS attack by delaying/replaying a 0-RTT packet by one RTT against a server that does Retries.This PR looks good to me as an editorial improvement. Regarding NAME point, the transport draft explicitly states that a server sending a Retry can buffer 0-RTT packets that carried the original DCID, and process them after receiving a response to the Retry. To put it differently, I think we are clear on the semantics and that there are ways to implement this without having security concerns. That said, I understand that NAME has opened a design issue () to discuss if that is the design we want to have.LGTM

from a packet number of 0.  Thus, 0-RTT packets could need to use a longer packet number encoding. <del> A client SHOULD instead generate a fresh cryptographic handshake message and start packet numbers from 0.  This ensures that new 0-RTT packets will not use the same keys, avoiding any risk of key and nonce reuse; this also prevents 0-RTT packets from previous handshake attempts from being accepted as part of the connection. </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 contain any response to frames from 1-RTT packets.  For instance, a


Question:
This
The transport draft talks about QUIC being resilient to NAT rebindings. However, if a QUIC server is behind an L3 load balancer which simply routes based on 5-tuple, then connections to this server will (likely) not survive rebinding. I couldn't find any language which addressed whether such a deployment was "OK" or not. I think that since this load balance does not support NAT binding resilience, it is implicitly "bad" according to the draft, but others might disagree. In any case, I think there should be text to address this. Note, if the server advertised a preferredaddress which routed around the load balancer, and if clients were required to use this address, then that could obviously work. But preferredaddress support is a SHOULD, not a MUST.
We did discuss this issue. Use of empty connection IDs and demultiplexing based on source addresses. The conclusion there was that this was OK: people could do that, as long as it was clear that what they were getting was not different than TCP. That is, we made it clear that if you don't use the identifiers that you control (your addresses, the connection ID), then you accept that you can't handle migration at all and connections will drop (well, unless you do something like trial decryption, which has some obvious scaling issues). The outcome of that discussion was captured in . Assuming that Ryan finds this answer satisfactory (and I haven't missed anything), then I suggest we just close this as a duplicate of .
I don't think this captures Ryan's concern: when the server is using a non-zero CID and also is behind 4-tuple routing. I think he's looking for a section of text to coherently describe what you have to do here: send either disablemigration or preferredaddress; or forward packets between servers; or either don't use a common stateless_reset key, or put client address/port in the reset token. This is sort of ops-drafty but there real requirements on servers to behave in a secure way. I think someone with full command of the transport draft would figure this out, but it is not clearly stated anywhere.
I agree with Martin Duke and the language he suggests makes sense to me.
This does feel a bit ops-drafty to me, but I'd be happy to review a PR.
NAME notes that: We need to normatively reference 7540 for this capability.
See also discussion thread starting at URL Hi, RFC7540 allows crumbling cookies for compression efficiency with HPACK. However, neither quic-http nor quic-qpack drafts mention cookies or crumbling. Is it considered safe for a QPACK implementation to crumble cookies before compression and concatenate them after decompression? If so, should the draft include clarification on this? Thanks, Bence

compression-induced head-of-line blocking.  See that document for additional details. <ins> To allow for better compression efficiency, the cookie header field RFC6265 MAY be split into separate header fields, each with one or more cookie-pairs, before compression.  If a decompressed header list contains multiple cookie header fields, these MUST be concatenated before being passed into a non-HTTP/2, non-HTTP/3 context, as described in HTTP2, Section 8.1.2.5. </ins> An HTTP/3 implementation MAY impose a limit on the maximum size of the message header it will accept on an individual HTTP message.  A server that receives a larger header field list than it is willing to


Question:
This just writes down what was proposed in .
Looks fine, but what about some guidance on stream vs connection errors? And also the principle of nearly always choosing a hard protocol violation error, or a more granular error with same effect?
Stream vs. connection is something that application protocols (like HTTP) need to worry about. The transport only concerns itself with connection errors. The idea that you pick the most helpful error is not something that we need to codify (in my view).
Isn't that worth mentioning? Or do we already have such text?
This is already discussed in Section 11, which makes me wonder if it would make more sense for text on this PR to be placed there too.
This I do not disagree with. I was merely suggesting that we explain why, as a general rule, a hard error is preferred as a design principle, not the nature of the error message which I do think should have a large degree of freedom.
Maybe there is a followup needed for the HTTP draft. If the principles are generally the same, we can copy the text and then add some more to cover the idea that we make most errors fatal to the connection rather than the stream.
I like Lucas suggestion of adding this text to the existing and already fairly complete text in section 11.
I moved the second paragraph up to the error handling section, but the text about how codes are selected really belongs with the codes themselves.
We don't really have any real principles that we agree on for deciding what error codes we are describing. Proposal: if the error carries distinct semantics (like stream rejection in HTTP), then it gets a new error code if the error is frequent or particularly significant, then it gets a new error code otherwise, target a more generic error code that identifies the broad area of the protocol finally, if there is no more specific applicable error code, use PROTOCOL_VIOLATION
is example of an issue discussed where it would have been useful to have principles.

The most appropriate error code (error-codes) SHOULD be included in the frame that signals the error.  Where this specification identifies error conditions, it also identifies the error code that <del> is used. </del> <ins> is used; though these are worded as requirements, different implementation strategies might lead to different errors being reported.  In particular, an endpoint MAY use any applicable error code when it detects an error condition; a generic error code (such as PROTOCOL_VIOLATION or INTERNAL_ERROR) can always be used in place of specific error codes. </ins> A stateless reset (stateless-reset) is not suitable for any error that can be signaled with a CONNECTION_CLOSE or RESET_STREAM frame.


Question:
This just writes down what was proposed in .
Looks fine, but what about some guidance on stream vs connection errors? And also the principle of nearly always choosing a hard protocol violation error, or a more granular error with same effect?
Stream vs. connection is something that application protocols (like HTTP) need to worry about. The transport only concerns itself with connection errors. The idea that you pick the most helpful error is not something that we need to codify (in my view).
Isn't that worth mentioning? Or do we already have such text?
This is already discussed in Section 11, which makes me wonder if it would make more sense for text on this PR to be placed there too.
This I do not disagree with. I was merely suggesting that we explain why, as a general rule, a hard error is preferred as a design principle, not the nature of the error message which I do think should have a large degree of freedom.
Maybe there is a followup needed for the HTTP draft. If the principles are generally the same, we can copy the text and then add some more to cover the idea that we make most errors fatal to the connection rather than the stream.
I like Lucas suggestion of adding this text to the existing and already fairly complete text in section 11.
I moved the second paragraph up to the error handling section, but the text about how codes are selected really belongs with the codes themselves.
We don't really have any real principles that we agree on for deciding what error codes we are describing. Proposal: if the error carries distinct semantics (like stream rejection in HTTP), then it gets a new error code if the error is frequent or particularly significant, then it gets a new error code otherwise, target a more generic error code that identifies the broad area of the protocol finally, if there is no more specific applicable error code, use PROTOCOL_VIOLATION
is example of an issue discussed where it would have been useful to have principles.

See iana-error-codes for details of registering new error codes. <ins> In defining these error codes, several principles are applied.  Error conditions that might require specific action on the part of a recipient are given unique codes.  Errors that represent common conditions are given specific codes.  Absent either of these conditions, error codes are used to identify a general function of the stack, like flow control or transport parameter handling. Finally, generic errors are provided for conditions where implementations are unable or unwilling to use more specific codes. </ins> 20.1. Application protocol error codes are 62-bit unsigned integers, but


Question:
As discussed, this is a) hard to do in all cases, b) impossible to do in many, and c) not that useful.
good
When multiple connections share the same network path or multiple connection IDs are used for the same path, using the spin bit is complicated by the difficulty in correlating forward and reverse flows. There is currently text that uses RFC 6919 language in the draft recommending coordination of the spin to avoid this problem. However, as , this coordination seems difficult and therefore unlikely. It also creates a strong signal that indicates that all the connections on the path are terminated at the same endpoint on both ends. Maybe we don't want to be generating that signal. Also, because this coordination can't be guaranteed, the on-path observer is forced to build code for correlating/segregating measurements by connection ID in the case where there is no coordination. Maybe it's best just to remove this recommendation entirely.
+1 to removing the recommendation, or changing the statement to just a caution (e.g., if you are to coalesce connections that spin, then you'd better be aware of ...). FWIW I am not sure if I agree with this observation. IIUC, the connections sharing a 5-tuple will be terminated by the same endpoint on both ends; it is impossible for a middlebox to coalesce multiple QUIC connections. It is impossible because CIDs can change mid-connection, and a middlebox cannot determine to where the packets containing the new CID must be sent.
There definitely needs to be a degree of coordination between the middlebox and the endpoints, but that doesn't mean that you can't use something like the quic-lb stuff on either end (or both ends).
Yes, I think it is better to remove that recommendation, but perhaps replace it with a note to observers.

process performed at the beginning of the connection SHOULD be applied for all paths used by the connection. <del> In case multiple connections share the same network path, as determined by having the same source and destination IP address and UDP ports, endpoints should try to co-ordinate across all connections to ensure a clear signal to any on-path measurement points. </del> When the spin bit is disabled, endpoints MAY set the spin bit to any value, and MUST ignore any incoming value.  It is RECOMMENDED that endpoints set the spin bit to a random value either chosen


Question:
Use more uniform and more specific language in description of instructions, like "follows" instead of "includes" or implicit mention of a field. Add references to integer and string encodings where missing. Prefer internal references to external ones (also in Section 1.2). Explicitly allow a header block to not have any instructions after the mandatory prefix. Refer to Literal Header Field With Name Reference section in Literal Header Field Without Name Reference instead of verbatim repeating text on 'N' bit. Reshuffle sentences, insert references instead, remove sentences where references suffice.
Mostly good improvements; a few things to fix up further.

Indicates that x is A bits long Indicates that x uses the prefixed integer encoding defined in <del> Section 5.1 of RFC7541, beginning with an A-bit prefix. </del> <ins> prefixed-integers, beginning with an A-bit prefix. </ins> Indicates that x is variable-length and extends to the end of the region.


Question:
Use more uniform and more specific language in description of instructions, like "follows" instead of "includes" or implicit mention of a field. Add references to integer and string encodings where missing. Prefer internal references to external ones (also in Section 1.2). Explicitly allow a header block to not have any instructions after the mandatory prefix. Refer to Literal Header Field With Name Reference section in Literal Header Field Without Name Reference instead of verbatim repeating text on 'N' bit. Reshuffle sentences, insert references instead, remove sentences where references suffice.
Mostly good improvements; a few things to fix up further.

An encoder informs the decoder of a change to the dynamic table capacity using an instruction which begins with the '001' three-bit <del> pattern.  The new dynamic table capacity is represented as an integer with a 5-bit prefix (see Section 5.1 of RFC7541). </del> <ins> pattern.  This is followed by the new dynamic table capacity represented as an integer with a 5-bit prefix (see prefixed- integers). </ins> The new capacity MUST be lower than or equal to the limit described in maximum-dynamic-table-capacity.  In HTTP/3, this limit is the


Question:
Use more uniform and more specific language in description of instructions, like "follows" instead of "includes" or implicit mention of a field. Add references to integer and string encodings where missing. Prefer internal references to external ones (also in Section 1.2). Explicitly allow a header block to not have any instructions after the mandatory prefix. Refer to Literal Header Field With Name Reference section in Literal Header Field Without Name Reference instead of verbatim repeating text on 'N' bit. Reshuffle sentences, insert references instead, remove sentences where references suffice.
Mostly good improvements; a few things to fix up further.

or the dynamic table using an instruction that starts with the '1' one-bit pattern.  The second ("S") bit indicates whether the reference is to the static or dynamic table.  The 6-bit prefix <del> integer (see Section 5.1 of RFC7541) that follows is used to locate the 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 entry in the dynamic table. </del> <ins> integer (see prefixed-integers) that follows is used to locate the 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 entry in the dynamic table. </ins> The header name reference is followed by the header field value <del> represented as a string literal (see Section 5.2 of RFC7541). </del> <ins> represented as a string literal (see string-literals). </ins> 4.3.3. An encoder adds an entry to the dynamic table where both the header field name and the header field value are represented as string <del> literals (see primitives) using an instruction that starts with the '01' two-bit pattern. </del> <ins> literals using an instruction that starts with the '01' two-bit pattern. </ins> <del> The name is represented as a 6-bit prefix string literal, while the value is represented as an 8-bit prefix string literal. </del> <ins> This is followed by the name represented as a 6-bit prefix string literal, and the value represented as an 8-bit prefix string literal (see string-literals). </ins> 4.3.4. An encoder duplicates an existing entry in the dynamic table using an <del> instruction that starts with the '000' three-bit pattern.  The relative index of the existing entry is represented as an integer with a 5-bit prefix. </del> <ins> instruction that begins with the '000' three-bit pattern.  This is followed by the relative index of the existing entry represented as an integer with a 5-bit prefix (see prefixed-integers. </ins> The existing entry is re-inserted into the dynamic table without resending either the name or the value.  This is useful to mitigate


Question:
Use more uniform and more specific language in description of instructions, like "follows" instead of "includes" or implicit mention of a field. Add references to integer and string encodings where missing. Prefer internal references to external ones (also in Section 1.2). Explicitly allow a header block to not have any instructions after the mandatory prefix. Refer to Literal Header Field With Name Reference section in Literal Header Field Without Name Reference instead of verbatim repeating text on 'N' bit. Reshuffle sentences, insert references instead, remove sentences where references suffice.
Mostly good improvements; a few things to fix up further.

After processing a header block whose declared Required Insert Count is not zero, the decoder emits a Header Acknowledgement instruction. <del> The instruction begins with the '1' one-bit pattern and includes the header block's associated stream ID, encoded as a 7-bit prefix integer.  It is used by the peer's encoder to know when it is safe to evict an entry (blocked-insertion), and possibly update the Known Received Count (known-received-count). </del> <ins> The instruction begins with the '1' one-bit pattern which is followed by the header block's associated stream ID encoded as a 7-bit prefix integer (see prefixed-integers). This instruction is used as described in known-received-count and in state-synchronization. </ins> If an encoder receives a Header Acknowledgement instruction referring to a stream on which every header block with a non-zero Required


Question:
Use more uniform and more specific language in description of instructions, like "follows" instead of "includes" or implicit mention of a field. Add references to integer and string encodings where missing. Prefer internal references to external ones (also in Section 1.2). Explicitly allow a header block to not have any instructions after the mandatory prefix. Refer to Literal Header Field With Name Reference section in Literal Header Field Without Name Reference instead of verbatim repeating text on 'N' bit. Reshuffle sentences, insert references instead, remove sentences where references suffice.
Mostly good improvements; a few things to fix up further.

When a stream is reset or reading is abandoned, the decoder emits a Stream Cancellation instruction.  The instruction begins with the <del> '01' two-bit pattern.  The instruction includes the stream ID of the affected stream encoded as a 6-bit prefix integer.  See state- synchronization. </del> <ins> '01' two-bit pattern, which is followed by the stream ID of the affected stream encoded as a 6-bit prefix integer. This instruction is used as described in state-synchronization. </ins> 4.4.3. The Insert Count Increment instruction begins with the '00' two-bit <del> pattern.  The instruction specifies the total number of dynamic table inserts and duplications since the last Insert Count Increment or Header Acknowledgement that increased the Known Received Count for the dynamic table (see known-received-count).  The Increment field is encoded as a 6-bit prefix integer.  The encoder uses this value to determine which table entries might cause a stream to become blocked, as described in state-synchronization. </del> <ins> pattern, followed by the Increment encoded as a 6-bit prefix integer. The value of the Increment is the total number of dynamic table insertions and duplications processed by the decoder since the last time it sent a Header Acknowledgement instruction that increased the Known Received Count (see known-received-count) or an Insert Count Increment instruction.  The encoder uses this value to update the Known Received Count, as described in state-synchronization. </ins> An encoder that receives an Increment field equal to zero or one that increases the Known Received Count beyond what the encoder has sent


Question:
Use more uniform and more specific language in description of instructions, like "follows" instead of "includes" or implicit mention of a field. Add references to integer and string encodings where missing. Prefer internal references to external ones (also in Section 1.2). Explicitly allow a header block to not have any instructions after the mandatory prefix. Refer to Literal Header Field With Name Reference section in Literal Header Field Without Name Reference instead of verbatim repeating text on 'N' bit. Reshuffle sentences, insert references instead, remove sentences where references suffice.
Mostly good improvements; a few things to fix up further.

4.5. <del> Header blocks contain compressed representations of header lists and are carried in frames on streams defined by the enclosing protocol. These representations reference the static table, or dynamic table in a particular state without modifying it. </del> <ins> A header block consists of a prefix and a possibly empty sequence of representations defined in this section.  Each representation corresponds to a single header field.  These representations reference the static table or the dynamic table in a particular state, but do not modify that state. Header blocks are carried in frames on streams defined by the enclosing protocol. </ins> 4.5.1.


Question:
Use more uniform and more specific language in description of instructions, like "follows" instead of "includes" or implicit mention of a field. Add references to integer and string encodings where missing. Prefer internal references to external ones (also in Section 1.2). Explicitly allow a header block to not have any instructions after the mandatory prefix. Refer to Literal Header Field With Name Reference section in Literal Header Field Without Name Reference instead of verbatim repeating text on 'N' bit. Reshuffle sentences, insert references instead, remove sentences where references suffice.
Mostly good improvements; a few things to fix up further.

absolute index less than the Base, this representation starts with the '1' 1-bit pattern, followed by the "S" bit indicating whether the reference is into the static or dynamic table.  The 6-bit prefix <del> integer (see Section 5.1 of RFC7541) that follows is used to locate the table entry for the header name.  When S=1, the number represents </del> <ins> integer (see prefixed-integers) that follows is used to locate the table entry for the header field.  When S=1, the number represents </ins> the static table index; when S=0, the number is the relative index of the entry in the dynamic table.


Question:
Use more uniform and more specific language in description of instructions, like "follows" instead of "includes" or implicit mention of a field. Add references to integer and string encodings where missing. Prefer internal references to external ones (also in Section 1.2). Explicitly allow a header block to not have any instructions after the mandatory prefix. Refer to Literal Header Field With Name Reference section in Literal Header Field Without Name Reference instead of verbatim repeating text on 'N' bit. Reshuffle sentences, insert references instead, remove sentences where references suffice.
Mostly good improvements; a few things to fix up further.

than or equal to the Base, the representation starts with the '0001' 4-bit pattern, followed by the post-base index (see post-base) of the matching header field, represented as an integer with a 4-bit prefix <del> (see Section 5.1 of RFC7541). </del> <ins> (see prefixed-integers). </ins> 4.5.4.


Question:
Use more uniform and more specific language in description of instructions, like "follows" instead of "includes" or implicit mention of a field. Add references to integer and string encodings where missing. Prefer internal references to external ones (also in Section 1.2). Explicitly allow a header block to not have any instructions after the mandatory prefix. Refer to Literal Header Field With Name Reference section in Literal Header Field Without Name Reference instead of verbatim repeating text on 'N' bit. Reshuffle sentences, insert references instead, remove sentences where references suffice.
Mostly good improvements; a few things to fix up further.

absolute index less than the Base, this representation starts with the '01' two-bit pattern. <del> Only the header field name stored in the static or dynamic table is used.  Any header field value MUST be ignored. </del> The following bit, 'N', indicates whether an intermediary is permitted to add this header to the dynamic header table on subsequent hops.  When the 'N' bit is set, the encoded header MUST


Question:
Use more uniform and more specific language in description of instructions, like "follows" instead of "includes" or implicit mention of a field. Add references to integer and string encodings where missing. Prefer internal references to external ones (also in Section 1.2). Explicitly allow a header block to not have any instructions after the mandatory prefix. Refer to Literal Header Field With Name Reference section in Literal Header Field Without Name Reference instead of verbatim repeating text on 'N' bit. Reshuffle sentences, insert references instead, remove sentences where references suffice.
Mostly good improvements; a few things to fix up further.

header field with the 'N' bit set, it MUST use a literal representation to forward this header field.  This bit is intended for protecting header field values that are not to be put at risk by <del> compressing them (see Section 7.1 of RFC7541 for more details). </del> <ins> compressing them (see security-considerations for more details). </ins> The fourth ("S") bit indicates whether the reference is to the static <del> or dynamic table.  The 4-bit prefix integer (see Section 5.1 of RFC7541) that follows is used to locate the 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 entry in the dynamic table. </del> <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. When S=1, the number represents the static table index; when S=0, the 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 header field value is encoded as an 8-bit prefix string literal (see string-literals). </ins> 4.5.5. <del> If the name entry is in the dynamic table with an absolute index greater than or equal to the Base, the representation starts with the '0000' four-bit pattern.  The fifth bit is the 'N' bit as described in literal-name-reference.  Finally, the header field name is represented using the post-base index of that entry (see post-base) encoded as an integer with a 3-bit prefix. </del> <ins> A literal header field with post-base name reference represents a header field where the name matches the header field name of a dynamic table entry with an absolute index greater than or equal to the Base. This representation starts with the '0000' four-bit pattern.  The fifth bit is the 'N' bit as described in literal-name-reference. This is followed by a post-base index of the dynamic table entry (see post-base) encoded as an integer with a 3-bit prefix (see prefixed- integers). Only the header field name is taken from the dynamic table entry; the header field value is encoded as an 8-bit prefix string literal (see string-literals). </ins> 4.5.6. <del> An addition to the header table where both the header field name and the header field value are represented as string literals (see primitives) starts with the '001' three-bit pattern. The fourth bit, 'N', indicates whether an intermediary is permitted to add this header to the dynamic header table on subsequent hops. When the 'N' bit is set, the encoded header MUST always be encoded with a literal representation.  In particular, when a peer sends a header field that it received represented as a literal header field with the 'N' bit set, it MUST use a literal representation to forward this header field.  This bit is intended for protecting header field values that are not to be put at risk by compressing them (see Section 7.1 of RFC7541 for more details). The name is represented as a 4-bit prefix string literal, while the value is represented as an 8-bit prefix string literal. </del> <ins> The literal header field without name reference representation encodes a header field name and header field value as string literals. This representation begins with the '001' three-bit pattern.  The fourth bit is the 'N' bit as described in literal-name-reference. The name follows, represented as a 4-bit prefix string literal, then the value, represented as an 8-bit prefix string literal (see string- literals). </ins> 5.


Question:
Is that not implied by the text here?
Kind of, but this text talks about the sender's perspective. Furthermore, checking for duplicates is inherently racy if you're using tokens for new connections while the old connection is still alive.
You know, I think that I put some proposed fixes for this in c016639e39c with rather than this branch. Moved it over here. NAME you approved , so I hope that moving that commit over here works for you.
Sec 13.2 of QUIC Transport explains what to do for the loss of a packet for every stream type except NEW_TOKEN. IMO it's nice but not critical to repair this loss, but I care less about how we resolve it than that we write it up.
Somewhat related question: Does this section fit better in the recovery draft?
I don't think so. This talks about the repair semantics of these frames, not the process of detecting loss. Recovery doesn't care about the distinction between PING and STREAM when it comes to repair.
LGTM, but do we need some text saying that when receiving a NEW_TOKEN frame, you MUST make sure that it's not a duplicate? Otherwise, a client might just save the token twice, and effectively reuse the token.

RETIRE_CONNECTION_ID frames and retransmitted if the packet containing them is lost. <ins> NEW_TOKEN frames are retransmitted if the packet containing them is lost.  No special support is made for detecting reordered and duplicated NEW_TOKEN frames other than a direct comparison of the frame contents. </ins> PING and PADDING frames contain no information, so lost PING or PADDING frames do not require repair.


Question:
Is that not implied by the text here?
Kind of, but this text talks about the sender's perspective. Furthermore, checking for duplicates is inherently racy if you're using tokens for new connections while the old connection is still alive.
You know, I think that I put some proposed fixes for this in c016639e39c with rather than this branch. Moved it over here. NAME you approved , so I hope that moving that commit over here works for you.
Sec 13.2 of QUIC Transport explains what to do for the loss of a packet for every stream type except NEW_TOKEN. IMO it's nice but not critical to repair this loss, but I care less about how we resolve it than that we write it up.
Somewhat related question: Does this section fit better in the recovery draft?
I don't think so. This talks about the repair semantics of these frames, not the process of detecting loss. Recovery doesn't care about the distinction between PING and STREAM when it comes to repair.
LGTM, but do we need some text saying that when receiving a NEW_TOKEN frame, you MUST make sure that it's not a duplicate? Otherwise, a client might just save the token twice, and effectively reuse the token.

An opaque blob that the client may use with a future Initial packet. <ins> An endpoint might receive multiple NEW_TOKEN frames that contain the same token value.  Endpoints are responsible for discarding duplicate values, which might be used to link connection attempts; see validate-future. </ins> 19.8. STREAM frames implicitly create a stream and carry stream data.  The


Question:
The initial request was simply to add sub-headings. I still feel like Security Considerations is a bit of a hodge-podge, and would welcome concrete suggestions or PRs for tightening it up.
Can you add a sub-heading(##) for each of these considerations, like in the transport draft? Originally posted by NAME in URL

10. The security considerations of HTTP/3 should be comparable to those <del> of HTTP/2 with TLS.  Note that where HTTP/2 employs PADDING frames and Padding fields in other frames to make a connection more resistant to traffic analysis, HTTP/3 can either rely on transport- layer padding or employ the reserved frame and stream types discussed in frame-grease and stream-grease. </del> <ins> of HTTP/2 with TLS; the considerations from Section 10 of HTTP2 apply in addition to those listed here. </ins> When HTTP Alternative Services is used for discovery for HTTP/3 endpoints, the security considerations of ALTSVC also apply. <ins> 10.1. Where HTTP/2 employs PADDING frames and Padding fields in other frames to make a connection more resistant to traffic analysis, HTTP/3 can either rely on transport-layer padding or employ the reserved frame and stream types discussed in frame-grease and stream- grease.  These methods of padding produce different results in terms of the granularity of padding, the effect of packet loss and recovery, and how an implementation might control padding. 10.2. </ins> Several protocol elements contain nested length elements, typically in the form of frames with an explicit length containing variable- length integers.  This could pose a security risk to an incautious implementer.  An implementation MUST ensure that the length of a frame exactly matches the length of the fields it contains. <ins> 10.3. </ins> The use of 0-RTT with HTTP/3 creates an exposure to replay attack. The anti-replay mitigations in HTTP-REPLAY MUST be applied when using HTTP/3 with 0-RTT. <ins> 10.4. </ins> Certain HTTP implementations use the client address for logging or access-control purposes.  Since a QUIC client's address might change during a connection (and future versions might support simultaneous


Question:
I assume the intention was to forbid the same setting identifier occurring multiple times in a SETTINGS frame, regardless of the values. However, since a parameter is defined as an identifier-value pair, the current text does not forbid duplicate identifiers if the values are different.
An alternative below. But this is good either way.Looks fine to me, modulo the line length.

peer.  For example, a client might be willing to consume a very large response header, while servers are more cautious about request size. <del> Parameters MUST NOT occur more than once in the SETTINGS frame.  A receiver MAY treat the presence of the same parameter more than once as a connection error of type HTTP_SETTINGS_ERROR. </del> <ins> The same setting identifier MUST NOT occur more than once in the SETTINGS frame.  A receiver MAY treat the presence of duplicate setting identifiers 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


Question:
The nonce can just be opaque bytes. The block counter is tricky, as noted in the issue. The "obvious" choice is little-endian, as that is consistent with the philosophy of the designer (as I understand it), but that is not anything more than a guess. Absent strong evidence that big-endian is a better choice, I'm going to err on the side of not making a substantive change here. But I think that we need a consensus call to support that viewpoint.
Here's what 5.4.4 says However, RFC 8439 says: This is a little confusing, but I think the correct way to read this is that the ChaCha function takes the nonce as a 96-bit opaque quantity, and the fact that it's little-endian is an internal detail irrelevant to our purposes, so we should just replace the second sentence with "The remaining 12 bytes are used as the 96-bit ChaCha nonce". This is reinforced by the fact that the test vectors have the nonce as a byte block. The situation with the counter is more complicated because we are told it is both a counter and 32-bits (which might make you think it's an integer parameter), but it is also treated as a little-endian integer, which makes one think it's a bitstring. And then the test vectors treat it as a value, rather than as a bitstring. If you look at code it typically thinks of this as an integer: See, for instance: URL So, I think the right answer here is to think of ChaCha as having the following API: Note that because the value on the wire is in big-endian format, this would invert the nonce, but I think it's the right answer anyway. Otherwise, you're having to pass in the counter in the wrong format on big-endian platforms.
As a completely general note, I don't think it is right to change endianness of crypto wire formats. AES-GCM has a completely backwards neither little nor big endian encoding (byte and bit swapped so no platform wins) this remains so on the wire. Especially for HW processing this is significant. But little endian is also more performant on nearly all relevant platforms today so there is no need to go over board forcing big endian if it can be avoided without insulting RFC's.
Yeah, this should simplify that text some.
I like the proposed change, the text had somewhat confused me when implementing this in a previous life.
I don't think that the reinterpretation of the little-endian thing is safe, as per NAME comment. The takes a byte buffer as input rather than a . That is to allow for different block counter sizes. I'll make the editorial change, but leave the suggestion there alone.
I have a question out to the editor of the PKCS spec. It appears like there are no solid rules in the spec to support any particular position here. I'm going with my best guess at WWDJBD here, but I'm happy to make a change if there is a strong case made for something else.
NAME NAME can we flip this to please? I don't think that we need to change the substance here, but I want to confirm the little-endian/big-endian choice here. I hope to have more information to share on the question soon, but that might not be definitive. We might just have to make a call ourselves. Absent better information, I'm going to suggest that we continue with little-endian, but I this is far from an obvious conclusion.
The API I'm using for ChaCha20 takes a const uint8_t nonce[12], and I currently pass in a pointer (offset by 4 bytes) to the sample as the nonce, without doing any endianness conversions. I'm in favor of clarifying the language for this input. (Given that this has the design label instead of the editorial label, my design opinion is to keep with the simple thing of passing the bytes directly from the sample to the nonce input of ChaCha20 and not do any endianness conversions. I'm assuming that other crypto libraries have similar APIs though.)
The API I have takes . PKCS takes pointers just like the API NAME refers to (but it fails to specify the endianness, which is a critical oversight, compounded by the fact that the size of the counter can vary). I suspect that - as NAME says - the right idea is to note that ChaCha20 will interpret the nonce as three little-endian integers and that the counter might be expressed as a byte sequence that is then (probably) interpreted as a little-endian integer.
I understand and appreciate this being marked as “design”, though I consider the text that landed in master as editorial, as it (in my view) ended as adding practical advice while retaining the definition we have had.
Yes, but as a design issue, we want to confirm that "no action" is the correct outcome. It's cheap enough to do so.
I like the updated text. It reads good as it gives the practical advice first, then amends that with the formal definition.

ChaCha20 function as defined in Section 2.4 of CHACHA.  This uses a 256-bit key and 16 bytes sampled from the packet protection output. <del> The first 4 bytes of the sampled ciphertext are interpreted as a 32-bit number in little-endian order and are used as the block count. The remaining 12 bytes are interpreted as three concatenated 32-bit numbers in little-endian order and used as the nonce. </del> <ins> The first 4 bytes of the sampled ciphertext are the block counter.  A ChaCha20 implementation could take a 32-bit integer in place of a byte sequence, in which case the byte sequence is interpreted as a little-endian value. The remaining 12 bytes are used as the nonce.  A ChaCha20 implementation might take an array of three 32-bit integers in place of a byte sequence, in which case the nonce bytes are interpreted as a sequence of 32-bit little-endian integers. </ins> The encryption mask is produced by invoking ChaCha20 to protect 5 zero bytes.  In pseudocode:


Question:
Invariants text was unclear on where the Destination Connection ID field was and how big it could be.
Section 4.2 of the Invariant Short header are not explicit that the Destionation CID follows the first byte: "A QUIC packet with a short header includes a Destination Connection ID. The short header does not include the Connection ID Lengths, Source Connection ID, or Version fields." As see it just says that the destination CID is part of the short header, not where to find it.
Also the length of the DCID in the short header is unclear. With the long header's restriction on both source and destination CID I don't think the CID is of arbitrary length as stated in Section 4.3. I think it is 0 to 18 bytes long as set agreed by the connection.
Yes, DCID follows the first octet, and we should say that. As for the length, 0/4-18 is a restriction that we agreed applies only to long headers. You can - if the QUIC version allows it - negotiate other lengths for connection IDs using other methods.
Ok, the spec is in some sense clear that this can be of arbitary length. However, that this is not required to be consistent with the long header is far from obvious and probably should be explicitly remarked. An realization I didn't have before over this design is that a given destination IP address and UDP port that deals with multiple QUIC connections will be foreced to use one commond DCID length across all QUIC connections using that port, otherwise it will have to use prefixes for DCIDs to be able to determine how long the actual DCID field are to enable decoding the header protection. It might have been a misstake to not include an DCID length field.
The endpoint can decide on its own DCID length encoding, so that is fine.
One editorial suggestion, but LG

set to 0. A QUIC packet with a short header includes a Destination Connection <del> ID.  The short header does not include the Connection ID Lengths, Source Connection ID, or Version fields. </del> <ins> ID immediately following the first byte.  The short header does not include the Connection ID Lengths, Source Connection ID, or Version fields.  The length of the Destination Connection ID is not specified in packets with a short header and is not constrained by this specification. </ins> The remainder of the packet has version-specific semantics.


Question:
This changes HTTPUNEXPECTEDFRAME, which is used only once, to HTTPFRAMEUNEXPECTED that is used multiple times.
During the London interim, it was noted that error codes refer to things being "malformed," "bad," "invalid," and "unexpected." Pick one and use it consistently, or establish precise definitions for each. This applies to both the current document and .
The three words are not equivalent: while both "malformed" and "invalid" are "bad," "malformed" is not the same as "invalid."
Minor correction here, "bad" is only used in . The only instance of "bad" in current document is a substring of example "0x1abadaba". I'll fix the PR before it lands, whatever order we end up doing things in.
The different value-judgement terms are corrected by the flurry of settings PRs that NAME already did. HTTPFRAMEERROR has replaced malformed, and UNEXPECTEDFRAME and WRONGSTREAM are reasonably descriptive (though see , ). We do still have some variation between HTTPTHINGDESCRIPTOR, HTTPTHINGERROR, and HTTPDESCRIPTORTHING (e.g. HTTPUNEXPECTEDFRAME versus HTTPREQUESTREJECTED) that might be worth reconciling. Is it worth turning into HTTPFRAMEUNEXPECTED?
Resolved by and something prior to it.
oops, thanks

Frame types which were used in HTTP/2 where there is no corresponding HTTP/3 frame have also been reserved (iana-frames).  These frame types MUST NOT be sent, and receipt MAY be treated as an error of <del> type HTTP_UNEXPECTED_FRAME. </del> <ins> type HTTP_FRAME_UNEXPECTED. </ins> 8.


Question:
Fixing
changed the rules regarding what kind of packet an endpoint is allowed to send after receiving a non-ack-eliciting packet. The intention of that text used to be to prevent peers from getting stuck in an ACK-loop, but now the text says: Among others, this means that once an endpoint receives a non-ack-eliciting packet, it's not allowed to send a packet containing only PADDING. This would be valuable for defeating traffic analysis. While it would be possible to hack your way out of this by sending a PING + PADDING packet, I'm not sure if this was the intention of .
To me, the key phrase here is 'in response to'. If you want to send PADDING for some other reason and the congestion controller allows it, then you can send it.
This feels editorial...
I don't intend to do anything about this one. As Ian notes, you can use other reasons to justify sending of anything, just don't justify the sending on the basis of having received a non-ack-eliciting packet or you will get into the sort trouble that doesn't end.
I think the text is clear that sending PADDING is allowed at any time. I'm closing this with no action.
quic-transport draft 13.2.1: An endpoint MUST NOT send a packet containing only an ACK frame in response to a non-ACK-eliciting packet (one containing only ACK and/or PADDING frames), NAME points out that this allows one to send an ACK+PADDING packet, which seems to violate the spirit of the rule. This loophole breaks . I believe this may be Editorial if we all know what we meant.
Yes, it was intended that ACK should not elicit an ACK+PADDING packet.
I agree with this change, but I disagree that this is a satisfactory fix for .
This issue should have been
LGTM. Though all the "non-ACK-eliciting packets" is a bit of a mouth full.

Packets containing only ACK frames are not congestion controlled, so there are limits on how frequently they can be sent.  An endpoint MUST NOT send more than one ACK-frame-only packet in response to <del> receiving an ack-eliciting packet (one containing frames other than ACK and/or PADDING).  An endpoint MUST NOT send a packet containing only an ACK frame in response to a non-ack-eliciting packet (one containing only ACK and/or PADDING frames), even if there are packet gaps which precede the received packet.  Limiting ACK frames avoids an infinite feedback loop of acknowledgements, which could prevent the connection from ever becoming idle.  However, the endpoint acknowledges non-ack-eliciting packets when it sends an ACK frame. </del> <ins> receiving an ack-eliciting packet.  An endpoint MUST NOT send a non- ack-eliciting packet in response to a non-ack-eliciting packet, even if there are packet gaps which precede the received packet.  Limiting ACK frames avoids an infinite feedback loop of acknowledgements, which could prevent the connection from ever becoming idle.  However, the endpoint acknowledges non-ACK-eliciting packets when it sends an ACK frame. </ins> An endpoint SHOULD treat receipt of an acknowledgment for a packet it did not send as a connection error of type PROTOCOL_VIOLATION, if it


Question:
Came from the discussion of
NAME : I realized after we did this PR yesterday that this does not I It reduces burstiness in general, which is still a good thing, but it doesn't change slow start increase. I'll send a PR or ask Vidhi if she wants to.
When an ACK arrives that acknowledges N packets (where N could be very large), QUIC will increase the congestion window by more than 1MSS per congestion window during Congestion Avoidance causing bursty send. This could also affect the slow start. if (congestionwindow < ssthresh): // Slow start. congestionwindow += URL else: // Congestion avoidance. congestionwindow += kMaxDatagramSize * URL / congestionwindow One could argue that QUIC ACKs every other packet, but there ACK packet could get lost or ACK for ACK packet could get lost. The draft is probably assuming packet pacing but not all implementations support that. I discussed this with Jana and he thinks that we should add something in pseudo code or some specific text in the draft.
PR added more specific text on avoiding bursts into the network, FYI.
Please note that PR addresses the issue of app-limited or pacing-limited cases to not increase cwnd when it is under utilized. My concern is cwnd being inflated due to an ACK frame containing ACKs for large number of packets.
NAME : The problem here is that we don't talk about burstiness that can occur when pacing is not in place, and a stretch-ack is received. PRR handles this, but we don't do PRR in the draft.
No we need to spend lots of time on Reno as everybody appears to be doing Cubic or BBR?
Cubic has this problem. BBR cannot be assumed.
Except Cubic opens the window based on time spent, not just ack received
I don't get the scenario. It seems that NAME is worried about receiving an ACK for a large number of packets. This is not a congestion window problem, because it happens even if the congestion windows size remains constant. The root cause is that the ACK of N packets now reduces the number of packet in flow by N. So this is strictly about pacing, not about congestion control.
NAME yes, and it can and still does burst because of stretch acks
Not just stretch Acks -- deliberate Ack compression by the network happens too. Very few alternative to pacing.
URL describes
Discussed in Cupertino. NAME to prepare a PR that hopefully can incorporate some ideas from RFC3465 to add safety features for non-pacing stacks.
But 3465 does not solve the burst problem. It limits the growth of the window, but does not do pacing of any kind.
I think we can change to something like this
NAME I think we need to do 2MSS clamping per ACK received and not per packet acked. I am thinking that we can remove OnPacketAckedCC and instead do OnAckReceivedCC. Pseudocode change something like this:
I think we can keep but just move cwnd update to as you propose.
Yup, that's an important missing fix, though it needs to be applied only when not pacing. Additionally, helps with burstiness in general. NAME : I would make a variable, and apply the min here when not pacing.
The proposed change has the effect of reducing the effective Window if ACKs are compressed. Say you have a window of 12 packets, acks are delayed, and a single ACK acknowledges 12 packets. Now you have a window of 2 packets, your transmission rate is 6 times slower than before, and it will take you 10 RTT to recover. Is that really what you want?
NAME This is what ABC (RFC 3465) does for TCP. ACK compression has side-effects, and the way to deal with it is, as you say, to pace packets out. This reduction in performance is only when the endpoint does not pace, and it is the only way to limit bursts in that case.
Also, ACK compression does not necessarily imply ACK pruning. I have traces in which 1 RTT worth of ACKs is compressed and delivered back to back. In that case, the proposed algorithm will do nothing: you will get 2 MSS added for each ACK, back to back within less than 1 ms.
FYI. FreeBSD implements rfc3465 (and enabled by default) URL Linux removed tcpabc implementation but saying it does in a different way: URL Considering FreeBSD doesn't have pacing by default, I think current proposed algorithm is not worse (or aggressive) than what tcp implementation does.
NAME I am working on the PR for pseudocode changes.
I agree that when not pacing, using an ABC limit is appropriate. FWIW Windows TCP was using a limit of 4 and not 2 for a long time and now uses 8 which is pretty close to IW10 burst in slow start phase.
For the WG: The proposed PR attempts to include a mechanism to limit CWND increase during slow start when not pacing. I'll note that by itself, it does not guarantee we follow the existing MUST: "Implementations MUST either use pacing or limit such bursts to the initial congestion window, which is recommended to be the minimum of 10 maxdatagramsize and max(2 maxdatagramsize, 14720)), where maxdatagramsize is the current maximum size of a datagram for the connection, not including UDP or IP overhead." I don't think we should add this to the pseudocode, because we say SHOULD pace packets and the pseudocode currently only covers SHOULDs, not MAYs. Adding all MAYs to the pseudocode is too complex. I don't have a concern about adding a reference to the existing RFC as a mechanism that MAY help limit bursts during slow start. I'd like a decision from the WG on whether to include this change and if so, some criteria on what should be included going forward.
I agree with the comment that burst from ACK compression or Stretch-ACKs result in bursts. I think the ways to change that is implement a maximum burst size or use pacing. The bursts do not appear a CC function to me.
Bringing conversation in from the list: With regards to , one of the things we found in testing was that it could (a) still allow an implementation to be vulnerable to too much bursting (the original problem), but more importantly (b) locked receivers into an ack pattern of acking every other packet. My main concern is that this causes problems if receivers want to experiment with and change acking strategies going forward in way that otherwise would be fine. One way to look at the problem described by this issue is that the existing text has a minor underspecification, in which it has the notion of a burst limit but says nothing about the frequency of such bursts. Two "bursts" of 10MSS that are sent within a couple nanoseconds are really one burst that's too big. Another way of fixing this is to remove even the specification of the 10MSS limit if we really don't want too much text about the non-pacing cases. Essentially, what this comes down to is: pace, or even if you don't pace, you need to not send bursts above some frequency; and if you don't set some pacing timers, you'll likely underutilize the link.
I'm going to put this out there: the answer is MUST pace. You might formalize this by saying the number of packets sent in any interval cannot exceed by more than the initial cwnd (i.e., the 10MSS). That allows for bursting, but requires a period of quiet either preceding or following any burst.
NAME Yes, it does largely reduce to "implementations MUST pace" (even if the pacing algorithm isn't very nuanced, in which case you'll perform poorly). The formalization of the rate, one option for which was specified in , I think is the heart of what should be done here.
There are two routes that we can take: Specify clearly what 'delay` refers to in the below statement, which is done in OR Change MUST pace to SHOULD and remove the 10 MSS burst size limit and provide general recommendation if an implementation doesn't pace. Something like:
I'm sympathetic to saying MUST pace, and I like the formatlization that NAME suggests. I've heard some pushback on saying MUST pace in the past, but it might be worth giving it a shot again. NAME NAME NAME : What do you think?
That said... I will note that NAME 's formalization allows for a burst at the end of an RTT. Consider for example when a single ACK is received that acknowledges an entire window. That would be legal by this definition. I think that's ok.
Discussed in ZRH. Proposed resolution is to define and REQUIRE pacing.
NAME I think requiring pacing makes good sense, with some formalization of what that means.
To clarify the status here, my understanding is that the proposal is to close this design issue with no action. The discussions have identified editorial improvements that will be addressed independently.
Let's continue the editorial discussion on .
Closing this issue and continuing discussion on
This needs to run through the consensus call procedure, so we'll keep it open for another week.
Let's get this in -- it addresses some burstiness anyway.LGTM

frames should therefore not be paced, to avoid delaying their delivery to the peer. <ins> Sending multiple packets into the network without any delay between them creates a packet burst that might cause short-term congestion and losses.  Implementations MUST either use pacing or limit such bursts to minimum of 10 * kMaxDatagramSize and max(2* kMaxDatagramSize, 14720)), the same as the recommended initial congestion window. </ins> As an example of a well-known and publicly available implementation of a flow pacer, implementers are referred to the Fair Queue packet scheduler (fq qdisc) in Linux (3.11 onwards).


Question:
Came from the discussion of
NAME : I realized after we did this PR yesterday that this does not I It reduces burstiness in general, which is still a good thing, but it doesn't change slow start increase. I'll send a PR or ask Vidhi if she wants to.
When an ACK arrives that acknowledges N packets (where N could be very large), QUIC will increase the congestion window by more than 1MSS per congestion window during Congestion Avoidance causing bursty send. This could also affect the slow start. if (congestionwindow < ssthresh): // Slow start. congestionwindow += URL else: // Congestion avoidance. congestionwindow += kMaxDatagramSize * URL / congestionwindow One could argue that QUIC ACKs every other packet, but there ACK packet could get lost or ACK for ACK packet could get lost. The draft is probably assuming packet pacing but not all implementations support that. I discussed this with Jana and he thinks that we should add something in pseudo code or some specific text in the draft.
PR added more specific text on avoiding bursts into the network, FYI.
Please note that PR addresses the issue of app-limited or pacing-limited cases to not increase cwnd when it is under utilized. My concern is cwnd being inflated due to an ACK frame containing ACKs for large number of packets.
NAME : The problem here is that we don't talk about burstiness that can occur when pacing is not in place, and a stretch-ack is received. PRR handles this, but we don't do PRR in the draft.
No we need to spend lots of time on Reno as everybody appears to be doing Cubic or BBR?
Cubic has this problem. BBR cannot be assumed.
Except Cubic opens the window based on time spent, not just ack received
I don't get the scenario. It seems that NAME is worried about receiving an ACK for a large number of packets. This is not a congestion window problem, because it happens even if the congestion windows size remains constant. The root cause is that the ACK of N packets now reduces the number of packet in flow by N. So this is strictly about pacing, not about congestion control.
NAME yes, and it can and still does burst because of stretch acks
Not just stretch Acks -- deliberate Ack compression by the network happens too. Very few alternative to pacing.
URL describes
Discussed in Cupertino. NAME to prepare a PR that hopefully can incorporate some ideas from RFC3465 to add safety features for non-pacing stacks.
But 3465 does not solve the burst problem. It limits the growth of the window, but does not do pacing of any kind.
I think we can change to something like this
NAME I think we need to do 2MSS clamping per ACK received and not per packet acked. I am thinking that we can remove OnPacketAckedCC and instead do OnAckReceivedCC. Pseudocode change something like this:
I think we can keep but just move cwnd update to as you propose.
Yup, that's an important missing fix, though it needs to be applied only when not pacing. Additionally, helps with burstiness in general. NAME : I would make a variable, and apply the min here when not pacing.
The proposed change has the effect of reducing the effective Window if ACKs are compressed. Say you have a window of 12 packets, acks are delayed, and a single ACK acknowledges 12 packets. Now you have a window of 2 packets, your transmission rate is 6 times slower than before, and it will take you 10 RTT to recover. Is that really what you want?
NAME This is what ABC (RFC 3465) does for TCP. ACK compression has side-effects, and the way to deal with it is, as you say, to pace packets out. This reduction in performance is only when the endpoint does not pace, and it is the only way to limit bursts in that case.
Also, ACK compression does not necessarily imply ACK pruning. I have traces in which 1 RTT worth of ACKs is compressed and delivered back to back. In that case, the proposed algorithm will do nothing: you will get 2 MSS added for each ACK, back to back within less than 1 ms.
FYI. FreeBSD implements rfc3465 (and enabled by default) URL Linux removed tcpabc implementation but saying it does in a different way: URL Considering FreeBSD doesn't have pacing by default, I think current proposed algorithm is not worse (or aggressive) than what tcp implementation does.
NAME I am working on the PR for pseudocode changes.
I agree that when not pacing, using an ABC limit is appropriate. FWIW Windows TCP was using a limit of 4 and not 2 for a long time and now uses 8 which is pretty close to IW10 burst in slow start phase.
For the WG: The proposed PR attempts to include a mechanism to limit CWND increase during slow start when not pacing. I'll note that by itself, it does not guarantee we follow the existing MUST: "Implementations MUST either use pacing or limit such bursts to the initial congestion window, which is recommended to be the minimum of 10 maxdatagramsize and max(2 maxdatagramsize, 14720)), where maxdatagramsize is the current maximum size of a datagram for the connection, not including UDP or IP overhead." I don't think we should add this to the pseudocode, because we say SHOULD pace packets and the pseudocode currently only covers SHOULDs, not MAYs. Adding all MAYs to the pseudocode is too complex. I don't have a concern about adding a reference to the existing RFC as a mechanism that MAY help limit bursts during slow start. I'd like a decision from the WG on whether to include this change and if so, some criteria on what should be included going forward.
I agree with the comment that burst from ACK compression or Stretch-ACKs result in bursts. I think the ways to change that is implement a maximum burst size or use pacing. The bursts do not appear a CC function to me.
Bringing conversation in from the list: With regards to , one of the things we found in testing was that it could (a) still allow an implementation to be vulnerable to too much bursting (the original problem), but more importantly (b) locked receivers into an ack pattern of acking every other packet. My main concern is that this causes problems if receivers want to experiment with and change acking strategies going forward in way that otherwise would be fine. One way to look at the problem described by this issue is that the existing text has a minor underspecification, in which it has the notion of a burst limit but says nothing about the frequency of such bursts. Two "bursts" of 10MSS that are sent within a couple nanoseconds are really one burst that's too big. Another way of fixing this is to remove even the specification of the 10MSS limit if we really don't want too much text about the non-pacing cases. Essentially, what this comes down to is: pace, or even if you don't pace, you need to not send bursts above some frequency; and if you don't set some pacing timers, you'll likely underutilize the link.
I'm going to put this out there: the answer is MUST pace. You might formalize this by saying the number of packets sent in any interval cannot exceed by more than the initial cwnd (i.e., the 10MSS). That allows for bursting, but requires a period of quiet either preceding or following any burst.
NAME Yes, it does largely reduce to "implementations MUST pace" (even if the pacing algorithm isn't very nuanced, in which case you'll perform poorly). The formalization of the rate, one option for which was specified in , I think is the heart of what should be done here.
There are two routes that we can take: Specify clearly what 'delay` refers to in the below statement, which is done in OR Change MUST pace to SHOULD and remove the 10 MSS burst size limit and provide general recommendation if an implementation doesn't pace. Something like:
I'm sympathetic to saying MUST pace, and I like the formatlization that NAME suggests. I've heard some pushback on saying MUST pace in the past, but it might be worth giving it a shot again. NAME NAME NAME : What do you think?
That said... I will note that NAME 's formalization allows for a burst at the end of an RTT. Consider for example when a single ACK is received that acknowledges an entire window. That would be legal by this definition. I think that's ok.
Discussed in ZRH. Proposed resolution is to define and REQUIRE pacing.
NAME I think requiring pacing makes good sense, with some formalization of what that means.
To clarify the status here, my understanding is that the proposal is to close this design issue with no action. The discussions have identified editorial improvements that will be addressed independently.
Let's continue the editorial discussion on .
Closing this issue and continuing discussion on
This needs to run through the consensus call procedure, so we'll keep it open for another week.
Let's get this in -- it addresses some burstiness anyway.LGTM

sender should not consider itself application limited if it would have fully utilized the congestion window without pacing delay. <del> Sending multiple packets into the network without any delay between them creates a packet burst that might cause short-term congestion and losses.  Implementations SHOULD either use pacing or reduce their congestion window to limit such bursts to minimum of 10 * kMaxDatagramSize and max(2* kMaxDatagramSize, 14720)), the same as the recommended initial congestion window. </del> A sender MAY implement alternative mechanisms to update its congestion window after periods of under-utilization, such as those proposed for TCP in RFC7661.


Question:
If the Retry token is known to be invalid by the server, then the server can close the connection with INVALID_TOKEN instead of waiting for a timeout. Came out of discussion of Issue Related to
You need to update the "Transport Error Codes" section too.
Thanks NAME I did that in a subsequent update. Refresh and tell me if it looks correct.
NAME you're missing the section that gives some text to each error code .
NAME do you have a suggestion about what the client should do? Clearly it could start a new connection, but it would't want to do that an unlimited number of times, and it may depend upon whether a TCP fallback is available. Given that, it wasn't clear I should recommend something. And I agree making them distinguishable should be a MUST, as stated in
I think that the best thing we can say is "the connection attempt failed". The first version of the PR strongly suggested that making another connection attempt was the right thing to do, but that gets into lots of difficult questions about identifying why. Given that this is an error that only happens if a Retry is spoofed, then I think that all we can say is: !
When the server receives a Token that is corrupted, it may want to close the connection quickly. This issue suggests adding a new error code to indicate the token is corrupted, INVALID_TOKEN.
This is - I believe - a design issue. The PR seems to have approval, we just need the appropriate process run.
We currently say that these tokens SHOULD be distinguishable by a server on the basis that an error in a NEW_TOKEN token is recoverable, but an error in a Retry token is fatal. I don't see why this can't be MUST. If we make it "MUST", then we can rely on this property in the design.
Agreed, that'd be a useful property to rely on.
Does that property need to hold if there are bit flips in the token? If you’re encrypting the token, this would be easy to ensure: put the distinguishing bit under crypto cover. If you’re using a map lookup, you’ll probably have to work a little bit harder.
Please ignore my last post, I mixed up Retry packets and Initials. Tokens in the client’s Initial are part of the AAD, so we don’t have to worry about bit flips.
Depending how the server distinguishes them, a change of method / confusion about what server you're sending it to could make you appear to be presenting a Retry token instead of a regular token. This error code enables the client to recover and retry quickly, but there's no language about what the client should do when it receives the code.Rereading the changes, I have the following editorial suggestion.

be routed to a server instance with more resources available for new connections. <ins> If a server receives a client Initial that can be unprotected but contains an invalid Retry token, it knows the client will not accept another Retry token.  The server can discard such a packet and allow the client to time out to detect handshake failure, but that could impose a significant latency penalty on the client.  A server MAY proceed with the connection without verifying the token, though the server MUST NOT consider the client address validated.  If a server chooses not to proceed with the handshake, it SHOULD immediately close (immediate-close) the connection with an INVALID_TOKEN error. Note that a server has not established any state for the connection at this point and so does not enter the closing period. </ins> A flow showing the use of a Retry packet is shown in fig-retry. 8.1.2.


Question:
If the Retry token is known to be invalid by the server, then the server can close the connection with INVALID_TOKEN instead of waiting for a timeout. Came out of discussion of Issue Related to
You need to update the "Transport Error Codes" section too.
Thanks NAME I did that in a subsequent update. Refresh and tell me if it looks correct.
NAME you're missing the section that gives some text to each error code .
NAME do you have a suggestion about what the client should do? Clearly it could start a new connection, but it would't want to do that an unlimited number of times, and it may depend upon whether a TCP fallback is available. Given that, it wasn't clear I should recommend something. And I agree making them distinguishable should be a MUST, as stated in
I think that the best thing we can say is "the connection attempt failed". The first version of the PR strongly suggested that making another connection attempt was the right thing to do, but that gets into lots of difficult questions about identifying why. Given that this is an error that only happens if a Retry is spoofed, then I think that all we can say is: !
When the server receives a Token that is corrupted, it may want to close the connection quickly. This issue suggests adding a new error code to indicate the token is corrupted, INVALID_TOKEN.
This is - I believe - a design issue. The PR seems to have approval, we just need the appropriate process run.
We currently say that these tokens SHOULD be distinguishable by a server on the basis that an error in a NEW_TOKEN token is recoverable, but an error in a Retry token is fatal. I don't see why this can't be MUST. If we make it "MUST", then we can rely on this property in the design.
Agreed, that'd be a useful property to rely on.
Does that property need to hold if there are bit flips in the token? If you’re encrypting the token, this would be easy to ensure: put the distinguishing bit under crypto cover. If you’re using a map lookup, you’ll probably have to work a little bit harder.
Please ignore my last post, I mixed up Retry packets and Initials. Tokens in the client’s Initial are part of the AAD, so we don’t have to worry about bit flips.
Depending how the server distinguishes them, a change of method / confusion about what server you're sending it to could make you appear to be presenting a Retry token instead of a regular token. This error code enables the client to recover and retry quickly, but there's no language about what the client should do when it receives the code.Rereading the changes, I have the following editorial suggestion.

An endpoint detected an error with protocol compliance that was not covered by more specific error codes. <ins> A server received a Retry Token in a client Initial that is invalid. </ins> An endpoint has received more data in CRYPTO frames than it can buffer.


Question: This was inspired by the comment by afrind at URL For each representation, uniformly use the word "representation" in the first sentence; use the word "identifies" if it's a verbatim entry; "encodes" otherwise; identify relative versus post-base indexing in the first sentence; move drawing right after first sentence; use consistent language describing binary format.

4.5.2. <del> An indexed header field representation identifies an entry in either the static table or the dynamic table and causes that header field to be added to the decoded header list. 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 the '1' 1-bit pattern, followed by the 'T' bit indicating whether the reference is into the static or dynamic table.  The 6-bit prefix integer (see prefixed-integers) that follows is used to locate the 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 the entry in the dynamic table. </del> <ins> An indexed header field representation identifies an entry in the static table, or an entry in the dynamic table with an absolute index less than the Base. This representation starts with the '1' 1-bit pattern, followed by the 'T' bit indicating whether the reference is into the static or dynamic table.  The 6-bit prefix integer (see prefixed-integers) that follows is used to locate the 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 the entry in the dynamic table. </ins> 4.5.3. <del> If the entry is in the dynamic table with an absolute index greater than or equal to the Base, the representation starts with the '0001' 4-bit pattern, followed by the post-base index (see post-base) of the matching header field, represented as an integer with a 4-bit prefix (see prefixed-integers). </del> <ins> An indexed header field with post-base index representation identifies an entry in the dynamic table with an absolute index greater than or equal to the Base. </ins> <del> 4.5.4. </del> <ins> This representation starts with the '0001' 4-bit pattern.  This is followed by the post-base index (see post-base) of the matching header field, represented as an integer with a 4-bit prefix (see prefixed-integers). </ins> <del> A literal header field with name reference represents a header field where the header field name matches the header field name of an entry stored in the static table or the dynamic table. 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 the '01' two-bit pattern. </del> <ins> 4.5.4. </ins> <del> The following bit, 'N', indicates whether an intermediary is permitted to add this header to the dynamic header table on subsequent hops.  When the 'N' bit is set, the encoded header MUST always be encoded with a literal representation.  In particular, when a peer sends a header field that it received represented as a literal header field with the 'N' bit set, it MUST use a literal representation to forward this header field.  This bit is intended for protecting header field values that are not to be put at risk by compressing them (see security-considerations for more details). </del> <ins> A literal header field with name reference representation encodes a header field where the header field name matches the header field name of an entry in the static table, or the header field name of an entry in the dynamic table with an absolute index less than the Base. This representation starts with the '01' two-bit pattern.  The following bit, 'N', indicates whether an intermediary is permitted to add this header to the dynamic header table on subsequent hops.  When the 'N' bit is set, the encoded header MUST always be encoded with a literal representation.  In particular, when a peer sends a header field that it received represented as a literal header field with the 'N' bit set, it MUST use a literal representation to forward this header field.  This bit is intended 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 or dynamic table.  The 4-bit prefix integer (see prefixed-integers)


Question: This was inspired by the comment by afrind at URL For each representation, uniformly use the word "representation" in the first sentence; use the word "identifies" if it's a verbatim entry; "encodes" otherwise; identify relative versus post-base indexing in the first sentence; move drawing right after first sentence; use consistent language describing binary format.

4.5.5. <del> A literal header field with post-base name reference represents a header field where the name matches the header field name of a dynamic table entry with an absolute index greater than or equal to the Base. </del> <ins> A literal header field with post-base name reference representation encodes a header field where the header field name matches the header field name of a dynamic table entry with an absolute index greater than or equal to the Base. </ins> This representation starts with the '0000' four-bit pattern.  The fifth bit is the 'N' bit as described in literal-name-reference.


Question: This was inspired by the comment by afrind at URL For each representation, uniformly use the word "representation" in the first sentence; use the word "identifies" if it's a verbatim entry; "encodes" otherwise; identify relative versus post-base indexing in the first sentence; move drawing right after first sentence; use consistent language describing binary format.

4.5.6. The literal header field without name reference representation <del> encodes a header field name and header field value as string </del> <ins> encodes a header field name and a header field value as string </ins> literals. This representation begins with the '001' three-bit pattern.  The


Question:
In , we say that one of the required operations on a connection is "enable 0-RTT". NAME : The current text implies that implementations are required to support 0-RTT, which is not the case. It would be more correct to say that applications must be able to prevent early data from being offered on a connection, even if the implementation supports it.
Alternately, the server section prefaces certain bullets with "If Early Data is supported, ...." That formulation might be used here as well. Also, this section uses "0-RTT" in the client piece but "Early Data" in the server piece. Should be consistent.

open a connection, which begins the exchange described in handshake; <del> enable 0-RTT; and </del> <ins> enable 0-RTT when available; and </ins> be informed when 0-RTT has been accepted or rejected by a server.


Question:
It is permissible for a client to do 0-RTT resumption with a NST from one connection and a token from another. This clarifies that a client only needs to associate a token (from a NEW_TOKEN frame) with the server it was from and no additional state, and that a server can't require that a token be from the same connection as the NST in use.
The token is used for deciding whether the server believes the packet it's receiving actually comes from the address it says it's coming from. I could believe a server might trust a client only if it's doing a 1-RTT handshake, but not trust it if it's doing a 0-RTT handshake, so might encode that bit in the token. I don't think the server should be putting more specific bits like "this is the NST that should be used" or "here are the SETTINGS from the previous connection" in the token. If the server doesn't like a token it receives with a 0-RTT handshake, it can send a Retry packet but still continue with the 0-RTT crypto handshake (even though it's now 1-RTT from a transport perspective). I don't see the need for there to be fate sharing between accepting a token and accepting 0-RTT - a server can reject one without rejecting the other. I've created to express the problem statement as an issue.
If you're going to continue with 0-RTT, but not trust the token, why not just limit the amount sent by the amplification factor? The receipt of a Handshake packet constitutes path validation. Also, a Retry doesn't allow you to acknowledge the client's Initial or any 0-RTT packets.
For a 0-RTT resumption in H3, a client needs 4 pieces of state previously received from a server: 1) a NewSessionTicket 2) Transport Parameters 3) SETTINGS 4) a token (from a NEW_TOKEN frame) The last isn't strictly necessary, but without it the server is likely to burn a round trip with a Retry packet to prove the client controls the source address. On a given connection, there is only one server SETTINGS and Transport Parameters, though there may be multiple NewSessionTickets and tokens. The first 3 pieces of state are necessarily required to have come from the same connection. A NST (from any connection) is needed since without it resumption can't happen. Transport Parameters and SETTINGS must come from somewhere for 0-RTT to be useful; the obvious and currently specified place is the SETTINGS and Transport Parameters that were sent on the same connection the NST came from. There's no such need that the 4th piece of state come from the same connection as the others. The token is used to prove that the client controls the address the packet is purportedly coming from. This can be done separately from any crypto state (in the NST), application state (SETTINGS), or the rest of the transport state (Transport Parameters). For a client to store the state needed for 0-RTT resumption, one possible implementation is to create cache entries containing an NST, TP, and SETTINGS. It's easy to do that where the TP and SETTINGS get duplicated N times, assuming N NSTs were received on a connection. If M tokens were received on that connection, and tokens need to be added to those cache entries, either the M tokens need to be paired to the N NSTs somehow, or all M tokens are in all N cache entries. This introduces noticeable complexity to a client's session cache implementation to correlate unrelated things. It should be clarified that the client doesn't need to associate the token with the rest of the state needed for 0-RTT. has suggested wording for that clarification.
As pointed out in NAME in URL, the proposed change essentially forbids a server from storing SETTINGS in the token. I think I'm persuaded by NAME argument above that we'd better forbid such coordination, as it would be a burden to certain client implementations. OTOH, I think that untying the relationship between session tickets and tokens does open a new issue, see .
I have some editiorial comments, but the design looks fine to me. While we might embed some information related to 0-RTT configuration in QUIC tokens, they would be decryptable and be usable, so we do not think there would be a problem for us (thanks to NAME for checking).A couple of editorial suggestions.

a token that is not applicable to the server that it is connecting to, unless the client has the knowledge that the server that issued the token and the server the client is connecting to are jointly <del> managing the tokens. </del> <ins> managing the tokens.  A client MAY use a token from any previous connection to that server. </ins> A token allows a server to correlate activity between the connection where the token was issued and any connection where it is used.


Question:
It is permissible for a client to do 0-RTT resumption with a NST from one connection and a token from another. This clarifies that a client only needs to associate a token (from a NEW_TOKEN frame) with the server it was from and no additional state, and that a server can't require that a token be from the same connection as the NST in use.
The token is used for deciding whether the server believes the packet it's receiving actually comes from the address it says it's coming from. I could believe a server might trust a client only if it's doing a 1-RTT handshake, but not trust it if it's doing a 0-RTT handshake, so might encode that bit in the token. I don't think the server should be putting more specific bits like "this is the NST that should be used" or "here are the SETTINGS from the previous connection" in the token. If the server doesn't like a token it receives with a 0-RTT handshake, it can send a Retry packet but still continue with the 0-RTT crypto handshake (even though it's now 1-RTT from a transport perspective). I don't see the need for there to be fate sharing between accepting a token and accepting 0-RTT - a server can reject one without rejecting the other. I've created to express the problem statement as an issue.
If you're going to continue with 0-RTT, but not trust the token, why not just limit the amount sent by the amplification factor? The receipt of a Handshake packet constitutes path validation. Also, a Retry doesn't allow you to acknowledge the client's Initial or any 0-RTT packets.
For a 0-RTT resumption in H3, a client needs 4 pieces of state previously received from a server: 1) a NewSessionTicket 2) Transport Parameters 3) SETTINGS 4) a token (from a NEW_TOKEN frame) The last isn't strictly necessary, but without it the server is likely to burn a round trip with a Retry packet to prove the client controls the source address. On a given connection, there is only one server SETTINGS and Transport Parameters, though there may be multiple NewSessionTickets and tokens. The first 3 pieces of state are necessarily required to have come from the same connection. A NST (from any connection) is needed since without it resumption can't happen. Transport Parameters and SETTINGS must come from somewhere for 0-RTT to be useful; the obvious and currently specified place is the SETTINGS and Transport Parameters that were sent on the same connection the NST came from. There's no such need that the 4th piece of state come from the same connection as the others. The token is used to prove that the client controls the address the packet is purportedly coming from. This can be done separately from any crypto state (in the NST), application state (SETTINGS), or the rest of the transport state (Transport Parameters). For a client to store the state needed for 0-RTT resumption, one possible implementation is to create cache entries containing an NST, TP, and SETTINGS. It's easy to do that where the TP and SETTINGS get duplicated N times, assuming N NSTs were received on a connection. If M tokens were received on that connection, and tokens need to be added to those cache entries, either the M tokens need to be paired to the N NSTs somehow, or all M tokens are in all N cache entries. This introduces noticeable complexity to a client's session cache implementation to correlate unrelated things. It should be clarified that the client doesn't need to associate the token with the rest of the state needed for 0-RTT. has suggested wording for that clarification.
As pointed out in NAME in URL, the proposed change essentially forbids a server from storing SETTINGS in the token. I think I'm persuaded by NAME argument above that we'd better forbid such coordination, as it would be a burden to certain client implementations. OTOH, I think that untying the relationship between session tickets and tokens does open a new issue, see .
I have some editiorial comments, but the design looks fine to me. While we might embed some information related to 0-RTT configuration in QUIC tokens, they would be decryptable and be usable, so we do not think there would be a problem for us (thanks to NAME for checking).A couple of editorial suggestions.

limit its use of tokens to only the information needed to validate client addresses. <ins> Clients MAY use tokens obtained on one connection for any connection attempt using the same version.  When selecting a token to use, clients do not need to consider other properties of the connection that is being attempted, including the choice of possible application protocols, session tickets, or other connection properties. </ins> Attackers could replay tokens to use servers as amplifiers in DDoS attacks.  To protect against such attacks, servers SHOULD ensure that tokens sent in Retry packets are only accepted for a short time.


Question:
, requires transport stacks to support abrupt closure of a stream even after the stream has been cleanly closed. This reflects the possibility to transition from "Data Sent" (stream has ended) to "Reset Sent" by sending a RESETSTREAM. Based on the state diagram in the transport doc, if an implementation was already in the "Data Recvd" state, they wouldn't actually send a RESETSTREAM, as they're already in a terminal state; a reset call would be a no-op or an error of some kind. However, NAME points out that in a handle-based API, this essentially means that handles for closed streams have to be recognizable as such for the lifetime of the connection. This might be burdensome for some implementations. Should we reword the requirement here to say that it's permitted if the stream hasn't yet reached the "Data Recvd" state.
This was clearly just a miss on the wording. I don't think that the intent was ever to require that streams be kept around indefinitely. Sure, some stacks might be able to send RESET_STREAM at a whim, but it's not necessary

write data, understanding when stream flow control credit (data- flow-control) has successfully been reserved to send the written <del> data </del> <ins> data; </ins> 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 <del> frame (frame-reset-stream), even if the stream was already ended. </del> <ins> frame (frame-reset-stream), if the stream was not already in a terminal state. </ins> On the receiving part of a stream, application protocols need to be able to: <del> read data </del> <ins> read data; and </ins> abort reading of the stream and request closure, possibly <del> resulting in a STOP_SENDING frame (frame-stop-sending) </del> <ins> resulting in a STOP_SENDING frame (frame-stop-sending). </ins> Applications also need to be informed of state changes on streams, including when the peer has opened or reset a stream, when a peer