Subject: Stream0 Proposal: CRYPTO_HS frame encoding (Re: Stream0 Design Team Proposal)

From: Kazuho Oku <[email-masked]>
Date: Thu, 24 May 2018 09:05:58 +0900

2018-05-24 8:51 GMT+09:00 Martin Thomson <[email-masked]>:
> I'm not a fan of micro-optimizations like this, especially when it means
> taking space we don't need.
>
> We don't need ANY of the bits on the CRYPTO stream.  The savings for an
> absent length and offset are marginal and not worth burning 8 frame types
> on, and the stream never really ends.  So if we made it have a type of
> 0x18, then you would run it through the same code and get the same result.

Clarification question: are you and EKR suggesting something like below?

0x10 - 0x17: STREAM frame
0x18: CRYPTO_HS frame

if (0x10 <= type && type <= 0x18)  {  // STREAM or CRYPTO_HS frame
     if (type != 0x18) streamID = varint_consume(frame);
     if ((type & 0x04) || type == 0x18) offset = varint_consume(frame);
     if ((type & 0x02) || type == 0x18) length = varint_consume(frame);
     if (type & 0x01) fin = 1;
     read body if present
}

I think that is workable, although it makes me sad because we will
have more conditional branches.

> On Wed, May 23, 2018 at 4:40 PM Jana Iyengar <[email-masked]> wrote:
>
>> Thank you for your implementation and your report -- this is terrific and
> helpful!
>
>> On your note about CRYPTO_HS frames, I think your idea is clever. Just to
> ensure that I am understanding it correctly, I believe you're proposing
> defining:
>> 0x10 - 0x17: STREAM frame
>> 0x18 - 0x1F: CRYPTO_HS frame
>
>> We can the same frame format for CRYPTO_HS as STREAM except that
> CRYPTO_HS will not have the stream ID field. This means an implementation
> handling these frames can reuse the stream machinery with a small change to
> parse the header bits as follows:
>
>> if (type & 0x10)  {  // STREAM or CRYPTO_HS frame
>>      if (type & 0x08) streamID = varint_consume(frame);
>>      if (type & 0x04) offset = varint_consume(frame);
>>      if (type & 0x02) length = varint_consume(frame);
>>      if (type & 0x01) fin = 1;
>>      read body if present
>> }
>
>> On Tue, May 22, 2018 at 9:57 PM, Kazuho Oku <[email-masked]> wrote:
>
>>> FWIW, I am happy to tell you that I have a working PoC code for the
>>> proposed approach.
>
>>> On the QUIC side, I saw 2% (124 lines) increase in code size (more on
>>> that below). On the TLS side, I also saw 2% (175 lines) increase.
>>> However please look at the code size change especially on the QUIC
>>> side with grain of salt, as it is just a PoC.
>
>>> As expected, the key and encryption-level management on the QUIC side
>>> became very clean. Following are some of the design decisions I made
>>> as well as the outcomes:
>
>>> * All the interaction between picotls and quicly are accompanied by
>>> "epoch". Handshake messages are attributed with their epochs so that
>>> they do not get protected by the wrong key, or so that octets received
>>> under an incorrect encryption context cannot be misused.
>
>>> * All the traffic keys are governed by quicly (managing some traffic
>>> keys in TLS while managing PNE key in QUIC seems messy).
>
>>> * The Initial key is setup by quicly, wheeras other traffic keys are
>>> installed by picotls by calling a callback named
>>> update_traffic_key_cb. The callback accepts and installs 6 keys in
>>> total: for three levels (i.e. 0-RTT, handshake, 1-RTT) in two
>>> directions (send-side and receive-side).
>
>>> * Three PN spaces have their own AEAD encryption key. Initial PN and
>>> Handshake PN spaces have one aead decryption key each. Application PN
>>> space has up to two decryption keys: either for 0-RTT and 1-RTT or for
>>> two 1-RTT keys during key update.
>
>>> It was a pain to have a dedicated frame encoding for CRYPTO_HS, even
>>> though we can reuse (and I reused) the retransmission and reassembly
>>> logic of QUIC streams for the handshake flows. About a half of the
>>> code size increase comes from that (the other half comes from the
>>> added abstraction for having two contexts for the handshake). I would
>>> prefer reusing the STREAM frame encoding for the handshake data. We
>>> could possibly use a different base offset (i.e. for CRYPTO_HS frames
>>> we could use 0b00011XXX, whereas the STREAM frames use 0b00010XXX), as
>>> well as omitting the Stream ID field.
>
>>> Overall, now that I have a PoC, I am more confident that the proposed
>>> approach is the correct path forward. It *simplifies* the QUIC stack
>>> at the same time giving us better security properties as well as
>>> fixing various issues in the current design (as discussed in the
>>> design doc).
>
>
>>> 2018-05-23 10:30 GMT+09:00 Ian Swett <ianswett=
> [email-masked]>:
>>> > Dear QUIC WG,
>>> >
>>> >
>>> > On behalf of the Stream 0 Design Team, I am pleased to report that we
> have
>>> > consensus on a proposed approach to share with the WG. The DT's
> proposal
>>> > will make QUIC and TLS work closer together and incorporates ideas from
>>> > DTLS, but it does not use the DTLS protocol itself.
>>> >
>>> >
>>> > The DT believes this solves the important open Stream 0 issues. The
> proposal
>>> > will be a bit more invasive in TLS, but we believe it is the right
> long-term
>>> > direction and several TLS stacks (BoringSSL, PicoTLS, NSS, and Mint)
> are
>>> > willing and able to do the work necessary.. A number of stacks are
> currently
>>> > working on implementations of this new approach, which we hope to have
> in
>>> > time for the Interim meeting.
>>> >
>>> >
>>> > A design document describing the overall approach can be found at:
>>> >
>>> >
> https://docs.google.com/document/d/1fRsJqPinJl8N3b-bflDRV6auojfJLkxddT93j6SwHY8/edit
>>> >
>>> >
>>> > A PR making the changes to the QUIC documents can be found at:
>>> >
>>> > https://github.com/quicwg/base-drafts/pull/1377
>>> >
>>> >
>>> > A few design details did not have clear consensus, but it was felt it
> would
>>> > be better to discuss those in the wider WG than delay the design team.
>   A
>>> > consistent choice was made in the PR and these issues are mentioned in
>>> > Appendix B of the design doc.
>>> >
>>> >
>>> > As always, comments and questions welcome. That said, this is a big PR
> and
>>> > we recognize that some editorial work is going to be needed before
> merging.
>>> > In the interest of letting people follow along, and to keep github from
>>> > falling over, we ask people to keep discussion on the mailing list and
>>> > refrain from making PR comments.
>>> >
>>> >
>>> > See you in Kista!
>>> >
>>> >
>>> > Ian and Eric
>
>
>
>>> --
>>> Kazuho Oku

