In , NAME noted that the concatenation approach used to combine multiple PSKs is not well validated. At the 2021-10-04 interim, we discussed moving from this approach to a "cascaded HKDF" approach, with the idea of doing the same thing here as the main key schedule. This PR implements that suggestion. One nice implication of the algorithm here is that when there are no PSKs, it produces the zero vector as a degenerate case. So you could implement it pretty cleanly:
The spec currently doesn't distinguish much between an external and a member commit w.r.t. how proposals by reference are handled. In theory, the server could keep track of proposals and send the external committer all proposals that were sent up until the time they want to commit externally. However, there are a few difficulties in committing proposals by reference in an external commit. Membership tags: The external committer doesn't have the ability to verify membership tags on proposals. This is probably not a show-stopper, as proposals still need to be signed by a group member, but if nothing else, this should be noted in the spec. Leaf positioning: The spec was changed such that the committer is added into the tree without an Add proposal being present. If there are other Add (and Remove) proposals present, it is unclear in what order they should be processed. Should the committer be added when the path is processed? Or should it still be processed as if it were an Add? There are probably other things that can go wrong when committing referenced proposals externally, but those are the first that come to mind. The simplest solution would be to simply ignore proposals by reference, although this would break the current principle that a commit must always include all valid proposals that were sent during the epoch. The consequence would be that Parties that sent proposals will have to re-send them if the epoch is ended by an external commit. It should be noted, that a Preconfigured sender will have no way to detect if their proposal was committed or not.
I'm not sure your worries here are very real: Membership tags: I don't think the membership tag issue matters, because the existing members will reject the proposal (and thus the commit) if the membership tag is invalid; at worst the joiner gets themselves wedged into a group by themselves. And as you say, they're signed, and they keys can be tied back to the tree hash in the GroupInfo. Leaf positioning: We need to clear this up regardless of proposals-by-reference. It seems like basically having a synthetic Add at the beginning or end of the proposal list would work fine. Preconfigured proposer: This is a problem only when Commits are encrypted, so it's not a problem in the external commit case (since an external Commit cannot be encrypted). Nonetheless, the point that an external joiner can't fully verify a Proposal seems to argue pretty strongly for forbidding proposals by reference. I don't think this even violates the semantic of "include all valid proposals received in the epoch", since (1) that requirement is receiver oriented (it's not enough for the proposals to have been sent) and (2) the external joiner can't tell if the proposals or valid. Overall, I would lean slightly toward removing proposals by reference. And if we do that, it might even make sense to just define an ExternalCommit message that packages together the few things that are still allowed.
I'm not sure I understand how the receiver-orientation of the "all valid proposals must be included" rule matters to its application to this case. If we remove the requirement for external commits to include proposals by reference and don't soften that rule to exclude external commits, receivers will consider all external commits invalid in the presence of valid pending proposals by reference. In any case, I'm also in favor of keeping it simple and restrict the External Commit to a few, well defined components that do not include any proposals by reference.
Discussion on working call: "No proposals by reference" kind of follows from "all valid proposals", since the joiner can't necessarily determine that proposals are valid Maybe allow attempts? Might not get feedback to retry, though. Whole point of external commit is to allow async. Agreement to prohibit proposals by reference in external Commit
I believe there's a small mismatch between the text and the Key Schedule figure. The text says that if there are no PSK proposals, the should be a zero-length string, while the Key Schedule figure seems to indicate that it's a string of zeros of length .
Fixed in
One brief comment, otherwise looks good!


The definition of blank node needs to move ahead into ## Ratchet Tree Terminology as it is used in the section immediately following (## Views of a Ratchet Tree).


This PR introduces a notion of a proposal type being "path safe", in the sense that it is safe for the path to be omitted. (Better phrasing welcome!) The field is required default, and allowed to be omitted only if all the proposals in the Commit are path-safe.
Thanks for taking care of this fix! One suggestion: Instead of having a global list of which types are path safe and which ones aren't, we could make it a property of the proposal. Maybe something like this: Regarding the name, the only thing I can think of is that we could invert the property and call it : In fact, I think I'd be slightly in favor of the latter proposal.
Discussion on virtual interim: OK to keep using type TODO(NAME "path safe" -> "path required" Otherwise clear to merge
The authoritative definition (in Section ) of when to include a is not the same as the summary below. For example, if an implementation adds a custom proposal type, according to the summary, a path is required, whereas according to the definition above it, no path is required. Ultimately, it depends on the nature of the proposal and the desired security guarantees if a path is required. However, I think the safer option is to require a path by default and maybe leave it to the definition of the respective custom proposal to explicitly specify if a path is not required.


Based on , it should be made explicit that sub-group branching requires a fresh key package for each member. I think there is still a conflict of wording here that I would like feedback on because of this line (which is what triggered me thinking there was a bug)
Thanks for the PR and thanks for digging into this. I think what the sentence is meant to say is that the receiver MUST check that the members of the new group are a subset of the members of the old group at that specific epoch. Since we're using new LeafNodes in the new group and we don't have a fixed notion of identity (at least not within MLS), we have to refer to the application/the AS to ensure that the identity expressed by the credential in each LeafNode in the subgroup has a corresponding identity (i.e. one that represents the same party) expressed by a credential in a LeafNode in the original group. Maybe something like: This leaves the possibility of a collision, where the identifiers in a given leaf in the new group would be valid updates to more than one member in the old group. This should probably be forbidden in general, although outside of this case it's not really a problem, because updates are always specific to one leaf. Alternatively, we could somehow include LeafNodeRefs that specifically indicate which member represents which other member. Or just leave it up to the application to sort this all out.
Curious what you think about saying the the credential has to be equal? We can determine equality of the credential itself just by doing tls serialization.
Sure, requiring the credential to be exactly the same would be a simple way to figure out if it's the same party in both groups. However, I can imagine that some applications will want distinct credentials for each group. If we mandate that credentials are the same, this would force the application to use the same credential in every key package/LeafNode. Since subgroups can potentially be branched at any time and in any group, all key packages that a party publishes then have to have the same credential as all of the groups that party is in, so another party branching a subgroup can get hold of a KeyPackage with the same credential as the group the subgroup is branched off of. Personally, I'd prefer asking the application to check that the identities of the new group are a subset of the identities of the original group.
I can see that being an issue, although I am somewhat worried about us not being opinionated in same way about how to determine equality here, mainly because of interop type things. We punt on how to validate the credentials as well in general so I guess this is in line with prior decisions
Hey NAME and NAME check the change I just posted. NAME and I put that together, it's a bit tough to get a generic definition of equivalency.
There is currently a logical flaw in the subgroup branching flow. In order to branch, you need to compose a message . A Welcome message includes which is defined as follows: The problem with this is that the field of type cannot be made for existing members of the group because the leaf nodes are now types. is now only used to add new members to the group, so we can't reuse this for sub group initialization.
I'm not sure I understand correctly. Are you suggesting when branching a group off of another group, one re-uses the leaves of the old group? If so, that's not how it's meant to work. The new group created in the process of subgroup branching should be independent from the "parent" group safe for the PSK, which all member have to provide upon creation of the group. In fact one should never use the same key material in the leaves of multiple groups except when the supply of key packages that a party provides via the DS is exhausted (see Section 17.4).
That was part of my question. When there were key packages in the leaves, you theoretically could fork the group from a given point in time and reuse the current tree, but I wasn't sure if that was a proper solution or not. Sounds like it isn't, but either way, I think there should be clarification in the document about this. Let me write up a quick PR on it.
NAME opened URL Would like your feedback there. We have conflicting wording here as well. What threw me off in the first place was the line indicating that LeafNodes should be the same in the sub-group, and if we are fetching new key packages they will not be the same.
NAME Konrad, could you safely derive new keys for the members of the subgroup instead? If so, that would be an enormous increase in efficiency and (especially in the federated case) make group creation timing consistent enough to do dynamically.
NAME Nice idea! But it's tricky, since we're dealing with public key pairs that the creator of the new group doesn't know the private keys for. If we were only using HPKE schemes that support updateable public key encryption, the creator of the new group could indeed update the key in the existing group and send the "delta" encrypted to each corresponding member (which would still represent an overhead, but we might be able to optimize this somehow). This is similar to the RTreeKEM mechanism that NAME et al. proposed at some point. To go all the way here, we'd have to do the same for the public key material in the credentials as well, which is not something we had considered before. It would be a new primitive, something like an updateable signature scheme? Probably something to consider for HPKE/MLS 2.0. We could also allow the re-use of LeadNodes for this particular case. It would be the simpler solution and I don't think it would be catastrophic, since we have to assume that LeafNode (or KeyPackage) key material is re-used in the "last resort" scenario anyway, but this would definitely be something we should discuss on the mailing list, as it would break a pretty basic assumption we've made until now.
Just catching up on this. NAME nice catch here. It seems like we have two options here: Require new KeyPackages (as in ) Invent some way to make a new group based on LeafNodes alone I probably agree that we should do (1) in the short term, but (2) also has some appeal -- it doesn't feel right to require new KeyPackages for folks you're already in touch with -- despite it probably being more spec text. So here's a sketch of (2) in the interest of completeness. Suppose we defined a message of the following form: ... which would pretty closely parallel to Commit. You would send it in MLSMessage and require a in MLSMessageAuth. Where Commit instructs the recipient to create a new epoch from the current one within the same group, Branch would instruct the recipient to create a new epoch that is the first epoch of a new group. In both cases, you would form an epoch / GroupContext as specified, then use the to ratchet into the new epoch. I'm pretty sure that would work and have the expected security properties, though obviously formal analysis would be appreciated. The main awkardness that occurs to me is that any members joining after the branch would have to provide new key packages. And obviously, it would require a fair bit more spec text. All that said, though, the exercise of working all that out has increased my impression that we should do (1) for now. If we want something like (2) later, it can be done pretty cleanly in an extension.
Fixed by
Well put!


I drew this to help explain the relevant patch in MLSpp to someone, and thought it might be helpful to other folks.
Excellent! This is a very nice addition to explain the design introduced .


I'm not sure if this is actually necessary, but there usually seem to be issues in Merkle trees when the leaves are not explicitly distinct from the parents.
I would find it nicer to describe things with a like in other parts of the protocol: It is equivalent to your modification. Stating things that way ensures there is no ambiguity: TreeHashInput is parseable, which is I think the property you want to ensure with this PR.
Use enum instead and then ready for merge.
Interim 2022-05-26: Nicer with an NAME to update to , then merge
When computing the tree hash, should there be an indicator bit for whether the value being hashed represents a leaf or parent node?
This doesn't seem like a terrible idea. You could either add a field to LeafNodeHashInput and ParentNodeHashInput, or define an enum and do the same, or make a . For example:


Also covers the PSK part of
Interim 2022-05-26: Ready to go once merge conflicts are resolved.
The collection of structs could be less chatty. Suggest PreSharedKeys is only used once, can be inlined in GroupSecrets Use GroupContext for the internals of GroupInfo
Note: was fixed in In , we did: So only the following remain: Use GroupContext for the internals of GroupInfo
currently described at time of use. In the "Pre-Shared Keys" section, we should just specify that it must always be a fresh value of length The ReInit provisions require , but those for branching do not. Presumably these should be the same? Currently, the processing rules for PreSharedKey proposal says “MUST be external”. This seems overly restrictive; for example, it rules out the scenario in Figure 8. Suggest “MUST NOT be branch/reinit” instead.


The enum was missing the limit. The wasn't using a TLS environment.
NAME shouldn't ResumptionPSKUsage include (255)? enum { reserved(0), application(1), reinit(2), branch(3), (255) } ResumptionPSKUsage;
Fixed in
Seems reasonable to me.


Interim 2022-12-08: Distinguish "time-invalid" from "invalid" There is a slightly more general issue: When catching up, there might be messages sent by members whose credentials were valid when the message was sent, but have since expired Recommend accepting messages from members with time-invalid credentials when catching up
I think a note on consistency would work a little better in the architecture document, especially because it applies in the general case as well as the time-varying case. I filed URL
Latest change uses "expired" to refer generically to all time based reasons for a transition. (So, excluding , as NAME suggests.)
We ran into this case, which does not seem to have a defined solution within the RFC and I'm wondering if this ambiguity should be resolved at the RFC level, or be left up to the application. Existing group with Alice, and Bob. Bob had a valid certificate upon being added to the group by Alice. Bob's certificate expires . Alice does a commit to add Charlie to the group. Bob accepts the valid commit to add Charlie. Charlie follows 8.3 and validates the tree. In this process if certificate expiration is enforced, then Charlie would drop the Welcome, which would not be optimal. Solution 1: Leave this behavior up to the application, consider it part of the AS. Solution 2: Add an explicit mention that credentials that expire should be considered valid after expiration once they are a member of the group. Solution 3: A committer MUST validate that all existing credentials in the tree are still considered valid at the time a commit is made. If an existing credential is no longer considered valid, then the resulting commit MUST contain a Remove proposal for the leaf containing that credential.
Of course it would be better to have the same credential validation algorithm, but at the protocol level, there is no reason to constrain all clients to that, applications can enforce a consistent policy across clients. So, in my mind this is not symmetric between senders and receivers and solutions 1 and 3 are not incompatible, however 2 is not acceptable IMO. I would suggest the following: Alice MUST verify that she considers all credentials valid before adding Charlie and act if it is not the case (Solution 3) Charlie MUST check all the credentials upon joining and act according to its authentication policy* Charlie MUST NOT send any application message before she considers all credentials valid
Are you suggesting that it should not be a requirement that each member of the group has the same validation algorithm? That would create forks in any case that individuals disagree on a particular credential's validity. We can of course punt to the application, but I feel like that should at least be cautioned against?
I don't quite see how the validation algorithms are different. They are the same, just executed at different points in time. Or am I missing something here? In any case it would be unfortunate if Charlie couldn't send any messages until someone committed to their proposal. So I would propose to change point 2 of the algorithm suggested by NAME to the following: Charlie MUST check all the credentials upon joining. If any group members have expired credentials, Charlie MUST issue a commit to remove the affected members.
I am saying that it is not a requirement. Most applications will want to use the same algorithm, but the protocol doesn't need to make that mandatory, it is just a convenience for applications. The protocol is designed so that each client can make its own choice. It would not create forks, as for example the case Alice decides to remove Bob, Bob is removed for everyone. I would be - ok - with the change proposed by Konrad but we need to be aware that this is a restriction on what clients can decide with respect to authentication (especially in a context where there is no AS).
I think it's also important to remember that there may be a delay between when a user is sent a Welcome and when they process it. So when a user processes a Welcome, the first epoch they see after joining may be quite old and have expired credentials for that reason, and this epoch may not even be the most recent epoch anymore. So I would lean towards solution 2 actually, of "members that are joining a group just need to ignore expired credentials." And if the application wants to have a policy of not sending new messages to a group if there are expired credentials present, then that's fine but not something we need to require in the spec. Edit: Or maybe we do want to specify that, but I'd be clear that it's a check done before sending new messages, not part of processing the Welcome.
Although I started the dialog here around certificates and expiration, I think maybe it is useful to ignore the idea of "expired" credentials for now, because I agree with the comment by NAME that we should try to avoid being opinionated in that regard. I think what I am trying to get across is that regardless of what credential system you use that the protocol can only operate without forks if the validation of that credential is consistent across all members while also taking into account the asynchronous nature of the protocol. If each member of the group has the same state for epoch N, they should all independently determine that new credentials presented as part of a commit are valid or invalid regardless of when they process that commit. I'm viewing a credential being invalid as a trigger for rejecting a commit, which maybe in itself is a bias. Not sure if the above belongs in the protocol, the AS definition, or no where at all :-). If everyone thinks this is too restrictive of a statement I'm fine with that. Maybe a better solution to this issue is that there could be a separately defined standard way of dealing with certificates within MLS that can be more opinionated than the base protocol and introduce concepts discussed above like MUST remove of expired certs on commit, use an external packet timestamp for consistent expiration checking on the receive side, etc. I think a lot of applications would benefit from that and NAME NAME and I have been doing a lot of experimentation in that area.
After a couple long chats with NAME and NAME here's where I stand on this. A guiding thought I have is this: IMO creds aren't just for getting in to a group. They should also be necessary for continued participation. After all MLS is designed for sessions that can last years. That means, I'm uneasy with an MLS design that allows me to join a group (epoch even) where someone has an invalid (e.g. expired) cred. Worse yet, I don't want MLS to let me send application messages to users with invalid creds at the time of my sending. With that in mind, how about this approach which tries to leave as much freedom to an app/AS while still giving guidance to an app/AS that wants to ensure valid creds are needed for ongoing participation in a group and, optionally, that forks are being avoided as much as possible. MLS clients MUST validate the creds at all leaves in a ratchet tree (via the AS) in the following cases: a. Sending a commit (validation done on new epoch's tree after applying commit). b. Receiving a commit (validation done on new epoch's tree after applying commit). c. When joining a group. d. When client wants to send an application message. Based on this MLS now gives 3 guarantees. Call an application/AS "consistent" if it ensures that for any given epoch and cred in its ratchet tree, the result of any client validating the cred is the same (i.e. regardless of the time the validation is performed and which rule of a. b. or c. triggered it). (Edit: Just to further clarify "consistency" doesnt mean validating the same cred but when taken from different ratchet trees should have the same result. It only concerns validations pertaining to the same epoch but performed by different clients at different times.) 1) Regardless of the app/AS's consistency and any insider attacks, clients only ever join epochs in which all members have valid creds. 2) Regardless of the app/AS's consistency and any insider attacks, clients will never send application messages to receiver with invalid creds (at the time of sending). 3) If the app/AS is consistent, all parties follow the protocol and all packets are delivered by the DS then no forks will occur. My justification for this approach is that it strikes the following balance: On the one hand, we now give guidance to app/AS devs for how they can enforce (1-3) if thats what they want. On the other hand, we don't actually tie an app/AS's hands at all (e.g. compared to mandating almost no checks ever). That's because MLS says nothing about how cred validation should be implemented. Hard-coding always returning true is an MLS spec compliant AS. So if an app/AS doesn't want the constraints imposed by the above rules it is still free to hard-code those validations as returning true; i.e. it can skip the checks if it really needs to. In summary: the idea is basically to tell app/AS devs when/which are the right checks to perform to get good security & availability but also to leave it up to them to make those checks meaningful. Finally, regarding "consistency": in my mind creds are validated relative to a context (e.g. a time-stamp for checking expiration, a root CA cert, a blockchain or even particular block for checking wallets and/or balances, etc.) One way to get consistency is for the app/AS to ensure that all clients use the same context when checking the creds in a ratchet tree regardless of when they perform their checks.
In my mind, applications are only ever in one of two states: A.) I'm reading backlogged messages, or B.) I've read all the messages and I'm waiting for new ones. When you're in state B, you can send messages and also check time-related things. When you're in state A, you have no idea when the messages you're reading were truly sent. We don't currently provide any way to communicate a "context" against which time-related things should be validated in state B. So I don't see how we can implement b or c from this list?
Implementing b. and c. in some spec compliant way is actually trivial because the spec doesnt require "consistency" (in the sense that I defined that term in my previous post.) The only thing the spec says about consistency is that if you want guarantee (3.) then you need consistent cred validation. So e.g. always returning true when validating in cases b. and c. while doing something more interesting for cases (a.) would be fine; i.e. spec compliant. You just wouldn't necessarily get guarantee (3.) is all. But I figure what you meant is how to implement (a.) b. and c. to be consistent, right? And that'd be a good question that any app trying to avoid forks would have to think about. (Notice, the RFC already creates this concern for such apps with out my proposed changes because to avoid forks now you already need commit sender and receivers to agree on their validation.) In our opinion, we landed on leaving it up to the app/AS how it guarantees consistent validations because we think there are a bunch of different but reasonable ways this could be done. Yet, which of those is best (even among just the couple we could come with on the fly) depends on things like the app's security assumptions, communication models and access to third-party services, not to mention the details of how the AS works. To start off with, it wasn't even totally clear to us that the "context"-based syntax really applies to all reasonable AS designs. (Thats how we ended up on the notion of "consistency" as that is the more abstract and minimal guarantee we really needed to avoid forks. Context is just one way we thought of to get consistency.) But say the AS does conform to "context"-based syntax. Maybe the app/AS ensure consistency by only ensuring that the (possibly different!) contexts being used by clients still produce the same result. Further, even if we are more opinionated and assume an app/AS will ensure an identical context is used in all validation cases this still leaves room for many variations. Who chooses the context used for a given epoch? Maybe the commitor? Maybe the AS? Maybe the DS (which we already have to trust it to avoid forks)? Maybe a group-context extension? Also, how is the context distributed? Out-of band? By the AS? By the DS along with commit/welcome packets? Along with group contexts? Each made sense to us in the right setting... One thing we toyed with for a while was the requirement that the commitor authenticates the particular context C they used when creating a commit packet (e.g. by using C as associated data or a PSK or something). But, not only is that already pretty opinionated about how consistency should be enforced, it also wasn't clear to us what the actual point is. TBH at first I was for it at first out of instinct (and ignoring the being opinionated part for the moment), but NAME did a good job of convincing me that I couldn't actually justify it beyond "it feels right". What kind of, otherwise not possible, attack is this trying to defend against? In the end, the point is there were enough plausible but diverging options for how to get consistency that we didnt feel comfortable enforcing anything that constrained an app/AS's options. But it's not due to lack of ways to solve for consistency. We just didn't want to be opinionated in a way we couldn't STRONGLY justify.
Thanks NAME I'm not sure I agree with checking every time I send an application message as TLS sets a precedent for not checking in that case. I do agree though that I view each epoch as a session renegotiation of sorts, which means that you reevaluate if credentials you previously thought were valid are still valid according to whatever rules that are in place. I believe this conversation winds up in a place where its a very small statement in the main doc that explains how to avoid forks, and then an entirely separate document for each credential type (starting with certificates) that aims to set guidelines on how one could achieve various goals like not allowing expired certs etc. Im all for helping write that document as Wickr will be using the x509 system in production quite a bit.
Solution 4: Bob has to do an UpdatePath before his Credential expires with a LeafNode with a valid Credential (certificate). If he doesn't, the other members should Remove him from the group. Same goes if Bob's Credential is revoked.
NAME I think the core of the discussion at this point is more around the fact that the base protocol does not have any concept of "expiration" of credentials, so their behavior is undefined. Either we put an opinionated statement around it in the core protocol, or just punt to applications / another RFC. Thoughts on that?
Hi NAME , My opinion is that whatever credentials you have in an MLS group should be valid (including not expired, not revoked, not too early, etc.). Therefore it would logically follow: It is the responsibility of each member to make sure their LeafNode credentials are valid and consistent with the group policy at all times. Any member of the group that detects another member is out of policy or invalid can remove the offending member. To the extent possible a DS that detects that a member is out of compliance with group policy or no longer has valid credentials, can try to remove the member (ex: via an external proposal). The definition of "valid" could be done per-credential type or per profile.
Side note: under the general rubric of "validity changes over time", revocation is also an issue! Overall, my thinking aligns with what NAME says here. Saying that the credentials presented by group members should be valid at all times is a concise, clear standard. It is arguably implied by the current requirement to validate credentials on joining (if you also assume that the group should be joinable at any time). The more difficult question is how it is implemented in practical reality. I don't think we can really write hard requirements around this. The only remediation is Remove, and we don't have other situations where we say a client MUST send a specific proposal. In part because this might conflict with application-level policies about who is allowed to send Proposals/Commits. And it seems like there are valid situations where availability might trump authenticity (cf. browsers' click-through on expired certs). Overall, this seems like more of an operational question than a core protocol question here. In terms of how to reflect all this in documents: The details of how to manage credential freshness seem better suited for an implementation guidance document. (I believe NAME is gathering ideas for one?) In the MLS protocol spec, we could add some high-level guidance, something like the following: I think that would fit well either in the Credentials section (between "Credential Validation" and "Uniquely Idenitfying Clients") or in the security considerations.
We could use the sender_generation field of the MLS message to signal what was the global app generation of the sender. Functionally, this allows receivers to know if they received all previous application messages from the sender. In the current design, if S sent M0, M1, M2 you can know if you missed M1 when receiving M2, but you cannot know if you missed M2. There are many ways of doing this: global sender counter, counter between two operations of the sender...
We seemed in agreement at the interim because it prevents the application message suppression attacks
Looks great thanks NAME I like the separate treatment of expired vs. revoked. Looks good to me now!


URL This definition of a handshake message is being defined in terms of a Proposal or Commit object, neither of which are defined in this session. Furthermore, PublicMessage or PrivateMessage aren’t defined either as this point in the text. To improve clarity, consider defining these key terms. The use of "respectively" in the text is suggesting that PrivateMessages don’t have integrity protection which is not accurate. (see the Cryptographic Dependencies section of the HPKE specification for more information). Is this Section 4 of RFC9180?  If so, why not say that? What would be the circumstance where the application_id extension would be treated with equal standing as something from the AS?  Why can’t this be a "MUST"? Unsaid is what to do if the proposal list is not valid.  Please clarify. Is this normative behavior required, or could it be left up to application?  The mixing of updates/removes on the same node and the SHOULD guidance already means that this "recovery from an invalid node" may not be consistent across clients/DS-es. Same observation on recovering from ReInit with any other proposals. Is there any additional retry mechanism?  Let’s say the sender of the proposal doesn’t see it reflect in the epoch after it sent proposal.  And then not in the next epoch.  Does it try resending indefinitely? This setup seems a little simplistic. If the application data is encrypted to begin with, how does the adversary know the question being posed by Alice?


The draft states that Shouldn't be the result 6b2611489cba2b63a9e873e2 instead of 6b2611489cba2b63a9a873e2?
Yes, I submitted PR for this issue.


From PR from NAME
Let's do another editorial pass and evtl. some re-org first and then address this issue. I think we all already agreed that we only need one of the two. So we only need to decide with name we keep.
I prefer to use the name "Path ID", as it is more intuitive in the context of multipath.
The conflict was solved. LGTM.Sounds good.


Let's give the facts to the implementers, without providing too specific guidance neither. and may also address .
Just to not forget this, but this is mostly editorial. Probably present first the ACKMP frame, and then PATHABANDON and PATHSTATUS Drop reference to I-D.liu-multipath-quic as the PATHSTATUS frame is now integrated in the draft. Based on and , and If we agree on , we could also drop the text in PATh_ABANDON stating that on any path, not only the path on which the referenced Destination Connection ID is used.
We probably need to add some text in the scheduling considerations where control frames (PATHSTATUS, PATHABANDON, but also MAX_DATA,...) should be sent on a good working path, ideally with low latency.
I would say the same applies to ACK_MP
For ACK we recommend (SHOULD) to send it on the same path because of RTT calculations and I believe the scheduling text discusses at already.
Thinking about this some more. I believe we should say NOTHING. The point of the standard is to specify expected behavior when a node receives protocol messages from its peer. The basic statement is that most frames, including control frames, can be sent on any path -- the only exception being PATH CHALLENGE frames and PATH_RESPONSE frames. So my preference would be: 1) say nothing, because the purpose of standards is to specify requirements for Interop, not provide guidance to implementors. 2) if we do say something, just say that these frames can be sent on any valid path, based on preferences of the application and the implementation.
As for ACKMP frames, the text says "ACKMP frame (defined in ) SHOULD be sent on the same path as identified by the Path Identifier. However, an ACKMP frame can be returned via a different path, based on different strategies of sending ACKMP frames." That's an OK compromise, leaving open the possibility for implementations to use any strategy they want.
Alternatively, spell out the considerations to make when picking a path? Does it matter, if so, why? This would be useful to know when new frames come along in the future and need to think for themselves how multipath might affect them.
Sure, but at this stage we have preciously little experience. For example, I have a user request to specify affinity between a stream and a path. I can certainly provide them an API to do that, but this is basically research. And then, affinity applies to stream frames, but whether it should be applied to control frames is debatable. I am concerned that the guidance that we provide will be so generic as not to be useful, while still requiring long debates and delaying the publication of the draft. I think guidance is best provided in research papers, and possibly in text books.
I agree we shoukd avoid the potential time sink. I wasn't suggesting guidance but rather a fairly succinct statement like "The path that frames are sent on is a local choice, which could effect the behavior or performance of the QUIC transport or delivery of application data. There is no guidance provided by this document on strategies for path selection for frames beyond the control frames for the multipath extension itself." And perhaps gathering consensus on that now avoids people asking the question in later review rounds.
NAME I agree. Your proposal is basically equivalent to my suggestion that "if we do say something, just say that these frames can be sent on any valid path, based on preferences of the application and the implementation." Your text is good.
Sorry, I did read your comment and immediately paged it out. Credit to you for proposing that option earlier
Looks good, but the description of congestion is imprecise. Either drop it or fix it.


