Spuriously declaring packets as lost leads to unnecessary retransmissions and may result in degraded performance due to the actions of the congestion controller upon detecting loss. <del> Implementations that detect spurious retransmissions and increase the reordering threshold in packets or time MAY choose to start with smaller initial reordering thresholds to minimize recovery latency. </del>  5.1.1.

by clarifying that one may start with a smaller time threshold, not a smaller packet threshold.
The doc says: "Implementations that detect spurious retransmissions and increase the reordering threshold in packets or time MAY choose to start with smaller initial reordering thresholds to minimize recovery latency." For the packet based threshold does that this say that a smaller initial value than 3 packets could be used? Is that really sensible? And for a time based threshold I don't really understand what this means because the threshold is anyway calculated dynamically based on the RTT, so there is no real initial value. I guess there was a time when the threshold was a fixed value and this sentence is maybe a left-over?
Given the current guidance to never use packet thresholds below 3, I think this text should be changed to be clear that you can start with a smaller fraction of RTT and it only applies to time threshold.
I think the fix will be editorial, but once a PR is out we'll know for sure.
If it's only about time-based threshold, any advise should be moved in the respective subsection. However, I would also be okay to just remove this sentence/paragraph...

Spuriously declaring packets as lost leads to unnecessary retransmissions and may result in degraded performance due to the actions of the congestion controller upon detecting loss.  <ins> Implementations can detect spurious retransmissions and increase the reordering threshold in packets or time to reduce future spurious retransmissions and loss events.  Implementations with adaptive time thresholds MAY choose to start with smaller initial reordering thresholds to minimize recovery latency. </ins> 5.1.1.


6. <del> This document specifies a Reno congestion controller for QUIC RFC6582. </del>  The signals QUIC provides for congestion control are generic and are designed to support different algorithms.  Endpoints can unilaterally 6.2. <del> QUIC begins every connection in slow start and exits slow start upon loss or upon increase in the ECN-CE counter.  QUIC re-enters slow start any time the congestion window is less than ssthresh, which only occurs after persistent congestion is declared.  While in slow start, QUIC increases the congestion window by the number of bytes acknowledged when each acknowledgment is processed. </del>  6.3. <del> Slow start exits to congestion avoidance.  Congestion avoidance in NewReno uses an additive increase multiplicative decrease (AIMD) approach that increases the congestion window by one maximum packet size per congestion window acknowledged.  When a loss is detected, NewReno halves the congestion window and sets the slow start threshold to the new congestion window. </del>  6.4.  A recovery period is entered when loss or ECN-CE marking of a packet is detected.  A recovery period ends when a packet sent during the recovery period is acknowledged.  This is slightly different from round trip.  During recovery, the congestion window remains unchanged irrespective of new losses or increases in the ECN-CE counter. <del> 6.5. </del>  During the handshake, some packet protection keys might not be available when a packet arrives.  In particular, Handshake and 0-RTT that might arrive before the peer has packet protection keys to process those packets. <del> 6.6. </del>  Probe packets MUST NOT be blocked by the congestion controller.  A sender MUST however count these packets as being additionally in acknowledgement is received that establishes loss or delivery of packets. <del> 6.7. </del>  When an ACK frame is received that establishes loss of all in-flight packets sent over a long enough period of time, the network is response on a Retransmission Timeout (RTO) in TCP RFC5681 after Tail Loss Probes (TLP) RACK. <del> 6.8. </del>  This document does not specify a pacer, but it is RECOMMENDED that a sender pace sending of all in-flight packets based on input from the of a flow pacer, implementers are referred to the Fair Queue packet scheduler (fq qdisc) in Linux (3.11 onwards). <del> 6.9. </del>  When bytes in flight is smaller than the congestion window and sending is not pacing limited, the congestion window is under-

Clarifies a few points and moves the initial window recommendation from the pseudocode to the text. I could take this a bit farther, but I'd like suggestions on what's currently missing in this PR.
LGTM
It starts like this: The next sections below roughly describe the behavior of different phases of congestion control. More concrete pseudo-code is only given in the Appendix later on but it's not linked from the original section. On first reading, I missed the Appendix and felt the text lacking for concrete enough explanations to implement the feature. The question is what to describe where and what the authoritative definitions should be. If you know the NewReno RFC by heart you can probably make the right inferences to figure out how everything should work in comparison but is that how it should be? An alternative (more convenient for the reader / skimmer) approach could be to mention NewReno only in passing and then provide a complete and self-contained description of the algorithms in the congestion control section directly, maybe even inlining the pseudo-code. If that is deemed to verbose and would bore readers that know enough about those algorithms already, it would be good to provide some guidance how to read the algorithm and what the assumptions are in the introductory paragraph about congestion control.
The intent is that the text is complete without reading the referenced RFCs or pseudocode, so I think this section needs more text and possibly some inlined pseudo-code.
Hi NAME I wrote which fixes some obvious errors I found, but I think more could be done. Can you take a look and tell me what other points you'd like added or moved up from the pseudocode?
Looks good! Thanks!

6.  <ins> This document specifies a congestion controller for QUIC similar to TCP NewReno RFC6582. </ins> The signals QUIC provides for congestion control are generic and are designed to support different algorithms.  Endpoints can unilaterally 6.2.  <ins> QUIC begins every connection in slow start with the congestion window set to an initial value.  Endpoints SHOULD use an initial congestion window of 10 times the maximum datagram size (max_datagram_size), limited to the larger of 14720 or twice the maximum datagram size. This follows the analysis and recommendations in RFC6928, increasing the byte limit to account for the smaller 8 byte overhead of UDP compared to the 20 byte overhead for TCP. The minimum congestion window is the smallest value the congestion window can decrease to as a response to loss, ECN-CE, or persistent congestion.  The RECOMMENDED value is 2 * max_datagram_size. </ins> 6.3.  <ins> While in slow start, QUIC increases the congestion window by the number of bytes acknowledged when each acknowledgment is processed, resulting in exponential growth of the congestion window. QUIC exits slow start upon loss or upon increase in the ECN-CE counter.  When slow start is exited, the congestion window halves and the slow start threshold is set to the new congestion window.  QUIC re-enters slow start any time the congestion window is less than the slow start threshold, which only occurs after persistent congestion is declared. </ins> 6.4. <ins> Slow start exits to congestion avoidance.  Congestion avoidance uses an additive increase multiplicative decrease (AIMD) approach that increases the congestion window by one maximum packet size per congestion window acknowledged.  When a loss or ECN-CE marking is detected, NewReno halves the congestion window and sets the slow start threshold to the new congestion window. 6.5. </ins> A recovery period is entered when loss or ECN-CE marking of a packet is detected.  A recovery period ends when a packet sent during the recovery period is acknowledged.  This is slightly different from round trip.  During recovery, the congestion window remains unchanged irrespective of new losses or increases in the ECN-CE counter.  <ins> 6.6. </ins> During the handshake, some packet protection keys might not be available when a packet arrives.  In particular, Handshake and 0-RTT that might arrive before the peer has packet protection keys to process those packets.  <ins> 6.7. </ins> Probe packets MUST NOT be blocked by the congestion controller.  A sender MUST however count these packets as being additionally in acknowledgement is received that establishes loss or delivery of packets.  <ins> 6.8. </ins> When an ACK frame is received that establishes loss of all in-flight packets sent over a long enough period of time, the network is response on a Retransmission Timeout (RTO) in TCP RFC5681 after Tail Loss Probes (TLP) RACK.  <ins> 6.9. </ins> This document does not specify a pacer, but it is RECOMMENDED that a sender pace sending of all in-flight packets based on input from the of a flow pacer, implementers are referred to the Fair Queue packet scheduler (fq qdisc) in Linux (3.11 onwards).  <ins> 6.10. </ins> When bytes in flight is smaller than the congestion window and sending is not pacing limited, the congestion window is under-


is expected to both expire earlier than the PTO and be less likely to spuriously retransmit data. <del> 5.3. </del>  The initial probe timeout for a new connection or new path SHOULD be set to twice the initial RTT.  Resumed connections over the same Initial packets and Handshake packets could be never acknowledged, but they are removed from bytes in flight when the Initial and <del> Handshake keys are discarded.  When Initial or Handshake keys are discarded, the PTO and loss detection timers MUST be reset, because discarding keys indicates forward progress and the loss detection timer may have been set for a now discarded packet number space. </del>  <del> 5.3.1. </del>  When a PTO timer expires, a sender MUST send at least one ack- eliciting packet in the packet number space as a probe, unless there Sending different payloads each time reduces the chances of spurious retransmission. <del> 5.3.2. </del>  Delivery or loss of packets in flight is established when an ACK frame is received that newly acknowledges one or more packets. detection proceeds as dictated by packet and time threshold mechanisms; see ack-loss-detection. <del> 5.4. </del>  A Retry packet causes a client to send another Initial packet, effectively restarting the connection process.  A Retry packet Version Negotiation packet is received.  The client MAY use this value in place of its default for the initial RTT estimate. <del> 5.5. </del>  When packet protection keys are discarded (see Section 4.10 of QUIC- TLS), all packets that were sent with those keys can no longer be

I believe this section was mistakenly on the wrong level. I also added a reference to a later section to the last sentence. Alternatively this sentence could just be removed as this is normatively specified later on.

