Subject: [hybi] Proposal: move Ping/Pong/Close into a Control frame

From: John Tamplin <[email-masked]>
Date: Wed, 8 Sep 2010 08:41:19 -0400

It sounds like we have basic agreement on the framing as proposed in
-01, with some wording changes and restrictions on control frames.  I
would like to make one proposal before incorporating that feedback
into a new draft:

   - replace the Ping/Pong/Close opcodes with a Control opcode
   - the Control payload contains a control-type field the remainder
of the payload is defined by the control type

This accomplishes three things:

  - it makes it clearer which frames are Control frames, so that when
we talk about Control frames being limited to 125 bytes in length and
may not be fragmented or may appear in the middle of a fragmented
message, implementations can be aware of those constraints even for
Control frame types introduced in extensions the implementation
doesn't know about.  In particular, how would an intermediary know
whether some opcode was an extension it didn't understand was allowed
in the middle of a fragmented message?
   - It seems to me that Control frames will be much less frequent
than other frames (for example, Close will only occur once per
connection) -- it seems wasteful to allocate 3 of our few opcodes to
these functions.  If we kept adding more opcodes (which obviously may
not happen), eventually we would have to introduce some extension
opcode which would use one or more bytes from the payload to define
the real opcode.  In that case we would have given less frequently
occurring Control opcodes the favored single-byte status, while
potentially more frequently used opcodes would require multiple bytes.
   - It makes the cost of defining new Control frame types for
extensions significantly less.

Granted there is the disadvantage of slightly increasing complexity
for the simplest implementations, but I really don't think it is that
bad -- a simple implementation might look like this:

short header = readShort();
bool more = (header & 0x8000) != 0;
int opcode = (header & 0x0f00) >> 8;
char lengthByte = header & 0x7f;
long long length;
switch (lengthByte) {
  case 126:
     length = readShort();
     break;
  case 127:
     length = readLongLong();
     break;
  default:
     length = lengthByte;
}
if (opcode == CONTROL) {
  opcode = -readByte();
}
switch (opcode) {
   case -PING:
      ....
   case -PONG:
      ...
   case -CLOSE:
      ...
   case TEXT:
      ...
}

So I don't really think the complexity argument overrides the benefits.

--
John A. Tamplin
Software Engineer (GWT), Google

---

From: Greg Wilkins <[email-masked]>
Date: Thu, 9 Sep 2010 13:43:28 +1000

John,

I definitely think it is a good idea to clearly identify control frames.

But are we really that short of op-code space and reserved bits that
we have to put the control code into the payload?

I would prefer to allocated the RSV3 bit to indicate a control frame,
giving us 16 normal opcodes (with text and
binary allocated (maybe mime)) and 16 control op codes (with
ping,pong,close allocated).

cheers

On 8 September 2010 22:41, John Tamplin <[email-masked]> wrote:
> _______________________________________________
> hybi mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/hybi
>

---

From: John Tamplin <[email-masked]>
Date: Thu, 9 Sep 2010 00:32:09 -0400

> we have to put the control code into the payload?

The cost is hard to determine until you run out, and then it is
expensive for the later ones.  We already have discussed a number of
extension ideas, each of which requires either a per-frame bit, one or
more opcodes, or a general metadata mechism which itself would use one
bit.  With just those, we would be able to allocate the bit as you
suggest, but the problem is those are only the ideas we have today
about how it might be extended.  If you look at how other protocols
have been extended, the extension ideas that were present when the
protocol was designed were only a tiny fraction of those that were
later considered or implemented.

That is why I would prefer to preserve flexibility as much as possible.

To phrase your question differently, are we really so short of payload
bytes in control frames that using one of them is a problem?  What is
the actual objection to the proposal?

> ping,pong,close allocated).

That would be equivalent to allocating RSV3 as an opcode bit, other
than being forced to split control and data opcodes evenly.

---

From: Patrick McManus <[email-masked]>
Date: Thu, 09 Sep 2010 08:46:12 -0400

> of the payload is defined by the control type

I am fine with ping/pong and future control codes, but close is a
special case - analogous to the opening handshake and necessary for any
engine that only wants to bridge messages back and forth.

Can you put your proposal into a proposed change to the text?

---

From: John Tamplin <[email-masked]>
Date: Thu, 9 Sep 2010 11:30:12 -0400

> engine that only wants to bridge messages back and forth.