Each line is limited to 72 characters.
I verified that this PR produces the draft that we expect. I did one small change: remove spaces at the end of lines.
There are many isntances in the markdown source where a whole paragraph is entered in a single line of text. This makes the reviewing harder, and messes up the "what changed" view when reviewing PRs.
Just to be on the same page, how should we format the text? I see two possible ways. Hard-constraint the number of characters per line to a given limit (e.g., 80) Make one sentence = one line I have a slight preference for option 2, but I can of course live with the other or a mix of both.
I would prefer something like 72 characters, because the last 8 positions are used for numbering the cards in your deck.


My understanding is that if I am a receiver that wants to proactively cause a sender to stop sending, I should use the explicit way of sending a path abandon frame. The idle timeout handles the case when a receiver is unaware of a path change that leads to communication failure. If that is the purpose, as long as the receiver can receive packets in the sender-to-receiver direction and acknowledge on another path, it looks to me that we should still allow the sender to use that path. But I feel if we do that, we are diverging from the bidirectional nature of a path described in the draft, which worths more discussion.
I agree with NAME that if you want to close a path, you should use the abandon frame. This sentence covers the case where a path was idle for a while and therefore should be closed silently as it might not work anymore anyway. However, if you have received packet on the path or have an indication that packets you've sent on the path were received by the other end, the path is clearly not idle and working. I also agree with NAME that that means you only know for sure that the path is still working in one direction and that might need further discussion.
This was discussed in issue and PR .
Regarding the maxidletimeout, RFC9000 says "To avoid excessively small idle timeout periods, endpoints MUST increase the idle timeout period to be at least three times the current Probe Timeout (PTO)." As in multi-path QUIC, the PTO is per-path, when maxidletimeout is used to close a connection, it makes sense to set maxidletimeout = 3max(PTO1, PTO2). But when it comes to close a path, I am not sure if the timeout for path1 should also depend on the PTO of path 2?
I think there are two idle timeouts: the "connection idle timeout" and the "path idle timeout". For the "connection idle timeout", I agree that it should be at least 3PTO_max. Triggering this timeout closes the connection. For the "path idle timeout", this might be a way to have a stronger guarantee to stop using paths (closing them) after some inactivity than the text proposed in . Still, if we define such a mechanism, we need to know whether all paths should have the same path timeout value or not (in the latter case, a transport parameter might not be the best way).
I would avoid having a different timer for a path than for a connection. That would make the protocol more complex without a lot of benefits. If a host wants to use a shorter timeout per path than the connection timeout, it can simply define it and use a PATH_ABANDON frame to indicate that the path is abandoned. However, we need to be clearer in 3.2.3 to indicate that a path can be considered valid if either packets are received on that path or packets sent on that path are acknowledged (possibly on another path)
Presented at IETF-113 but no time for discussion.
I have submitted PR to resolve this issue according to the above discussions.
PR was merged but forgot to close issue...
In 3.2.3, we have the following paragraph: When more than one path is available, servers shall monitor the arrival of non-probing packets on the available paths. Servers SHOULD stop sending traffic on paths through where no non-probing packet was received in the last 3 path RTTs, but MAY ignore that rule if it would disqualify all available paths. To avoid idle timeout of a path, endpoints can send ack-eliciting packets such as packets containing PING frames Section 19.2 of [QUIC-TRANSPORT] on that path to keep it alive. Sending periodic PING frames also helps prevent middlebox timeout, as discussed in Section 10.1.2 of [QUIC-TRANSPORT]. This paragraph implicitly indicates that we consider a path to be active if we send and receive packets on this path. This definition works well for MPTCP where each data packet triggers an ACK on the same path. In MPQUIC, a server could have two paths that it uses to send data. However, the client might decide to always send the ACKs on only one path (e.g. the lowest delay one). In this case, the two paths are clearly active, but the server could consider the higher delay path to be inactive since it does not receive packets over this path. It could send PING frames to trigger data packets, but then it would need to send one PING every 3 RTT, which seams excessive. I would suggest to rewrite the text as follows (I removed Server, because I think the recommendation should be generic): When more than one path is available, hosts shall monitor both the arrival of non-probing packets and the acknowledgements for the packets sent over each path. Hosts SHOULD stop sending traffic on paths whether either: (a) no non-probing packet was received in the last 3 path RTT or (b) no non-probing packet sent over this path was acknowledged during the last 3 path RTT, but MAY ignore that rule if it would disqualify all available paths. To avoid idle timeout of a path, endpoints can send ack-eliciting packets such as packets containing PING frames Section 19.2 of [QUIC-TRANSPORT] on that path to keep it alive. Sending periodic PING frames also helps prevent middlebox timeout, as discussed in Section 10.1.2 of [QUIC-TRANSPORT].
The word "either" means that STOP sending becomes true If either (a) is true or (b) is true. If I understand correctly, the actual meaning is to use the word "both" here, instead. So stop sending if both (a) no non-probing packets received and (b) no packets was acknowledged.
Yes, I think this is a good clarification and yes "either" might be confusing here.
I commented on the PR. I don't like the idea that receivers would have to stop acknowledging packets in order to signal preference to not use a path. We need to think that a little more.
So we have a choice: either say that nodes should regularly send non-probing packets over the path that they want to keep using, or if we don't want that, accept the idea that acks on other paths show continuity, and require "abandon path" to terminate a path that is otherwise working, acknowledges packets, etc..
Yes, I think the second option is what we are aiming for with the new text. Stopping ack'ing packets to stop someone sending on a path is wrong and is something you should never do. If you receive a packet successfully you should ack it (also because that would otherwise confuse congestion control). But we have the abandon frame exactly for that case. However, it also seems wrong to me to require sending acks on the same path taen the data is received. So if you decide to always only send ack on one path and all payload data flows into one direction only over multiple paths, the current text would require you to send potentially unnecessary ping packets over each non-ack'ing paths or you would end up closing those paths which also is wrong as everything seems to work fine. So the question is really is, do we want to require pings in this scenario (payload traffic over multiple paths but acks only on one path) in order to ensure that all paths are usable bidirectionally? I think that's unnecessary overhead and believe Olivier's proposed addition is the correct thing to do.
I can look at what I implemented in picoquic: If there is no traffic in either direction for the idle period, the path is automatically abandoned. If traffic is sent and acked, or traffic is received, the path is not considered idle. This is pretty much what Olivier proposed. If there is a "timer loss", and if there is an alternate, the path is put in a low priority list. The node will send ping and try get an ack. If no ack is received after N trials, the path is abandoned. If "abandon path" frame is received, the path is abandoned.
I marked this issue as editorial as we have an editorial PR for it. However, I open a new issue for the per path idle timeout discussion (). Do we need another issue to clarify path closure or sending of keep-alive traffic? However this might be covered in other parts of the draft already...?
The modified text looks good.Looks good to meThe text looks good to me.