is expected to both expire earlier than the PTO and be less likely to spuriously retransmit data.  <ins> 5.2.2. </ins> The initial probe timeout for a new connection or new path SHOULD be set to twice the initial RTT.  Resumed connections over the same Initial packets and Handshake packets could be never acknowledged, but they are removed from bytes in flight when the Initial and  <ins> Handshake keys are discarded, as described below in Section discarding-packets.  When Initial or Handshake keys are discarded, the PTO and loss detection timers MUST be reset, because discarding keys indicates forward progress and the loss detection timer might have been set for a now discarded packet number space. </ins>  <ins> 5.2.3. </ins> When a PTO timer expires, a sender MUST send at least one ack- eliciting packet in the packet number space as a probe, unless there Sending different payloads each time reduces the chances of spurious retransmission.  <ins> 5.2.4. </ins> Delivery or loss of packets in flight is established when an ACK frame is received that newly acknowledges one or more packets. detection proceeds as dictated by packet and time threshold mechanisms; see ack-loss-detection.  <ins> 5.3. </ins> A Retry packet causes a client to send another Initial packet, effectively restarting the connection process.  A Retry packet Version Negotiation packet is received.  The client MAY use this value in place of its default for the initial RTT estimate.  <ins> 5.4. </ins> When packet protection keys are discarded (see Section 4.10 of QUIC- TLS), all packets that were sent with those keys can no longer be


target host to the server during the TLS handshake.  If the server is identified by a DNS name, clients MUST send the Server Name Indication (SNI) RFC6066 TLS extension unless an alternative <del> mechanism for indicate the target host is used. </del>  QUIC connections are established as described in QUIC-TRANSPORT. During connection establishment, HTTP/3 support is indicated by

Follow-up to URL
Or "for indicating" :man_shrugging: not sure what the most appropriate one is.

target host to the server during the TLS handshake.  If the server is identified by a DNS name, clients MUST send the Server Name Indication (SNI) RFC6066 TLS extension unless an alternative  <ins> mechanism to indicate the target host is used. </ins> QUIC connections are established as described in QUIC-TRANSPORT. During connection establishment, HTTP/3 support is indicated by


Prior to validating the client address, servers MUST NOT send more than three times as many bytes as the number of bytes they have received.  This limits the magnitude of any amplification attack that <del> can be mounted using spoofed source addresses.  In determining this limit, servers only count the size of successfully processed packets. </del>  Clients MUST ensure that UDP datagrams containing Initial packets have UDP payloads of at least 1200 bytes, adding padding to packets

