Subject: [BEHAVE] TURN status and direction

From: Jonathan Rosenberg <[email-masked]>
Date: Mon, 08 Oct 2007 18:08:22 -0400

Almost a month back, I sent a note to the behave list about a possible 
change in TURN to try to simplify things:

http://www1.ietf.org/mail-archive/web/behave/current/msg02742.html

the design team has been meeting 1-2 times a week since then, working 
through this proposal, and also working through the details on TCP and 
forking. Our original plan was to have a revised TURN available by 
October 1, but given the issues we were tackling, this has proven to be 
impossible. We're aiming for a draft now prior to the IETF.

So, until then, I wanted to summarize our current thinking on changes in 
TURN.

As in my previous note, we still think its a good idea to add a small 
shim to UDP that is always used. This would be a 1-byte shim, which 
signals a channel number. Channel 0 is reserved for TURN traffic between 
  the client and the TURN server. THe other channels are for data to a 
specific remote destination (IP address and port). Except for channel 0, 
the channel numbers in each direction are independent. The Send and Data 
indications remain, allowing the client and TURN server respectively to 
send TURN-encapsulated data in each direction. However, these messages 
will now carry a channel attribute that is used to establish a channel. 
So, for example, when the client begins sending data through the TURN 
server to a particular remote destination, it will use the Send 
indication. It picks an unallocated channel, and includes that in the 
Send indication. The TURN server will, in response to a Send indication, 
send an empty Data indication back to the client, echoing the channel 
number. It will also store a mapping that binds this channel to the 
remote destination (IP and port) in the Send. The empty Data indication 
allows the client to know that the channel allocation has succeeded. 
Once the client has received this empty Data indication, it knows it is 
free to send data on that allocated channel. The client can then send 
UDP packets for that remote destination by just sending a UDP packet to 
the TURN server with the 8-byte header including the allocated channel.

For data from the TURN server back to the client, it works just the 
same. The TURN server includes a channel number in its Data indications, 
and the client acknowledges this with an empty Send indication echoing 
the channel number back. When the TURN server receives data from a 
remote peer (assuming there is a permission), it sends it on the 
appropriate data channel if one has been set up, else sends it 
unencapsulated which has the side-effect of allocating a channel.

We debated many variations on this technique; the leading alternative 
was to use a separate transaction for channel allocation, rather than 
piggybacking on the data and send indications. This works less 
effectively with ICE (sends more packets and increases delay), and 
requires a STUN server side transaction model in clients, which is not 
required here. So we chose the approach above.

The second issue we debated extensively was in response to Matt Mathis' 
comments, about the case of two remote tcp connections being multiplexed 
into a single tcp connection from client to TURN server. This is 
something that WILL happen; there are several use cases for it. The most 
likely one is a call from an ICE agent to a non-ICE peer, where the 
media stream is actually something like MSRP, and the request forks to 
two remote agents. This case will cause the two remote agents to each 
open a tcp connection to the default candidate in the SDP - usually a 
TURN candidate. Typically only one of these connections survives in the 
long term; depending on which remote agent answers. But during the early 
media period both remain.

We worked through some approaches for causing multiple tcp connections 
to get formed from client to turn server when this happens. In the end, 
they were all really complicated. We felt it was better to simplify turn 
than add features to deal with this case. So instead our proposal is to 
make clear in the spec that, should this case happen (multiple tcp 
connections to remote agents), and more than a trivial amount of data is 
to be sent (and we'll quantify that), the agent MUST do a re-invite (or 
equivalent for non-SIP cases) to get a new turn candidate and thus 
separate connections for one of the remote peers. This is a purposeful 
limitation in TURN - its only meant to be used with a single remote 
correspondent at a time.

We are now beginning the process of integrating these changes into the 
TURN draft, while working through the other issues raised in the review 
period. Comments or questions on the above proposals are welcome.

Thanks,
Jonathan R.

-- 
Jonathan D. Rosenberg, Ph.D.                   600 Lanidex Plaza
Cisco Fellow                                   Parsippany, NJ 07054-2711
Cisco Systems
[email-masked]                              FAX:   (973) 952-5050
http://www.jdrosen.net                         PHONE: (973) 952-5000
http://www.cisco.com

_______________________________________________
Behave mailing list
[email-masked]
https://www1.ietf.org/mailman/listinfo/behave

---