Let's be clearer about what it means to proxy here. Also, since it is the same text, talk about TLS assumptions more directly. This will interact poorly with . Sorry about that NAME
From Vienna chat. A proxy is not a generic HTTP intermediary, and therefore is not subject to the same rules.
From NAME Generic proxies would forward unknown HTTP headers between clients and targets. We should be very clear that oblivious proxies are not generic, and should not be doing things like forwarding unknown headers arbitrarily between client and target, or target and client.
requests. This includes the Via field, the Forwarded field {{?FORWARDED=RFC7239}}, and any similar information. A client does not depend on the proxy using an authenticated and encrypted connection to the oblivious request resource, only that information about the client not be attached to forwarded requests. Obviously, you need encryption on one or the other side of the proxy, right? Probably this document should require it on the inbound side, but I don't see the text that requires it. Did I just miss it?
Yeah, it's only necessary on the inbound side, per the analysis, though the document assumes it's on both sides.


NAME noted that it was important to note that the Gateway needs to be trusted if gateway != target. As a practical matter, that means that you probably want gateway == target.
Hi, Since the gateways are seeing request/response pairs, there is a potential here for a side-channel attack at the gateway itself or at a relay. (if I understood the draft correctly - if not, please clarify my misunderstanding here). Are there mitigations planned for this type of issue that you could expand on in the Security Considerations section perhaps?
Does the new text in address this concern?
I don't think so, because the claim is the gateway does not have intimate knowledge of the contents of the request/response. If that assertion is true, the gateway shouldn't be able to ascertain information about the actual contents from those pairs (directly or indirectly, via a side-channel attack). If the burden is on the application developer to ensure that request/response pairs traversing the gateway are to be resilient to side-channel attacks of those pairings, then it would be good to explicitly call that out in this spec (perhaps in the security considerations section?) Thoughts?
NAME the gateway necessarily sees all request and response pairs, and will therefore always learn information about each transaction. This is fundamental to the design. Whether or not the gateway uses that information for malicious purposes, e.g., by providing different responses to different requests for the same target resource, depends on the trust model. And the change in addresses that. I don't think further text is needed here.
"Finally, a relay can also generate responses, though it assumed to not be able to examine the content of a request (other than to observe the choice of key identifier, KDF, and AEAD), so it is also assumed that it cannot generate an Encapsulated Response." I feel that a relay could certainly perform a side-channel attack here as well using R/R pairings.
Can you please be more specific here? What type of attack could the relay do to violate client privacy that is not already documented in the draft?
Typically in HTTPs scenarios, it is more difficult to perform side channel attacks as a flow observer, especially if multiplexing of requests and responses are performed. In this design, if I understand it correctly, the requests and responses are being broken out into distinct exchanges that the relay can see and therefore it makes it easier for a relay to perform these types of attacks that would be more difficult had the relay not been used in the first place. If my understanding of the protocol is incorrect here, please feel free to point that out.
You seem to be describing fingerprinting attacks, wherein the observer uses features extracted from otherwise encrypted traffic to try and learn information about the underlying (unencrypted) data. Yes, the relay does have a more fine-grained view of each exchange compared to a network observer in this protocol since it can see explicit message boundaries, and that might impact the efficacy of these attacks. This is partly already addressed in the document: We could extend this to note the relay's view of message boundaries. I don't think any additional text is needed for the gateway.
Whatever you feel is best. I think some language around the message level risks this creates vs non-relay scenarios might be helpful. Does the client intentionally break things up in a way that crosses the underlying application message boundaries (e.g. more like packetization) with a relay? I have more questions about the gateway functionality that we can discuss separately.
This means that the client really needs to trust that the gateway doesn't muck with it. As a practical matter, this incentivizes clients to choose a gateway that is effectively the same entity as the target. They don't have to, but the amount of trust required of the gateway is higher if gateway and target aren't operated by the same entity. We touch on this, but we could be clearer about it.