Creating PR on behalf of NAME This is a tweak to the text suggested in .
URL only says that padding is done by PADDING frame. URL But it looks like it is a common practice to add 0 or garbage outside QUIC packet to fill remaining UDP packet payload to expand packet size. If it is a good practice (and/or recommended), I think draft should say something about it. It might slightly simplify the implementation. Padding in this way has different properties than PADDING frame; for example, it does not contribute to in-flight bytes.
The only time you can do this is when you have only long header packets (essentially during the handshake). Because short header packets are not length delimited, if you pad on the outside it will be interpreted as part of the packet and the deprotection will fail.
Just FYI: Adding 0-value bytes outside QUIC packets to fill remaining UDP datagram is the way that neqo currently performs padding. Of course, as NAME says, it only does this when it is possible (there are only QUIC packets with long headers in the datagram). I, too, think it would be nice to have some clarification like this in the standard.
Yes, this kind of padding is for handshake packets only.
As NAME pointed out, this type of padding doesn't allow coalescing 0.5RTT server data with the server's first flight, so it limits coalescing in common use cases, though I agree may be easier to implement. To me, the biggest risk here is that the non-QUIC padding is removed by something on the path. However, that risk seems similar to the risk that something on the path would split a coalesced packet into two UDP datagrams(discussed in ), so it seems acceptable. Because it prevents coalescing in some useful cases, I don't think it should be recommended, but I can't come up with a strong reason for disallowing it, so I think it should continue to be allowed.
Another concern about this way of padding is that it does not increase the transmission limit for server. URL Because bytes outside of QUIC packet are most likely unprocessable, it is not counted to the data size that server has received. That means that server might get choked during handshake with packet losses.
NAME my understanding is that amplification limit is based on the UDP datagram layer, not the QUIC packet layer. If you receive any bytes from the peer you can see 3x times that in response. They don't have to be valid QUIC packets. I thought there was text that was explicit about that. If not, the text should probably be clarified.
This is my understanding as well
A server is not required to pad, is it? URL (edited for typo.)
No, padding is not required on the server side, though it's still sometimes useful for path MTU.
This behavior should continue to be allowed. We allow endpoints to send a QUIC packet (as part of a coalesced packet) knowing that the peer would de unable to decrypt it (see ). The issue being discussed here is a variant of that. That said, I am not sure if we want to suggest padding outside of a QUIC packet is a possibility, because such a design might have wired implications to the CC logic. IIUC, current congestion logic is based on the following principles: an ack-eliciting packet consumes bytes-in-flight an ack-only packet does not consume bytes-in-flight we allow ack-eliciting packets and ack-only packets to be coalesced, because that does not change the load on the network I am not sure how we can adjust these principles so that padding outside QUIC packets can be taken into consideration (when necessary), or if making such adjustment is worth the additional complexity.
I think the current text "In determining this limit, servers only count the size of successfully processed packets." has 2 problems. It is unclear that what "packets" indicate: is it UDP packet or QUIC packet? It is unclear what "successfully processed packets" means.
NAME raises an interesting point here. While it's certainly not impossible to use the size of the UDP datagram for the amplification limit, this would be quite hard to do in my implementation. Undecryptable packets are discarded way down in the stack, and passing up that information to the congestion controller would require quite a bit of extra work.
It seems that the text regarding the amplification limit () has been last changed in response to which is marked as editorial. While the intension of the change was to clarify that the amplification limit applies at the QUIC packet level (rather than UDP datagram level), I think NAME might be correct in pointing out that our understanding have been the contrary (see URL). Consider the case where a client is using 0-RTT. When building the first datagram consisting of an Initial and a 0-RTT packet, the only way the client can build the datagram in one-pass is by first building the Initial packet, then the 0-RTT packet due to . As it is hard to tell the size of the 0-RTT packet before building it, the most likely outcome is that the 0-RTT packet would be padded. However, the server might not be able to process the 0-RTT packet, when it has lost it's resumption secret. That means that if we are to say that the amplification limit is applied at the QUIC packet layer (for the QUIC packets that are only processed successfully), the client might end up in having very little room. I do not think that is a good outcome.
I concur with Kazuho. My understanding had always been that the amplification liit was on the UDP datagram, more or less for the reasons Kazuho lays out here.
The CC implications are interesting for sure, but though amplification should be based on bytes received from an address prior to processing (so that discarded 0-RTT and junk counts): So .
NAME Thank you for opening a new issue. I share the view that the issue deserves its own. Going back to the original topic, I was a bit confused in my previous comment, but I think that my argument still holds; we should recommend padding inside QUIC packet, because CC happens at QUIC packet layer, and the size of padding affects CC. I am afraid that the specification would become needlessly complex, if we are to suggest that padding outside of QUIC packet is a possibility.
Many other implementations didn't initially work against Neqo (which pads outside) and the Wireshark QUIC dissector does not currently handle it either. If the spec doesn't say anything then future implementations are bound to incorrectly handle outside padding. People are bound to assume it is not allowed if the spec says nothing, or just not consider the possibility of it.
My point is that while I'm not against making an editorial change stating that a receiver should process coalesced packets of a datagram until it sees a broken packet, I would not prefer adding a statement that implies that a client might pad outside of QUIC packets, because doing so is might have negative effects, and trying to resolve those negative effects are likely to introduce complexity. To be clear, my understanding is that this issue is about a misbehaving client failing to talk to some servers. The specification says that an endpoint MUST expand the packet to 1,200 bytes by "padding to packets in the datagram as necessary," () which explicitly means that padding should happen at the packet level, not at the datagram level. We might even argue that some servers not handling those broken datagram is a benefit, as it helps us find bugs in the client.
Well, this is lawyering, but it doesn't say "by adding", but rather "URL necessary". My argument here is that it's not necessary if you pad in the datagram.
I have to admit that I found Kazuho's points about congestion control somewhat compelling. However, I would say that there is nothing inherently wrong with the approach we take, except for the way that we count those junk bytes toward bytes-in-flight, and particularly how we remove them in the absence of a direct ACK. But ACKs in Initial packets are worthless anyway, so counting them as though they were Initial probably goes most of the way to fixing the problem. Right now we don't account for these bytes at all, which avoids accounting errors, but might not be ideal in terms of congestion control. INITCWND 10 becomes INITCWND "10 and a bit".
I think it is much simpler to say that: 1) For the purpose of verifying that an Initial packet is larger than the required minimum, all bytes count. 2) For all other purposes, including congestion control and amplification control, only valid packets count. Then it becomes a choice of implementations. Padding with padding frames is not hard, many implementations do it. Implementations that want to save a few lines of code or a few CPU cycles could pad their initial packets otherwise, but they will bear the impact on performance. Also, consider the coalescing requirement about DCID: "Senders MUST NOT coalesce QUIC packets for different connections into a single UDP datagram. Receivers SHOULD ignore any subsequent packets with a different Destination Connection ID than the first packet in the datagram." That's consistent with "ignore the junk for all purposes except maybe verifying packet size".
Do note that appending zeroes in the UDP datagram makes it very easy to fingerprint QUIC connections for neqo and others that do the same. Is the flexibility on using PADDING frames or appending garbage necessary to achieve the minimum datagram size necessary? It does not feel entirely right.
" For the purpose of verifying that an Initial packet is larger than the required minimum, all bytes count. For all other purposes, including congestion control and amplification control, only valid packets count. " I don't agree that these bytes shouldn't count for amplification control. Amplification control is about received byes, and these are bytes.
Does that mean random garbage (that's not appended to a QUIC packet) is supposed to count as well? That would require implementations to do 5-tuple tracking.
That doesn't seem to follow necessarily. If a UDP datagram contains a valid QUIC packet, count the bytes of the datagram. Otherwise, drop the datagram.
IIRC we discussed and agreed that amplification control happens an the UDP datagram level. The rational was that unless we do so, clients using 0-RTT by coalescing Initial and 0-RTT packets into a single datagram would see inferior performance.
Whatever the language about outside padding, an endpoint must not discard the entire datagram, just because data after a long-header packet looks unparseable to it. It is possible that the client is trying a 0-RTT resumption with packet coalescing using a QUIC version/extension that is unknown to the server. The right behavior is to process packets that are understood (which may result in version negotiation) and stop processing that datagram.
This "unparseable PAD" looks like a "kindness of strangers" issue. An implementer may want to do that because it looks like it will save 3 CPU cycles somewhere, but that only works if all other implementation agree that it is a good idea. Experience shows that they don't. So just accept that this will just not work well.
"For the purposes of avoiding amplification prior to address validation, servers MUST count the bytes received in datagrams that are uniquely attributed to a connection. This includes datagrams that contain packets that are successfully processed and datagrams that contain packets that are entirely discarded."
NAME "that are uniquely attributable to a connection" is vague and will differ by implementation (for example, one implementation will not even try to attribute a packet with an invalid "SCID Len" but another will). How about: "... well-formed packets that are uniquely attributable to a connection" ?
Discussed in ZRH. Proposed resolution is NAME quoted comment above.
MT "packets that are discarded, including when those packets are the sole packets in the datagram"
I don't like the idea of allowing any kind of data in the datagram outside of the QUIC framework. Even for coalesced packets, they are required to belong to the same connection, possibly even the same packet number space. Additional information in the datagram can be removed or fingerprinted, as already mentioned, but worse, information can be added. This could be used to leak cooperate data, tracking etc. - preventing this requires dropping all datagrams that cannot be fully decrypted, but don't I think there is consensus for that.

Prior to validating the client address, servers MUST NOT send more than three times as many bytes as the number of bytes they have received.  This limits the magnitude of any amplification attack that  <ins> can be mounted using spoofed source addresses.  For the purposes of avoiding amplification prior to address validation, servers MUST count all of the payload bytes received in datagrams that are uniquely attributed to a single connection.  This includes datagrams that contain packets that are successfully processed and datagrams that contain packets that are all discarded. </ins> Clients MUST ensure that UDP datagrams containing Initial packets have UDP payloads of at least 1200 bytes, adding padding to packets


migrates away from a local address SHOULD retire all connection IDs used on that address once it no longer plans to use that address. <del> An endpoint can cause its peer to retire connection IDs by sending a NEW_CONNECTION_ID frame with an increased Retire Prior To field. Upon receipt, the peer MUST first retire the corresponding connection IDs using RETIRE_CONNECTION_ID frames and then add the newly provided connection ID to the set of active connection IDs.  Failure to retire the connection IDs within approximately one PTO can cause packets to be delayed, lost, or cause the original endpoint to send a stateless reset in response to a connection ID it can no longer route correctly. An endpoint MAY discard a connection ID for which retirement has been requested once an interval of no less than 3 PTO has elapsed since an acknowledgement is received for the NEW_CONNECTION_ID frame requesting that retirement.  Until then, the endpoint SHOULD be prepared to receive packets that contain the connection ID that it has requested be retired.  Subsequent incoming packets using that connection ID could elicit a response with the corresponding stateless reset token. </del>  5.2.

Removes guidance on specific times in favor of descriptions of what the CID issuer might have to do if it can no longer support outstanding CIDs. I believe the impact of this is editorial, but I touched normative statements, so if anyone disagrees please scream.
Marking this as design to avoid accidental closure of the linked issue (which is design). Including this change in changelogs won't hurt either, though it is extremely minor.
NAME there are a couple of outstanding comments -- can you handle those and close this off?
Requiring RETIRECONNECTIONID has lead to all sorts of corner cases. For instance, a server can force a client to retire the connection ID that is still in use for the handshake. (We're uncertain yet as to whether the newly provided connection ID usable here, so this only might cause genuine problems.) More to the point, this design assumes things about the nature of connection IDs and routing infrastructure that imply a coupling between path and endpoint state. The reason you might want to retire a connection ID forcibly in this fashion is that it is no longer usable for routing purposes (or that it results in suboptimal routing, perhaps where packets need to be bounced between nodes in a cluster). All of this implies that the address tuple is part of the routing assessment. That makes this feature fragile and it also creates some uncertainty about whether it is possible to build such a system without having a stateless reset oracle.
Just to clarify, my understanding of the problem here isn't itself, but rather that an issuer of a CID can send to require that the consumer MUST retire the CID immediately?
NAME I'm not sure if I follow what you state here. Much like a TLS server having a master key that encrypts TLS session tickets, a server might have a master key that is used for encrypting all the CIDs, and it rotates that key periodically. In such a design (which I'd assume to be pretty normal), there is nothing related to paths.
MsQuic also uses CIDs and RetirePriorTo to encode "on machine" routing information, such as per-CPU partitioning. When we rebalance load across these partitions, we generate new CIDs to match. I also don't quite understand the point of this issue. If the problem is just that we don't want CIDs to change before a certain time, I'm fine with adding some extra restrictions on that. But I'm very strongly against removing this feature (assuming that's the goal of this issue).
This isn't about being bound to addresses, though. It's about anything in the server infrastructure changing. That could be rotation of the keys used to encrypt a structure to produce packet IDs. I think there's a separate discussion about whether CIDs can be bound to server IPs for preferred addresses, but that's covered by already.
Discussed in ZRH. Proposed resolution is to prepare a PR that clarifies the consequences for both sides on what happens when an endpoint sends a NEWCONNECTIONID frame, and for the receiver for what it means if it doesn't retire. Also, remove PTO from text related to retirement.
Thanks for writing this up!

migrates away from a local address SHOULD retire all connection IDs used on that address once it no longer plans to use that address.  <ins> An endpoint might need to stop accepting previously issued connection IDs in certain circumstances.  Such an endpoint can cause its peer to retire connection IDs by sending a NEW_CONNECTION_ID frame with an increased Retire Prior To field.  The endpoint SHOULD continue to accept the previously issued connection IDs until they are retired by the peer.  If the endpoint can no longer process the indicated connection IDs, it MAY close the connection. Upon receipt of an increased Retire Prior To field, the peer MUST stop using the corresponding connection IDs and retire them with RETIRE_CONNECTION_ID frames before adding the newly provided connection ID to the set of active connection IDs.  This ordering allows an endpoint that has already supplied its peer with as many connection IDs as allowed by the active_connection_id_limit transport parameter to replace those connection IDs with new ones as necessary. Failure to cease using the connection IDs when requested can result in connection failures, as the issuing endpoint might be unable to continue using the connection IDs with the active connection. </ins> 5.2.


its peer invalidates the connection ID via a RETIRE_CONNECTION_ID frame (frame-retire-connection-id).  Connection IDs that are issued and not retired are considered active; any active connection ID is <del> valid for use at any time, in any packet type.  This includes the connection ID issued by the server via the preferred_address transport parameter. </del>  An endpoint SHOULD ensure that its peer has a sufficient number of available and unused connection IDs.  Endpoints store received

This isn't strictly necessary, as it might be implied by the definition of a connection ID. I tried tweaking the connection ID definition, but it is hard to say anything concrete because of the way that addressing can also be used in combination with connection IDs to identify a connection at an endpoint. So I left that alone.
I agree that a connection ID identifies one session, and due to forbidden correlation, it is clear that a connection ID in use cannot proliferate. However, this is not implied for newly issued connection IDs. There is precedence for new tokens which do apply to new connections, so I think it is worth being precise here.
added the text " Connection IDs that are issued and not retired are considered active; any active connection ID is valid for use at any time, in any packet type. " As far as I can tell there is no text specifying that an issued connection ID cannot be used with subsequent connections. The introduction to connection IDs state that there must not be correlation across connections, but if an issued connection ID was never consumed, there would be no correlation because it was transmitted encrypted.
Not really sure if this is necessary. The text around this sentence implies that connection IDs identify one connection (which shouldn't be too surprising in the first place). It's only when you take this one sentence out of context, that the "problem" of occurs.

its peer invalidates the connection ID via a RETIRE_CONNECTION_ID frame (frame-retire-connection-id).  Connection IDs that are issued and not retired are considered active; any active connection ID is  <ins> valid for use with the current connection at any time, in any packet type.  This includes the connection ID issued by the server via the preferred_address transport parameter. </ins> An endpoint SHOULD ensure that its peer has a sufficient number of available and unused connection IDs.  Endpoints store received


number spaces, the timer MUST be set for the packet number space with the earliest timeout, except for ApplicationData, which MUST be ignored until the handshake completes; see Section 4.1.1 of QUIC-TLS. <del> Not arming the PTO for ApplicationData prioritizes completing the handshake and prevents the server from sending a 1-RTT packet on a PTO before before it has the keys to process a 1-RTT packet. </del>  When a PTO timer expires, the PTO period MUST be set to twice its current value.  This exponential reduction in the sender's rate is

Clarifies exactly what the implications of not arming PTO for ApplicationData prior to handshake complete. This came from a discussion with a coworker who read the transport and recovery drafts, but not TLS.
Thanks! I think this is much clearer from implementation perspective.

number spaces, the timer MUST be set for the packet number space with the earliest timeout, except for ApplicationData, which MUST be ignored until the handshake completes; see Section 4.1.1 of QUIC-TLS.  <ins> Not arming the PTO for ApplicationData prevents a client from retransmitting a 0-RTT packet on a PTO expiration before confirming that the server is able to decrypt 0-RTT packets, and prevents a server from sending a 1-RTT packet on a PTO expiration before it has the keys to process an acknowledgement. </ins> When a PTO timer expires, the PTO period MUST be set to twice its current value.  This exponential reduction in the sender's rate is


stream. When processing header blocks, the decoder expects the Required <del> Insert Count to exactly match the value defined in blocked-streams. If it encounters a smaller value than expected, it MUST treat this as a connection error of type QPACK_DECOMPRESSION_FAILED; see invalid- references.  If it encounters a larger value than expected, it MAY treat this as a connection error of type QPACK_DECOMPRESSION_FAILED. </del>  2.2.2.

Restating the definition of Required Insert Count in the first sentence is probably not necessary. However, I believe rewording the second and the third sentences is an improvement.
If an encoder produces a Required Insert Count that is invalid, what can a decoder do? The encoder could provide a value that is too high. In the case where the value is high, but the number of entries is already acknowledged, this is probably harmless. However, the decoder might block unnecessarily, if the values were not already present and the encoder references none of those entries or only references lower-indexed entries than it declared. This is a performance problem up until the point where the encoder hasn't provided the "required" entries and finds no reason to do so, at which point the performance bug could be come a deadlock. The encoder could provide a value that is too low. The decoder might then reasonably reject an encoding, but some will not. If the value is only too low and the entry is present in the dynamic table, some encoders will happily pull the value from the table and use it. Those decoders will only generate an error if the entry isn't there yet. I think that it would be better for the protocol if a decoder were able to generate an error if the maximum index referred to in a header block did not exactly match the Required Insert Count.
The "finds no reason to do so" is a functional, not performance, bug in the encoder. As for performance, that's why there is the setting. I thought there was verbiage to that effect already, but I don't see it in the .
Alan found this in . It's a little obtuse though.
I am assuming that you are referring specifically to this paragraph?
I interpreted this as saying that the encoder produced a RIC for entries that don't exist in the table yet and perhaps never will. The bug is still producing a RIC that isn't referenced in the encoded header block.
I feel like the draft explicitly says this is an error. Here's the current text: Which part is obtuse? Should I replace 'URL value defined in Section 2.1.3.' and '...smaller/larger than expected' with a restatement of the expected values? I'm pretty sure it used to say that and we changed it to this. Or would moving it to another section make it easier to find? Regarding whether this is a functional or performance bug, I expect many implementations won't parse the header block until the RIC is met, so the encoder can cause the decoder to block a stream indefinitely. This can lead to other poor compression performance effects, but I don't think it can cause an actual deadlock on any request other than the one with the bad RIC, which is the encoder's fault anyways. Maybe a security consideration recommending timeouts for this case?
Which value matches the Required Insert Count? I think that you mean that the Required Insert Count value and the lowest index found in the block have to be equal. If the lowest index found is smaller than RIC, then MUST; else if lowest > RIC, then MAY. Does that help clarify?
I think it already says the right thing, but I agree that "the value defined in Section 2.1.3 is a bit opaque. IIRC, you're right that it was the result of a compromise. It would probably be clearest to have another defined term, but we have a lot of those already.
This is not only a performance problem or a potential deadlock. If the encoder sends an unnecessarily large Required Insert Count but does not consider the stream blocked if the Known Insert Count is at least as large as the actual Required Insert Count, and the decoder does not start decoding until the Known Insert Count reaches the Required Insert Count received in the header block prefix (therefore it does not realize that the stream is in fact not blocked), then the encoder and the decoder might have a different idea about the number of blocked streams. This might result in the decoder closing the connection due to SETTINGSQPACKBLOCKED_STREAMS violation. Of course is the encoder uses the same incorrect value for sending as Required Insert Count and for bookkeeping of blocked streams then this problem does not arise.
Moving it to another section, like to 2.1.3 (within the encoder section), might help the text flow better. However, since this paragraph is discussing requirements on the decoder, the subsection where it is right now is probably the most suitable one.

stream. When processing header blocks, the decoder expects the Required  <ins> Insert Count to equal the lowest possible value for the Insert Count with which the header block can be decoded, as prescribed in blocked- streams.  If it encounters a Required Insert Count smaller than expected, it MUST treat this as a connection error of type QPACK_DECOMPRESSION_FAILED; see invalid-references.  If it encounters a Required Insert Count larger than expected, it MAY treat this as a connection error of type QPACK_DECOMPRESSION_FAILED. </ins> 2.2.2.


congestion window, or a pacer might pace out packets handed to it by the congestion controller.  Timely delivery of ACK frames is important for efficient loss recovery.  Packets containing only ACK <del> frames should therefore not be paced, to avoid delaying their </del>  delivery to the peer. Sending multiple packets into the network without any delay between A sender that paces packets (see pacing) might delay sending packets and not fully utilize the congestion window due to this delay.  A <del> sender should not consider itself application limited if it would </del>  have fully utilized the congestion window without pacing delay. A sender MAY implement alternative mechanisms to update its

This my need a design issue because I suggest to use two SHOULDs instead of should. However, I would first like to get some feedback if people agree.
I think this is editorial, because it fixes an error. I always intended these to be normative SHOULD/SHOULD NOT
I'm going with editorial also.
Yup, this is editorial.
Thanks, this is certainly what I meant when I wrote this text.

congestion window, or a pacer might pace out packets handed to it by the congestion controller.  Timely delivery of ACK frames is important for efficient loss recovery.  Packets containing only ACK  <ins> frames SHOULD therefore not be paced, to avoid delaying their </ins> delivery to the peer. Sending multiple packets into the network without any delay between A sender that paces packets (see pacing) might delay sending packets and not fully utilize the congestion window due to this delay.  A  <ins> sender SHOULD NOT consider itself application limited if it would </ins> have fully utilized the congestion window without pacing delay. A sender MAY implement alternative mechanisms to update its


The initial probe timeout for a new connection or new path SHOULD be set to twice the initial RTT.  Resumed connections over the same <del> network SHOULD use the previous connection's final smoothed RTT value as the resumed connection's initial RTT.  If no previous RTT is </del>  available, the initial RTT SHOULD be set to 500ms, resulting in a 1 second initial timeout as recommended in RFC6298.

The recovery draft says: "Resumed connections over the same network SHOULD use the previous connection's final smoothed RTT value as the resumed connection's initial RTT." I wonder if we should change this to a MAY, as there is also a temporal aspect. Or maybe we should say more about this? Also note that draft-ietf-tcpm-2140bis is probably related.
I would agree with switching to MAY. Even mentioning this leads to some incentive to implement, so the effect on implementation is similar. But we really don't need to recommend something like this.

The initial probe timeout for a new connection or new path SHOULD be set to twice the initial RTT.  Resumed connections over the same  <ins> network MAY use the previous connection's final smoothed RTT value as the resumed connection's initial RTT.  If no previous RTT is </ins> available, the initial RTT SHOULD be set to 500ms, resulting in a 1 second initial timeout as recommended in RFC6298.


be used again and requests that the peer replace it with a new connection ID using a NEW_CONNECTION_ID frame. <del> As discussed in migration-linkability, each connection ID MUST be used on packets sent from only one local address.  An endpoint that migrates away from a local address SHOULD retire all connection IDs used on that address once it no longer plans to use that address. </del>  An endpoint can cause its peer to retire connection IDs by sending a NEW_CONNECTION_ID frame with an increased Retire Prior To field. sent by a server.  Servers MAY choose to only send a preferred address of one address family by sending an all-zero address and port (0.0.0.0:0 or ::.0) for the other family.  IP addresses are <del> encoded in network byte order.  The CID Length field contains the length of the Connection ID field. </del>  The active connection ID limit is an integer value specifying the maximum number of connection IDs from the peer that an endpoint is

This is some of the editorial changes from . Maybe we can take these and then continue that with fewer changes.
At the moment, states: I think these paragraphs have two issues: It begins with "once handshake is finished," but I am not sure if we define when a handshake finishes. I think we should change this to "once handshake is confirmed," as that is the point where we allow connection migration. It states that the connection ID to be used on the new path would be the one using TP.preferredaddress. I think that is incorrect, as the server might have requested retirement of the CID provided by that transport parameter by the time the client initiates (or finishes) path validation. It should either be an address provided by that transport parameter, or an unused CID provided by the server (i.e. through NCID frames). In fact, when the server sends an NCID frame with Retire Prior To set to a value greater than 1, the client should not be using the CID provided by the transport parameter. Note that this problem is orthogonal to , because these actions in relation to preferred address happens at some point after the handshake is confirmed.
This text precedes a clear definition of handshake confirmed, so I strongly agree with your first suggested clarification. I can imagine cases when the CID should be based on the path, so I'm less clear on the correct behavior for the second point. By definition, if the CID provided in the transport param has been retired it should not be used to initiate path validation. But I'm unclear if the preferred behavior is don't migrate if you haven't already initiated migration or to use one of the newer CIDs.
NAME Thank you for your comments. First of all, let me state that we do not need to forbid such a design. However, in a design that expects CIDs to be specific to the server address being used, a server cannot issue a new CID until the migration to the preferred address completes. This is because if a server sends a NCID frame from the original server address before the client completes migration to the preferred address, the server cannot tell if the client would use that issued CID on the original path (this happen when the client fails to migrate to the preferred address), or if it uses that CID on the migrated path. Therefore, this issue does not have any effect on such a design. The question at stake is the client behavior we want to recommend, when the server sends a new CID (or retires CIDs) before migration to the preferred address completes. My view is that TP.preferredaddress is a mechanism of specifying an alternative IP address, that "happens" to also carry a new CID, so that the client can always have an unused CID in hand when it initiates migration to the preferred address. It is actually simple to implement in such way. What you would do is this: When receiving TP.preferredaddress, store preferredaddress.CID exactly the same way as you would do when receiving an NCID frame, and separately store preferredaddress.IPaddress. When handshake is confirmed, if preferredaddress.IPaddress is stored, initiate a migration to that address, using a CID from a bucket that holds the unused CIDs. As stated above, such a design would work perfectly fine with servers issuing CIDs specific to server addresses, because a client would have only one unused CID to pick from when talking with such a server. The design is also simpler than having special case code that associates preferredaddress.IPaddress and preferredaddress.CID, and handles retirement cleanly.
It's a little trickier for servers. Let's say, hypothetically, that the server knows it's using a different CID pool on the alternative address; it picks one for the public address in the handshake, then gives one from the preferred address's CID pool in the TP. It doesn't send NCID frames right away. That's fine. But let's say the client doesn't migrate successfully. At some point, the server wants to start issuing the client new CIDs for one or the other of the paths, but QUICv1 doesn't have a way to identify path-bound CIDs. So the server has to give up on the preferred address if it can't handle CIDs coming to either interface. There isn't a defined cut-off for when the server should give up expecting the connection attempt, so the best way to do this is to put RPT=2 in the NCID frames. It seems cleaner initially to special-case the CID from the Preferred Address, and say that you have to use that specific CID to do the migration. But even with that code, the server has to do the same thing: wait to see whether the client migrates or not before issuing new CIDs. I think the piece we're missing is that when a client declares a migration unsuccessful, it MUST/SHOULD retire the CID it used to attempt the migration. That gives the server a clear signal, succeed or fail; it can then proceed to issue CIDs for the surviving server address.
Good suggestion on retiring the CID immediately if the migration was unsuccessful. That does provide a clearer signal than the ambiguity we have today.
NAME That's a keen observation. I personally favor the idea of using the RETIRECONNECTIONID frame to indicate if the client has finished migration to the preferred address or if it would continue using the original address. However, such change would require every client to recognize TP.preferredaddress, in sense that even a client lacking support intentional migration would be required to signal the retirement of the CID associated to TP.preferredaddress. I think we have considered until now that a client that does not implement migration (or migration to preferred address) to simply ignore this transport parameter. We need to be clear about that.
I think this is only particularly relevant to SPA, since that's the only time that a server is expecting a migration. Definitely worth being clear about what to do if you also disable migration, but implementations that disable migration still need 99% of the rest of the machinery for other purposes, requiring them to immediately send retire for the TP.preferredaddress seems okay, if slightly less convoluted than having some "ignore this if that" statement around the whole thing.
As requested, I'm forwarding regarding: This implies a semantic to the retirement of connection IDs that is not already defined. It says that in addition to releasing the resource, the server can say definitively that those other network paths won't be used. But this is misleading because retiring CID 1 does not prevent CID 4 from being used on that path. Nothing says that the connection IDs sent in NEWCONNECTION_ID have to be used on one or other path. Better to keep the requirement where it is: don't migrate back if you use a preferred address. Yes, that means that servers can't be sure of behaviour of clients here, but they can use the destination address to confirm acceptance of the preferred address or not. That should suffice.
NAME I think that the point being missed here is that under a given condition the server can determine the path being used for CID4. If the CID4 is issued prior to the client choosing the path, it is true the server would not be able to determine the path on which CID4 will be used. But if the server withholds NCID frames until the client chooses the path and notifies its choice by retiring one of the first two CIDs, then the server can tell for sure. Because either of the first or the second CID would be retired first, and that tells the server the path the client has chosen. As we agree, we already state that a client cannot come back to the original address, once it migrates to the preferred address. The benefit of requiring such retirement is that the server can issue CIDs specific to the server address, as pointed out by URL Assuming that such server deployment would be within our design scope, I think requiring such retirement is not a bad idea.
If the migration is unsuccessful, the client must never try the preferred address again (MUST continue sending all future packets....). So as NAME says, the server can issue address specific (not path-specific) CIDs once it knows whether the client's migration succeeded or failed. But NAME is correct that this is implicitly sending a signal about failed migration. If the server sees a CID retired, apparently never used, it might be able to infer that the CID was used for a failed migration, but this case is unique in that the server needs to take some action for a failed migration and uses this signal to do it.
So far, we haven't tied the server's knowledge of the client's address to anything about a CID, and a lot of the attacks against migration involve an actor on the network changing the path & address from which the server thinks the packets are coming, even if the client does not actually change anything. I haven't thought this all the way through yet, but it seems as though trying to have the server issue "address-specific" CIDs leads us into some potentially tricky territory.
NAME made me realize the nature of the hazard here. It seems fairly natural for an server to bind connection IDs to the current socket address. That is part of why we have new connection IDs attached to the preferred address and the forced retirement. After all, if the server uses a different target address, the hash at a load balancer could change and cause packets to be badly routed. Not including the target address in the hash would work, but it would force connection IDs to be bigger. But this all assumes that you never expect the server to migrate. If the server ever wanted to migrate, then the client is stuck with a bunch of unusable connection IDs for its own migrations. We decided that only clients can initiate migration in this version, which might mean that we don't need to worry about this case. We decided not to allow server migration primarily to avoid having to deal with the complex problems that ICE handles. But it was only the lack of mechanisms, not a structural constraint. Now it seems like we have a structural constraint that would prevent server migration. At a minimum, it seems like we should probably acknowledge this constraint somehow.
The summary of our discussion over dinner last night is that there are various mechanisms a server could employ, but they all depend on one key point: Both endpoints are under joint control, because they're cooperating to handle the connection. Therefore, it's possible to generate a CID that each endpoint will be able to work with. (There are various approaches to generating such a CID, which are implementation-specific; one or more approaches might be described in the QUIC-LB draft.) That means we don't need to separate CIDs by endpoint; if the migration is successful, the server could choose to issue CIDs that aren't valid on the handshake endpoint, which is now out of the picture.
Seems like a great task for V2: define a policy for handling CID. Or we could try do that in V1 and ship in 2021.
Discussed in ZRH. Proposed resolution is to close with no action. New Editorial issue to be opened to explain the intended use of and the CID it contains.
An important point to capture here is that the server is responsible for ensuring that the connection IDs are not bound to one or other address at this stage (if migration is successful, later ones might be). Thus, the connection ID in the transport parameter is no different to any other connection ID; it only exists here because using the new address is unusable without more connection IDs being available.
NAME Please do note that this issue points out other editorial concerns (see the original problem statement). I'd prefer seeing them being addressed to (and we have a WIP text in ).
My hope was that the changes in would help with the editorial parts of the . However, that seems to be stalled. I am going to mark this as proposal ready and will open an editorial issue to track the remaining work on clarifying the text. To provide abundant clarity: the proposed resolution to the design issue is to make no substantive changes to the protocol.
To echo : I think was going to be replaced with something a bit more condensed, which means that the result here will be "no design changes to be made, editorial text clarified via and ".

be used again and requests that the peer replace it with a new connection ID using a NEW_CONNECTION_ID frame.  <ins> As discussed in migration-linkability, endpoints limit the use of a connection ID to a single network path where possible.  Endpoints SHOULD retire connection IDs when no longer actively using the network path on which the connection ID was used. </ins> An endpoint can cause its peer to retire connection IDs by sending a NEW_CONNECTION_ID frame with an increased Retire Prior To field. sent by a server.  Servers MAY choose to only send a preferred address of one address family by sending an all-zero address and port (0.0.0.0:0 or ::.0) for the other family.  IP addresses are  <ins> encoded in network byte order. The Connection ID field and the Stateless Reset Token field contain an alternative connection ID that has a sequence number of 1 (issue-cid).  Having these values bundled with the preferred address ensures that there will be at least one unused active connection ID when the client initiates migration to the preferred address. </ins> The active connection ID limit is an integer value specifying the maximum number of connection IDs from the peer that an endpoint is


8. <del> This document has no IANA actions.  Yet. </del>  9.  References

Let's use the text from Section 9.1 of RFC 8126 with no embellishment.

8.  <ins> This document has no IANA actions. </ins> 9.  References


maximum ack delay.  Packets that are not ack-eliciting are only acknowledged when an ACK frame is sent for other reasons. <del> When sending a packet for any reason, an endpoint should attempt to </del>  bundle an ACK frame if one has not been sent recently.  Doing so helps with timely loss detection at the peer.

I believe this always should have been normative, so this change is editorial, but I'll let the editors confirm that.

maximum ack delay.  Packets that are not ack-eliciting are only acknowledged when an ACK frame is sent for other reasons.  <ins> When sending a packet for any reason, an endpoint SHOULD attempt to </ins> bundle an ACK frame if one has not been sent recently.  Doing so helps with timely loss detection at the peer.


acknowledged, an endpoint SHOULD declare an earlier packet lost if it was sent a threshold amount of time in the past.  To avoid declaring packets as lost too early, this time threshold MUST be set to at <del> least kGranularity.  The time threshold is: </del>  If packets sent prior to the largest acknowledged packet cannot yet be declared lost, then a timer SHOULD be set for the remaining time.

Adds more text about what the constant kGranularity represents where it's first used and changes a SHOULD to a RECOMMENDED. Inspired by
This seems reasonable.

acknowledged, an endpoint SHOULD declare an earlier packet lost if it was sent a threshold amount of time in the past.  To avoid declaring packets as lost too early, this time threshold MUST be set to at  <ins> least the local timer granularity, as indicated by the kGranularity constant.  The time threshold is: </ins> If packets sent prior to the largest acknowledged packet cannot yet be declared lost, then a timer SHOULD be set for the remaining time.


smaller than the min_rtt.  This limits the underestimation that a misreporting peer can cause to the smoothed_rtt. <del> On the first RTT sample for a network path, the smoothed_rtt is set to the latest_rtt. </del> smoothed_rtt and rttvar are computed as follows, similar to RFC6298. <del> On the first RTT sample for a network path: </del>  On subsequent RTT samples, smoothed_rtt and rttvar evolve as follows: acknowledged, an endpoint SHOULD declare an earlier packet lost if it was sent a threshold amount of time in the past.  To avoid declaring packets as lost too early, this time threshold MUST be set to at <del> least kGranularity.  The time threshold is: </del>  If packets sent prior to the largest acknowledged packet cannot yet be declared lost, then a timer SHOULD be set for the remaining time. has not yet caught up. The RECOMMENDED time threshold (kTimeThreshold), expressed as a <del> round-trip time multiplier, is 9/8. </del>  Implementations MAY experiment with absolute thresholds, thresholds from previous connections, adaptive thresholds, or including RTT 5.2.2. <del> The initial probe timeout for a new connection or new path SHOULD be set to twice the initial RTT.  Resumed connections over the same network SHOULD use the previous connection's final smoothed RTT value as the resumed connection's initial RTT.  If no previous RTT is available, the initial RTT SHOULD be set to 500ms, resulting in a 1 second initial timeout as recommended in RFC6298. </del>  A connection MAY use the delay between sending a PATH_CHALLENGE and receiving a PATH_RESPONSE to set the initial RTT (see kInitialRtt in <del> ld-consts-of-interest) for a new path, but the delay SHOULD NOT be considered an RTT sample. Until the server has validated the client's address on the path, the amount of data it can send is limited to three times the amount of data received, as specified in Section 8.1 of QUIC-TRANSPORT.  If no data can be sent, then the PTO alarm MUST NOT be armed until datagrams have been received from the client. Since the server could be blocked until more packets are received from the client, it is the client's responsibility to send packets to unblock the server until it is certain that the server has finished its address validation (see Section 8 of QUIC-TRANSPORT).  That is, the client MUST set the probe timer if the client has not received an acknowledgement for one of its Handshake or 1-RTT packets, and has not received a HANDSHAKE_DONE frame. </del>  Prior to handshake completion, when few to none RTT samples have been generated, it is possible that the probe timer expiration is due to keys indicates forward progress and the loss detection timer might have been set for a now discarded packet number space.  5.2.3. When a server receives an Initial packet containing duplicate CRYPTO other packet number spaces with in-flight data, coalescing packets if possible.  When the PTO timer expires, and there is new or previously sent unacknowledged data, it MUST be sent. can be established by consecutive PTOs, but since the PTO timer is reset when a new ack-eliciting packet is sent, an explicit duration must be used to account for those cases where PTOs do not occur or <del> are substantially delayed.  This duration is computed as follows: </del>  For example, assume: scenario would illustrate persistent congestion: The first three packets are determined to be lost when the <del> acknowlegement of packet 4 is received at t=8.  The congestion period is calculated as the time between the oldest and newest lost packets: (3 - 0) = 3.  The duration for persistent congestion is equal to: (1 * kPersistentCongestionThreshold) = 3.  Because the threshold was reached and because none of the packets between the oldest and the newest packets are acknowledged, the network is considered to have experienced persistent congestion. </del>  When persistent congestion is established, the sender's congestion window MUST be reduced to the minimum congestion window

As pointed out in , I think that we should assume same amount of RTT variance when there is no RTT sample and when there is one sample, because variance is unknown in both cases. This PR makes that change while keeping the initial PTO being the same (i.e. 1 second), and slightly simplifies the timeout logic.
NAME Thank you for the review. Would you ming checking if the updated text has what you have suggested?
NAME NAME Thank you for the suggestions. I've applied them.
For each connection, the loss detection timeout is defined as 2 InitialRTT. I wonder if this should be 3 InitialRTT instead, because, once you have one RTT sample, PTO is calculated as 3 * InitialRTT. I do not think we have a reason to believe that the variance of RTT (which applies as a multiplier here) increases when we obtain the first sample? PS. The other benefit of making these two consistent is that we can get rid of in SetLossDetectionTimer. It would be possible to always rely on PTO calculation.
I wasn't sure if this would be design or not, but now that the PR is complete, I believe it's editorial, as NAME PR changes the pseudocode, but proscribes exactly the same behavior as the existing text.
To be precise, there is one thing that does change, and that is the initial timeout when a connection is resumed on the same path. At the moment, the only variable we recommend to remember is SRTT, and when resuming a connection on the same path, use 2 SRTT as the initial timeout. The proposed change increases that to 3 SRTT. I think this is better than status-quo, assuming that we agree that RTTVAR should be 2 * sample when there is only one known sample.
Thanks for pointing that out, I noticed that the first time around and then forgot. I'll let NAME make the call about whether he thinks this is design then.
LGTM - thanks, NAME Can you resolve the merge conflict?

smaller than the min_rtt.  This limits the underestimation that a misreporting peer can cause to the smoothed_rtt.  smoothed_rtt and rttvar are computed as follows, similar to RFC6298.  <ins> When there are no samples for a network path, and on the first RTT sample for the network path: Before any RTT samples are available, the initial RTT is used as rtt_sample.  On the first RTT sample for the network path, that sample is used as rtt_sample.  This ensures that the first measurement erases the history of any persisted or default values. </ins> On subsequent RTT samples, smoothed_rtt and rttvar evolve as follows: acknowledged, an endpoint SHOULD declare an earlier packet lost if it was sent a threshold amount of time in the past.  To avoid declaring packets as lost too early, this time threshold MUST be set to at  <ins> least the local timer granularity, as indicated by the kGranularity constant.  The time threshold is: </ins> If packets sent prior to the largest acknowledged packet cannot yet be declared lost, then a timer SHOULD be set for the remaining time. has not yet caught up. The RECOMMENDED time threshold (kTimeThreshold), expressed as a  <ins> round-trip time multiplier, is 9/8.  The RECOMMENDED value of the timer granularity (kGranularity) is 1ms. </ins> Implementations MAY experiment with absolute thresholds, thresholds from previous connections, adaptive thresholds, or including RTT 5.2.2.  <ins> Resumed connections over the same network MAY use the previous connection's final smoothed RTT value as the resumed connection's initial RTT.  When no previous RTT is available, the initial RTT SHOULD be set to 333ms, resulting in a 1 second initial timeout, as recommended in RFC6298. </ins> A connection MAY use the delay between sending a PATH_CHALLENGE and receiving a PATH_RESPONSE to set the initial RTT (see kInitialRtt in  <ins> pto-handshake) for a new path, but the delay SHOULD NOT be considered an RTT sample. </ins> Prior to handshake completion, when few to none RTT samples have been generated, it is possible that the probe timer expiration is due to keys indicates forward progress and the loss detection timer might have been set for a now discarded packet number space. <ins> 5.2.2.1. Until the server has validated the client's address on the path, the amount of data it can send is limited to three times the amount of data received, as specified in Section 8.1 of QUIC-TRANSPORT.  If no additional data can be sent, the server's PTO alarm MUST NOT be armed until datagrams have been received from the client, because packets sent on PTO count against the anti-amplification limit.  Note that the server could fail to validate the client's address even if 0-RTT is accepted. Since the server could be blocked until more packets are received from the client, it is the client's responsibility to send packets to unblock the server until it is certain that the server has finished its address validation (see Section 8 of QUIC-TRANSPORT).  That is, the client MUST set the probe timer if the client has not received an acknowledgement for one of its Handshake or 1-RTT packets, and has not received a HANDSHAKE_DONE frame. </ins> 5.2.3. When a server receives an Initial packet containing duplicate CRYPTO other packet number spaces with in-flight data, coalescing packets if possible. <ins> If the sender wants to elicit a faster acknowledgement on PTO, it can skip a packet number to eliminate the ack delay. </ins> When the PTO timer expires, and there is new or previously sent unacknowledged data, it MUST be sent. can be established by consecutive PTOs, but since the PTO timer is reset when a new ack-eliciting packet is sent, an explicit duration must be used to account for those cases where PTOs do not occur or  <ins> are substantially delayed.  The rationale for this threshold is to enable a sender to use initial PTOs for aggressive probing, as TCP does with Tail Loss Probe (TLP) RACK, before establishing persistent congestion, as TCP does with a Retransmission Timeout (RTO) RFC5681. The RECOMMENDED value for kPersistentCongestionThreshold is 3, which is approximately equivalent to two TLPs before an RTO in TCP. This duration is computed as follows: </ins> For example, assume: scenario would illustrate persistent congestion: The first three packets are determined to be lost when the  <ins> acknowledgement of packet 4 is received at t=8.  The congestion period is calculated as the time between the oldest and newest lost packets: (3 - 0) = 3.  The duration for persistent congestion is equal to: (1 * kPersistentCongestionThreshold) = 3.  Because the threshold was reached and because none of the packets between the oldest and the newest packets are acknowledged, the network is considered to have experienced persistent congestion. </ins> When persistent congestion is established, the sender's congestion window MUST be reduced to the minimum congestion window


contain the final size of the stream.  Setting this bit indicates that the frame marks the end of the stream. <del> An endpoint that receives a STREAM frame for a send-only stream MUST terminate the connection with error STREAM_STATE_ERROR. </del>  The STREAM frames are shown in fig-stream.

I assume this has been the intention all along, but I do not find normative language in the current draft.

contain the final size of the stream.  Setting this bit indicates that the frame marks the end of the stream.  <ins> An endpoint MUST terminate the connection with error STREAM_STATE_ERROR if it receives a STREAM frame for a locally- initiated stream that has not yet been created, or for a send-only stream. </ins> The STREAM frames are shown in fig-stream.


pseudo-header fields after fields, <del> an invalid sequence of HTTP messages, or </del>  <del> the inclusion of uppercase field names. </del>  A request or response that includes a payload body can include a "content-length" header field.  A request or response is also 10. The security considerations of HTTP/3 should be comparable to those <del> of HTTP/2 with TLS; the considerations from Section 10 of HTTP2 apply in addition to those listed here. </del>  <del> TODO: This is going to be a big import, probably worthy of its own PR. </del>  <del> When HTTP Alternative Services is used for discovery for HTTP/3 endpoints, the security considerations of ALTSVC also apply. </del>  <del> 10.1. </del>  Where HTTP/2 employs PADDING frames and Padding fields in other frames to make a connection more resistant to traffic analysis, terms of the granularity of padding, how padding is arranged in relation to the information that is being protected, whether padding is applied in the case of packet loss, and how an implementation <del> might control padding. </del>  <del> 10.2. </del>  Several protocol elements contain nested length elements, typically in the form of frames with an explicit length containing variable- implementer.  An implementation MUST ensure that the length of a frame exactly matches the length of the fields it contains. <del> 10.3. </del>  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. <del> 10.4. </del>  Certain HTTP implementations use the client address for logging or access-control purposes.  Since a QUIC client's address might change are relevant or explicitly accept that the original address might change.  11. This document registers a new ALPN protocol ID (iana-alpn) and

The follow-on to ; depends on . DO NOT MERGE before lands. Imports most of the HTTP/2 Security Considerations text, modulo updates to reference HTTP/3 or http-core concepts. Suggestions on a more sensible structuring of concerns welcome; this still feels like a bit of a grab-bag.
There's a few places where it seems the new "field" terminology is applied inconsistently, but given the PR dependency chain maybe that will shake out. I'll take another look once other things have landed.
Given the chain, this PR should consistently use the new terminology. If I missed something, please flag it.
HTTP-core is making some terminology changes that HTTP/3 should track: In URL, "non-final" responses are "interim" responses In URL, terminology around header, field, value is changing Related to , but this is more than just pointing at the new documents.
NAME these comments/suggestion address my earlier one about use of headers. I might be applying things wrong.Looks good to me. Thanks for doing the hard work here.

pseudo-header fields after fields,  <ins> an invalid sequence of HTTP messages, </ins>  <ins> the inclusion of uppercase field names, or the inclusion of invalid characters in field names or values </ins> A request or response that includes a payload body can include a "content-length" header field.  A request or response is also 10. The security considerations of HTTP/3 should be comparable to those  <ins> of HTTP/2 with TLS.  However, many of the considerations from Section 10 of HTTP2 apply to QUIC-TRANSPORT and are discussed in that document. </ins>  <ins> 10.1. </ins>  <ins> HTTP/3 relies on the HTTP definition of authority.  The security considerations of establishing authority are discussed in Section 11.1 of SEMANTICS. </ins>  <ins> 10.2. The use of ALPN in the TLS and QUIC handshakes establishes the target application protocol before application-layer bytes are processed. Because all QUIC packets are encrypted, it is difficult for an attacker to control the plaintext bytes of an HTTP/3 connection which could be used in a cross-protocol attack on a plaintext protocol. 10.3. The HTTP/3 field encoding allows the expression of names that are not valid field names in the syntax used by HTTP (Section 4.3 of SEMANTICS).  Requests or responses containing invalid field names MUST be treated as malformed (malformed).  An intermediary therefore cannot translate an HTTP/3 request or response containing an invalid field name into an HTTP/1.1 message. Similarly, HTTP/3 allows field values that are not valid.  While most of the values that can be encoded will not alter field parsing, carriage return (CR, ASCII 0xd), line feed (LF, ASCII 0xa), and the zero character (NUL, ASCII 0x0) might be exploited by an attacker if they are translated verbatim.  Any request or response that contains a character not permitted in a field value MUST be treated as malformed (malformed).  Valid characters are defined by the "field- content" ABNF rule in Section 4.4 of SEMANTICS. 10.4. Pushed responses do not have an explicit request from the client; the request is provided by the server in the PUSH_PROMISE frame. Caching responses that are pushed is possible based on the guidance provided by the origin server in the Cache-Control header field. However, this can cause issues if a single server hosts more than one tenant.  For example, a server might offer multiple users each a small portion of its URI space. Where multiple tenants share space on the same server, that server MUST ensure that tenants are not able to push representations of resources that they do not have authority over.  Failure to enforce this would allow a tenant to provide a representation that would be served out of cache, overriding the actual representation that the authoritative tenant provides. Pushed responses for which an origin server is not authoritative (see connection-reuse) MUST NOT be used or cached. 10.5. An HTTP/3 connection can demand a greater commitment of resources to operate than an HTTP/1.1 or HTTP/2 connection.  The use of field compression and flow control depend on a commitment of resources for storing a greater amount of state.  Settings for these features ensure that memory commitments for these features are strictly bounded. The number of PUSH_PROMISE frames is constrained in a similar fashion.  A client that accepts server push SHOULD limit the number of Push IDs it issues at a time. Processing capacity cannot be guarded as effectively as state capacity. The ability to send undefined protocol elements which the peer is required to ignore can be abused to cause a peer to expend additional processing time.  This might be done by setting multiple undefined SETTINGS parameters, unknown frame types, or unknown stream types. Note, however, that some uses are entirely legitimate, such as optional-to-understand extensions and padding to increase resistance to traffic analysis. Compression of field sections also offers some opportunities to waste processing resources; see Section 7 of QPACK for more details on potential abuses. All these features - i.e., server push, unknown protocol elements, field compression - have legitimate uses.  These features become a burden only when they are used unnecessarily or to excess. An endpoint that doesn't monitor this behavior exposes itself to a risk of denial-of-service attack.  Implementations SHOULD track the use of these features and set limits on their use.  An endpoint MAY treat activity that is suspicious as a connection error (errors) of type H3_EXCESSIVE_LOAD, but false positives will result in disrupting valid connections and requests. 10.5.1. A large field section (request-response) can cause an implementation to commit a large amount of state.  Header fields that are critical for routing can appear toward the end of a header field section, which prevents streaming of the header field section to its ultimate destination.  This ordering and other reasons, such as ensuring cache correctness, mean that an endpoint likely needs to buffer the entire header field section.  Since there is no hard limit to the size of a field section, some endpoints could be forced to commit a large amount of available memory for header fields. An endpoint can use the SETTINGS_MAX_HEADER_LIST_SIZE (settings- parameters) setting to advise peers of limits that might apply on the size of field sections.  This setting is only advisory, so endpoints MAY choose to send field sections that exceed this limit and risk having the request or response being treated as malformed.  This setting is specific to a connection, so any request or response could encounter a hop with a lower, unknown limit.  An intermediary can attempt to avoid this problem by passing on values presented by different peers, but they are not obligated to do so. A server that receives a larger field section than it is willing to handle can send an HTTP 431 (Request Header Fields Too Large) status code RFC6585.  A client can discard responses that it cannot process. 10.5.2. The CONNECT method can be used to create disproportionate load on an proxy, since stream creation is relatively inexpensive when compared to the creation and maintenance of a TCP connection.  A proxy might also maintain some resources for a TCP connection beyond the closing of the stream that carries the CONNECT request, since the outgoing TCP connection remains in the TIME_WAIT state.  Therefore, a proxy cannot rely on QUIC stream limits alone to control the resources consumed by CONNECT requests. 10.6. Compression can allow an attacker to recover secret data when it is compressed in the same context as data under attacker control. HTTP/3 enables compression of fields (header-formatting); the following concerns also apply to the use of HTTP compressed content- codings; see Section 6.1.2 of SEMANTICS. There are demonstrable attacks on compression that exploit the characteristics of the web (e.g., BREACH).  The attacker induces multiple requests containing varying plaintext, observing the length of the resulting ciphertext in each, which reveals a shorter length when a guess about the secret is correct. Implementations communicating on a secure channel MUST NOT compress content that includes both confidential and attacker-controlled data unless separate compression dictionaries are used for each source of data.  Compression MUST NOT be used if the source of data cannot be reliably determined. Further considerations regarding the compression of fields sections are described in QPACK. 10.7. Padding can be used to obscure the exact size of frame content and is provided to mitigate specific attacks within HTTP, for example, attacks where compressed content includes both attacker-controlled plaintext and secret data (e.g., BREACH). </ins> Where HTTP/2 employs PADDING frames and Padding fields in other frames to make a connection more resistant to traffic analysis, terms of the granularity of padding, how padding is arranged in relation to the information that is being protected, whether padding is applied in the case of packet loss, and how an implementation  <ins> might control padding.  Redundant padding could even be counterproductive. </ins>  <ins> To mitigate attacks that rely on compression, disabling or limiting compression might be preferable to padding as a countermeasure. Use of padding can result in less protection than might seem immediately obvious.  At best, padding only makes it more difficult for an attacker to infer length information by increasing the number of frames an attacker has to observe.  Incorrectly implemented padding schemes can be easily defeated.  In particular, randomized padding with a predictable distribution provides very little protection; similarly, padding payloads to a fixed size exposes information as payload sizes cross the fixed-sized boundary, which could be possible if an attacker can control plaintext. 10.8. </ins> Several protocol elements contain nested length elements, typically in the form of frames with an explicit length containing variable- implementer.  An implementation MUST ensure that the length of a frame exactly matches the length of the fields it contains.  <ins> 10.9. </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.10. </ins> Certain HTTP implementations use the client address for logging or access-control purposes.  Since a QUIC client's address might change are relevant or explicitly accept that the original address might change. <ins> 10.11. Several characteristics of HTTP/3 provide an observer an opportunity to correlate actions of a single client or server over time.  These include the value of settings, the timing of reactions to stimulus, and the handling of any features that are controlled by settings. As far as these create observable differences in behavior, they could be used as a basis for fingerprinting a specific client. HTTP/3's preference for using a single QUIC connection allows correlation of a user's activity on a site.  Reusing connections for different origins allows for correlation of activity across those origins. Several features of QUIC solicit immediate responses and can be used by an endpoint to measure latency to their peer; this might have privacy implications in certain scenarios. </ins> 11. This document registers a new ALPN protocol ID (iana-alpn) and


6.2. <del> When a client receives a Version Negotiation packet, it MUST abandon the current connection attempt.  Version Negotiation packets are designed to allow future versions of QUIC to negotiate the version in use between endpoints.  Future versions of QUIC might change how implementations that support multiple versions of QUIC react to Version Negotiation packets when attempting to establish a connection using this version.  How to perform version negotiation is left as future work defined by future versions of QUIC.  In particular, that future work will need to ensure robustness against version downgrade attacks; see version-downgrade. </del>  6.2.1.

The text was a little too broad. This breaks up the text and adds two caveats to handling rules: If you have handled another packet, then assume that everything is good. If you the VN includes QUIC v1, then assume that something got corrupted. The issue addresses the second, but I think we were implying the first for some time.
The "packet corruption" test that NAME added to the interop-runner has surfaced an issue about version number corruption. Suppose that a long header packet is corrupted in transit, and that the corruption changes the value of the version number. Per spec, the server will respond with a version number negotiation packet. In the test, we observe that when clients receive such packets, they abandon the connection attempt. Obviously, this is a rare event. We don't know how frequent packet corruption is today. The measured "1 packet in 5000". In theory, only one in 2^16 of these corruptions would not be detected by the UDP checksum, leading to undetected corruption in 1 in 300 million packets. That's a very small number, but I don't know whether the 1997 measurements are still valid. They could be higher. I also don't know whether the loss patterns would be detected by the UDP checksum, since some middleboxes appear to rewrite these checksums. Faced with exactly the same issue, we decided recently to add a checksum to the Retry packets. Clients can protect against such corruptions by checking the incoming VN packets for sanity. If the server issues a VN in response to a corrupted packet, the list of supported versions will include the version proposed by the client. That should normally never happen. Ignoring such bogus VN does protect against most version number correction issues. Should we point that out in the spec?
We used to require that a client ignore Version Negotiation packets that include the version the client attempted. NAME edited that out. Maybe we should restore it. That pushes the odds of failure to 1 in 90 quadrillion. Would that help? I want to say that this is a design issue.
The problem with the "one in 300 million" figure is that it relies on the UDP checksum. If you cannot trust the UDP checksum, the figure is "1 in 5000", which is more concerning. And there are scenarios in which uncontrolled middle-boxes do rewrite that checksum.
I'd support restoring that check that NAME edited out. Anecdotally, we've found packet corruption to be very rare and basically a non-issue. Or maybe something in the network or the host is just dropping them, but we don't receive them in userspace.
I'm fine with adding this check back in. It's not onerous to implementers. This is a design issue.
Now if we were really paranoid, we would consider the possibility that the VN packet itself is corrupted.
I did. If we don't trust the UDP checksum on either, the error rates are still pretty high, but either checksum bumps it up to 1.5 trillion and both is 90 quadrillion.
I was thinking about that -- it doesn't need paranoia, it's just a question of how often -- and in this case the chances are vanishingly small.
To clarify the statement, that happened when we decided to move version negotiation to an extension. The states that - I'd be supportive of adding a sentence such as
I support the change. Does the general concern about security proofs that is driving us to authenticate the CIDs also apply to the version field?
NAME The security concerns do apply to the version field, our current plan to address that is .
NAME Thanks, but is it sufficient for this to be an extension? What if the base spec isn't provably secure?
NAME there's still time for us to make the use of that extension mandatory before we ship, but I'd suggest having that discussion on a separate issue.
Works for me.

6.2.  <ins> Version Negotiation packets are designed to allow future versions of QUIC to negotiate the version in use between endpoints.  Future versions of QUIC might change how implementations that support multiple versions of QUIC react to Version Negotiation packets when attempting to establish a connection using this version. A client that supports only this version of QUIC MUST abandon the current connection attempt if it receives a Version Negotiation packet, with the following two exceptions.  A client MUST discard any Version Negotiation packet if it has received and successfully processed any other packet, including an earlier Version Negotiation packet.  A client MUST discard a Version Negotiation packet that lists the QUIC version selected by the client. How to perform version negotiation is left as future work defined by future versions of QUIC.  In particular, that future work will ensure robustness against version downgrade attacks; see version-downgrade. </ins> 6.2.1.


has not yet caught up. The RECOMMENDED time threshold (kTimeThreshold), expressed as a <del> round-trip time multiplier, is 9/8. </del>  Implementations MAY experiment with absolute thresholds, thresholds from previous connections, adaptive thresholds, or including RTT A connection MAY use the delay between sending a PATH_CHALLENGE and receiving a PATH_RESPONSE to set the initial RTT (see kInitialRtt in <del> ld-consts-of-interest) for a new path, but the delay SHOULD NOT be considered an RTT sample. </del>  Until the server has validated the client's address on the path, the amount of data it can send is limited to three times the amount of can be established by consecutive PTOs, but since the PTO timer is reset when a new ack-eliciting packet is sent, an explicit duration must be used to account for those cases where PTOs do not occur or <del> are substantially delayed.  This duration is computed as follows: </del>  For example, assume:

Moves some RECOMMENDEDs up and removes others which are already elsewhere in the draft and reference those sections. This was intended to address , which I can still do in this PR if NAME would like, but I still think of Min CWND and Initial CWND as constants per-connection, even if they're not truly constants.

has not yet caught up. The RECOMMENDED time threshold (kTimeThreshold), expressed as a  <ins> round-trip time multiplier, is 9/8.  The RECOMMENDED value of the timer granularity (kGranularity) is 1ms. </ins> Implementations MAY experiment with absolute thresholds, thresholds from previous connections, adaptive thresholds, or including RTT A connection MAY use the delay between sending a PATH_CHALLENGE and receiving a PATH_RESPONSE to set the initial RTT (see kInitialRtt in  <ins> pto-handshake) for a new path, but the delay SHOULD NOT be considered an RTT sample. </ins> Until the server has validated the client's address on the path, the amount of data it can send is limited to three times the amount of can be established by consecutive PTOs, but since the PTO timer is reset when a new ack-eliciting packet is sent, an explicit duration must be used to account for those cases where PTOs do not occur or  <ins> are substantially delayed.  The rationale for this threshold is to enable a sender to use initial PTOs for aggressive probing, as TCP does with Tail Loss Probe (TLP) RACK, before establishing persistent congestion, as TCP does with a Retransmission Timeout (RTO) RFC5681. The RECOMMENDED value for kPersistentCongestionThreshold is 3, which is approximately equivalent to two TLPs before an RTO in TCP. This duration is computed as follows: </ins> For example, assume:


pto-handshake) for a new path, but the delay SHOULD NOT be considered an RTT sample. <del> Until the server has validated the client's address on the path, the amount of data it can send is limited to three times the amount of data received, as specified in Section 8.1 of QUIC-TRANSPORT.  If no data can be sent, then the PTO alarm MUST NOT be armed until datagrams have been received from the client. Since the server could be blocked until more packets are received from the client, it is the client's responsibility to send packets to unblock the server until it is certain that the server has finished its address validation (see Section 8 of QUIC-TRANSPORT).  That is, the client MUST set the probe timer if the client has not received an acknowledgement for one of its Handshake or 1-RTT packets, and has not received a HANDSHAKE_DONE frame. </del> Prior to handshake completion, when few to none RTT samples have been generated, it is possible that the probe timer expiration is due to an incorrect RTT estimate at the client.  To allow the client to keys indicates forward progress and the loss detection timer might have been set for a now discarded packet number space.  5.2.3. When a server receives an Initial packet containing duplicate CRYPTO

By not arming the alarm, we avoid the possibility of having the alarm expire, send nothing, and then be immediately re-armed over and over again. We've hit bugs like that a few times in our implementation, which is why I prefer this structure of not setting the alarm if you can't send anything.
From NAME in , this is not a nit: than three times as many bytes as the number of bytes they have received. I think that the intent was that the server limit its sending, including PTO.
Discussed in ZRH. Proposed resolution is "yes".
Repeating what I said earlier: it's not clear to me that not being able to arm the PTO timer when at the limit implies that PTO packets count against the amplification limit. The reason is that you could have already armed the PTO timer at the time you hit the limit. E.g., you might be streaming 0.5RTT and so send Initial, Handshake, arm timer, 0.5RTT.
NAME the PTO should be updated when an ack-eliciting packet is sent, since the right edge moves, so that case should already be handled correctly. I'll write an editorial PR to try to make all this extra clear. Actually, the PTO section already has "A sender computes its PTO timer every time an ack-eliciting packet is sent."
Not sure how to split the difference between the limit applying to the packet that was just sent (causing PTO to be armed) or the packet that arming the PTO timer implies. Personally, I would have put the test at the point that you send a packet, but that's not how this is structured, and you do have "because packets sent on PTO count against the anti-amplification limit", which captures the important concept neatly.

pto-handshake) for a new path, but the delay SHOULD NOT be considered an RTT sample.  Prior to handshake completion, when few to none RTT samples have been generated, it is possible that the probe timer expiration is due to an incorrect RTT estimate at the client.  To allow the client to keys indicates forward progress and the loss detection timer might have been set for a now discarded packet number space. <ins> 5.2.2.1. Until the server has validated the client's address on the path, the amount of data it can send is limited to three times the amount of data received, as specified in Section 8.1 of QUIC-TRANSPORT.  If no additional data can be sent, the server's PTO alarm MUST NOT be armed until datagrams have been received from the client, because packets sent on PTO count against the anti-amplification limit.  Note that the server could fail to validate the client's address even if 0-RTT is accepted. Since the server could be blocked until more packets are received from the client, it is the client's responsibility to send packets to unblock the server until it is certain that the server has finished its address validation (see Section 8 of QUIC-TRANSPORT).  That is, the client MUST set the probe timer if the client has not received an acknowledgement for one of its Handshake or 1-RTT packets, and has not received a HANDSHAKE_DONE frame. </ins> 5.2.3. When a server receives an Initial packet containing duplicate CRYPTO