Subject: Consistency in Stream Beginnings in HTTP/3

From: Mike Bishop <[email-masked]>
Date: Thu, 13 Jun 2019 17:30:02 +0000

This discussion has happened across a collection of GitHub issues with largely the same participants, and I'd like to get a sense of the larger working group's opinion before I work on any of these directions (or close all the related issues).

To recap the current state:

  *   Unidirectional streams begin with a type byte; interpretation of the remainder of the stream data depends on the type
  *   Push streams continue with a header - the Push ID as a varint - followed by frames as usual
  *   Control streams don't have a header; frames begin immediately after the type byte.  However, a SETTINGS frame is required to be the first frame of the stream.
  *   SETTINGS cannot be sent again thereafter or on any other stream

That means there are currently two different models of how you put required-to-be-first information:  As a required-initial frame or as a header before framing begins.

There are a couple of paths forward that have been discussed:

  *   Stay as-is.  SETTINGS is a frame in HTTP/2, so we're keeping it that way for consistency.  We've previously decided not to permit changes to settings to avoid the question of synchronization.  The reasons to change are not urgent.
  *   Make the Push ID a required-initial frame.  Proposed in #2526; rejected in London.
  *   Make SETTINGS a stream header.  Proposed in #2783.
     *   Argument for:  Gets rid of the concept of required-initial frames, in combination with #2781.  Consistent with the Push Stream header.
     *   Argument against:  Push ID isn't a length-prefixed structure, just a single varint.  We already have facilities for reading length-prefixed structures in frame handling.
  *   Allow multiple SETTINGS.  The synchronization requirement on SETTINGS is hairy when values are reduced, because in-flight activity might now violate a peer's restriction.  Allowing SETTINGS to be sent at any time, but only increase values, would also mean they need not be the first frame, eliminating the concept of required-initial frames.
     *   Complication:  What does this mean for clients' remembered settings and server's remembered settings as stored in NSTs?  If a client updates stored settings but retains a NST from earlier in the connection, the server and client could have divergent views of the server's settings on a future 0-RTT connection.
  *   Allow omitting SETTINGS.  If the server doesn't need to change anything from the client's initial settings, then the server can just not send a SETTINGS frame.  Once you've sent something else on the control stream, though, you can't send one later if you change your mind.

Thoughts?

---

From: Lucas Pardue <[email-masked]>
Date: Thu, 13 Jun 2019 19:15:05 +0100

Thanks for summarising all of this individual discussions Mike.

I have a derivative design on the SETTINGS stream header which would
replace length-prefix with count. So rather than Express total length of
parameter ID and value, you tell the receiver how many variant pairs to
process before processing frames.

I dont think that strongly pushes the needle in one direction though.

If we reject changing the control stream, I'm presently 50/50 split between
revisiting push stream framing and just living with what we have.

On Thu, 13 Jun 2019, 18:30 Mike Bishop, <[email-masked]> wrote:

> This discussion has happened across a collection of GitHub issues with
> largely the same participants, and I’d like to get a sense of the larger
> working group’s opinion before I work on any of these directions (or close
> all the related issues).
>
>
>
>    - Unidirectional streams begin with a type byte; interpretation of the
>    remainder of the stream data depends on the type
>    - Push streams continue with a header – the Push ID as a varint –
>    followed by frames as usual
>    - Control streams don’t have a header; frames begin immediately after
>    the type byte.  However, a SETTINGS frame is required to be the first frame
>    of the stream.
>    - SETTINGS cannot be sent again thereafter or on any other stream
>
>
>
> That means there are currently two different models of how you put
> required-to-be-first information:  As a required-initial frame or as a
> header before framing begins.
>
>
>
>    - *Stay as-is.*  SETTINGS is a frame in HTTP/2, so we’re keeping it
>    that way for consistency.  We’ve previously decided not to permit changes
>    to settings to avoid the question of synchronization.  The reasons to
>    change are not urgent.
>    - *Make the Push ID a required-initial frame.  *Proposed in #2526;
>    rejected in London.
>    - *Make SETTINGS a stream header.*  Proposed in #2783.
>       - *Argument for:*  Gets rid of the concept of required-initial
>       frames, in combination with #2781.  Consistent with the Push Stream header.
>       - *Argument against:*  Push ID isn’t a length-prefixed structure,
>       just a single varint.  We already have facilities for reading
>       length-prefixed structures in frame handling.
>    - *Allow multiple SETTINGS.*  The synchronization requirement on
>    SETTINGS is hairy when values are reduced, because in-flight activity might
>    now violate a peer’s restriction.  Allowing SETTINGS to be sent at any
>    time, but only increase values, would also mean they need not be the first
>    frame, eliminating the concept of required-initial frames.
>       - Complication:  What does this mean for clients’ remembered
>       settings and server’s remembered settings as stored in NSTs?  If a client
>       updates stored settings but retains a NST from earlier in the connection,
>       the server and client could have divergent views of the server’s settings
>       on a future 0-RTT connection.
>    - *Allow omitting SETTINGS.*  If the server doesn’t need to change
>    anything from the client’s initial settings, then the server can just not
>    send a SETTINGS frame.  Once you’ve sent something else on the control
>    stream, though, you can’t send one later if you change your mind.
>
>
>
> Thoughts?
>