An implementation is required to recognize a Ping and send a Pong, so
I am not sure Close is any different.

> Can you put your proposal into a proposed change to the text?

Proposed changes to draft-ietf-hybi-thewebsocketprotocol-01.txt (also
incorporating your suggested changes where relevant):

  - section 42, page 21: replace frame-opcode and application data with:
        frame-opcode    = %x0 ; continuation frame
                                   / %x1 ; control frame
                                   / %x2 ; text frame
                                   / %x3 ; binary frame
                                   / %x4-F ; reserved

        application-data = control-type * ( %x00-FF ) ; if frame-opcde=%x1
                                   / * ( %x00-FF ) ; otherwise

        control-type        = %x00 ; connection close
                                   / %x01 ; ping
                                   / %x02 ; pong
                                   / %x03-FF ; reserved

  - reorder section 4.4 to be 4.3, and the existing 4.3 to be 4.4
  - existing section 4.4, page 22, replaced with:
     "Control frames have an frame-opcode value of %x1.  They are used
to communicate state
      about the websocket.

      All control frames MUST be 125 bytes or less in length and MUST
NOT be fragmented (see [new] 4.4).

      The first byte of application-data indicates the type of control
frame, with the remainder
      of the application-data interpreted as defined by that type:

      Close (control-type = %x00)

         An endpoint MUST NOT send any more data messages after
sending a close message.

         A received close message is deemed to be an acknowledgement
if the application-data
         matches that of a close message previously sent by the
receiver.  Otherwise, the close
         message is a close initiated by the sender.

         Upon receipt of an initiated close the endpoint MUST send the
close acknowledgment,
         which SHOULD be sent as soon as practical.

         The websocket is considered fully closed when an endpoint has
either received or sent
         a close acknowledgment.

      Ping (control-type = %x01)
      Pong (control-type = %x02)

         Upon receipt of a Ping message, an endpoint MUST send a Pong
message in response.
         It SHOULD do so as soon as is practical.  The
application-data of the Pong response
         MUST be identical to that from the Ping message.  An endpoint
MAY send a Ping message
         at any frame boundary.

     Reserved (control-type = %x03-FF)

         Control frames with these control-type values are reserved
for future use and MUST NOT be
         used absent negotiation of an extension defining their use
during the handshake (see 4.7)."

  - existing section 4.3, page 22:
     - append to the second bullet item:
        "Zero or more control frames (those with frame-opcode=%x1) may
be inserted between each of these frames."
     - replace the third bullet item with:
        "Control frames MUST NOT be fragmented (see [new] 4.3)."

---

From: Patrick McManus <[email-masked]>
Date: Thu, 09 Sep 2010 13:33:36 -0400

> I am not sure Close is any different.

that's why I referred to an engine that only wants to bridge messages
back and forth. The assumption was an intermediary of some kind. They
are very interested in open/close and message delimiting.. other frames
they might just forward opaquely but open and close are important.

---

From: John Tamplin <[email-masked]>
Date: Thu, 9 Sep 2010 14:00:31 -0400

> they might just forward opaquely but open and close are important.

Is it really so hard for them to recognize the control opcode and read the
first byte of the payload for the control frame type?

Having some control frames be opcodes and other control frames being a
sub-opcode seems unnecessarily complex for practically no gain.

---

From: Scott Ferguson <[email-masked]>
Date: Thu, 09 Sep 2010 11:47:42 -0700

> I am not sure Close is any different.

The difference is that Close is intrinsic to the structure of the stream 
and can't be split out without messing up the whole protocol. You can't 
really describe the protocol without Close.

Ping and Pong in contrast are really extensions, though they're very 
important (and mandatory) extensions. It's possible to describe the 
protocol without describing Ping and Pong, which isn't true for Close. I 
think there's an important distinction between the core protocol (Close, 
Continue, Text, Binary) and mandatory extensions (Ping and Pong) and 
merging them together just because they're both mandatory isn't helpful.

It may also be productive to have mandatory extensions (including 
ping/pong) in a separate document, to make it easier to focus on the 
core and make absolutely certain the core is right.

-- Scott

---

From: John Tamplin <[email-masked]>
Date: Thu, 9 Sep 2010 15:02:44 -0400

