Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewriting W3C SysApps Raw Socket API to be based on Streams #64

Closed
ClaesNilsson opened this issue Jan 23, 2014 · 35 comments

Comments

4 participants
@ClaesNilsson
Copy link

commented Jan 23, 2014

Hi,

I am the editor of the W3C SysApps Raw Socket API (UDP, TCP and TCP server sockets), http://www.w3.org/2012/sysapps/raw-sockets/. This is a perfect API to be based on Streams and I have started to investigate how the API could be rewritten to be based on Streams. I think that placing the discussion here, at the Github Streams API repository, is better than having it at the SysApps Raw Socket Github repository, as this exercise probably will highlight potential issues with the current Streams API and bring the work forward.

So let us start by showing the current WebIDL for the TCPSocket interface:

[Constructor (DOMString remoteAddress, unsigned short remotePort, 
optional TCPOptions options)] 
interface TCPSocket : EventTarget { 
   readonly attribute DOMString       remoteAddress; 
   readonly attribute unsigned short remotePort; 
   readonly attribute DOMString       localAddress; 
   readonly attribute unsigned short localPort; 
   readonly attribute boolean            addressReuse; 
   readonly attribute boolean            noDelay; 
   readonly attribute unsigned long  bufferedAmount; 
   readonly attribute ReadyState     readyState; 
                  attribute EventHandler  ondrain; 
                  attribute EventHandler  onopen; 
                  attribute EventHandler  onclose; 
                  attribute EventHandler  onerror; 
                  attribute EventHandler  ondata; 
   void       close (); 
   void       halfclose (); 
   void       suspend (); 
   void       resume (); 
   boolean send ((DOMString or Blob or ArrayBuffer or ArrayBufferView) data); 
};

The current design is inspired by http://dev.w3.org/html5/websockets/, http://nodejs.org/api/net.html and https://developer.mozilla.org/en-US/docs/Web/API/TCPSocket.

Looking at the send() method it returns true or false as a hint to the caller that they may either continue sending more data immediately, or should want to wait until the transport layer has transmitted buffered data that already have been written to the socket before buffering more. The ondrain event handler is executed upon detection that previously-buffered data has been written to the network and it is possible to buffer more data received from the application. This is typical functionality that could be handled by the general Streams API.

So let us make a first attempt to modify the interface to be based on Streams API:

[Constructor (DOMString remoteAddress, unsigned short remotePort, 
optional TCPOptions options)] 
interface TCPSocket : EventTarget { 
   readonly attribute DOMString          remoteAddress; 
   readonly attribute unsigned short    remotePort; 
   readonly attribute DOMString          localAddress; 
   readonly attribute unsigned short    localPort; 
   readonly attribute boolean               addressReuse; 
   readonly attribute boolean               noDelay; 
   readonly attribute ReadyState         readyState; // Can we skip this attribute and
                                                                                 use                                                         
                                                                                 ReadableStreamState and
                                                                                 WritableStreamStat instead? 
                  attribute EventHandler      onclose;      // Miss ReadableStream state for
                                                                               closed TCP connection
                  attribute ReadableStream in;
                  attribute WritableStream   out;


   void       close ();        // Use ReadableStream.abort()  instead?
   void       halfclose ();  // Use WritableStream.abort()  instead ?
};

As a TCP socket is both a Stream Consumer (data sink) and a Stream Producer (data source) I defined both an “in” attribute of type ReadableStream and an “out” attribute of type WritableStream.

The “onopen” EventHandler is skipped. The TCPSocket constructor attempts to create a TCP connection to the selected host and port and the idea is to just return the socket object directly. As long as the connection is setting up both the “in” and “out” attributes are in “waiting" state. If opening the socket succeeds, “out” will transition to "writable" state but “in” will remain in “waiting" state. If opening the socket fails both the “in” and “out” attributes will transition to "errored" state. The application using the API will call wait() to be notified of the state transition.

So taking the example in the Github Streams API definition on writing the contents of a readable stream to the console as fast as it can and adapting it to let the source be a TCP socket:

var mySocket = new TCPSocket("127.0.0.1", 6789);
pump (); 
function pump() { 
   while (mySocket.in.readableState === "readable") {  
      console.log (mySocket.in.read()); 
   } 
   if (mySocket.in.readableState === "closed") { 
       console.log("--- all done!"); 
   } else { 
       mySocket.in.wait().then(pump, e => console.error(e)); 
   }
 }

How to close the TCP connection and how to get an indication that the remote peer closed the connection? It is not obvious how to map TCP connection close and half close to Streams API.

The current Raw Socket API has the close() method that issues a complete close of the TCP connection. Using Streams ReadbleStream.abort() may be possible here.

halfclose() means that FIN is sent and that the TCP socket no longer can be used to send data. However, receiving is still possible. Could we use WritableStream.abort()?

In the current version of the Raw Socket API we have the onclose EventHandler stating that the connection to the server has been closed, either cleanly by the server, or cleanly by the client calling close(), or if the connection was lost. However, for ReadableStream the "closed" state means all data has been read from both the source and the buffer, which is not the same thing as an indication of a closed TCP connection. Accordingly I suggest addition of a new ReadableStreamState stating that the data source has been completely closed and that no more data can be expected. I would prefer to call this state “closed” and have another name for the state that is called “closed” today, e.g. “nodata” or “empty” or whatever appropriate. Naming this new ReadableStreamState “closed” would also be consistent with the state “closed” for WritableStream.

For WritableStream I think that the current states are ok.

Above would mean that the attribute “ReadyState” is redundant with ReadableStreamState and WritableStreamState and can be removed. As we no longer have EventHandler attributes we don’t have to inherit EventTarget.

So the modified TCPSocket interface would then be:

[Constructor (DOMString remoteAddress, unsigned short remotePort, 
optional TCPOptions options)] 
interface TCPSocket { 
   readonly attribute DOMString          remoteAddress; 
   readonly attribute unsigned short    remotePort; 
   readonly attribute DOMString          localAddress; 
   readonly attribute unsigned short    localPort; 
   readonly attribute boolean               addressReuse; 
   readonly attribute boolean               noDelay; 
                  attribute ReadableStream in;
                  attribute WritableStream   out;
};

The stepwise descriptions in the API specification would have to define the TCP specific semantics of the ReadableStream and WritableStream methods.