From: Lars Eggert <[email-masked]>
Date: Wed, 10 Oct 2007 09:03:42 -0500

Hi,

On 2007-10-8, at 17:08, ext Jonathan Rosenberg wrote:
> We worked through some approaches for causing multiple tcp  
> connections to get formed from client to turn server when this  
> happens. In the end, they were all really complicated.

these approaches are complicated because multiplexing different TCP  
connections onto a single one *is* a complex operation.

> We felt it was better to simplify turn than add features to deal  
> with this case. So instead our proposal is to make clear in the  
> spec that, should this case happen (multiple tcp connections to  
> remote agents), and more than a trivial amount of data is to be  
> sent (and we'll quantify that), the agent MUST do a re-invite (or  
> separate connections for one of the remote peers. This is a  
> purposeful limitation in TURN - its only meant to be used with a  
> single remote correspondent at a time.

I'm not sure I fully understand this proposal based on this short  
description, but I can see some reasons why this could be  
problematic. First, the TURN server cannot enforce how an application  
uses TCP. Second, the ability to re-invite may not be present in many  
TCP-using applications.

If TURN needs the ability to multiplex TCP connections, it needs to  
specify a mechanism that works for *all* uses of TCP, and not just a  
convenient subset.

Lars

---

From: Jonathan Rosenberg <[email-masked]>
Date: Wed, 10 Oct 2007 10:49:51 -0400

inline.

Lars Eggert wrote:

>> We felt it was better to simplify turn than add features to deal with 
>> this case. So instead our proposal is to make clear in the spec that, 
>> should this case happen (multiple tcp connections to remote agents), 
>> and more than a trivial amount of data is to be sent (and we'll 
>> quantify that), the agent MUST do a re-invite (or equivalent for 
>> non-SIP cases) to get a new turn candidate and thus separate 
>> connections for one of the remote peers. This is a purposeful 
> description, but I can see some reasons why this could be problematic. 
> First, the TURN server cannot enforce how an application uses TCP. 
> Second, the ability to re-invite may not be present in many TCP-using 
> applications.
> 

TURN has always assumed that there was some kind of rendezvous protocol 
working with it. The entire permission model is built off of the idea 
that a client knows the IP addresses of its remote peers, through that 
rendezvous mechanism.

So, what we have here is just another requirement on the rendezvous 
protocol. We require that, it either return a single address for a 
remote peer, or if it ends up with multiple, it have the ability to 
provide an updated IP address and port to the remote peer.

This requirement is readily met by SIP and also seems to be OK for other 
things you can think of that might use TURN. The design team couldn't 
think of cases where this limitation was a problem.

So I am reluctant to add functionality into the TURN layer when we can 
address the problem in the rendezvous protocol. Doing something in one 
place is better than two.

-Jonathan R.

---

From: Laine Stump <[email-masked]>
Date: Wed, 10 Oct 2007 13:44:11 -0400

(Sorry I didn't pipe up last month when you sent the original message 
and asked for comments. My only excuse is that I wasn't paying attention.)

Jonathan Rosenberg wrote:
 > signals a channel number.

As an implementor of a TURN server based on the previous drafts, I have 
a couple concerns with this change in the design that I didn't see 
addressed in this mail or your earlier message on the subject: Adding a 
single byte at the beginning of every relayed packet will 1) necessitate 
recalculation of the UDP checksum, as well as 2) potentially causing 
alignment problems on architectures that care about that (for relays 
that want to do something with the payload on its way through, eg 
collect statistics).

Have you considered 1) making the channel number byte a negotiated 
option, so that simplistic uses can enjoy lower overhead on the relay, 
and/or 2) using 2 bytes for the channel number when it is there, to make 
recomputing the checksum simpler (it could be adjusted rather than 
recomputed from scratch), or 4 bytes, to also alleviate alignment problems?

The current model of 0 modifications to the payload (once the active 
destination is set) certainly makes for a more efficient relay process 
in all of the cases where there is a single destination per allocation. 
Although I understand the need for adding the channel functionality (and 
cleaning out the "Set Active Destination" stuff), it would be sad to 
lose the current model's post-setup elegance even when the extra 
functionality isn't required.

 > The client can then send
 > UDP packets for that remote destination by just sending a UDP packet
 > to the TURN server with the 8-byte header including the allocated
 > channel.