On Thu, Sep 9, 2010 at 2:47 PM, Scott Ferguson <[email-masked]> wrote:
>
> The difference is that Close is intrinsic to the structure of the stream and can't be split out without messing up the whole protocol. You can't really describe the protocol without Close.
> Ping and Pong in contrast are really extensions, though they're very important (and mandatory) extensions. It's possible to describe the protocol without describing Ping and Pong, which isn't true for Close. I think there's an important distinction between the core protocol (Close, Continue, Text, Binary) and mandatory extensions (Ping and Pong) and merging them together just because they're both mandatory isn't helpful.

I don't really see how Ping/Pong are extensions other than nobody is
ever required to send a Ping -- a prescribed behavior is required on
receipt of a Ping message.
I am proposing merging them togeher because they are all control
frames, which has very specific features:

  - they can't be fragmented
  - they are limited to the 2-byte header
  - they can appear in the middle of a fragmented message
  - they do not carry end-user data

It certainly seems to me they have more in common with each other than
Close and Text do, for example.

---

From: Dave Cridland <[email-masked]>
Date: Thu, 09 Sep 2010 20:06:38 +0100

On Thu Sep  9 20:02:44 2010, John Tamplin wrote:
> > The difference is that Close is intrinsic to the structure of the  
> stream and can't be split out without messing up the whole  
> protocol. You can't really describe the protocol without Close.
> > Ping and Pong in contrast are really extensions, though they're  
> very important (and mandatory) extensions. It's possible to  
> describe the protocol without describing Ping and Pong, which isn't  
> true for Close. I think there's an important distinction between  
> the core protocol (Close, Continue, Text, Binary) and mandatory  
> extensions (Ping and Pong) and merging them together just because  
> they're both mandatory isn't helpful.
> 

+1, and it also means implementations will have to know about Control  
frames, which is no bad thing at all.

Dave.
-- 
Dave Cridland - mailto:[email-masked] - xmpp:[email-masked]
  - acap://acap.dave.cridland.net/byowner/user/dwd/bookmarks/
  - http://dave.cridland.net/
Infotrope Polymer - ACAP, IMAP, ESMTP, and Lemonade

---

From: Scott Ferguson <[email-masked]>
Date: Thu, 09 Sep 2010 12:41:32 -0700

> receipt of a Ping message.
Close is part of the grammar of the stream:

  stream ::= message* Close

Or more fully:

  stream ::= (message | control)* Close
  message ::= (fragment | control)* final-fragment

Neither Ping nor Pong are part of the core grammar; they're just control 
messages. Close is not just control message; it's in the grammar itself.
 
>   - they can appear in the middle of a fragmented message
This is not true of Close, and that difference is the critical point. 
Close changes the whole operation of the client/server. It's not just a 
status message like Ping/Pong.
>   - they do not carry end-user data

The other similarities are not particularly important.

-- Scott

---

From: John Tamplin <[email-masked]>
Date: Thu, 9 Sep 2010 15:46:43 -0400

>  message ::= (fragment | control)* final-fragment

Ping/Pong are there as well, since control expands to Ping/Pong/OtherControl.

> messages. Close is not just control message; it's in the grammar itself.

I still don't see the distinction, but I guess we can just disagree there.

> This is not true of Close, and that difference is the critical point. Close
> changes the whole operation of the client/server. It's not just a status
> message like Ping/Pong.

A Close message *can* appear in the middle of a fragmented message --
if I am in the middle of sending a fragmented message and I receive a
Close message, I will send a Close message out and drop the connection
rather than wait until I finish sending the fragmented message.

---

From: Scott Ferguson <[email-masked]>
Date: Thu, 09 Sep 2010 13:02:18 -0700

> Ping/Pong are there as well, since control expands to Ping/Pong/OtherControl.

That's true of any opcode you could ever name.

control expands to Compress/Mux/OtherControl.

By the way, why do you think we're going to run out of opcodes? The main 
ones I can think of are:

  1. Text
  2. Binary
  3. Continue
  4. Close
  5. Ping
  6. Pong
  7. Control/Extension
  8. Private (expands into 256-64k private opcodes like control does.)
  9. Mux

Any other opcode would be put into the control/extension group, still 
leaving 7 free just in case someone comes up with something crazy. 
That's a lot of free opcodes. I don't see the value of complicating the 
spec just to reserve 3 opcodes.

> rather than wait until I finish sending the fragmented message.

That's not the "middle" because the entire stream is closed. There are 
no tokens after the Close. The actual grammar is more complicated, 
that's true.

-- Scott

---