Taking the TCP echo client example, that sends "Hello World" to the server and
logs what has been received, from the Raw Socket API specification and modifying it to the Stream API based TCPSocket interface would give something like:

    // Create a new TCP client socket and connect to remote host
    var mySocket = new TCPSocket("127.0.0.1", 6789);


    // I assume that I can call write() immediately without waiting for 
    // the TCP socket connection to be established and state changed to 
    // “writable” as the data to send will be buffered and sent
    // immediately when state becomes “writeable” (TCP connection has 
    // been established). 

    mySocket.out.write("Hello World").then (

    // Data has been sent so promise’s fulfill algorithm is executed 
    function () {          
        console.log("Data has been sent to server"): 

        // Wait for data reveived from the server, i.e. wait for the 
        // readable state to change to “readable”

        mySocket.in.wait().then (
          // Data from server received, state has changed to “readable”
          function () {

            console.log('Data received from server: ' + 
                                mySocket.in.read());

          }     
          // Error in receiving
          function (e) {
            console.error("Receiving error. Error was " + e);
          }     

        )   
      }

      // Sending failed, promise’s reject algorithm is executed
      function (e) {
        console.error("Failed to send to server. Error was " + e);

      }

    )

    // Close the TCP connection
    mySocket.in.abort();

I am happy for all comments on whether I am on the right track with this API or if I got it all wrong :-)

@domenic

This comment has been minimized.

Copy link
Member

commented Jan 23, 2014

This is super-exciting to see that our API seems to be solving real problems. Raw sockets is the perfect use case. I'm really happy to have you engaging here so we can work together on making sure the API solves this crucial case.

I need more time to look over all the details of your post and give it the full consideration it deserves; I hope to be able to do that later tonight. Upon skimming to the end, the modified TCPSocket interface, and the example, look pretty close. Only a few points jump out at me:

  • The intention is that you write to the input (in), so in is a WritableStream. Whereas out is a a ReadableStream. Data flows into writable streams, and out of readable streams. Relatedly, in #63 we are discussing a potential renaming to { input, output } instead of { in, out }---do you think that would be clearer?
  • I need to investigate the .abort() and .close() usage more closely, but if the intent is to signal you are not interested in reading any more after the first chunk, then you would want to abort after reading, not after queueing a write.

So my revision would be something like

var mySocket = new TCPSocket("127.0.0.1", 6789);

mySocket.in.write("Hello World").then(
    () => {
        console.log("Data has been sent to server");

        mySocket.out.wait().then(
            () => {
                console.log("Data received from server:" + mySocket.out.read());

                // Signal loss of interest in reading any more.
                mySocket.out.abort();
            },
            e => console.error("Receiving error: ", e)
        );
    },
    e => console.error("Sending error: ", e);
);

// Signal that we won't be writing any more and can close the write half of the connection.
mySocket.in.close();

although again I am not sure I got the whole close/abort thing right. I bet that has to do with the paragraphs I skipped over :).

@domenic

This comment has been minimized.

Copy link
Member

commented Jan 24, 2014

OK, let's do this!

How to close the TCP connection and how to get an indication that the remote peer closed the connection? It is not obvious how to map TCP connection close and half close to Streams API.

The current Raw Socket API has the close() method that issues a complete close of the TCP connection. Using Streams ReadbleStream.abort() may be possible here.

What's interesting here is that it sounds like you can't close the readable side (out) without also closing the writable side (in) at the same time. I.e. there is no way to only close "the other half." This is kind of tricky to map to the API, I agree. I can think of a few ideas:

  • The one you propose, where mySocket.out.abort() happens to also kill mySocket.in.
  • Something a bit more explicit, where e.g. you override mySocket.out.abort to throw an error (e.g. "Cannot close only the output side of a TCP socket"), and add a mySocket.abort() method that closes both.

Could you help me understand TCP a bit better? What are the semantics of "a complete close of the TCP connection"---e.g., if there is still data that hasn't been read, should it be thrown away? If there is still data that has been queued for writing, but hasn't finished writing, should it be thrown away, or should we wait for it to go through the pipes? I bet my mental model is all wrong here, as you can probably tell from me saying things like "through the pipes" :)

halfclose() means that FIN is sent and that the TCP socket no longer can be used to send data. However, receiving is still possible. Could we use WritableStream.abort()?

I think WritableStream's close() is a better match here, since---from what I understand---sending a FIN would mean that you also flush through any queued-up writes as well. It sounds like a graceful close, whereas abort() is meant to be a more forceful close.

The ideas over in #67 clarify this, I think.


In the current version of the Raw Socket API we have the onclose EventHandler stating that the connection to the server has been closed, either cleanly by the server, or cleanly by the client calling close(), or if the connection was lost. However, for ReadableStream the "closed" state means all data has been read from both the source and the buffer, which is not the same thing as an indication of a closed TCP connection. Accordingly I suggest addition of a new ReadableStreamState stating that the data source has been completely closed and that no more data can be expected. I would prefer to call this state “closed” and have another name for the state that is called “closed” today, e.g. “nodata” or “empty” or whatever appropriate. Naming this new ReadableStreamState “closed” would also be consistent with the state “closed” for WritableStream.

Hmm, I'm having a hard time understanding the difference between these two states. Does it come down to the possibility of having a closed TCP connection, but data still in the buffer that hasn't been read? Is that useful information for the user to have? From a streams perspective, what's useful is knowing when there's no more data, period---the underlying connection transport is a secondary detail. Could you help me understand the TCP perspective?

Do you think this should be a property of the stream at all, or of the connection? E.g., is this something useful to all streams, or is it TCP-socket specific?

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Jan 24, 2014

Thanks a lot for your feedback Domenic.

The intention is that you write to the input (in), so in is a WritableStream. Whereas out is a
ReadableStream. Data flows into writable streams, and out of readable streams. Relatedly, in #63
we are discussing a potential renaming to { input, output } instead of { in, out }---do you think that
would be clearer?

I think that { input, output } is fine and your definition that a WritableStream needs input from the web application and that a ReadableStream provides output of data to the web applications is logical. However, some people may think in the opposite direction, i.e. that a web application provides an output of data to the WritableStream and reads input data from the ReadableStream. The latter definition is used in http://whatwg.github.io/serial/. So we may see some confusion here but let us leave that for now and stick to your definition.

What's interesting here is that it sounds like you can't close the readable side (out) without also
closing the writable side (in) at the same time. I.e. there is no way to only close "the other half."
This is kind of tricky to map to the API, I agree. I can think of a few ideas:

Yes, when the readable side (out) has been closed it means that the writable side (in) at the same time has been closed. But as stated you can do a “half close” meaning that the writable side (in) is closed but the readable side (out) remains live. For half close I agree that WritableStream's close() is a better choice and your rewrite of the echo client example looks fine!

Your comment on options for closing a TCP connection:

*The one you propose, where mySocket.out.abort() happens to also kill mySocket.in.
*Something a bit more explicit, where e.g. you override mySocket.out.abort to throw an error (e.g.
"Cannot close only the output side of a TCP socket"), and add a mySocket.abort() method that closes both.

I am not yet sure which option I prefer. The question is whether you want to map the general Streams API methods to the specifics of the underlying layer or whether you want to add underlying layer specific stuff as separate methods.

When it comes to the ReadableStreamState and my request to distinguish between “ all data has been read from both the source and the buffer” and “connection closed, no more data can be expected” it is important for example because it must be possible to notify the user that the TCP connection with the server has been lost. This would be the same for example for a Stream API based version of Web Sockets. So we have two options here, either to let the Streams API provide the ability to detect a “connection lost” state or have this as specific state information in Streams based APIs which managed connection oriented communication.

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Jan 30, 2014

I am coming back to handling of TCP connection close down. Regarding #67 I prefer ReadableStream.close() for a clean TCP connection termination (http://en.wikipedia.org/wiki/File:TCP_CLOSE.svg ). I don’t know if we need ReadableStream.abort() as well but for a TCP Socket API a method for a clean close is only needed.

Regarding your comment:

Hmm, I'm having a hard time understanding the difference between these two states. Does it come >down to the possibility of having a closed TCP connection, but data still in the buffer that hasn't
been read? Is that useful information for the user to have? From a streams perspective, what's
useful is knowing when there's no more data, period---the underlying connection transport is a >secondary detail. Could you help me understand the TCP perspective?

Thinking about this more, you are right. The current ReadableStreamState “closed” state should be ok for a closed TCP connection.

You also ask:

Could you help me understand TCP a bit better? What are the semantics of "a complete close of the >TCP connection"---e.g., if there is still data that hasn't been read, should it be thrown away? If there
is still data that has been queued for writing, but hasn't finished writing, should it be thrown away,
or should we wait for it to go through the pipes? I bet my mental model is all wrong here, as you
can probably tell from me saying things like "through the pipes" :)

If there is data in the write buffer that not yet has been sent when connection close is issued then this data is sent prior to the termination handshake. So you should be able to do write() and then immediately close(). For reading it is more complicated and you may lose data if you call read() and then immediately close() without waiting for the promise to be fulfilled. So an application shouldn’t issue close() until there is no interest in more data.

Going further, https://github.com/othiym23 wonders if full duplex connection oriented stream sources/stream sinks (such as TCP sockets and Web Sockets) should be treated as Readable/WritableStream pairs or single objects representing both the readable and the writable. Which is most intuitive and logical model from an application programmer point of view? I guess that someone familiar with traditional TCP socket APIs will find the single object model most familiar. However, from a forward thinking point of view, where we want the same powerful Streams API to be used applied on many types of sources and sinks, it might be better to continue treating the reading part and the writing part of a TCP socket as two separate objects. We just have to find a way to map the connection stuff on the Streams methods and states.

So let us look at the different ways of tearing down a TCP connection taking the current version of the Raw Socket API as a starting point and see how it could be mapped on the Streams API methods and states.

  • Normal TCP close (http://en.wikipedia.org/wiki/File:TCP_CLOSE.svg): As stated earlier we can use “Readable.close()” and describe in the Raw Socket API specification that this also means that the WritableStream is closed. Or we can have a specific Raw Socket API method for closing socket, i.e. both the Readable and WritableStreams objects. Any subsequent calls to read() and write() will result in error but potential remaining data in the write buffer from a previous write() is sent prior to initiating the close handshake.
  • TCP half close: WritableStream.close() can be used.
  • Established TCP connection is lost: In the current Raw Socket API error and close events are issued. I assume that for the Streams based API this situation must result in execution of the ReadableStream.wait() promise’s reject algorithm and the WritableStreams wait() or write() promise’s reject algorithm. As mentioned before the problem we have here is that we must either be actively waiting or trying to write to get a notification from the Streams API on a lost connection. But we must also be able to get notified on a connection closing from the other peer or an error causing the connection to go down even when the application has not issed any asynchronous method call. Should this be out of scope for the Streams API or should we have some kind of “onerror” handler in the Streams API? WDYT?

To summarize the issues:

  • Need a ReadableStream.close() method for a clean close of the ReadableStream.
  • Full duplex connection-oriented protocol specific: Should ReadableStream.close() also give a WritableStream.close() or should we have an overlaying method in all “Full duplex connection-oriented protocol APIs” that closes both the Readable and the Writable.
  • Full duplex connection-oriented protocol specific: Is notification of a lost connection in scope for the Streams API or out of scope?
@othiym23

This comment has been minimized.

Copy link

commented Jan 31, 2014

However, from a forward thinking point of view, where we want the same powerful Streams API to be used applied on many types of sources and sinks, it might be better to continue treating the reading part and the writing part of a TCP socket as two separate objects.

I agree. I'm familiar with duplex streams instanced as single objects from Node, but I'm by no means convinced they're the only, or right, way to do it. Thinking about duplex streams as a { ReadableStream, WritableStream } pair is simpler and more approachable if you're thinking about streams as a concept (and not in terms of particular instances).

As mentioned before the problem we have here is that we must either be actively waiting or trying to write to get a notification from the Streams API on a lost connection. But we must also be able to get notified on a connection closing from the other peer or an error causing the connection to go down even when the application has not issed any asynchronous method call. Should this be out of scope for the Streams API or should we have some kind of “onerror” handler in the Streams API?

I think it should be possible to use the existing properties on the streams to communicate this kind of (potentially early) failure at the socket layer, between setting the states of the Readable and Writable to errored and rejecting [[startupPromise]], [[readablePromise]], and [[closedError]] with the underlying error. Synchronous calls to read and write will throw with the correct error, unless I'm misreading the spec. Am I overlooking something?

@domenic

This comment has been minimized.

Copy link
Member

commented Feb 3, 2014

I am coming back to handling of TCP connection close down. Regarding #67 I prefer ReadableStream.close() for a clean TCP connection termination (http://en.wikipedia.org/wiki/File:TCP_CLOSE.svg ).

After thinking about this harder, along with the discussion in #67, I think this is probably not the right approach. The streams should have only operations that make sense on the level of an abstract data stream, and protocol-level or socket-level operations should live on the socket object. So I think it should be TCPSocket.prototype.close. There should be no way to close a readable stream, just signal loss of interest ("cancel").

I don’t know if we need ReadableStream.abort() as well but for a TCP Socket API a method for a clean close is only needed.

The discussions in #67 help inform this a little bit. It's useful for the consumer of the stream to be able to signal loss of interest; e.g. any buffered data can be dropped on the floor. (This is important, as a general ability, since otherwise you can effectively "leak" data that nobody ever intends to read, keeping it alive forever on a long-lived browser-based app.) But such a cancellation signal should not close the underlying connection, since that would have an impact on the writable side.

So my conclusion is:

  • mySocket.output.cancel(): drops buffered readable data on the floor but does nothing to the underlying connection. However, further reads fail, due to stream semantics---even though the underlying connection is still open. Thus, any data received can be thrown away instead of surfaced through the readable stream.
  • mySocket.input.close(): cleanly closes the write side of the socket (waiting for buffered writes to finish).
  • mySocket.close(): performs full TCP close of both sides of the socket. From the user's perspective, this behaves as if you did mySocket.output.cancel() + mySocket.input.close() + some TCP-specific stuff to actually close the read side as well as the write side.

If there is data in the write buffer that not yet has been sent when connection close is issued then this data is sent prior to the termination handshake. So you should be able to do write() and then immediately close(). For reading it is more complicated and you may lose data if you call read() and then immediately close() without waiting for the promise to be fulfilled. So an application shouldn’t issue close() until there is no interest in more data.

This matches the streams model perfectly :).

