Re: Framing and control-frame continuations

On 6/02/2013 11:28 a.m., Roberto Peon wrote:
> The proposal for framing is that there are always 8 bytes to read for 
> every frame, both control and data frames. (Thanks to Jeff Pinner for 
> modifying this for me already!)
>
> Generically, this is the format of a frame.
>
>   0 1 2 3 4 5 6 7
>   +--------+--------+--------+--------+--------+--------+--------+--------+
>   | Length(16) |Type(8) |Flags(8)| Num-of-Entries-or-Stream-ID-or-ID | ->
>   +--------+--------+--------+--------+--------+--------+--------+--------+
>   8..N
>   +========+
>   | Data |
>
>   +========+
>
> Length: An unsigned 16-bit value representing the number of bytes of
>    the data field.
>    Type: The frame type.
>    Flags: Flags related to this frame. Flag definitions are dependent
>    upon the frame type.
>    Data: data associated with this control frame. The format of this
>    data is controlled by the frame type.
> For a data frame, TYPE is set to zero, and the Num-of-Entries-or-Stream-ID-or-ID field (gotta get a better name :) ) contains a '0' followed by 31 bits of stream ID. Valid flags are:
>       0x01 = FLAG_FIN - signifies that this frame represents the last
>       frame to be transmitted on this stream. See Stream Close
>       (Section 2.3.7) below.
>       0x02 = MSG_DONE - signifies that this frame represents the last
>       frame of a message. This is relevant for layering of message-
>       based protocols on top of SPDY.
>
> For control frames, a non-zero value will exist in the TYPE field.  The following flags are valid for all control frames:
>
>       0x01 = FLAG_FIN - signifies that this frame represents the last
>
>       frame to be transmitted on this stream.
>
>       0x02 = MSG_DONE - signifies that this frame represents the last
>       frame of a sequence of a run of same-type control frames.
> If MSG_DONE is not set for a control-frame, then the particular control message was unable to fit within a single control-frame. Implementations may process the control message up to the amount received, but MUST not treat the message as done. Implementations SHOULD finish sending the control-message as soon as possible and when finished with the particular control message, they MUST set MSG_DONE.
>
> What do people think about the FLAGS/TYPE arrangement? If swapped, we gain more contiguous reserved bits.
> -=R

The SPDY framing is a bit clumsy, in particular its way of transmitting 
flow state as individual frames. Below is an alternative basic frame 
layout I have been working on to extend the original network-friendly 
draft frames using the SPDY and WebSockets signal types.


Using the below frame layout, in particular with two flags up front in 
specific positions we are able to transmit HTTP/2 and WebSockets frames 
on the same connection. With some small amount of magic only necessary 
during the transition period we are also able to identify SPDY v2, SPDY 
v3 and HTTP/1.x connection attempts. I am talking algorithm magic here 
*not* bits dedicated to a BOM. see "Relevant Magic" below.  I've been 
holding this off while I try to figure out what bit ranges the TLS 
handshakes are detectible with. It seems 32-bits is required if we merge 
TLS port 443 traffic into this magic, but I'm not yet completely certain 
of that.


NP: I use "flow" here to mean a series of frames which form 1..N 
requests and 1..N responses in a session from one client. A client may 
send multiple flows. For example; a proxy with many clients, browser 
with many tabs, spider doing parallel domain fetches, web service doing 
parallel fetches to a remote backend, etc. etc.

The Frame format:

         0                   1
         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
        +-+-+-----------+---------------+
        |F|C|  type     |               |
        +-+-+-----------+               +
        |        Frame Length (24)      |
        +-------------------------------+
        |       opaque ID (16)          |
        +-------------------------------+
        |     Frame Data (16...N)       |
        +-------------------------------+

  flags =  2-bits : frame flags shared by all frames with the following 
meaning:

   F = final frame. There are no more frames with this request ID 
following. This signals termination of the flow from this sender.
   C = continuation. There are more frames expected to follow in this flow.

* these are separate bits because F relates to the flow/session. C 
relates to the request, response, or entity being transferred.
   A flag combination of (F=0,C=0) signals the flow may continue but the 
object this frame relates to is now completed
   A flag combination of (F=0,C=1) signals the flow will continue with 
more frames for this particular object (request response, or entity)
   A flag combination of (F=1,C=0) signals the flow is terminating and 