I think I missed something here. Which 8 byte header? Did you mean 8 *bit*?

---

From: Rémi Denis-Courmont <[email-masked]>
Date: Thu, 11 Oct 2007 13:50:28 +0300

> recomputed from scratch), or 4 bytes, to also alleviate alignment problems?

I wouldn't mind using 2 bytes instead of 1 if that really matters. In any 
case, MTU limits tend to be multiple of 8 bytes, so adding two rather than 
one bytes will make no practical difference with regard to packets size.

As regard alignment, I think the most important thing is that, the 
(mis)alignment MUST be always the same, so that it can be compensated easily 
(as with the Ethernet 14-bytes header), rather than have it retain some kind 
of alignment. In fact, if the payload is RTP, the needed alignment would be 4 
bytes, but what if, as Lars pointed out, is not RTP? For the reference, somey 
IETF protocols use multiple of 8-bytes boundaries (IPv6 and ICMPv6 comes to 
mind), and who knows what the requirement for the upper-layer protocol will 
be??

> in all of the cases where there is a single destination per allocation.

Bah, modern IP stacks (both in kernel- and user-land) support vectorized I/O 
to work around this.

> functionality isn't required.

I'd say the opposite. I'd rather have some unique and harmonized way of doing 
things, that is simple to implement, easy to demultiplex and has predictible 
alignment properties.

-- 
Rémi Denis-Courmont

---

From: "Pete Cordell" <[email-masked]>
Date: Thu, 11 Oct 2007 12:52:31 +0100

-Original Message From: "Rémi Denis-Courmont" 
<[email-masked]>

> mind), and who knows what the requirement for the upper-layer protocol 

Isn't the issue more a case of what the various processors will accomodate 
as word alignment rather than what the protocols natural alignment is?

My understanding is that x86 is quite happy to do 32-bit word accesses that 
are not aligned to 32-word boundaries.  However, it will do two rather than 
one bus accesses to do it (sub-optimal, but not a disaster).  In the past, 
processors such as PowePC and Sparc couldn't cope at all with doing 
non-aligned accesses (more of a disaster!), although I think this situation 
is now improved.  Problems may occur on embedded processors such a ARM/ARC, 
and DSP cores that has code optimized to exploit RTP's alignment to 32-bit 
boundaries.

To me, having a 4 byte header seems safest, and least likely to cause 
problems.

> Bah, modern IP stacks (both in kernel- and user-land) support vectorized 

I've not heard of such stacks (or is it something like Windows I/O 
completion ports?), but can they vectorize with non-aligned boundaries?

Pete.
=============================================
Pete Cordell
Codalogic
XML Schema to C++ data binding
 http://www.codalogic.com/
=============================================

---

From: Rémi Denis-Courmont <[email-masked]>
Date: Thu, 11 Oct 2007 15:07:51 +0300

> as word alignment rather than what the protocols natural alignment is?
(...)
> To me, having a 4 byte header seems safest, and least likely to cause

It does not matter what the processors want if the protocol does not abide by 
the processor wishes in the first place. If we have a non-RTP protocol that 
includes 64-bits quantity (e.g. doing IPv6 tunneling through a TURN relay), 
we'd need a 8-bytes alignment to make 64-bits processors happy/happier. Does 
that mean the TURN header should be a multiple of 8 bytes?

What if I have a 128-bits processor and a protocol with 128-bits quantities? 
TURN can be used with any upper-layer protocols... It seems a lot more safe 
to have a *fixed-size* *always* *included* header. That's how Ethernet 
drivers cope with the 14 bytes Etherne II header; receive the packet with a 
two bytes initial offset from the "natural" boundary (14+2=16) so the network 
header is well-aligned. However, for this to work, the header size must be 
known in advance.

> > Bah, modern IP stacks (both in kernel- and user-land) support vectorized
> > I/O to work around this.
>

I meant, scatter-gather I/O arrays: struct iovec with POSIX sendmsg/recvmsg, 
or WSABUF with Windows WSASend/WSARecv.

-- 
Rémi Denis-Courmont

---

From: "Pete Cordell" <[email-masked]>
Date: Thu, 11 Oct 2007 14:57:46 +0100

- Original Message From: "Rémi Denis-Courmont"

> the processor wishes in the first place. ...

If I wasn't convinced by the utility of the scatter-gather I/O you mention 
below I would dispute that.  Fortunately I can save myself (and yourself) 
the effort!