However, from a forward thinking point of view, where we want the same powerful Streams API to be used applied on many types of sources and sinks, it might be better to continue treating the reading part and the writing part of a TCP socket as two separate objects.

This is my inclination, but I admit it's a tough problem, and you're certainly surfacing all of the edges. I largely agree with @othiym23's points here too. In general I feel like we're approaching a pretty good understanding of how to make this fit, so I'm happy to proceed with this separated input + output approach if you are.

As mentioned before the problem we have here is that we must either be actively waiting or trying to write to get a notification from the Streams API on a lost connection. But we must also be able to get notified on a connection closing from the other peer or an error causing the connection to go down even when the application has not issed any asynchronous method call. Should this be out of scope for the Streams API or should we have some kind of “onerror” handler in the Streams API? WDYT?

We actually have taken care of this pretty nicely, I think! The readable and writable streams both have closed properties, which are promises that become fulfilled upon successful close, or rejected if an error occurs. You can subscribe to these any time, even after the stream is already closed or errored, to get the appropriate callback called. For example,

mySocket.input.closed.then(onWritableSideClosed, onErrorWriting);
mySocket.output.closed.then(onReadableSideClosed, onErrorReading);

My understanding of the socket API is that these four callbacks will necessarily be called at related times (e.g. if onReadableSideClosed occurs, then so will onWritableSideClosed). Making sure we specify the order in which the stream objects' state transitions occur, and thus the order in which user-queued callbacks are called, will be an important detail to get right. But in general we are in good shape.

You could optionally provide more idiomatic promises, of the same sort, on your socket object. This might be more familiar or semantic, and I can see no real downside in it. In that case you might do

mySocket.halfClosed.then(onHalfClosed, onSomethingBroke);
mySocket.closed.then(onFullyClosed, onSomethingBroke);
@domenic

This comment has been minimized.

Copy link
Member

commented Feb 3, 2014

FYI per #63 I switched to { input, output } instead of { in, out }. I edited my previous post to reflect that.

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Feb 5, 2014

Thanks a lot for your feedback Domenic and Forrest. What you are saying make sense and here is an updated proposal for the TCPSocket interface.

[Constructor (DOMString remoteAddress, unsigned short remotePort, 
optional TCPOptions options)] 
interface TCPSocket { 
   readonly attribute DOMString           remoteAddress; 
   readonly attribute unsigned short     remotePort; 
   readonly attribute DOMString           localAddress; 
   readonly attribute  unsigned short    localPort; 
   readonly attribute boolean                addressReuse; 
   readonly attribute boolean                noDelay; 
   readonly attribute ReadyState          readyState;
                  attribute ReadableStream output;
                  attribute WritableStream   input;
   void   socketClose ();
};

enum ReadyState {
    "opening",
    "open",
    "closing",
    "closed",
    "halfclosed"
};

A short description on semantics follows. More details will of course be given in the real specification..

TCPSocket constructor is invoked
  1. Create a new TCPSocket object (“mySocket”), a let the output attribute be a new ReadableStream object (“mySocket.output”) and the input attribute be a new WritableStream object (“mySocket.input”).
  2. If there are any error(s) in the Constructor arguments throw an exception and abort, else set mySocket.output.state to “waiting” and set mySocket.input.state to “writable”.
  3. Set mySocket.readyState to “opening”.
  4. Attempt to connect to the requested address and port in the background (without blocking scripts).
The attempt to create a new TCP socket and establish a new TCP connection (mySocket.readyState is “opening”) was successful
  1. Set mySocket.readyState to “open”.
The attempt to create a new TCP socket and establish a new TCP connection (mySocket.readyState is “opening”.) failed
  1. Set mySocket.output.state to “errored”.
  2. Set mySocket.input.state to “errored”.
  3. Set mySocket.readyState to “closed”.
    The assumption here is that an application can detect a failed TCP connection attempt through the onErrorWriting and onErrorReading callbacks in:
    mySocket.input.closed.then(onWritableSideClosed, onErrorWriting);
    mySocket.output.closed.then(onReadableSideClosed, onErrorReading);
mySocket.close()
  1. Execute mySocket.output.cancel() + mySocket.input.close(). This means that mySocket.output.state will be “closed” and mySocket.input.state will be “closing”.
  2. If there is data in the write buffer that not yet has been sent then this data is sent.
  3. Start TCP connection close down.
  4. Set mySocket.readyState to “closing”.
mySocket.input.close()
  1. If there is data in the write buffer that not yet has been sent then this data is sent.
  2. Send FIN.
  3. Set the mySocket.readyState attribute to "halfclosed".
An established TCP connection (mySocket.readyState is "open") is lost
  1. Set mySocket.output.state to “errored”.
  2. Set mySocket.input.state to “errored”.
  3. Set mySocket.readyState to “closed”.
    The assumption here is that an application can detect a lost TCP connection through the onErrorWriting and onErrorReading callbacks in:
    mySocket.input.closed.then(onWritableSideClosed, onErrorWriting);
    mySocket.output.closed.then(onReadableSideClosed, onErrorReading);
The TCP connection has been closed cleanly, either by the server, or by the client calling mySocket.close()
  1. Set mySocket.output.state to “closed”.
  2. Set mySocket.input.state to “closed”.
  3. Set mySocket.readyState to “closed”.
    The assumption here is that an application can detect a closed TCP connection through onWritableSideClosed and onReadableSideClosed callbacks in:
    mySocket.input.closed.then(onWritableSideClosed, onErrorWriting);
    mySocket.output.closed.then(onReadableSideClosed, onErrorReading);

