DataChannels API

So, on a totally different topic:

We discussed the DataChannels API at the Stockholm Interim.  I'd like to 
move this part of the spec forward, as Mozilla has an implementation in 
testing.

The API, per the decisions at the Interim and mailing list is based 
closely on the WebSockets interface.  For example, you are supposed to 
wait for onopen to fire before calling send(), at both ends. (The 
low-level IETF protocol supports sending before onopen, but WebSockets 
doesn't.)  Currently I have no limit on send(DOMString); WebSockets 
limits DOMStrings to 123 characters.

I should note that the immediately-following IETF Interim appeared to 
have the opposite conclusion on onopen and send(), and so the IETF 
protocol design allows sending immediately, while the API I propose for 
WebRTC DataChannels here does not.

Supported channel types are reliable (and and out of order) channels, 
unreliable and partly-reliable channels.  (See the dictionary below.)

I'd *REALLY* like to move forward on resolving the open issues listed 
below, as we're preparing to expose a preliminary DataChannels 
implementation.   Thanks.

Open Issues:
==========

When can createDataChannel() be called?
----------------------------------------------------------

Currently pc.createDataChannel() must be called after onconnected 
fires.  This was a point of discussion at the meeting and on the rtcweb 
list, since DataChannels need some RTTs to establish; allowing the 
application to pre-request data channels would allow them to be included 
in the signaling (and thus save 1 RTT off the setup time).

In order to allow pc.createDataChannel() to be called earlier the only 
difference at the offerer's JS API level would be whether you can do:

    pc = new PeerConnection(...);
    pc.addStream(...);
    pc.createDataChannel("foo",{});
    pc.createOffer(...);

etc.  On the receive side, there's no way to reject a DataChannel (just 
as there's no way to reject one before it's created after the 
PeerConnection is connected).  You can avoid setting onDataChannel or 
respond to onDataChannel by closing it if you don't want it.  The 
receiver would be able to call pc.createDataChannel() before 
createAnswer().  In all cases the channel isn't usable until onopen fires.


Do we need a DataConnection object?
-----------------------------------------------------

It was suggested that we have a DataConnection object, and hang 
createDataChannel off of that.  (see the thread "DataConnection objects" 
from June.)  Doing so would minorly reduce the number of methods on 
PeerConnection (by moving onconnection/onclosedconnection/ondatachannel 
to it, and maybe an attribute for initial number of streams).

Pros:
* It gives you a clear way to easily drop all DataChannels.
* It gives you an easy point to query the association (mostly for 
maxstreams I'd guess, perhaps for total bytes queued across all 
DataChannels).
* A spec for a DataConnection object might be useful in some other 
context in the future - but it always could be added later if needed.  
(You could even indicate that PeerConnection implements DataConnection 
if you wanted.)
* It does give you a clear way to say "Don't bother to open an 
association" if you don't need one.
* You could add more DataConnections to a PeerConnections (but what does 
that gain you?)
* Allows future extension to allow other protocols to be used over the 
connection/association.
* Gives you something to hang an application identifier off of - but the 
application can do that in other ways.

Cons:
* More objects, more code required to set up a call with little benefit 
to the current usecases.
* If you want to add a DataConnection later, it will require a renegotiation
* Answering requires a few extra steps to see if the offerer offered a 
DataConnection.

Overall, I'm very mildly in favor of adding the DataConnection object, 
because it might be useful in some other contexts in the future.  I do 
think it complicates PeerConnection slightly, but only slightly  (less 
methods, but PeerConnection now needs to trigger renegotiation when 
createDataConnection is called.)


Label glare:
---------------
A previous discussion ("Data API symmetry", in April/May) covered this.  
This is the "what happens if both sides call createDataChannel("foo",{}) 
at the same time" question.  You can:

1) Create two channels labelled "foo".  Each side would get an onopen to 
their createDataChannel, and each would get an onDataChannel and onopen 
for the one created by the other side. Handling the glare would be the 
application's domain.
2) Fail both (or find some agreed-on tiebreaker that lets you have one 
fail and the other succeed).
3) Create one channel labelled "foo", and each side would believe they 
created it.  Both would simply get "onopen", and it might even come 
faster than normal.  NOTE: if the two sides select different options for 
the channel, then you still may need to return errors! (or create 
channels with the same label)

#3 is mildly appealing, until you think about how you handle errors with 
disagreement on reliability.  So I think I end up preferring #1.  #3 
also implies you should have a unique label for each channel, to avoid 
confusions with opening multiple channels with the same name at the same 
time when the other side tries to as well.


Attributes:
-------------
Is the single "reliable" attribute enough?  Do we need to expose the 
dict entries?  Do we need a FindDataChannel(label) to get a reference to 
an existing channel?

(My opinion: Probably no to all of these, though I can see exposing the 
dict entries.)



This is the dictionary for RTCPeerConnection's createDataChannel() 
method: (xpidl)

/* If either maxRetransmitTime or maxRetransmitNum are set, it's
    unreliable, else it's a reliable channel.  If both are set it's an
    error.  outOfOrderAllowed can be used with any type of channel.  The
    equivalent of UDP is { outOfOrderAllowed: true, maxRetransmitNum: 0 }.
    The TCP equivalent is {}. */

dictionary DataChannelInit {
   boolean outOfOrderAllowed;
   unsigned short maxRetransmitTime; // in ms
   unsigned short maxRetransmitNum;
};


And in PeerConnection: (xpidl)

   /* Data channel */
   nsIDOMDataChannel createDataChannel([optional] in ACString label,
                                       /* DataChannelInit */ [optional] in jsval options);
   attribute RTCPeerConnectionCallbackVoid onConnection;
   attribute RTCPeerConnectionCallbackVoid onClosedConnection;
   attribute RTCPeerConnectionCallback onDataChannel;


This is the webidl for DataChannel I have currently (note: it might have 
errors as we use xpidl internally, though we're switching to webidl).

interface DataChannel : EventTarget {
   [Infallible]
   readonly attribute DOMString label;

   [Infallible]
   readonly attribute boolean reliable;

   // ready state
   const unsigned short CONNECTING = 0;
   const unsigned short OPEN = 1;
   const unsigned short CLOSING = 2;
   const unsigned short CLOSED = 3;

   [Infallible]
   readonly attribute unsigned short readyState;

   [Infallible]
   readonly attribute unsigned long bufferedAmount;

   [TreatNonCallableAsNull, GetterInfallible]
   attribute Function? onopen;

   [TreatNonCallableAsNull, GetterInfallible]
   attribute Function? onerror;

   [TreatNonCallableAsNull, GetterInfallible]
   attribute Function? onclose;

   [Infallible]
   readonly attribute DOMString extensions;

   [Infallible]
   readonly attribute DOMString protocol;

   void close();

   // messaging
   [TreatNonCallableAsNull, GetterInfallible]
   attribute Function? onmessage;

   [GetterInfallible]
   attribute DOMString binaryType;

   void send(DOMString data);
   void send(Blob data);
   void send(ArrayBuffer data);
};


-- 
Randell Jesup
randell-ietf@jesup.org

Received on Friday, 31 August 2012 21:06:39 UTC