---

From: Ryan Hamilton <[email-masked]>
Date: Thu, 13 Jun 2019 14:19:27 -0700

Of the options, I'd personally prefer revisiting #2526 and making push ID a
required first frame on a push stream.

On Thu, Jun 13, 2019 at 11:47 AM Lucas Pardue <[email-masked]>
wrote:

> If we reject changing the control stream, I'm presently 50/50 split
> between revisiting push stream framing and just living with what we have.
>
>
>>    - Unidirectional streams begin with a type byte; interpretation of
>>    the remainder of the stream data depends on the type

---

From: "Martin Thomson" <[email-masked]>
Date: Fri, 14 Jun 2019 08:13:00 +1000

I tend toward the SETTINGS-as -header option, but not strongly.  Bespoke handling for settings is inevitable and a "fixed" header would eliminate any consideration of the frame type from a bunch of places.  (I prefer lengths over counts, to Lucas' suggestion here, either way.)

In thinking about multiple SETTINGS frames, I don't think that you can just say "only increase", because that constrains how we might use settings.  The rule is probably more that any use of a setting cannot be invalidated by an updated SETTINGS frame.  In other words, if s1 is the old value of a setting and s2 is the updated value, any use of s1 needs to be valid if s2 is set.

For numeric limits, that means that s2 needs to be greater than or equal to s1.  But it also means that there can't be any strong assumption about the value of s1 or s2 in their usage.  Usage has to allow for uncertainty about what value is in use.  For instance, a setting couldn't be used as the QPACK dynamic table capacity because there can't be uncertainty about that value (we don't have this problem, though we flirted with the notion for the initial capacity, but I hope that it illustrates the point).

We might also say that a new setting is a bitmask.  If values that are set imply permission to perform certain actions, then s2>=s1 isn't sufficient.  You need s2&s1==s1.  That implies s2>=s1, but it isn't quite the same.  If certain bits being set were associated with prohibiting actions, then a different rule would be needed.

And you need to ensure that every setting is assigned a compatible value when updated, or specify that existing values carry over if not specified (like 7540).

Under those terms, you might say that a client can attempt 0-RTT or resumption under an assumption of either s1 or s2, but there are some significant synchronization issues.  If the ticket was provided before the setting was sent, then we can't assume that the server has a way of recording s2 in the ticket.  That means that there needs to be some rule about what to do with settings.  The simplest rule would be that tickets don't bind to updated values at all.  Working out how to signal that updated settings apply to a given ticket requires a lot of crossing of lines.

In the end, updating settings is just too complicated.

If we don't allow updating settings, I realize that we need to be much clearer about how that first SETTINGS is bound into any session ticket.  Though that session ticket might arrive before the SETTINGS frame, it would be unfortunate if the client couldn't use those settings when it starts 0-RTT.  I'll open an issue.

On Fri, Jun 14, 2019, at 03:30, Mike Bishop wrote:
>  * *Stay as-is.* SETTINGS is a frame in HTTP/2, so we’re keeping it 
> that way for consistency. We’ve previously decided not to permit 
> changes to settings to avoid the question of synchronization. The 
> reasons to change are not urgent.
>  * *Make the Push ID a required-initial frame. *Proposed in #2526; 
> rejected in London.
>  * *Make SETTINGS a stream header.* Proposed in #2783.
>    * *Argument for:* Gets rid of the concept of required-initial 
>    * *Argument against:* Push ID isn’t a length-prefixed structure, 
>  * *Allow multiple SETTINGS.* The synchronization requirement on 
> SETTINGS is hairy when values are reduced, because in-flight activity 
> might now violate a peer’s restriction. Allowing SETTINGS to be sent at 
> any time, but only increase values, would also mean they need not be 
> the first frame, eliminating the concept of required-initial frames.
>    * Complication: What does this mean for clients’ remembered settings 
> and server’s remembered settings as stored in NSTs? If a client updates 
> stored settings but retains a NST from earlier in the connection, the 
> server and client could have divergent views of the server’s settings 
>  * *Allow omitting SETTINGS.* If the server doesn’t need to change 
> anything from the client’s initial settings, then the server can just 
> not send a SETTINGS frame. Once you’ve sent something else on the 
> control stream, though, you can’t send one later if you change your 
> mind.
> 
> Thoughts?
>