-- 
Kazuho Oku

---

From: Martin Thomson <[email-masked]>
Date: Thu, 24 May 2018 10:41:30 +1000

> 0x10 - 0x17: STREAM frame
...
> have more conditional branches.

Yeah.  We have an extremely limited space here.  And while Jana is right
that we haven't used much yet, we're only just starting.  As for
conditionals.  Maybe.  One way to implement this is a switch statement, and
those might not suffer as badly when you consider that you also have to
dispatch to other frame handlers in the same piece of code.

---

From: Christian Huitema <[email-masked]>
Date: Wed, 23 May 2018 19:07:15 -0700

On 5/23/2018 5:41 PM, Martin Thomson wrote:

> ....

I am with Martin on this one. Just one code point for crypto frames,
please. At best the optimization saves 2 bytes in the entire duration of
the connection -- one byte for the CH, one byte for the first frame of
the SH. And for the CH, you don't care because you are going to pas to
min length anyhow.

You can still do common code with the "stream" data even without that,
you just need two small branches of code to parse the stream id, stream
offset, and stream fin. Just default to "stream=Crypto, stream offset=
always present, stream fin= false" in the crypto branch.

-- Christian Huitema

---

From: Kazuho Oku <[email-masked]>
Date: Thu, 24 May 2018 14:54:08 +0900

> the SH. And for the CH, you don't care because you are going to pas to

FWIW, I disagree with the calculation.

My understanding is that having the OFF bit saves us 3 octets on the
server's flights and 2 to 3 octets on the client's flights per each
connection, because there will be 3 to 4 encryption levels and because
CRYPTO_HS will appear in all encryption levels except the client's
1-RTT flights.

The LEN bit has a bigger impact. It saves 4+2N octets where N is the
total number of Initial and Handshake packets sent by the server,
assuming that SH and NST will be larger than 63 octets.

So we will be using additional 13 octets in the first 3 packets that
the server sends.

OTOH, I do agree that additional 13 octets is big enough to justify
consuming more code points by itself. It's somewhere around 0.3% of
the amount of data you can send in 3 packets.

> always present, stream fin= false" in the crypto branch.

The thing that makes me sad is the omission the FIN bit making the
encoding side complex.

With the FIN bit, we have the guarantee that a payload of any size can
be sent without additional overhead. That simplifies the encoder
logic. What you do is:

if (payload_len <= window - 2) {
    set_len_bit()
    varint_encode(len)
    emit_payload()
} else if (payload_len <= window) {
    if (payload_len != window)
        prepend_padding_before_the_frame()
    clear_len_bit()
    emit_payload()
} else {
    partial_write...
}

Without the FIN bit, the framing code becomes completely different
because we will always have the overhead of the length field, and the
length of the length field depends on the amount of payload or the
space we have.

-- 
Kazuho Oku