> ... It seems a lot more safe
> to have a *fixed-size* *always* *included* header.

I  agree that a fixed sized, always present (or at least, predictably 
present) header is important.

> or WSABUF with Windows WSASend/WSARecv.

So, on a 64-bit machine that's handling a 64-bit aligned protocol, I take it 
you mean doing something like:

    char stun_channel[1];    // Could be just a char, but hey...
    int64_t protocol_buffer[LOTS];

    struct iovec recv_iovec[2] = {
        { (caddr_t)stun_channel, 1 },
        { (caddr_t)protocol_buffer, sizeof(protocol_buffer) } };

    int res = recvmsg( ???, recv_iovec, 2, ??? );

    if( stun_channel[0] != 0 )
        digest_protocol( protocol_buffer );

If there are no issues with this approach, then I don't have any alignment 
concerns.

Cheers,

Pete.

---

From: Laine Stump <[email-masked]>
Date: Thu, 11 Oct 2007 11:29:20 -0400

(A preface - our implementation tries to keep relayed packets in the 
kernel, and in the original buffers the ethernet driver put them in, as 
much as possible (for UDP->UDP anyway), so recvmsg() scatter-gather 
tricks won't help. On the other hand, for those who are copying every 
packet up to an application, even recomputing the checksum is a 
non-issue (you're doing it anyway), and you probably don't care about 
the other small differences in performance either (they're dwarfed in 
comparison to just copying from kernel to application, and context 
switching back and forth), which is why you likely think I'm 
nit-picking. At the application level, I have 0 problems with the 
proposed protocol changes, too.)

> to have a *fixed-size* *always* *included* header.

Your point about data other than RTP having broader alignment 
requirements is well taken (of course, I'd wager that the great majority 
  of alignment needs would be met by 32 bits, a much higher percentage 
than with 1 byte headers anyway).

But alignment for the sake of the hardware architecture is secondary for 
me. Since I'm working on an x86 architecture, I'm (selfishly) more 
concerned about spending the time to redo the entire checksum rather 
than just adjusting it* or leaving it alone (hardware assist makes that 
a moot point, but isn't always available), and moving the bytes around 
(or allocating auxiliary buffers) rather than not.

* It's a simple matter to adjust the checksum of data that has had an 
even number of bytes prepended (or any number of bytes appended), but 
I'm not aware of a simple algorithm to adjust the checksum when an odd 
number of bytes has been prepended - instead the entire checksum must be 
recomputed.

> That's how Ethernet 
> drivers cope with the 14 bytes Ethernet II header; receive the packet with a 

At the level that matters to those of us trying to do an in-kernel, 
0-copy implementation (ie the ethernet driver) even if you make the 
assumption that every packet will be IPv4, you still can't make the 
assumption that every packet will be a TURN "1-byte-channel + data" 
encapsulated packet (since other traffic for other protocols can be 
arrving on the same interface). Unless you dedicate an interface to 
receiving only the encapsulated TURN data, your stated goal of "fixed 
size, always included header" can't be met at this level, and you're 
doomed to either reduced performance due to non-aligned memory accesses, 
or copying the data to align it. (Again, alignment is only a problem 
when the relay wants to do something with the data, rather than just 
blindly passing it along, so it's not as important as the checksum 
problem, or the mere presence of any amount of extra data).

As far as predictability is concerned - since any processing taking 
place will have to be based on the address:port tuple flows anyway, as 
long as the format of the packet is predictable/identical for all 
packets in the same flow, code paths for the different formats 
(encapsulated, unencapsulated) can be made available and the code can be 
just as streamlined for each possibility. There would of course be some 
extra code to write, and a bit of setup at the beginning of the session, 
but that happens once, compared to thousands of relayed packets per session.

> or WSABUF with Windows WSASend/WSARecv.

Even in the kernel, when adding the encapsulation header at write time, 
yes - this works reasonably well, but different semantics (eg, a list of 
packet buffers) may require that you spend the time to acquire a second 
buffer, initialize it, attach it to the list, and free it once you're 
finished (actually, if the buffer has some headroom/tailroom built in, 
it may be more efficient to just move the ethernet/IP/UDP headers down 
by one byte, or the payload up by one byte and pop the channel in the 
middle). When reading encapsulated packets, I don't see how it could be 
any help at all (as described above).

I know that each of these things (recomputing the entire checksum, 
allocating and freeing a 2nd buffer to add in the encapsulation byte, 
accessing large words at unaligned addresses) doesn't take much by 
itself, but added together and repeated for every single media (or 
whatever) packet they can make a difference, especially when there's so 
little else that needs to happen to get the packet through. I'd rather 
pay a bit extra up-front in development time and session setup time.

---

From: Brian Korver <[email-masked]>
Date: Thu, 11 Oct 2007 16:05:04 -0700

On Oct 10, 2007, at 10:44 AM, Laine Stump wrote:
> (Sorry I didn't pipe up last month when you sent the original  
> message and asked for comments. My only excuse is that I wasn't  
> paying attention.)
>
> As an implementor of a TURN server based on the previous drafts, I  
> have a couple concerns with this change in the design that I didn't  
> see addressed in this mail or your earlier message on the subject:  
> Adding a single byte at the beginning of every relayed packet will  
> 1) necessitate recalculation of the UDP checksum, as well as 2)  
> potentially causing alignment problems on architectures that care  
> about that (for relays that want to do something with the payload  
> on its way through, eg collect statistics).
>
> option, so that simplistic uses can enjoy lower overhead on the  
> relay, and/or 2) using 2 bytes for the channel number when it is  
> there, to make recomputing the checksum simpler (it could be  
> adjusted rather than recomputed from scratch), or 4 bytes, to also  
> alleviate alignment problems?