read() and write() semantics will be according to the semantics described for Streams API.

I am considering if we need mySocket.readyState. Of course the API must keep this as an internal state variable but does it have to be exposed to the application developer? Are the states for Readable and WritableStream enough? One reason to keep this attribute would be to provide the possibility for web applications to differentiate between Readable and WritableStreams just closed or errored and the situation when the complete TCP connection has been closed. In both these cases the output.state and the input.state are “closed” or “errored”.

The echo client example will then be:

var mySocket = new TCPSocket("127.0.0.1", 6789);
mySocket.input.write("Hello World").then(
    () => {
        console.log("Data has been sent to server");
        mySocket.output.wait().then(
            () => {
                console.log("Data received from server:" + mySocket.output.read());

                // Close the TCP socket.
                mySocket.socketClose();
            },
            e => console.error("Receiving error: ", e);
        );
    },
    e => console.error("Sending error: ", e);
);
// Signal that we won't be writing any more and can close the write half of the connection.
mySocket.input.close();

// Handle closed Readable and Writable streams, either as a result of the application calling    
// socketClose() or the other side closing the TCP socket connection or an error causing the stream or 
// the complete TCP connection to be closed
mySocket.input.closed. then(
() => {
   console.log("Socket has been cleanly closed for writing");
   },
   e => console.error(“Socket closed for writing due to error: ", e);
);
mySocket.output.closed. then(
() => {
   console.log("Socket has been cleanly closed for reading");
   },
   e => console.error(“Socket closing for reading due to error: ", e);
);

I hope that I soon will be able to update the real Raw Socket API specification...

@domenic

This comment has been minimized.

Copy link
Member

commented Feb 6, 2014

That looks great!! I think it captures all that we have been saying really well.

My one piece of feedback would be to try to phrase the manipulation of the streams in terms of functions passed to their constructors, or their methods, since that makes it clearer exactly how they interact with the stream spec. For example, it's not actually possible to do input.state = "errored"; instead, you would e.g. reject the promise returned from the start function.

So for example, I might do something like:


TCPSocket constructor is invoked

  1. Create a new TCPSocket object (“mySocket”), and a new promise (“openingPromise”) let the output attribute be a new ReadableStream object (“mySocket.output”) and the input attribute be a new WritableStream object (“mySocket.input”), where:
    • mySocket.output is given a start function that returns openingPromise
    • mySocket.input is given a start function that returns openingPromise
    • (... other parameters here, maybe? ...)
  2. Set mySocket.readyState to “opening.”
  3. Attempt to connect to the requested address and port in the background (without blocking scripts).

The attempt to create a new TCP socket and establish a new TCP connection (mySocket.readyState is “opening”) was successful

  1. Resolve openingPromise with undefined.

The attempt to create a new TCP socket and establish a new TCP connection (mySocket.readyState is “opening”.) failed

  1. Reject openingPromise with a DOMException whose name is "NetworkError".
  2. Set mySocket.readyState to “closed”. The assumption here is that an application can detect a failed TCP connection attempt through the onErrorWriting and onErrorReading callbacks in: mySocket.input.closed.then(onWritableSideClosed, onErrorWriting); mySocket.output.closed.then(onReadableSideClosed, onErrorReading);

Similarly instead of specifying mySocket.input.close() you can specify the close parameter passed to the WritableStream constructor when creating mySocket.input. Or for clean-close, you can specify that you call mySocket.input.close(), and that the start function passed to the construction of mySocket.output calls its passed close() argument.

I am considering if we need mySocket.readyState.

I think this is pretty valuable! People who are interested in using the input and output as abstract streams will be more interested in their states, surely. But people who want to think in terms of TCP socket abstractions are probably interested in this level of information.

I'm curious if "readyState" is a TCP term? Is that where XHR got it from?

The echo client example will then be:

This looks lovely!

I hope that I soon will be able to update the real Raw Socket API specification...

Me too!! :D

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Feb 9, 2014

Thanks again for your feedback Domenic. I am trying to grasp your examples and figure out how this should be described in the Raw Socket API specification, http://raw-sockets.sysapps.org/.

TCPSocket constructor is invoked
  1. Create a new TCPSocket object (“mySocket”), and a new promise (“openingPromise”) let the output
    attribute be a new readableStream object (“mySocket.output”) and the input attribute be a new
    WritableStream object (“mySocket.input”), where:
    • mySocket.output is given a start function that returns openingPromis
    • mySocket.input is given a start function that returns openingPromise
      ... other parameters here, maybe? ...)

2.Set mySocket.readyState to “opening.”

3.Attempt to connect to the requested address and port in the background (without blocking scripts).

So here you define that the TCPSocket constructor creates a new promise, “openingProimse” but I am a bit puzzled that you in this example define that the construction of both the output and input stream objects return the same single promise. I probably have some problems in my understanding as the promises stuff is new to me but shouldn’t the creation of the output and input stream objects return different promises?

Regarding your question "other parameters here, maybe? ", what we need to say is what is described in http://raw-sockets.sysapps.org/, section 6.2 under “When the TCPSocket constructor is invoked, the User Agent must run the following steps:”

The attempt to create a new TCP socket and establish a new TCP connection

(mySocket.readyState is “opening”) was successful

  1. Resolve openingPromise with undefined.

We also need “Set mySocket.readyState to “open.”

Similarly instead of specifying mySocket.input.close() you can specify the close parameter passed to
theWritableStream constructor when creating mySocket.input. Or for clean-close, you can specify that
you callmySocket.input.close(), and that the start function passed to the construction of
mySocket.output calls its passed close() argument.

If I interpret you correctly your proposal is to define what the passed close() methods are doing with the TCP connection in these cases, i.e. a TCP connection “half close” and a full close of the TCP connection. Correct? If so that would mean that something like this should be added to the description TCPSocket constructor is invoked:

To support TCP connection “half close”:
The close() input parameter to the WritableStream constructor called when the mySocket.input object is created MUST execute the following:

  1. Send FIN to the remote peer.
  2. Set the mySocket.readyState attribute to halfclosed.

(I don’t have to state that if there is data in the write buffer that not yet has been sent then this data is sent as this is given by the Streams API specification.)

To support a complete TCP connection termination:
The close() argument of the start() parameter of the ReadableStream constructor called when the mySocket.output object is created the MUST execute the following:

  1. Start TCP connection close down.
  2. Set mySocket.readyState to closing.

Then it must be specificed that mySocket.close() calls this close() method.

I am not sure about above so happy for your feedback.

@domenic

This comment has been minimized.

Copy link
Member

commented Feb 10, 2014

I am a bit puzzled that you in this example define that the construction of both the output and input stream objects return the same single promise.

Well, I just tried to translate the info you gave above. Since your original algorithm made it sound like there would be a single success or failure state for opening both sides of the connection, you can then encapsulate that into a single promise; there is no need to create two promises with exactly the same information.

If so that would mean that something like this should be added to the description TCPSocket constructor is invoked:

This looks great! You probably need to return some promises to indicate when the close successfully finished (or errored, if that is possible).

I don’t have to state that if there is data in the write buffer that not yet has been sent then this data is sent as this is given by the Streams API specification.

Exactly!! :)

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Feb 11, 2014