I was just going to add something about correlating requests based on their content, but then it seemed like the text was duplicated some. Here is my attempt at making this better. I also expanded the notion of "configuration" to include the identity of the relay. That made it a little tricky - we really do want diversity of relays - but it makes more sense to talk about minimum anonymity set size rather than to imply that there can be a single configuration, because that is unrealistic.
Hi, The specification talks about State. Could I suggest that it explicitly mention 'or other user identifiable payloads.' Because things like JSON documents (e.g. a JWT) and SAML assertions are not 'cookies' but can carry information that might be user identifiable. Thoughts?
I think we've given short shrift to the way in which the content of requests can be used to link them. Perhaps some new text for Section 8...
NAME that text looks good to me Wanna toss up a PR?


Most of the text was correct in that we talk about making a request of a resource. Where the text refers to making requests of a server, those usages seem to be correct.
Section 5 details HTTP usage. It seems it's technically correct but the section closes with a sequence of paragraphs that jump back and forth between statements about how an Oblivious Gateway Resource treats requests or responses. Perhaps these could be consolidated to present requests first consistently. I.e. swap the final two paragraphs around
mnot recently made a PR (now merged) that replaced "requests of a server" with "requests to an origin server" and that feels both grammatically and technically more correct. However, there are still few cases peppered through the doc that use "requests of a server". I'd kindly ask the editors to revisit these and consider if they could be made more consistent with the above