the object being transferred is complete.
   A flag combination of (F=1,C=1) signals the flow is terminating and 
the object being transferred has been truncated.


  type =  6-bits : Frame Type code

* this frame layout intentionally overlaps with WebSockets in the first 
octet. Frame type codes 0x0, 0x1, 0x2, 0x8, 0x9, 0xA correlate directly 
to the WebSockets 'opcodes' of those values. We should probably reserve 
those frame types for WebSockets since from this octet onwards the WS 
frame differs markedly in field positioning. Doing this overlap allows 
us to multiplex HTTP/2 frames with WebSockets frames on the one 
connection without having to go through the Upgrade: handshake required 
with HTTP/1 protocol.


frame-length: 24-bit size numeric indicating how many octets the frame 
takes up. This size should include the headers 8 octets.


  opaque ID : 16-bit opaque identifier set by the sender to group a 
sequence of frames into a flow. The receiver must send it back on frames 
generated for the response or control frames related to that flow, or on 
frames of related responses when performing server-push.
    The request-ID of 0 is reserved for frames dealing with connection 
state, not with any particular request-response flow.

** Minimum size of Frame Data should be 16-bits, making frames 64-bit 
minimum with 32-bit alignment.


Relevant Magic:

* in HTTP/1 protocol the first 32-bits of a connection are ASCII characters.
   - The F flag is always 0 on the first frame of valid HTTP/1 traffic.
   - The C flag and type field will be random values.
   - the opaque ID field will never be 0x0.

* in SPDY protocol the first 32-bits of the connection is a control frame.
  - the F flag is set to 1.
  - the C flag is set to 0 (since the SPDY version field is 0x3)
  - the type field is 0x0 (since the SPDY version is 0x3)
  - the first octet of the frame length will be 0x1, 0x2 or 0x3 
corresponding to the SPDY version number.

* in WebSockets protocol the first 32-bits of a connection is either a 
HTTP/1 Upgrade request (to be handled as per HTTP/1) or a frame
  - the F flag may be set or not. with identical meaning to HTTP/2
  - the C bit is set to 0
  - the type field is set to a WebSockets opcode.

  * I propose in HTTP/2 the first frame be a connection feature 
negotiation frame...
  - have the F flag to 0. Differentiating it from HTTP/1 and signalling 
any WebSockets recipient to close the connection.
  - have the C flag set to 1. Differentiating it from WebSockets and 
SPDY. Also, signalling any WebSockets recipient to close the connection.
  - have the type field of 0x0. Differentiating it from HTTP/1 and 
signalling any HTTP/1 recipient with the '@ symbol as request method.
  - have a frame size limit of . Causing the \0 to be delivered to 
HTTP/1 recipients, such that their whole method string is '@'.
  - have the opaque ID of 0x0. Indicating a connection-level frame. And 
signalling any HTTP/1 recipient to abort the transaction.
  - the first 16 bits of the frame data be the version BOM.



This frame structure gives us several very important things:

* first 32-bits up front for any frame provide the magic needed to 
identify how it affects the flow, how big it is, and how to process it

* second 16-bits provide the opaque ID to group frames together for flow 
control and flow state management.

* clients and servers (including intermediaries) can maintain sessions 
and state using TCP details plus that opaque ID (IP:port:opaque-ID) as a 
unique identifier for any senders session.
For the vast majority of frames these fields are the only ones any 
intermediary software needs to actually care about for routing purposes. 
The flow opening frames are the exception here, where they are required 
processing to identify exactly what state to assign to the flow using 
that opaque ID. Possibly also some control frames to manage the flow 
control negotiation between machines - although most of these are likely 
to come under ID 0x0.

* intermediaries should be left free to re-write (or not) the opaque ID 
field as necessary to prevent collisions. This means the limit SPDY 
places on the ID counter in section 32K sessions before a connection 
MUST close is removed and any one connection is free to re-use identifiers

* with a 3-way separation of object termination (C flag) from flow 
termination (F flag) from TCP connection termination using no 
intermediary frames we are free to permit multiple requests and 
responses within one flow without additional overheads.
  + permitting simple server-push where one request fetch can trigger 
multiple responses using the same opaque ID.
  + permitting a whole browsing session to re-use the one opaque ID in 
loose equivalence to a sesison cookie.


Amos

Received on Wednesday, 6 February 2013 11:47:42 UTC