Subject: Clarification of dynamic table size change

From: Tatsuhiro Tsujikawa <[email-masked]>
Date: Tue, 20 Oct 2015 00:59:26 +0900

Hi,

I got the bug report from nghttp2 user:
https://github.com/tatsuhiro-t/nghttp2/issues/396

When nghttp client sends SETTINGS_HEADER_TABLE_SIZE=4096 in the first
SETTINGS, it looks like google server does not send back dynamic table size
update.  This is probably because they think that it is not necessary
because table size is not changed.

I'd like to clarify this is a valid behavior.  Specification is not clear
enough in this special case.
So the question is whether we are required to send dynamic table size
update as acknowledgement for SETTINGS_HEADER_TABLE_SIZE if it is included
in SETTINGS?

In https://tools.ietf.org/html/rfc7540#section-6.5.2
```
  SETTINGS_HEADER_TABLE_SIZE (0x1):  Allows the sender to inform the
      remote endpoint of the maximum size of the header compression
      table used to decode header blocks, in octets.  The encoder can
      select any size equal to or less than this value by using
      signaling specific to the header compression format inside a
      header block (see [COMPRESSION]).  The initial value is 4,096
      octets.
```
In https://tools.ietf.org/html/rfc7541#section-4.2
```
   A change in the maximum size of the dynamic table is signaled via a
   dynamic table size update (see Section 6.3).  This dynamic table size
   update MUST occur at the beginning of the first header block
   following the change to the dynamic table size.  In HTTP/2, this
   follows a settings acknowledgment (see Section 6.5.3 of [HTTP2]).
```

In https://tools.ietf.org/html/rfc7541#section-6.3
```
   The new maximum size MUST be lower than or equal to the limit
   determined by the protocol using HPACK.  A value that exceeds this
   limit MUST be treated as a decoding error.  In HTTP/2, this limit is
   the last value of the SETTINGS_HEADER_TABLE_SIZE parameter (see
   Section 6.5.2 of [HTTP2]) received from the decoder and acknowledged
   by the encoder (see Section 6.5.3 of [HTTP2]).
```

I can read that dynamic table size update is always necessary as
acknowledgement of SETTINGS_HEADER_TABLE_SIZE if it is present in SETTINGS.
But someone can read in the other way.

Best regards,
Tatsuhiro Tsujikawa

---

From: Cory Benfield <[email-masked]>
Date: Mon, 19 Oct 2015 17:12:50 +0100

> On 19 Oct 2015, at 16:59, Tatsuhiro Tsujikawa <[email-masked]> wrote:
> 
> I can read that dynamic table size update is always necessary as acknowledgement of SETTINGS_HEADER_TABLE_SIZE if it is present in SETTINGS.

Yeah, see, I read it the other way. My reading of section 4.2 of RFC 7541 emphasises this: “a *change* in the maximum size […] is signalled”. But you’re right that it’s ambiguous. In this case, I’d say that an implementation MAY emit a dynamic table size update if the size is unchanged (because it’s a no-op), but MUST do so if it has changed. For implementation simplicity, many implementations will chose to always emit that size update, but equally many implementations will not. If we’re counting, my own implementation does the same thing as Google’s: see here[0].

Specifically, I don’t think the HPACK layer should complain because a dynamic table size update was not received when the dynamic table size did not change. In this instance I believe nghttp2 is being overly strict about the relationship between the HTTP/2 layer and the HPACK layer.

Cory

[0]: https://github.com/python-hyper/hpack/blob/master/hpack/table.py#L188

---

From: Tatsuhiro Tsujikawa <[email-masked]>
Date: Tue, 20 Oct 2015 01:26:16 +0900

Hi,

On Tue, Oct 20, 2015 at 1:12 AM, Cory Benfield <[email-masked]> wrote:

> Yeah, see, I read it the other way. My reading of section 4.2 of RFC 7541
> emphasises this: “a *change* in the maximum size […] is signalled”. But
> you’re right that it’s ambiguous. In this case, I’d say that an
> implementation MAY emit a dynamic table size update if the size is
> unchanged (because it’s a no-op), but MUST do so if it has changed. For
> implementation simplicity, many implementations will chose to always emit
> that size update, but equally many implementations will not. If we’re
> counting, my own implementation does the same thing as Google’s: see
> here[0].
>
> Specifically, I don’t think the HPACK layer should complain because a
> dynamic table size update was not received when the dynamic table size did
> not change. In this instance I believe nghttp2 is being overly strict about
> the relationship between the HTTP/2 layer and the HPACK layer.
>
>
​Could be.  But I don't think we mixed HTTP/2 layer and HPACK layer.  More
specifically, we input the max header table size to HPACK, and see what it
react to it in this case.  It is specific to HPACK implementation behaviour.

In general, we choose simpler path in HTTP/2​, I mean single execution path
rather than "do this if we have x otherwise do that".  For example, we
require client preface even if we negotiated HTTP/2 over ALPN.
We send HTTP2-Settings in HTTP Upgrade request, but still we need to send
SETTINGS frame in client preface.
Taking into account of this split, it is more natural to always send header
table size update as acknowledgement for SETTINGS_HEADER_TABLE_SIZE.  We
can do more strict validation about the peer; it might forget to implement
it anyway.

Best regards,
Tatsuhiro Tsujikawa

---

From: Cory Benfield <[email-masked]>
Date: Mon, 19 Oct 2015 18:15:28 +0100