From: John Tamplin <[email-masked]>
Date: Thu, 9 Sep 2010 16:58:51 -0400

On Thu, Sep 9, 2010 at 4:02 PM, Scott Ferguson <[email-masked]> wrote:
> leaving 7 free just in case someone comes up with something crazy. That's a
> lot of free opcodes. I don't see the value of complicating the spec just to
> reserve 3 opcodes.

Let's say that we come up with a new payload type that winds up being
very popular, such that most WebSocket traffic is using it.  In your
proposal, it would fall under the Private opcode and incur a penalty
of one byte per frame, on the most common frame.  Close is sent
exactly once per direction on a WebSocket, so is certain to be less
frequent, so allocating an extra byte to it is negligible.

The point is we don't know all the ways we might want to extend the
protocol -- if we did, we wouldn't bother with extension mechanisms,
we would just define them.  Without knowing all the ways we might need
to extend it, prudence dictates treating the reserved bits/values as
valuable resources and allocating them only where the benefits are
justified.

I also don't see this as complicating the spec -- in the -01 spec, we
have to define a list of opcodes which are considered Control opcodes
and therefore have the various differences previously mentioned.  It
seems much cleaner to me to have one opcode which says that this is a
control frame, and all other frames are data-bearing, may be
fragmented, may not be interleaved with a fragmented message, etc.

> That's not the "middle" because the entire stream is closed. There are no
> tokens after the Close. The actual grammar is more complicated, that's true.

It still remains that it is valid to see while a fragmented message is
being processed, so a receiver has to handle it specially just like
any other Control frame.

---

From: Greg Wilkins <[email-masked]>
Date: Fri, 10 Sep 2010 16:30:38 +1000

John,

I'm a definite +1 for all your proposed text about the treatment of
control frames.

I'm still very dubious about the need to dereference control op-codes
to the payload.
I agree that the complexity is not high, but it does lead to the
impression of first class and second class op-codes, so we get debates
like the current one (ie is close more important than ping/pong).

I think that leaving space in the op-code range for some hypothetical
future extension that needs lots of op codes, but can't afford the
extra 1 byte per frame, is a pretty long bow to draw.

We have space in our current 4 bit op-code for 8 normal and 8 control
opcodes, and we have definitions for only 3 of each.   So we have more
that 50% spare capacity, not even considering the 3 reserved bits that
could give us a greatly expanded op-code range if it is ever needed.

So, while I don't really have a strong argument to NOT put the control
op-code into the payload, I don't think there is a strong reason to
put it in there either.  When in doubt, surely the simplest solution
(if only slightly simpler) should win out?

cheers

---

From: John Tamplin <[email-masked]>
Date: Fri, 10 Sep 2010 02:58:00 -0400

> like the current one (ie is close more important than ping/pong).

But I think there already is such a distinction -- data-bearing
opcodes and control opcodes.  Presumably, the point of a WebSocket
connection is to exchange data so the data-bearing frames will far
outnumber the control frames.  Why should Close, of which there will
never be more than 2 frames on a connection, be treated equally with
data-bearing frames?

> could give us a greatly expanded op-code range if it is ever needed.

I also think we need to reserve some of that for private-use (which
will never be assigned by a standard) so we can get real-world mileage
on proposed extensions without interfering with values defined by the
standard.

> (if only slightly simpler) should win out?

I really think it is important that control opcodes can be
differentiated from non-control opcodes, in that an intermediary can
know that some opcode it doesn't understand either is or isn't a
control opcode.  For example, imagine a security-oriented intermediary
interpreting the following:

    opcode=4 (text)/more=1; opcode=0 (continuation)/more=1; opcode=7/more=0

Does it assume opcode 7 is a control opcode and should be passed on to
the endpoint?  Or does it assume it is an attack and drop the
connection?

I think answering this question means it is not any simpler to have
them all top-level opcodes.

I think this example shows that having a table of which opcodes are
control opcodes (essentially what is in -01) is not acceptable.
Either we group them all together under one opcode, as I proposed, or
we define certain ranges of opcodes to be control opcodes.  If the
latter, we are fragmenting our limited address space when we don't
know how it will be used -- what if we split it 8/8 as you suggest and
we never add another control opcode but we want to add 8 more data
opcodes?  We would essentially have wasted space in the header that
can't be used effectively forcing us to have second-class opcodes
earlier than otherwise would have been required, and likewise if we
add more control opcodes than data opcodes.