Well, I just tried to translate the info you gave above. Since your original algorithm made it sound
like there would be a single success or failure state for opening both sides of the connection, you
can then encapsulate that into a single promise; there is no need to create two promises wit
exactly the same information.

Yes, opening a TCP connection is a process that opens both the read and write sides of the connection. So either the connection attempt succeeds or fails, which means that both the reading and the writing side succeeds or fails. However, I guess my problem relates to my limited experience with promises. What made me confused was that the same single promise object can be returned from two different asynchronous functions, i.e. the start() function given to the constructor of the ReadableStream object mySocket.output and the start() function given to the constructor of the WritableStream object mySocket.input. My view was that a single promise object was representing the result of one asynchronous method call, i.e. a call to the start() function for mySocket.output returns one promise object and a call to the start() function for mySocket.input returns another promise object

You probably need to return some promises to indicate when the close successfully finished (or errored, if that is possible).

For half close I don’t think we need to return a promise as this is basically a synchronous operation. FIN is just sent to the remote peer. For a full close we need a promise to indicate when connection has been cleanly closed or if there was an error.

I can now try to rewrite the stepwise descriptions for the TCPSocket interface and state how the Streams API is used in terms of functions passed to their constructors. However, one comment is that this will be specified at a very detailed, near implementation, level that is different from what I have seen in other W3C specifications. I am not taking a specific position here, this is just a note.

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Feb 27, 2014

Domenic, I am starting to update the tangible specification. Just a syntactical detail on examples.

I would define the promise resolve function as:

mySocket.input.write("Hello World").then (
function () {

When you rewrote the example it was:

mySocket.input.write("Hello World").then (
() => {

Where does the () => syntax come from? Is there a redefinition of function () somewhere?

@domenic

This comment has been minimized.

Copy link
Member

commented Feb 27, 2014

@marcoscaceres

This comment has been minimized.

Copy link
Member

commented Feb 27, 2014

@ClaesNilsson, yes. That's ES6 :)

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Feb 28, 2014

Ok, thanks for this clarification. As "Arrow functions" is an experimental syntax and currently only supported in one browser I suggest that we should stick to the traditional syntax in the examples in the TCP and UDP Socket API specification (the specification has been renamed: sysapps/tcp-udp-sockets#42)

@domenic

This comment has been minimized.

Copy link
Member

commented Feb 28, 2014

I wouldn't classify it that way; I would classify it as standard syntax which only one browser has implemented the specification for. As such I'll be using it in the stream spec and encouraging authors to use it in their specs going forward, as it helps clarity IMO. But, of course, this is a minor point, so please do whatever you feel is better for your readers.

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Feb 28, 2014

Ok, there are pros and cons. I haven't decided yet. Have to see what feels best when I work further on the specification.

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Mar 7, 2014

Domenic, I am in the process of rewriting the stepwise descriptions for the TCPSocket interface according to the discussion above. Some questions arise:

  1. You have suggested that a single promise openingPromise should be returned from the start() function of mySocket.output and mySocket.input constructors. So this means that on a successful TCP connection setup the openingPromise will resolve and it will reject with an error object if the connection setup failed. My question might be trivial but how is the syntax for handling this promise? How do I catch the openingPromise resolver and reject algorithms? As it is the start() argument of my.socket.output and my.socket.input constructors that returns openingPromise I guess that I can't write the following:
    var mySocket = new TCPSocket("127.0.0.1", 6789).then(
    () => {console.log("TCP connection setup successful”)},
    e => console.error("TCP connection setup failed", e.message);
    );
  2. You have suggested that I should phrase the manipulation of the streams in terms of functions passed to their constructors, or their methods. As the start() function is typically used to adapt a push-based data source, as it is called immediately, I assume that the specification should state that the semantics for creating the TCP connection should be described as done within the start() function. For example:
    “The start() function MUST initiate the TCP connection attempt to the requested address and port and run the TCP connection establishment in the background (without blocking scripts)".
    Is my interpretation correct?
  3. The semantics described for the close() argument of start() for the mySocket.output constructor could define a complete TCP connection closing handshake. However, I am not sure how this relates to mySocket.output.cancel(reason)? If I, to make it clear for developers, define a method mySocket.close () should this method call the close() argument of start() for the mySocket.output constructor or should it call mySocket.output.cancel(reason)?
  4. Similar question for halfClose (close the write side of the TCP connection but keep the read side). The semantics described for the close() argument for the mySocket.input constructor could define a TCP connection half close (just send FIN). If I, to make it clear for developers, define a method mySocket.halfClose () should this method call mySocket.input.close() or should the method call the close() argument of mySocket.input constructor ?

I am sorry if my questions are trivial but if I understand these basic principles of using the Streams API I think that rewriting the TCP and UDP Socket API will not be that hard.

@domenic

This comment has been minimized.

Copy link
Member

commented Mar 8, 2014

Awesome, great to get this kind of "beta testing" of the spec :)

My question might be trivial but how is the syntax for handling this promise? How do I catch the openingPromise resolver and reject algorithms?

I'm a little confused what you're asking. Are you asking how to write the spec text that resolves or rejects the promise? If so, I think w3ctag/promises-guide would be helpful; see e.g. the delay example. It's as simple as saying "Resolve openingPromise with undefined" or similar.

But then

As it is the start() argument of my.socket.output and my.socket.input constructors that returns openingPromise I guess that I can't write the following:
var mySocket = new TCPSocket("127.0.0.1", 6789).then(() => {console.log("TCP connection setup successful”)}, e => console.error("TCP connection setup failed", e.message); );

Whereas this seems to be asking about how users will use your API, which is a different question.

As-is, there is no way for users to be notified when the start process has completed, but no data is yet available. They can wait to be notified for successful start plus available data, by doing var mySocket = new TCPSocket(...); mySocket.input.wait().then(() => /* successful start and data available */);.

This was done in response to feedback from a few members of the Node community that they'd prefer to have stream objects be created and usable ASAP, leaving the actual "start" process as an implementation detail. It simplifies the user's view of the internal state machine, since from their perspective both "starting" and "waiting for data" are the same (called "waiting" by the API). You can see this somewhat explicitly in the state machine diagram, where there is an internal started flag that is not exposed as part of the "Waiting" state.

Now, I'm totally willing to take feedback that hiding this is not the right way to go, or hurts certain use cases. If you think that's true, and users should be able to distinguish starting vs. waiting for data (and potentially be notified), we have a few ways forward:

  • Leave it as a TCP-socket level concern. E.g., TCPSocket would expose the openingPromise as mySocket.opened, so people could do var mySocket = new TCPSocket(...); mySocket.opened.then(() => /* still probably no data available, but open succeeded */, e => /* open failed with error e */)
  • Include this mechanism on the streams. Similar, but you'd do e.g. mySocket.input.started.then(...) instead, and it'd be available for every stream. A bit more internal complexity exposed, and I'm not sure I like the precedent of doing so, but on the other hand it's easy to ignore.
  • Include this mechanism on the streams, and introduce a new state onto the streams ("starting"). I think this extra complexity would be harder to ignore, as now all code that handles the stream state-by-state now needs to deal with another state. But perhaps this is a fundamental state, and should not be merged into "waiting" like we are doing now?

I'd be curious of @tyoshino's opinions on the above possibilities. And of course yours!

I assume that the specification should state that the semantics for creating the TCP connection should be described as done within the start() function.

Yeah, exactly!

However, I am not sure how this relates to mySocket.output.cancel(reason)? If I, to make it clear for developers, define a method mySocket.close () should this method call the close() argument of start() for the mySocket.output constructor or should it call mySocket.output.cancel(reason)?

Good question. I can see how this is a bit confusing. The answer is that you should call mySocket.output.cancel(reason), because that will also clear the internal buffer and set all the related parts of the stream (e.g. its own .closed promise, and .state property) correctly. The functions you pass to the constructor are meant to interface with the underlying source only; you need to go through the public API to inform the stream.

I agree cancel() is a bit confusing of a name here, but I hope it makes sense... in #67 @othiym23 pointed out that calling it close() would be a somewhat false symmetry with writable streams, where close() is a more graceful process that doesn't e.g. throw away unread data.

If I, to make it clear for developers, define a method mySocket.halfClose () should this method call mySocket.input.close() or should the method call the close() argument of mySocket.input constructor ?

As above, go through the public API :).


I am sorry if my questions are trivial but if I understand these basic principles of using the Streams API I think that rewriting the TCP and UDP Socket API will not be that hard.

Not at all; these are great questions! I'm really glad we have you as such an understanding consumer of the spec in its early stages. It's very helpful.

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Mar 11, 2014

Thanks for your feedback Domenic. It helps a lot!

Regarding the socket opening I understand the feedback from the Node community. This makes sense from a pure “Streams” aspect. My original thinking was that, as we are dealing with a source and sink that is the same connection oriented source/sink, applications should be able to get some kind of confirmation when the connection has been established, alternatively failed. However, when the application calls mySocket.input.write() I assume that the promise returned by write() will be rejected if TCP connection attempt failed. Similar for the application calling wait(), i.e. the promise returned by wait() will be rejected if TCP connection attempt failed. So I may revisit my view that applications should be able to get some kind of confirmation when the connection has been established/failed.

I think I will, for now, add this to the specification according your suggestion to leave it as a TCP-socket level concern by exposing openingPromise as mySocket.opened(). It does not complicate the Streams API and having it at the TCPSocket level is also logical as it concerns both the readable and writable side. Furthermore, if we discover that applications don’t need this it can easily be removed from the specification at a later stage.

Thanks for clarifying how to use the public Streams API for defining socket closing and half closing. My interpretation is that the TCP specific logic for closing the TCP connection is implemented in the cancel() function argument of the mySocket.output constructor and the mySocket.output.cancel(reason) handles the internal buffer and that the related Stream parts as well as calling the constructor’s cancel() function argument. Similar for TCP connection half close and mySocket.output constructor’s close() function argument.

I have made a pull request in which the interface TCPSocket has been rewritten (UDP and TCPServer are still not updated): sysapps/tcp-udp-sockets#63. Happy for your feedback!

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Mar 14, 2014

See my commits in sysapps/tcp-udp-sockets#63

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Mar 16, 2014

New commit today: sysapps/tcp-udp-sockets#63

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Mar 19, 2014

The Pull Request has now been merged and the rendered version is here: http://raw-sockets.sysapps.org/.
Once again, please note that in this version only the TCPSocket interface has been rewritten to be based on the Streams API.
I am now continuing with rewriting the UD and TCPserver interfaces.

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Mar 25, 2014

I am now continuing with rewriting the UDP interface to be based on Streams API. Here I discover 3 issues. See the current version of the UDPSocket interface: http://sysapps.github.io/tcp-udp-sockets/#interface-udpsocket

  1. See the UPnP-SSDP multicast examples in the beginning. Here we must be able to retrieve the address and port of the remote peer. The current version of the API uses UDPMessageEvent, which inherits MessageEvent and adds the remoteAddress and remotePort attributes. How do we fix that when using ReadableStream? Should the push() argument of start() push a UDPMessageEvent object, which has the data attribute set to an ArrayBuffer whose contents are the received UDP data, and the remoteAddress and remotePort attributes set to the address/port of the sender, into the internal [STREAMS] receive buffer? I assume that then read() returns UDPMessageEvent.
  2. Similar problem for writing UDP data. If default address/port are not given as arguments in the UDP socket object constructor it have to be stated for each write(). Should the data argument of write() be a UDPMessageEvent?
  3. There are no public methods for allowing applications to pause and resume data reception.
@domenic

This comment has been minimized.

Copy link
Member

commented Mar 26, 2014

For multicast, what about an API that allows you to set receivers ahead of time? E.g. http://nodejs.org/docs/latest/api/dgram.html seems to do this, so that when you write data, the stream already knows where it should be written to. Alternately, a dictionary { data, address, port } seems OK (a class like UDPMessageEvent seems like overkill). But I don't really know enough about UDP multicast APIs to know if it makes sense.

There are no public methods for allowing applications to pause and resume data reception.

What does this mean, in terms of the underlying syscalls and/or network protocol? The model for streams is that you would not manipulate this directly as a stream user; instead, if you want to pause, you should stop calling read(), and the stream will automatically apply backpressure to the underlying source. Similarly if you want to resume after pausing, start calling read(). (Or call wait() if the underlying source is paused but there is no data buffered in the stream.)

Does that work for UDP, or is it important to access an inherent underlying pause/resume feature of the protocol?

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Mar 27, 2014

Thanks for your reply Domenic.

For multicast, what about an API that allows you to set receivers ahead of time? E.g.http://nodejs.org/docs/latest/api/dgram.html seems to do this, so that when you write data, the stream already knows where it should be written to.

We already have this option in the API. With the UDPSocket constructor’s option argument you can set default remote address and port for subsequent write operations. (However, I don’t see that option in the node.js UDP/datagram API. There is only bind(), which binds the socket to a local address/port pair)
Under all circumstances we need to be able to flexibly set the destination address and port with the write() operation as well as get the remote peer’s address and port with read(). One example is UPnP-SSDP M-SEARCH. You send a multicast search message, receive unicast responses and then reply with unicast to the peer(s) of interest. So it is not enough to be able to set receivers ahead of time.
You propose a dictionary instead of a class (interface) and it makes sense if it works both for reading and writing from a WebIDL point of view (I am not sure):

 dictionary UDPMessage {
                 any                     UDPData;
                 DOMString        remoteAddress;
                 unsigned short  remotePort;
 };

For writing it must be specified for the mySocket.input attribute constructor’s write(data, done, error) input parameter function that the type of data.UDPData must be DOMString or Blob or ArrayBuffer or ArrayBufferView (compare with the current version of the UDPSocket interface) and that the destination address / port is defined by data.UDPData.remoteAddress/remotePort.

For receiving the description of the semantics for the mySocket.output attribute constructor’s start() function must state that a UDPMessage object is created with UDPdata set to the received UDP datagram in the form of a new ArrayBuffer object and remoteAddress/Port set to sender’s address/port. The UDPMessage object is then pushed into the internal receive buffer with a call to push().
Does this make sense?

What does this mean, in terms of the underlying syscalls and/or network protocol? The model for streams is that you would not manipulate this directly as a stream user; instead, if you want to pause, you should stop calling read(), and the stream will automatically apply backpressure to the underlying source. Similarly if you want to resume after pausing, start calling read(). (Or call wait() if the underlying source is paused but there is no data buffered in the stream.)

Does that work for UDP, or is it important to access an inherent underlying pause/resume feature of the protocol?

If I understand this correctly what happens is that if the application stops calling read() then the internal buffer will be filled up to the high watermark and then the underlying data source will be stopped if possible. For TCP the data flow can be stopped through the TCP flow control mechanism but for UDP the underlying data source cannot be stopped.
So if an application wants to pause/suspend data reception it stops calling read() and for TCP the backpressure logic in the ReadableStream will stop the sender of TCP data when the internal receive buffer reaches it’s high watermark. This is slightly different from a suspend/pause method that immediately stops the sender of TCP data through the flow control mechanism. However, I don’t know if this difference is significant for existing use cases. So we might keep it as is for now and be prepared to add explicit suspend/resume methods later if required.
For UDP, which has no flow control mechanism, the underlying data source cannot be stopped and the application can just stop/resume calling read().

@domenic

This comment has been minimized.

Copy link
Member

commented Mar 30, 2014

So it is not enough to be able to set receivers ahead of time.

OK. A lot of this is going over my head, but I think I understand that much :)

You propose a dictionary instead of a class (interface) and it makes sense if it works both for reading and writing from a WebIDL point of view (I am not sure):

I think that works well, yeah. Maybe the shorter names of { data, address, port } though since it'll be easier for users to type.

For writing it must be specified for the mySocket.input attribute constructor’s write(data, done, error) input parameter function that the type of data.UDPData must be DOMString or Blob or ArrayBuffer or ArrayBufferView (compare with the current version of the UDPSocket interface)

I would encourage you to only accept ArrayBuffers. Definitely not DOMStrings, as then you have to deal with encoding issues, which should instead be handled by a transform stream. As for ArrayBufferViews or Blob, it is not that big a deal to include them, but I think it unnecessarily complicates the semantics. The world is moving toward ArrayBuffers.

So maybe

dictionary UDPMessage {
  ArrayBuffer data;
  DOMString address;
  unsigned short port;
}

What you do to signify that you only accept certain types of data, is that if the incoming type is not as expected (e.g. is not WebIDL-convertible to UDPMessage), you call error with a TypeError.

If I understand this correctly...

Yeah, it seems like you do. Sounds like that aspect of things is OK?

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Mar 30, 2014

Thanks Domenic,

Regarding the data types for send we had a discussion on that previously and came to the conclusion that we should allow those 4 data types for sending. I don't remember the exact reason but at least one argument was that this is in line with the Web Socket API. However, the world is changing and we have transform streams so let us assume only ArrayBuffer now both for sending and receiving. I'll make an editor's note that this has changed from the previous version of the specification and see if there are any objections.

I'll edit this and make a pull request within a few days.

@domenic

This comment has been minimized.

Copy link
Member

commented Mar 30, 2014

However, the world is changing and we have transform streams so let us assume only ArrayBuffer now both for sending and receiving. I'll make an editor's note that this has changed from the previous version of the specification and see if there are any objections.

Sounds good. As I said, I don't think it'd be a big deal to include ArrayBufferViews and/or Blobs if people have strong preferences that they should stay. But definitely not strings!

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Mar 31, 2014

Ok, now have submitted a PR including the interface UDPSocket rewritten: sysapps/tcp-udp-sockets#64

@othiym23

This comment has been minimized.

Copy link

commented Apr 4, 2014

/cc @Gozala

@ClaesNilsson

This comment has been minimized.

Copy link
Author

commented Apr 7, 2014

There will be a slot on this specification at the W3C San Jose SysApps meeting Tuesday April 8th at 14.00 (23:00 CET).
Any comments prior to that? Can I merge the PR.

@domenic

This comment has been minimized.

Copy link
Member

commented Apr 7, 2014

Sorry for the delay; will prioritize reviewing today. Been doing W3C TAG stuff for the last week…

@domenic

This comment has been minimized.

Copy link
Member

commented Jun 17, 2014

This seems to have gone really well.

We're investigating some potential binary-stream-specific APIs (e.g. #111), but when/if they are introduced, you will simply be able to upgrade from ReadableStream to BinaryReadableStream in your spec, and get a new method or two for free.

In the meantime, I'll close this bug, unless there are any objections. Feel free to open a new one, or @-mention me in the tcp-udp-sockets repo, if you have any questions.

@domenic domenic closed this Jun 17, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.