Original text:
PADDING and PING frames MAY appear in packets of any encryption level. <del> CRYPTO and CONNECTION_CLOSE frames MAY appear in packets of any encryption level except 0-RTT. </del>  ACK frames MAY appear in packets of any encryption level other than 0-RTT, but can only acknowledge packets which appeared in beyond the Error Code.  This SHOULD be a UTF-8 encoded string RFC3629.  19.20. QUIC frames do not use a self-describing encoding.  An endpoint

Reviewer comments:
As discussed in , use of application-level close is problematic in Initial / Handshake packets. This PR address the issue by limiting the use of that to 1-RTT packets, at the same time advising applications to use user_canceled TLS alert to abort the connection during the handshake.
At the moment, CONNECTIONCLOSE (0x1d) (i.e. the application-level close) is allowed "in packets of any encryption level except 0-RTT." (). I think that it cannot be sent or received in an Initial or Handshake packets, as only the QUIC and TLS are interacting at that layer. Note that the application protocol might not have been determined when Initial / Handshake packets are exchanged (before the server returns the EE indicating which application protocol has been chosen).
Two choices: no problem: let the application decide to use Handshake to abort a connection it doesn't want any more. problem: force the application to use 1-RTT packets to express its discontent. I can see reasons for either.
As stated above, I think option 1 is problematic in sense that the endpoint might not know which application protocol it is taking, though the error code being used is specific to the application protocol. Even for Handshake packets it is hard to tell if the peer has learnt the application protocol in use, as CRYPTO frames and CONNECTION_CLOSE might get delivered out of order.
Another issue with option 1 is that we might leak sensitive data. Consider the case where a server receives a coalesced Initial / 0–RTT packet, and decides to respond with an application-close as the outcome of processing the 0-RTT application data. In this case, application-close should not be sent in Initial packet, as it would be an act of an application responding to the client in cleartext.
I believe I prefer 2, but if the application tries to close the connection with an application close before any data can be sent in 1-RTT, advise the transport close the connection at Initial or Handshake instead? I believe that both honors the intent of the application and maintains some separation.
NAME Agreed. And for that purpose, I think transport close with an error code of 0x15a (TLS alert usercanceled) fits naturally. Quoting from , this alert notifies the recipient that the sender is canceling the handshake for some reason unrelated to a protocol failure.
I disagree with making a change. We discussed this exact topic when we allowed CONNECTION_CLOSE in Initial, and it was decided at the time that if an application wanted to terminate the connection at this stage, it may do so.
NAME Would you mind pointing out the link to the discussion? Besides, if we are to allow CONNECTION_CLOSE in Initial / Handshake, I think we need to clarify how an endpoint selects the error code (see URL) and how it is interpreted (URL). I think that would be more complex to implement and maintain than limiting the use of application-level close to 1-RTT.
I can't seem to find the discussion right now. I believe a lot of it was in person at the time as well. Things may be difficult in some scenarios, but we can leave the decision on how to handle things up to the application layer. For the 0-RTT scenario, you can use a generic application error code. There's no ALPN ambiguity there, right, since they have to use the same one as previous? I don't think we should add extra restrictions at the transport layer.
Would it make sense to allow connection-level CONNECTION_CLOSE in 0-RTT, while forbidding it in Initial and Handshake?
NAME I assume you mean application-level CONNECTIONCLOSE in 0-RTT. I do not have a strong opinion on that, though I might point out that allowing that creates a wired state; a client would might send CONNECTIONCLOSE before it knows if the server has accepted or rejected 0-RTT. The question here is if the client is required to retransmit CONNECTION_CLOSE in 1-RTT when the server rejects 0-RTT. Considering that, I think we can just forbid the use of application-close in 0-RTT. The client can either wait for the handshake to complete then send an application-level close in 1-RTT, or it can close the connection using a QUIC-level close during the handshake (without exposing why).
NAME Thank you for checking. The problem is that then there would be two requirements: Application protocols MUST define a generic error code that can be exposed unencrypted. QUIC stacks need to provide an API that tells the application if a CONNECTIONCLOSE frame is going to be sent using Initial packets. Each application needs to consult that in order to determine the error code to be sent. There is. The case is where there is an error in the 0-RTT data that the client sends, and the server responds with a CONNECTIONCLOSE frame. In this case, there is no ambiguity on the server-side, as the server has committed to processing a particular application protocol. However, there is an issue on the client-side. The client cannot tell which application protocol has been selected, as the CONNECTION_CLOSE frame (carried in either Initial or Handshake) may arrive before it receives the Encrypted Extensions handshake message that carries the name of the negotiated protocol. We can say that a server sending an application-level close before the client sends an 1-RTT packet is an alternative way of negotiating the protocol. But are we really going to say that? I think it's too complicated.
I agree with NAME there is an age old discussion on handling errors in early connection. Simply forbidding them without understanding this would be unfortunate. Consider errors in early handshake where the server does not wish to proceed. It doesn't like the client, it doesn't have the resources, it is about to reboot, or whatever. It can either drop the packets or wait if it cannot close immediately. It doesn't want to wait so it drops. Drops then probably results in unnecessary resets.
NAME That's not what this issue is about. CONNECTION_CLOSE using a transport error would still be permitted in Initial and Handshake. This is only about application-level error codes.
Ah I see. I agree that application errors should not happen at that level.
So with improved understanding, I think the correct approach is for send a PROTOCOL_VIOLATION the other direction, but since that is kind of pointless, all you can do it is to report a protocol violation the client. It would be wrong to forward the error code because that would promote bad behaviour and assumptions on application level design that might not hold in general.
NAME For the first bullet, that's a decision for the application protocols. For the second, I'd argue you don't need any special API. If the app terminates the connection before the "handshake complete" notification then any error code it would pass down to the QUIC API would not be sent in a 1-RTT packet. That state is enough for the app to make an intelligent decision. Another option is to have an API flag that indicate the application close is allowed to be send unencrypted. If you assume this is not the common case, then I don't think we need optimize for it. The app just closes the connection after the handshake completes.
NAME At the very least, the transport draft needs to suggest that, otherwise an application protocol might leak information through APPLICATIONCLOSE frames carried in Initial packets. Requiring such interaction seems more complicated than just forbidding the use of APPLICATIONCLOSE in Initial and Handshake packets in favor of CONNECTIONCLOSE(usercanceled). How a QUIC stack enforces that is an implementation issue, but for example, when an application instructs the QUIC stack to close the connection, the stack can decide which of the two frames to send based on the current state. I'd dispute that this is an optimization. This is about addressing a security concern that we have so far left open. PS. I agree that apps willing to postpone the closing of a connection can do so, please see the PR.
Maybe we need to add an API recommendation (not requirement) that says that "an application could be allowed to close a connection during handshake using a transport connection close frame". This is similar to my earlier suggestion that an application could be allowed to reject incoming connections because the server is busy or whatever - this suggestion was not accepted as a requirement because it isn't necessary, but it is still a good thing to support. The question is then, how is the connection taken down - i.e. silently or with a transport close?
NAME Ok. You've convinced me that it is just easier (and probably safer) to restrict application close to 1-RTT. Though, I'd like to point out, there's nothing preventing an implementation from doing it anyways.
I agree with that observation. I think the specification does not clarify what should be done when a CONNECTIONCLOSE frame is invalid. For example, we do not define how an endpoint should react when it receives a CONNECTIONCLOSE frame with a Reason that goes beyond the end of the packet payload. I think there are two logical ways of handing the situation: a) An endpoint that receives an invalid CONNECTIONCLOSE frame acts as if it received a valid CONNECTIONCLOSE frame. b) An endpoint that receives an invalid CONNECTIONCLOSE frame responds with a CONNECTIONCLOSE frame carrying an appropriate error code. We can mandate either a or b, or leave it up to the implementors (saying nothing in the text). Leaving it unspecified is a valid option here because the connection would promptly close regardless. Anyways, I think the problem deserves a separate issue, if some of us are interested in mandating a specific behavior. Personally, I do not care.
To NAME point b) Transport section 11.1 An endpoint that receives an invalid CONNECTION_CLOSE frame MUST NOT signal the existence of the error to its peer.
Clients don't send GOAWAY and when a client sends one, the server treats it as HTTPUNEXPECTEDFRAME. By contrast, servers don't send MAXPUSHID but the client treats receiving one as HTTPMALFORMEDFRAME. By contrast, if GOAWAY is sent on !control it's HTTPUNEXPECTEDFRAME, but if MAXPUSHID is sent on !control, it's HTTPWRONGSTREAM. I think the answers here are HTTPUNEXPECTEDFRAME and HTTPWRONGSTREAM respectively, but we should be consistent.
I think you're correct. There are some situations where newer errors would be more appropriate.

Revised text:
PADDING and PING frames MAY appear in packets of any encryption level.  <ins> CRYPTO frames and CONNECTION_CLOSE frames signaling errors at the QUIC layer (type 0x1c) MAY appear in packets of any encryption level except 0-RTT. CONNECTION_CLOSE frames signaling application errors (type 0x1d) MUST only be sent in packets at the 1-RTT encryption level. </ins> ACK frames MAY appear in packets of any encryption level other than 0-RTT, but can only acknowledge packets which appeared in beyond the Error Code.  This SHOULD be a UTF-8 encoded string RFC3629. <ins> The application-specific variant of CONNECTION_CLOSE (type 0x1d) can only be sent using an 1-RTT packet (QUIC-TLS, Section 4).  When an application wishes to abandon a connection during the handshake, an endpoint can send a CONNECTION_CLOSE frame (type 0x1c) with an error code of 0x15a ("user_canceled" alert; see TLS13) in an Initial or a Handshake packet. </ins> 19.20. QUIC frames do not use a self-describing encoding.  An endpoint