---

From: Roderick Baier <[email-masked]>
Date: Fri, 10 Sep 2010 10:59:24 +0200

Wouldn't it be simpler to use RSV3 to indicate control frames? We would 
have a clear separation between data and control frames and have enough 
space for reserved/private opcodes. This would also be good for 
extension specific control frames which contain data (for time 
synchronization or whatever).

Regards,
Roderick

---

From: Greg Wilkins <[email-masked]>
Date: Fri, 10 Sep 2010 19:03:13 +1000

On 10 September 2010 16:58, John Tamplin <[email-masked]> wrote:
> differentiated from non-control opcodes,

I totally agree on this point.

I don't agree on putting control opcodes in the payload, but then I'm
not all that against it either.   You appear to have very strong
feelings that we should, so if you can convince others, then I'm not
going to object further.

cheers

---

From: John Tamplin <[email-masked]>
Date: Fri, 10 Sep 2010 10:47:51 -0400

On Fri, Sep 10, 2010 at 4:59 AM, Roderick Baier
<[email-masked]> wrote:
> Wouldn't it be simpler to use RSV3 to indicate control frames? We would have
> a clear separation between data and control frames and have enough space for
> reserved/private opcodes. This would also be good for extension specific
> control frames which contain data (for time synchronization or whatever).

As mentioned previously, that is precisely the same as allocating RSV3
to be part of the opcode and then defining ranges of opcodes to be
data vs control.  The downside is you have a fixed break between the
two, and actual needs may not match how we divide them up now.

Note that all the control frames defined now have data (though
unspecified in the case of Close).

---

From: John Tamplin <[email-masked]>
Date: Fri, 10 Sep 2010 10:48:55 -0400

> feelings that we should, so if you can convince others, then I'm not

Apparently nobody else thinks it is a good idea, so I will drop it and
settle for defining a range of opcodes to be control frames.

---

From: John Tamplin <[email-masked]>
Date: Mon, 13 Sep 2010 14:51:35 -0400

> settle for defining a range of opcodes to be control frames.

So I propose the following rearrangement of opcodes from -01:
 - %0 - continuation frame
 - %x1-6 - control frames (may not be fragmented, must use short
length form, may appear even when there is a fragmented message in
progress
   - %x1 : close
   - %x2 : ping
   - %x3 : pong
   - %x4-6 : reserved for future assignment
 - %x7-F - data-bearing frames (may be fragmented, may not appear when
there is a fragmented message in progress
   - %x7 : text
   - %x8 : binary
   - %x9-F : reserved for future assignment

The reason for the control frames being at the low range is that if we
decide we need more bits in the opcode, the additional values are all
data-bearing opcodes since it seems more likely that we will need
extra values there.

---

From: Dave Cridland <[email-masked]>
Date: Mon, 13 Sep 2010 20:27:58 +0100

> > settle for defining a range of opcodes to be control frames.
I didn't think it was a *bad* idea...

>    - %x4-6 : reserved for future assignment

I have a minor concern about the "must use short length form". I'd be  
interested in "SHOULD use short length form, MUST NOT be fragmented".

My reasoning is that while fragmentation is obviously out if you want  
to send these in between fragments, I don't see that it's a  
requirement to prevent larger frame sizes. I agree people would be  
insane to use huge ones, but I'm willing to believe that use-cases  
for non-short control frames may exist.

> data-bearing opcodes since it seems more likely that we will need

I'm not sure I follow why, but I don't have any argument either way.

Dave.

---

From: John Tamplin <[email-masked]>
Date: Mon, 13 Sep 2010 15:42:35 -0400

On Mon, Sep 13, 2010 at 3:27 PM, Dave Cridland <[email-masked]> wrote:
> My reasoning is that while fragmentation is obviously out if you want to
> send these in between fragments, I don't see that it's a requirement to
> prevent larger frame sizes. I agree people would be insane to use huge ones,
> but I'm willing to believe that use-cases for non-short control frames may
> exist.

If they can't be fragmented and you can send a long one, then it poses
a problem for intermediaries that want to share the a physical channel
between multiple logical channels.

---

From: Dave Cridland <[email-masked]>
Date: Mon, 13 Sep 2010 20:48:18 +0100

> between multiple logical channels.

There are solutions to this, but you're right, I don't think they're  
worth bothering with.

MUST be short, then.

Dave.