I'm not following your reasoning for making the channel number being  
a negotiated option.  The TURN server has to re-compute the UDP  
checksum on relayed data because the address 4-tuple in the IP header  
changes, right?

I don't see the point of going to a 2 byte channel number tho'.   
Either the TURN server is relaying the packet or processing it  
locally (channel=0).  In the former case (which is the common case),  
alignment doesn't matter but bumping up against MTU limits does.  In  
the later case, alignment may be required (or at least desired), but  
these events don't occur often and so an extra memcpy for the  
purposes of alignment is a small price to pay for optimizing the  
common case.

>
> The current model of 0 modifications to the payload (once the  
> active destination is set) certainly makes for a more efficient  
> relay process in all of the cases where there is a single  
> destination per allocation. Although I understand the need for  
> adding the channel functionality (and cleaning out the "Set Active  
> Destination" stuff), it would be sad to lose the current model's  
> post-setup elegance even when the extra functionality isn't required.
>
>
> > The client can then send

-brian
[email-masked]

---

From: Rémi Denis-Courmont <[email-masked]>
Date: Fri, 12 Oct 2007 09:52:29 +0300

Laine,

As I already noted, real-life MTUs tend to be multiple of 8, and are pretty 
much always multiple of 4 bytes. As such, I have no problem using a two byte 
header, or even 4 bytes, if it is deemed that NAT-like O(1) IP&UDP checksum 
adjustment is a nice thing to have.

In fact, one big advantage with the 4 bytes header may be to harmonize the UDP 
and TCP framing of TURN, i.e. two bytes channel and two bytes length. That 
being noted, I am not an author to the TURN I-D, nor am I attending the DT.

-- 
Rémi Denis-Courmont

---

From: Rémi Denis-Courmont <[email-masked]>
Date: Fri, 12 Oct 2007 09:55:03 +0300

> purposes of alignment is a small price to pay for optimizing the

The point of 2 bytes rather than 1 byte channel ID is that you can "fix" the 
outer UDP checksum with a single substraction. Many NATs device "fix" the UDP 
checksum by simply substracting and adding the modified port numbers, rather 
than checksumming the entire UDP payload.

Of course, this type of optimization is only valid for raw (in-kernel) 
implementations, where the checksum field can be accessed.

-- 
Rémi Denis-Courmont

---

From: Brian Korver <[email-masked]>
Date: Fri, 12 Oct 2007 11:27:46 -0700

On Oct 11, 2007, at 11:55 PM, Rémi Denis-Courmont wrote
> implementations, where the checksum field can be accessed.

We're optimizing for in-kernel NAT implementations?  Hmm, I still
don't see the real point to the 2 byte channel number.

-brian
[email-masked]

---

From: Brian Korver <[email-masked]>
Date: Fri, 12 Oct 2007 11:32:03 -0700

> adjustment is a nice thing to have.

I have been assuming that packet sizes are essentially random, but  
perhaps
that's not a correct assumption.  Specifically, I assume that increasing
the packet size by 2 bytes instead of 1 byte will make it more likely  
that
the packet will then exceed the MTU.  Exceeding the MTU seems like an
especially bad thing for TURN to do.

-brian
[email-masked]

---

From: Jonathan Rosenberg <[email-masked]>
Date: Fri, 12 Oct 2007 17:41:54 -0400

The design team had a call today to discuss all of this. Our thinking 
was this:

1. its always a good thing to support real implementations; if folks 
want to do kernel level implementations and they get a measurable 
benefit by using an even number of bytes for the overhead, thats a good 
thing

2. There is a big jump in complexity once we make usage of the channel 
mechanism optional. In fact the overall protocol becomes more complex 
than if we had left it out. A big thing to remember is that, with ICE, 
TURN ends up being the 'mechanism of last resort', and if the rest of 
behaves deliverables are implemented, the need for turn goes away. So 
given that it is a protocol meant to address a corner case that goes 
away, I think you need to drive the complexity way down, even at the 
expense of a few more bytes. So I'd rather ALWAYS use it.

3. While you can work your way around the word aligmnent issues and 
still have a decent implementation that performs well, its likely this 
will be troubling for some implementors.

4. the unification of the framing between UDP and TCP is attractive.

So I'd propose that the framing for UDP also use the 32 bit framing that 
TCP uses. This would include a 16 bit length that is always set to zero 
for UDP. This also makes it predictable so it can be compressed out 
should someone have real heartache about it (a ROHC extension or 
something could handle that I suspect).

-Jonathan R.

---

From: Cullen Jennings <[email-masked]>
Date: Mon, 15 Oct 2007 11:44:26 -0700

I don't know what I think of this change - not sure I really care,  
but I don't view the reasons as very strong.

On Oct 12, 2007, at 2:41 PM, Jonathan Rosenberg wrote:

> The design team had a call today to discuss all of this. Our  
> thinking was this:
>
> 1. its always a good thing to support real implementations; if  
> folks want to do kernel level implementations and they get a  
> measurable benefit by using an even number of bytes for the  
> overhead, thats a good thing

I don't believe this would offer any performance improvement in the  
kernel or not - any modern processor I am aware of can copy and  
checksum the data just as fast as coping the data.

>
> 2. There is a big jump in complexity once we make usage of the  
> channel mechanism optional. In fact the overall protocol becomes  
> more complex than if we had left it out. A big thing to remember is  
> that, with ICE, TURN ends up being the 'mechanism of last resort',  
> and if the rest of behaves deliverables are implemented, the need  
> for turn goes away. So given that it is a protocol meant to address  
> a corner case that goes away, I think you need to drive the  
> complexity way down, even at the expense of a few more bytes. So  
> I'd rather ALWAYS use it.

Agree with the goal of making the protocol less complicated but I  
really have a hard time believing the inclusion of a length field  
that is always set to zero for UDP somehow makes the protocol  
substantially less complex.

> still have a decent implementation that performs well, its likely  
> this will be troubling for some implementors.

Again, I don't believe this at all. Dealing with a TCP stream is  
going to more or less make you deal with misaligned stuff anyways. We  
are talking a handful of lines of code.

> 4. the unification of the framing between UDP and TCP is attractive.

Personally I view this as amazingly minimal since the code for  
reading them will still be fairly separate due to the difference in  
handling connected TCP sockets and most likely a single file  
descriptor for the UDP implementations.

>
>
> So I'd propose that the framing for UDP also use the 32 bit framing  
> that TCP uses. This would include a 16 bit length that is always  
> set to zero for UDP. This also makes it predictable so it can be  
> compressed out should someone have real heartache about it (a ROHC  
> extension or something could handle that I suspect).

I don't know that I really have an opinion on if this is a good  
change or not but I just find the reasons sort of dubious.

---

From: Eric Rescorla <[email-masked]>
Date: Mon, 15 Oct 2007 11:50:59 -0700

At Mon, 15 Oct 2007 11:44:26 -0700,
> checksum the data just as fast as coping the data.

I tend to agree with Cullen here. Do we have some actual measurements?

> substantially less complex.

To the extent to which we care about on-the-wire packet size, it
seems like adding 2 bytes to avoid an if statement in the code
seems unwise.

-Ekr