I presume the idea here is to add jitter to the system (different delays for different requests) rather than compound delays on a single request? Even if not, this paragraph could probably benefit from some language fixups. There's a missing word and "might not be what some clients want" could benefit from a rephrasing to make it stronger.
clearer, thanks


This starts by moving the good text from to the "Errors" section, where I realized that it duplicated some of that language. It was more complete, so I merged the two. I then added some language about what happens to errors prior to decapsulation. This doesn't define an error signaling format. Though that might be useful, it is also a lot more work and I'm leery of scope creep in this document. My view: if you get an error, you have screwed up somewhere. You probably want to have a human look at what is going on. The privacy consequences of automated responses are difficult to work out properly. Hence this proposed change.
This is an improvement but still insufficient with respect to configuration mismatch signaling.
Let's start with this one and I'll look at addressing the open issue in a follow-up.
Thanks, I think this is clear enough now.


From NAME Interesting that this is the first time OHTTP appear in the text, excluding pseudocode and mediatype - please expand (or define somewhere else, for example in terminology).


Switches to an 8-bit key identifier here as well. This format now doesn't need varints. Yay. Implemented this in my ohttp library and it's fine.
Splitting this from . It seems likely that a single KEM key will support multiple KDF or AEAD options (NSS will soon support 3 of the former, and 2 of the latter, maybe 3 of each). Having a configuration that includes a single set of (KEM, key, and key identifier) with a list of KDF+AEAD pairs, akin to ECH, would make it easier to support that sort of deployment. Without that, key identifiers need to carry information about the KDF and AEAD. To support this, the format needs to be adjusted to carry the KDF and AEAD identifier. In doing so, we probably want to encode the choice of KDF and AEAD in the key schedule or AAD accordingly.
Should the key ID length be explicitly authenticated, e.g., by including it in the AAD, or should the AEAD implicitly authenticate things? Unclear!
Should we even bother with an AAD at all? The best reason is that the keyID determines which key the server uses, which is information that factors into decisions. (A related question might be why doesn't HPKE take variables as input so that things like key identifiers can be integrated into the key schedule.)
My take: yes. I need to think through the implications of no additional authentication at all, and in the mean time, I'd be more comfortable with simply authenticating everything possible. I think it was discussed, but it makes the interface a mess. And since applications can accomplish the same thing by shoving what they want authenticated into the AAD, it was voted down.
Yeah, you and I both know that putting stuff in the AAD isn't a real substitute for having it in the key schedule. I completely understand how this would mess with the purity of the design, but TLS managed by wrapping its labels better (which also addresses a known problem with HKDF not being injective).
Fixed by virtue of making the key ID fixed length in .


This came out of , where we realized that the relay padding is useful, but needs to employ different methods than client or gateway. The relay can't use binary HTTP padding; it has to insert stuff at the HTTP layer. Include citations for padding in those documents. Note that these citations are slightly misleading. Most relays won't have access at the level necessary to add that padding. More likely, the relay will need to do as NAME originally suggested, namely adding fields of an appropriate size. But we don't have good citations for that, so I'm cheaping out. (Note that RFC 9112, Section 7.1.1 talks about using chunk extensions for padding, but I'm going to pretend that doesn't exist; it's even less usable than the HTTP/2 and HTTP/3 options.)
Take it or leave it!


Comment by NAME In How does a client know this? Preconfiguration ? Are we talking ms? sec? minutes? Any advise to implementers ?
It can't be less than 2s, because the granularity of the field doesn't permit detecting anything finer than 1s. I've done what I can.
This still does not seem to address: Maybe just cut off the “unless” part if there is no signal for this? Paul
NAME also included URL which I think should address your concern:
Thanks. That commit does address the issue, mostly. I guess it moves it do an unspecified provisioning layer :P Paul


This isn't anything that special, but this is a case where a bunch of our established defenses don't work.
The new changes LGTM. Feel free to merge assuming you're done with edits!
This means that servers need anti-replay protections, especially if they are performing non-idempotent operations.


This was vague enough that an example might help.
Yeah, this seems good. Do we want to explicitly mention cookies?


This makes the choice of a 1:1 mapping clear: avoid being used as an open proxy through explicit configuration of your ACLs. cc NAME
The proxy has a fixed mapping. It's resource maps to just one request resource. That's a deliberate choice that we should probably be much more up front about.
Yes, I think this makes sense, but it took me a while to realize it when implementing. Having a section on mapping state on the proxy would be useful.
Vittorio says: This relates in particular to the potential for generic services with open destination (not a capability in the proposal) to be used for nefarious purposes (spamming, bot net command and control, hiding illegal activity, etc...).


(which didn't require any non-editorial changes; the review is done now)
Also call out invariants (esp. in header structure and handshake) explicitly: v2 may/will break functionality outside the invariants.
Document is currently in in with final invariants; do one last pass to make sure everything documented in Section 17 as expose information appears here (which is basically the same check).


This document should mention unidirectional streams Some words about the meaning of an abort signal vs. a clean close would be useful, both ingress and egress. Maximum stream bytes = 2^62-1. Saying "there are special streams for QUIC" is inaccurate (it's just one stream now, conceptually), and I would not bother to describe the CRYPTO frames this way.
Also: stream aborts are always unidirectional. what can/should applications assume about stream IDs? are they accessible, and does the app have any control over them?


However, we also use the terms Server/Client Hello datagram to cover all QUIC Packets of the first two datagrams sent. This term is not used in the transport draft (anymore) and I find it confusing given the TLS terminology. Should we maybe use a different term?


addresses
In "Connection ID and Rebinding": "Even when encrypted, a scheme could embed the unencrypted length of the Connection ID in the Connection ID itself, instead of remembering it, e.g. by using the first few bits to indicate a certain size of a well-known set of possible sizes with multiple values that indicate the same size but are selected randomly." I almost went and filed an issue against quic-lb to do this instead of just encoding a plaintext length like it does currently. But is this a good recommended practice? ISTM that long headers would provide a bit of a Rosetta Stone for this mapping, making it not particularly effective for the complexity.
The idea was not to conceal the length but to avoid likability by identifying linked connection based on the existence of a certain length field. This might look slightly more random but I guess you also have to change your mapping more time to time. So not sure. I'm fine to remove that half sentence and maybe add a sentence about likability instead...?
Yes, I would prefer deleting the quoted text, as I think it's a solution that adds complexity for little apparent benefit.
Okay, will do that change after your PR has landed.


Re SNI and ALPN: we can certainly point at the work in progress in TLS that will encrypt this information, but IMO the document should describe the current state of the world. I've just added something pointing to the SNI encryption document; while it seems that the tunneling technique described there could pretty clearly be applied to ALPN fronting as well, is there a reference for encrypting ALPN we can use here that doesn't leave details as an exercise for the reader?
I would actually prefer to describe the wire image of TLS and associated protocols in a separate document and just point to it because that's actually not only applicable for quic
I don't have a reference for encrypting ALPN. We don't have one. Maybe I should write one... On the plus side, it's something that benefits from the same techniques as SNI (and less likely to cause deployment issues, though that is only because we haven't ossified around it as much). As for the general point about now vs. later. One of the things we've tried to do is be very clear about what the invariants are, or what promises we are making. It's not the same thing as true ossification defense, but if we make it clear what we are committing to, maybe we won't have people relying on stuff we might want to change.


The point of is a little odd, because it seems to imply that ALL operations require access to the information that is integrity- or confidentiality-protected. That's clearly not the case, I think, as it is merely pointing out that if you want to modify data, then you have to be an endpoint. I think that this might be better phrased as: Note: This drops the last sentence of the original paragraph. If proxying is out of scope, then it doesn't help to talk about it so much. The point about cooperation should do.
Created a PR based on your proposed text but added some of the information about proxies to make it more clear what kind of proxies we are talking about.


This is a proposal by Gorry to address issue
This works for me.
Should TAPS be cited as an informational ref to an API that is being discussed by the IETF, and which could apply to QUIC - at least to define the primitives needed in an API, and has quite some insight into protocol racing/fallback as described in applicability?
I'd be happy to do so ;-) however we avoided in the end to talk to much about specific of the interfaces or needed nobs. So not sure it fits or where to fit it.... where would you want to say something?
I think the WG should consider writing a short section on this. It's important enough that we have a WG developing relevant specs for how to build APIs and the need for this type of fallback/racing.
I'm not sure the QUIC WG has enough experience to say anything on the matter. The fallback discussion is about how (Application Mapping + QUIC) could fallback to (alternative application mapping + TCP). For HTTP/3 this isn't a straight fallback - if QUIC fails then a different HTTP version over TCP is required. This has consequences for applications that use HTTP/3 as a substrate (MASQUE, WebTransport) which rely on features only it provides like unreliable DATAGRAM. I think the Applicability spec might need to say more here. What I'm trying to highlight is that there isn't a straight shootout between TCP and QUIC. If TAPS frames things like that then I don't know that it helps Applicability.
taps allows you to use the same interface for multiple transports and also provides you a way to state requirements (hard or soft) to restricts the selection of possible transports. In that sense for some application TCP and QUIC might be not make a real difference as long as all features requested are support, e.g reliability and in-order delivery. Still not sure what/if we want to say something in applicability statement...?
NAME we were trying to not focus on http in this document. But I do agree that the fact that there is a different version needed for http to fallback could still be interesting. How about adding as sentence like the add the end of the first paragraph in section 2: OLD In the case of HTTP, this fallback is TLS 1.3 over TCP. NEW In the case of HTTP, this fallback is TLS 1.3 over TCP. Note that for HTTP a fallback to TCP also requires the use of a different version of HTTP, as HTTP/3 has been optimised for use with QUIC, e.g., by removing multiplexing from the HTTP layer and used the provided mechanism within QUIC. We could even add something more like: "Other applications that are optimised for QUIC might need similar adaption in order to be able to function on TCP." However, not sure that adds much... What do you think?
I'm still not sure that helps. And I'm trying not to derail Gorry's OP. The main grounding I have in my understanding is HTTP. But I appreciate the Applicability document is general purpose, so apologies. Many HTTP applications operate at the semantic level. HTTP semantics are version-independent and get profiled into versions that have a wire-syntax, which depends on different properties of a transport. This could be an optimised use of QUIC features, or it could be an outright requirement for those features. There isn't an interoperable notion of "I want to do HTTP/1.1 over a reliable in-order transport, TAPS selected QUIC for me, great I'll use that" nor "I want to do HTTP/3 over a reliable, multiplexed, in-order transport, TAPS selected SCTP for me". The transport fallback behaviour for HTTP semantics is quite straightforward. Pick a different HTTP version. This could lend itself to a race like scenario. However, there are applications that use HTTP as a substrate and things like MASQUE and WebTransport place requirements on the use of features only available in HTTP/3 - I doubt anyone will try to backport features of QUIC to TCP in order to satisfy the requirements of layers above.
I was thinking of something like: /The IETF TAPS specifications [URL] describe a system in which multiple protocols can be provided below a common API. and describe some of the implications of fall back, which specifically precludes fallback to insecure protocols or to weaker versions of secure protocols. / Perhaps inserted after this: /These applications must operate, perhaps with impaired functionality, in the absence of features provided by QUIC not present in the fallback protocol./ ... and before mentioning fallback to TLS over TCP.
NAME you are right that QUIC provides features that TCP doesn't provide and if you use those features you cannot just straight-away replace one with the other. However, a simple one-to-one replacement not implicated when talking about falling back. Rather than trying to back-port features to TCP, what you need to do is implement that feature in your application instead (as H2 does for multiplexing). But this is also true if you try to use TCP and fall back to UDP but need reliability; then you have to implement it in the application layer. I was trying to be brief in my proposed text and maybe the word "optimise" is not the right one. We can of course say more. Do you want to give it a try and propose some text if you think it would be helpful to say more?
Thanks. I'll have a think on some text. The important thing is that H2 wasn't designed as a fallback for H3, it is its own thing. There are few features that an application desires that it couldn't also achieve by just using HTTP/1.1 and multiple connections for multiplexing.
May I ask what these features are? E.g., the ability to send unreliable datagrams should not matter in the best effort Internet (reliable transfer, when not needed, is just slower). In RFC 8923, we made an effort to identify exactly what MUST be exposed to an application because there's no such fall back. This covers TCP, MPTCP, UDP, UDP-Lite, SCTP and LEDBAT; alas, it excludes QUIC, since this work pre-dates its finalization (by far, despite the date: it spent a long time in the RFC Editor queue). So now I'm curious what QUIC adds to this list.
mainly multiplexing I would say but that probably covered by SCTP
The main feature that QUIC provides to the HTTP substrate is that it is a secure transport that can work on the Internet and is easily integrated with WebPKI. These are meta-features in a way. Having parity of the deployment and security models allows more straightforward rationalisation when designing applications and e.g. Web APIs on top of this substrate.
Thanks! About muxing: is a certain prioritization behavior guaranteed (not just a wish) when muxing with QUIC? SCTP could do that but we don't currently give such guarantees in the TAPS interface. About security: right... that's what I expected. TAPS isn't going to fall back to a less secure version of what you wish for... so I guess "mostly encrypted header" isn't a part of any API but as you say it's a meta-feature - it's implicit in the choice of QUIC, and when that's desired, falling back wouldn't happen, a TAPS system should always only give you QUIC. I think what's missing is a QUIC API that makes these wishes explicit...
Personally I'd say, for an HTTP application, that QUIC packet header protection is less of a concern. More that QUIC provides transport security on-par with TCP+TLS. Often an application can use the same TLS and crypto libraries for both. Fallback from QUIC to TCP can be a downgrade. But an application that does this can (and should) be aware of the security properties of the transport it is using. With HTTP, imagine a client that connects to URL and receives . This advertises the availability of QUIC and HTTP/3. If that transport connection fails for some reason, then there is not a viable fallback to TCP+TLS. This is an unlikely scenario but the specs allow for it to happen.
I would also have thought that encrypting data is more important - but surely having fall-back "under the hood" and just being aware of what you got (this can be queried in TAPS) is nicer than having to take care of it in the application. Fall-backs can get complex: e.g., when multi-homing, considering IPv6 ... a TAPS system can have a rich set to choose from and implement it all efficiently while shielding applications from this trouble - the application is still in control of things and can express limitations, as well as query what it got in case it did give the system some freedom. This HTTP case of preventing a fall-back... well, it is what it is :) No fall-back in this case, clearly.
I remain to be convinced that TAPS API will address the way that applications (I am familiar with) intend to use and interact with QUIC.
I think we're drifting towards a discussion of should a mapping document be written for TAPS to QUIC... which is a different topic. My observation was only that TAPS architecture is something the IETF is working upon, and it has documents that describe how to make an API that with functions and racing/fallback in a very different way to sockets. The guidance about what is an acceptable "alternative" connection appear common, at least enough to say there is a similarity and cite it.
The suggestion for a short informational reference seems good to me.
There is already PR
We merged PR . Is there anything we want to add or can this issue be closed?
The merged PR addresses all of my concerns. I'll let NAME be the judge whether his original issue has been addressed.
That looked good to me.
Thanks all!
o/ lgtm


Thanks to Mike for noticing this.
This is true, but there is nothing inherent to the design that prevents RESETSTREAM (and STOPSENDING) from using a different code space from CONNECTION_CLOSE. Though we have the policy in HTTP/3 that any stream error can become a connection error, the same isn't necessarily true of other protocols. Maybe we cab just avoid mentioning this possibility..,
I don't think I have a strong opinion on removing or keeping this sentence. Would it be any better if we say: "Application error codes are recommended to be defined from a single space that applies to all three frame types." ? Or is there any other guidance we can or want to give to application designer about the use of error codes?


proposed by NAME
Plan is to update the PR to point to the IANA registry instead.
Added a ref to the iana registration sec in the transport draft. Should be ready to go now!


This mainly wiggles the existing text around and tries to fully qualify transport vs application to help explain the ways an implementation might do things, which is independent of how an application protocol might be designed (for example H3 doesn't require incremental processing but most people come to a realizatopm that it is the best way to do things).
The new text is good and clear, but it doesn't really explain that the when credit is released at the receiver, could we say a little more: it takes 1/2 RTT for cerdit to reach the sender, that causes new data to be released, for a large RTT, this means that credit has to be sufficient to avoid blocking or to be sent promptly, otherwise transfers will stall for periods of an RTT. (I know TCP does this stuff for the app and grows the window, but QUIC I suspect needs something to happen to release the credit). I know people closer to the Apps can write this sentence better than me; from my perspective I just see the effects when an app blocks or release credit in big chunks... over a large path RTT and the network performance suffers.
Thanks Gorry. I think I agree. How about I tweak "Timely updates can improve performance" paragraph to say "Timing of updates can affect performance". I can add a mention that the extension of credits on the data recever side is enacted by emitting MAXDATA/MAXSTREAM_DATA frames, which take at least a 1/2 RTT to reach the data sender side. I think the useful additional applicability statement to be made based on the above is that the data consumer read rate may be disjoint from the delivery rate and responsiveness of signalling channel back. For a highly asymmetric link, the download rate can be application-limited well below the theoretical bandwidth simply because an application is spending too much share of the time reading packets and not giving enough time to emit QUIC packets. Does that sound ok, or am I missing your ask?
This is definitely heading in the direction I was hoping, it helps avoid people developing on a LAN and then discovering the effects on other paths later. Please add something.
I've reworked the paragraph with my attempt to address NAME comment. I've introduced the terms uplink and downlink, which I'm not totally happy with. If someone has a better suggestion I'm all ears.
Issue: Sect 4.3. Flow Control - we should have a subsection also encouraging timely credits This ID discusses flow control deadlock, whichI think is important. I suggest we should also note that timely release of flow control credit is also important (QUIC transport has some text to point to). This need is evident when the path has a large RTT, and there can be a large BDP, where the whole of operation of QUIC can be dominated by the way in which the application manages the flow credit. This might not be obvious, because the equivalent rwnd update in TCP is handled by the transport rather than the application.
The point is the audience for this draft is applications that use a quic library. I would assume that the actual credit handling is performed in the transport/in quic directly, usually without an interface for the application to directly impact it. Do you propose the application should have more direct impact on flow credit handling? I guess we can add a pointer to the right section in the transport draft but not sure we should/can say more here...?
I agree this seems mostly like a QUIC transport/library issue and I wouldn't expect the application to have much control here. I can imagine applications setting a max value?
I generally agree with Mirja and Ian. However, on a closer inspection of this section I think it would benefit from a crisper definition of what it means to read data vs. process data. Its a reasonable application mapping implementation design to read data out of the transport in order to release flow control but still buffer it (or just dropping it on the floor) in application space before processing that as application data.
NAME can you propose text?
I'm content that flow control is something the libraries need to get correct - the TCP people spent energy to get this automated, although QUIC's multi-streaming makes the trickier. Lucas text suggestion would improve things by making it clearer how the API is intended to be used.
NAME ?
I will add some text (just need to find the time).


Based on the discussion in webtrans at IETF-110, I guess we could be more explicit that QUIC streams do not guarantee to be received in any particular order.


It could be worth noting two general further things about DSCPs : (a) "When multiplexing multiple flows over a QUIC connection, the DSCP value selected should be the one associated with the highest priority requested for all multiplexed flows." (b) "If a packet enters a network segment that does not support the DSCP value, this could result in the connection not receiving the network treatment it expects. The DSCP value in this packet could also be remarked as the packet travels along the network path."
A sample PR: "More aspects of setting DSCPs"


Separates for manageability and this PR for applicability
I actually find this sentence weak: "Therefore it is not recommended to use different DiffServ Code Points (DSCPs) {{?RFC2475}} for ... Considering that QUIC v1 as defined has a single congestion controller and recovery handler and don't treat different DSCP values as separate transport flows I would think that this should be stronger formulated. However, I could see that one could provide a bit more context to this DSCP and single QUIC connections vs multiple QUIC streams discussion by referencing Section 5.1 of URL and be explicit about the point that QUIC v1 is similar to SCTP in regards to the issues raised.
I think the new text do work.


Suggesting a rewording to clarify various issues and correct some mistakes.
Previously scope of this text was agreed, but what appeared seems to me to have various NiTs: • Saying "every other packet is acknowledged" is a recommendation in RC9000. I suggest citing also. • The text seems to conflate endpoint processing optimisation, and network path performance optimisation: /constraint/ wasn't the correct word, but even /constrained/ was a value judgment on design traedoffs for any mobile/RF link - and many bandwidth-limited cabled link, which could be avoided by simply saying what is intended and saying either /which can impact performance of some types of network/ or /which can impact performance across some types of network/ ... depending on your viewpoint as an operator or application. • I think we can do better than URL, suggesting removing some and leaving might. This might be resolved in a PR .


One sentences added to address remaining bits for
NAME I didn't replace "five-tuple" with "connection" in this sentence in this PR: "QUIC’s stream multiplexing feature allows applications to run multiple streams over a single connection, without head-of-line blocking between streams, associated at a point in time with a single five-tuple. " because it already has connection in the sentence; so we could only remove the last half sentence, but I actually think there is a value in leaving this in because some application may actually care about the 5-tuple.
WFM. We should probably do an editorial pass to make sure we're using terms like "5-tuple" and "connection" and "path" properly everywhere anyway.
See also section on Streams in transport draft: "Stream offsets allow for the octets on a stream to be placed in order. An endpoint MUST be capable of delivering data received on a stream in order. Implementations MAY choose to offer the ability to deliver data out of order. There is no means of ensuring ordering between octets on different streams."
See PR
lgtm modulo editorial. circleci doesn't like this but can fix after it lands.


NAME Good point about being more explicit. I actually think is valuable to reference an existing encoding of public keys providing also key type and curve, like CREDX in URL Also, there is a discussion about format for public keys in COSE, which if it is specified probably will be referenced by EDHOC instead of CREDX. I would be good if there is a common format used in all these instances. For the time being I propose that we reference CRED_X with empty text string subject name, and keep track of the COSE activity.
We should consider replace the NIST reference with SECG [1] to harmonize with EDHOC [2]. [1] URL [2] URL (edit) ... but the commits in [3] mainly refers to the NIST spec. so I propose we keep it as is for the time being. [2] URL
It looks good to me; we can give more details on one point. For the two HKDF invocations in Section 2.3.1, we can define more explicitly how the public keys are expressed when concatenated to form IKM-Sender and IKM-Recipient. Given a public key, that can simply be: For OKP signature keys, the compressed 'x' coordinate, i.e. what would be expressed in the key parameter -2 of a corresponding COSE Key. For EC2 signature keys, the 'x' coordinate followed by the 'y' coordinate in this order, i.e. what would be expressed in the key parameters -2 and -3 of a corresponding COSE Key. This should be sufficient, i.e. it shouldn't be needed to have a more structured encoding of public keys providing also key type and curve, e.g. like done for CRED_X in URL


Back to one processing description Observe is now primarily inner Simplified processing Explicit cancellation by intermediary not supported


I don't know why your commit got included.
I think I do: I modified the commit in order to add the description (to close issues). Now there are 2 identical commits, one is the one above ( 30475e3) that does not close issues, the other is the one in the master branch (7d4cbc2)
Thanks for your contribution Jim


Replaced Media with Key distributor in on important place. Replaced the DTLS tunnel with TLS according to the latest tunnel draft. A couple of more wording fixes.


This is much more compatible with extensibility patterns in TLS stacks, and in a way that is very forward-compatible with the forthcoming DTLS 1.3, while still being implementable in pre-1.3 stacks. DTLS 1.3 has a notion of post-handshake handshake messages, like and , which are acknoweldged with an message. The framework described here simply sends as yet another post-handshake message. Depends on
r? NAME
I have a bunch of comments but really I could live with this like you have it now.


We're talking about the key wrap function here. Of course there's a requirement to remove padding, but it's explicitly clear in the AES Key Wrap RFCs. Seems like this text might be wrong now.
Thinking about this a bit, I think I will redo to just make it so decrypt has to remove the pad - I think this is way hold historic text that makes no sense given we use key wrap
Section 2.3 The decryption function returns a plaintext value P that is at least M bytes long, or returns an indication that the decryption operation failed because the ciphertext was invalid (i.e. it was not generated by the encryption of plaintext with the key K). These functions have the property that D(K, E(K, P)) = ( P concatenated with optional padding) for all values of K and P. Each cipher also has a limit T on the number of times that it can be used with any fixed key value. The EKTKey MUST NOT be used more that T times.
I think the goal was to avoid having a length indicator in the encrypted data as the stuff outside this knows the size (and thus how much pad to remove). Thoughts on what we should say here in the draft ? NAME NAME
should we clarify, how the code knows the length of the padding on the decryption side ?
I'm unsure what the question is. I reviewed that section of the current draft and it sounds clear. Perhaps the confusion is over the padding? The AES Key Wrap logic defines how to insert and remove padding. You can see how that's implemented here: URL There is a "with padding" function that inserts required padding per the RFC. So, what's the question?
NAME .. I think the question is on clarifying when the padding gets added and removed. Is it out of enc/dec or inside. We might just need to clarify tht
It's defined in the RFC. I don't think we should be trying to explain it again here.


NAME asked how to best approach logging from a server's perspective, given that things like version negotiation and stateless retry are not inherently tied to a single connection. We should add some informative guidance on how to best approach this to the spec, depending on how much state you're willing to keep around Some options: low state: keep a separate qlog file for the entire server. This logs vneg, retry, etc.. Then, when a connection is truly accepted, start a new .qlog for the individual connection, containing all events thereafter. The URL can then also contain an event signalling the acceptance of a new connection for later cross-linking between the files. low state: keep a single huge qlog file for everything, using the "groupid" field to allow later de-multiplexing into separate connections (I believe quant does this atm) stateful: if you already track vneg/retry and link them up with the final connection, you can output them in the per-connection qlog file as well Maybe also shortly talk about some of the trade-offs in each option. Also talk about how to approach server-level events like serverlistening and packet_dropped in separate scenarios.
I lean on the side of minimally and clearly identifying the challenges and maybe signpost some features and leave it up to implementers to figure out how to address this. They can always push back later in the specification lifecycle is something is deemed missing.


Also, add structure to the "changes from v1" section.
It would be a simple matter to switch around the packet type codes in long headers, e.g. 0x0 = Retry 0x1 = Handshake 0x2 = 0-RTT 0x3 = Initial so no one ossifies on 0x0 = Initial.
I'm ambivalent on this. It's fairly trivial to have the long header parsed in version-specific fashion, so it's easy to implement, but I'm not sure how much value it has. It also makes reading packets slightly harder.


The correct term from the VN document would be "original version" but just removing the word initial avoids the confusion
Fewer words means fewer nits


In we agreed on this. Considering that the decision affects handling of multiple frames, I think it's worth clarifying the general rule in the draft. For discussions regarding specific frames, please refer: MAXSTREAMS, MAXDATA, MAXSTREAMDATA - RESETSTREAM, STOPSENDING - ACK -
If you want to be explicit, perhaps also mention that stream ranges can be retransmitted with different boundaries or partially, if this isn't already covered by some existing text.
NAME While that's possible, I think we should better handle retransmission rules of specific frames in separate PRs. The reason I suggest this having this catch-all sentence is because to me it seems that the concept is sometimes being lost.
I agree that I'd like to discourage this a bit more than this PR does.
NAME NAME Your comments make sense to me. I'll update the PR.
Moved to 13.2, expanding the text to encourage reassembling frames containing up-to-date information. PTAL.
This is implicitly assumed, and, as NAME points out in , not doing so can cause undesired outcomes. There should be normative text in the transport draft requiring this behavior.
So the situation is that if one so far have ACKed: 1,2,3,6,7. Then if 4,5 shows up at the receiver and it is going to ACK those, it must at least send an ACK that contains 4,5 and 7, thus likely acking 4-7? It should not be allowed to send an ACK saying 4, 5 in the above situation as that doesn't indicate the receiver total progress, i.e. all the way to 7? I think requiring Largest Acknowledge to always indicate highest solves some issues and likely simplifies a lot of implementations. But it may result in larger ACK frames as one will have include the full current state between the largest Acknowledged and so far into the past PNs that you need to go. In severe re-ordering cases that could be a lot. Will this cause cause the ACK to be to large to include? Especially maybe in connections with a large delay bandwidth product going full steam encountering a serious hick up in the network. For example if an ACK is required to ack packet 4000 + 1E9 and you have 500 gaps that has arrived the last RTT/4 and really should be included. Resulting in an ACK frame that is at least 1000 bytes large, possibly bigger.
There are a few things this precludes, and we we should understand those before doing this. 1) A very limited implementation(ie: hardware) can't always send only one ACK block without giving up on reordered packets. I don't have such an implementation, but at some point I remember discussing this idea with people who seemed interested in it. 2) One cannot do multipath with QUIC v1 and it means the multipath design will have to use a PN space per path, as described in Christian's draft: URL I think that's likely the right choice for other reasons, but I'm hesitant to make that decision now. 3) An RTT sample can only be taken based on the largest acked. Therefore, if there is a large amount of reordering(ie: 1RTT), you don't get any RTT samples for an RTT. If we care about this, we can probably solve it a different way in the recovery draft, but that likely means using ack delay sometimes and not others. In regards to Martin's comment, I thought we already had text that said ACKs with lower packet numbers than the largest ACK packet number should be ignored, since they're old?
NAME regarding your question. "In regards to Martin's comment, I thought we already had text that said ACKs with lower packet numbers than the largest ACK packet number should be ignored, since they're old?" At least when it comes to ECN, the editors have been working hard to reformulate which has resulted in that there is nothing that applies this from the PN increasing for the ACKs. So to solve the ECN part, I think (re-)introducing that aspect into the text is the simpliest fix for ECN. But there clearly are other aspects here.
NAME The problem doesn't go away if you ignore acks that are lower than largest_acked. The problem is that the receiver sent the acks in the wrong order because packets were received in the wrong order, but ECN counts are going up. But you raise good points. This is a corner case. I think that with the current spec, under these conditions, ECN gets turned off since validation fails. I think that's a reasonable outcome, and I'm fine with it. NAME
ECN Validation as I currently understand it will not fail if the sender to receiver reordering happens as long as the ACKs from the receiver to sender are not reordered. So to my understanding what is missing is a requirement for the sender to no process verfication for out of order ACKs. It will not fail if the largest acknowledge is not increasing as the comparison is done on number of newly ACKed packets versus counts change. The current sentence in Section 13.3.2 (-17): "Counts MUST NOT be verified if the ACK frame does not increase the largest received packet number at the endpoint." Which I interpret to mean that what PN the ACK reference (sender-receiver direction), rather than the PN of the receiver to sender direction. Can NAME or NAME fix that?
I think is enough to address the original problem, so I'd like to close this issue. I think and are enough. NAME sound ok?
Yes, the ECN related issue is resolved. What I don't know if it is resolved fully is the implementation requirements is that a sender MUST be capable of handling an ACK frame with Largest Acknowledged that is smaller than highest received? From my perspective there are good and valid arguments why this may be required, however I think consensus needs to be established on that.
NAME I am not sure if I'm following the context, but my understanding is that a sender MUST be capable of that, because we allow an endpoint to simply retransmit the payload of a packet that is being deemed lost; see .
Just a stray thought: is this requirement really strictly needed? It would complicate trasnmitters that split work into multiple processing units which might not be fully synced on the latest ACK'ed.
NAME what NAME says. Also, a sender needs to be able to simply deal with reordering on the ack path. Did you mean something else?
My concern was primarily about the clearness of this behavior in the specification text. In transport I find the following which indicates this but are not explicit. : To limit receiver state or the size of ACK frames, a receiver MAY limit the number of ACK Ranges it sends. : Largest Acknowledged: A variable-length integer representing the largest packet number the peer is acknowledging; this is usually the largest packet number that the peer has received prior to generating the ACK frame. I think there are strong hints, with the above "usually the largest" indicating that it is not always. I would have made this explicit in 19.3, but then I prefer being more explicit than not. I think it is possible interop issue point. Anywhere else this is more explicit? (I have read github master version) NAME I think there are strong points for allowing an ACK frame to acknowledge only a part of the range between highest received and confirmed to be have been ACKed. The two main reasons I see. high rate flows suffering an packet loss burst so that the number of ACK ranges becomes to large to fit a single QUIC packet. Low complexity receivers ACKing a single range of received packets and doing this for reordered packets.
NAME As suggested by NAME in , might be the correct place. I am advocating for describing the design principle the PR, but we might (or might not) be interested in expanding how ACK could look like.
So results in certain implications, that an ACK could arrive that addresses older state, thus resulting in acknowledging some PNs that was previously unacknowledged. As 13.2 is about retransmissions, I think there are other desing principle aspects of ACKs that probably should be covered, but then earlier in 13.1.
We're in the process of making our implementation accept smaller largest_acked values, so I'm wonder if we're any closer to consensus(one way or the other) on this issue?
I think this is mostly addressed and what's left is OBE. There's text in the draft now that says: "Processing counts out of order can result in verification failure. An endpoint SHOULD NOT perform this verification if the ACK frame is received in a packet with packet number lower than a previously received ACK frame. Verifying based on ACK frames that arrive out of order can result in disabling ECN unnecessarily." This would be adequate, but there's other text (thanks to ) that says old ACKs can be resent. I think we simply add a note about that in the para above, which should be adequate.
Discussed in London: NAME to prepare a PR for those remaining bits
NAME any progress here?
I think the conclusion here is close with no further action, but it'd be good for NAME to confirm.
I've added , which I believe addresses what's left of this issue.
Note that we agreed that proposal-ready would be used when there were approvals for PRs (or at least apparent agreement, as determined by editors).
NAME : Yeah, my bad.
This gets fixed by , so marking this issue as well.
Fixed by .


(Note that the issue identifies two issues, but one was already fixed in .)
In , NAME correctly observed that I messed up , but he closed his PR and I'm not able to reopen it without more git wizardry than I'm interested in practicing today. Easier just to fix it in my own branch.
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.