---

From: Mike Bishop <[email-masked]>
Date: Fri, 14 Jun 2019 00:05:12 +0000

I think a lot of this complexity gets invoked in the transition from the 0-RTT values to the new SETTINGS frame as well.  We should probably expand that text, regardless of what we do.

-----Original Message-----
From: QUIC <[email-masked]> On Behalf Of Martin Thomson
Sent: Thursday, June 13, 2019 3:13 PM
To: [email-masked]
Subject: Re: Consistency in Stream Beginnings in HTTP/3

On Fri, Jun 14, 2019, at 03:30, Mike Bishop wrote:
> might now violate a peer’s restriction. Allowing SETTINGS to be sent 
> at any time, but only increase values, would also mean they need not 
> be the first frame, eliminating the concept of required-initial frames.
>    * Complication: What does this mean for clients’ remembered 
> settings and server’s remembered settings as stored in NSTs? If a 
> client updates stored settings but retains a NST from earlier in the 
> connection, the server and client could have divergent views of the 
> server’s settings on a future 0-RTT connection.

---

From: Kazuho Oku <[email-masked]>
Date: Fri, 14 Jun 2019 09:50:20 +0900

2019年6月14日(金) 6:19 Ryan Hamilton <rch=[email-masked]>:
>
> Of the options, I'd personally prefer revisiting #2526 and making push ID a required first frame on a push stream.

+1.

As I've commented on #2783, I prefer using one TLV structure (called
HTTP/3 frame) consistently, rather than inventing different structures
for different purposes.

We have states that expect particular frame; it's the design pattern
we have. The request stream decoder has a state that waits for HEADERS
frame.

I prefer consistently using one design pattern, unless there is a
practical benefit in using something special.

>
> On Thu, Jun 13, 2019 at 11:47 AM Lucas Pardue <[email-masked]> wrote:
>>
>> I have a derivative design on the SETTINGS stream header which would replace length-prefix with count.. So rather than Express total length of parameter ID and value, you tell the receiver how many variant pairs to process before processing frames.
>>
>> If we reject changing the control stream, I'm presently 50/50 split between revisiting push stream framing and just living with what we have.
>>
>>
>>> This discussion has happened across a collection of GitHub issues with largely the same participants, and I’d like to get a sense of the larger working group’s opinion before I work on any of these directions (or close all the related issues).
>>>
>>>
>>>
>>> Unidirectional streams begin with a type byte; interpretation of the remainder of the stream data depends on the type
>>> Push streams continue with a header – the Push ID as a varint – followed by frames as usual
>>> Control streams don’t have a header; frames begin immediately after the type byte.  However, a SETTINGS frame is required to be the first frame of the stream.
>>> SETTINGS cannot be sent again thereafter or on any other stream
>>>
>>>
>>>
>>> Stay as-is.  SETTINGS is a frame in HTTP/2, so we’re keeping it that way for consistency.  We’ve previously decided not to permit changes to settings to avoid the question of synchronization.  The reasons to change are not urgent.
>>> Make the Push ID a required-initial frame.  Proposed in #2526; rejected in London.
>>> Make SETTINGS a stream header.  Proposed in #2783.
>>>
>>> Argument for:  Gets rid of the concept of required-initial frames, in combination with #2781.  Consistent with the Push Stream header.
>>> Argument against:  Push ID isn’t a length-prefixed structure, just a single varint..  We already have facilities for reading length-prefixed structures in frame handling.
>>>
>>> Allow multiple SETTINGS.  The synchronization requirement on SETTINGS is hairy when values are reduced, because in-flight activity might now violate a peer’s restriction.  Allowing SETTINGS to be sent at any time, but only increase values, would also mean they need not be the first frame, eliminating the concept of required-initial frames.
>>>
>>> Complication:  What does this mean for clients’ remembered settings and server’s remembered settings as stored in NSTs?  If a client updates stored settings but retains a NST from earlier in the connection, the server and client could have divergent views of the server’s settings on a future 0-RTT connection.
>>>
>>> Allow omitting SETTINGS.  If the server doesn’t need to change anything from the client’s initial settings, then the server can just not send a SETTINGS frame.  Once you’ve sent something else on the control stream, though, you can’t send one later if you change your mind.
>>>
>>>
>>>
>>> Thoughts?

-- 
Kazuho Oku