> On 19 Oct 2015, at 17:26, Tatsuhiro Tsujikawa <[email-masked]> wrote:
> ​Could be.  But I don't think we mixed HTTP/2 layer and HPACK layer.  More specifically, we input the max header table size to HPACK, and see what it react to it in this case.  It is specific to HPACK implementation behaviour.
> 
> In general, we choose simpler path in HTTP/2​, I mean single execution path rather than "do this if we have x otherwise do that".  For example, we require client preface even if we negotiated HTTP/2 over ALPN.
> We send HTTP2-Settings in HTTP Upgrade request, but still we need to send SETTINGS frame in client preface.
> Taking into account of this split, it is more natural to always send header table size update as acknowledgement for SETTINGS_HEADER_TABLE_SIZE.  We can do more strict validation about the peer; it might forget to implement it anyway.

I don’t know that I buy that. The acknowledgement for SETTINGS_HEADER_TABLE_SIZE is a SETTINGS frame with the ACK flag set. Why is further acknowledgement required? As far as I can see it, there are two (slightly different) values here:

- SETTINGS_HEADER_TABLE_SIZE is the *maximum* value the encoder may use.
- Whatever is sent by the HPACK encoder in a dynamic table size update is the *actual* value being used.

You may be able to change the first without affecting the second (e.g. if you raise or leave unchanged the value of SETTINGS_HEADER_TABLE_SIZE). In the case that the second is not affected, it seems like unnecessary noise for the encoder to be forced to emit a dynamic table size update that has no change.

I don’t strongly object to adding an erratum to RFC 7541 that requires that a dynamic table update be emitted for any change to SETTINGS_HEADER_TABLE_SIZE, even if that change does not actually affect the size of the table the encoder will use, but my current reading of the specification does not require that such an update be emitted.

Cory

---

From: Hervé Ruellan <[email-masked]>
Date: Thu, 22 Oct 2015 18:18:17 +0200

I agree that the wording is ambiguous here.

However, my reading is the same a Cory's: you don't have to send a dynamic table update if the *actual* value is not changed.

Hervé

On 19/10/15 19:15, Cory Benfield wrote:
>
> > On 19 Oct 2015, at 17:26, Tatsuhiro Tsujikawa <[email-masked]
> > <mailto:[email-masked]>> wrote:
> > ​Could be.  But I don't think we mixed HTTP/2 layer and HPACK layer.
> > More specifically, we input the max header table size to HPACK, and
> > see what it react to it in this case.  It is specific to HPACK
> > implementation behaviour.
> >
> > In general, we choose simpler path in HTTP/2​, I mean single execution
> > path rather than "do this if we have x otherwise do that".  For
> > example, we require client preface even if we negotiated HTTP/2 over ALPN.
> > Taking into account of this split, it is more natural to always send
> > header table size update as acknowledgement for
> > SETTINGS_HEADER_TABLE_SIZE.  We can do more strict validation about
> > the peer; it might forget to implement it anyway.
>
> I don’t know that I buy that. The acknowledgement for
> SETTINGS_HEADER_TABLE_SIZE is a SETTINGS frame with the ACK flag set.
> Why is further acknowledgement required? As far as I can see it, there
> are two (slightly different) values here:
>
> You may be able to change the first without affecting the second (e.g.
> if you raise or leave unchanged the value of
> SETTINGS_HEADER_TABLE_SIZE). In the case that the second is not
> affected, it seems like unnecessary noise for the encoder to be forced
> to emit a dynamic table size update that has no change.
>
> I don’t strongly object to adding an erratum to RFC 7541 that requires
> that a dynamic table update be emitted for any change to
> SETTINGS_HEADER_TABLE_SIZE, even if that change does not actually affect
> the size of the table the encoder will use, but my current reading of
> the specification does not require that such an update be emitted.
>
> Cory

---

From: Tatsuhiro Tsujikawa <[email-masked]>
Date: Sat, 24 Oct 2015 00:45:49 +0900

Hi,

On Fri, Oct 23, 2015 at 1:18 AM, Hervé Ruellan <[email-masked]>
wrote:

> However, my reading is the same a Cory's: you don't have to send a dynamic
> table update if the *actual* value is not changed.
>
>
​I also found the discussion in this ML indicating you are right.  Thank
you for clarification.
I have to ask one more question: what is *actual* value? Is it the table
size both peer agreed before reading SETTINGS, or the value in
SETTINGS_HEADER_TABLE_SIZE decoder sent?

I think this is a good item to add in FAQ section..

Best regards,
Tatsuhiro Tsujikawa
​​

---

From: Ilari Liusvaara <[email-masked]>
Date: Fri, 23 Oct 2015 19:15:19 +0300

> I think this is a good item to add in FAQ section..

The way negotiation works:
- Decoder side sets the upper bound via SETTINGS_HEADER_TABLE_SIZE.
- Encoder side sets the actual size via dynamic table updates (inside
  HPACK bitstream) within limits set by decoder.
- If between headers decoder reduces the limit below size signaled by
  encoder, the encoder must first reduce the table size to the minimum
  it was between the frames or less (it can then increase it up to
  current limit).

As example of the last point:
[4k dynamic table size in use]
--> HEADERS
<-- SETTINGS(SETTINGS_HEADER_TABLE_SIZE=4k)
<-- SETTINGS(SETTINGS_HEADER_TABLE_SIZE=2k)
<-- SETTINGS(SETTINGS_HEADER_TABLE_SIZE=4k)
<-- SETTINGS(SETTINGS_HEADER_TABLE_SIZE=8k)
<-- SETTINGS(SETTINGS_HEADER_TABLE_SIZE=6k)
--> HEADERS

The second HEADERS must first reduce the dynamic table to at most
2k. It can then increase dynamic table size to up to 6k.

-Ilari