blob: 012ac8786abcf211c42ef2280d68712fb1b9c4bc [file] [log] [blame]
// Copyright 2013 The Emscripten Authors. All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License. Both these licenses can be
// found in the LICENSE file.
mergeInto(LibraryManager.library, {
$SOCKFS__postset: function() {
addAtInit('SOCKFS.root = FS.mount(SOCKFS, {}, null);');
},
$SOCKFS__deps: ['$FS'],
$SOCKFS: {
mount: function(mount) {
// If Module['websocket'] has already been defined (e.g. for configuring
// the subprotocol/url) use that, if not initialise it to a new object.
Module['websocket'] = (Module['websocket'] &&
('object' === typeof Module['websocket'])) ? Module['websocket'] : {};
// Add the Event registration mechanism to the exported websocket configuration
// object so we can register network callbacks from native JavaScript too.
// For more documentation see system/include/emscripten/emscripten.h
Module['websocket']._callbacks = {};
Module['websocket']['on'] = function(event, callback) {
if ('function' === typeof callback) {
this._callbacks[event] = callback;
}
return this;
};
Module['websocket'].emit = function(event, param) {
if ('function' === typeof this._callbacks[event]) {
this._callbacks[event].call(this, param);
}
};
// If debug is enabled register simple default logging callbacks for each Event.
#if SOCKET_DEBUG
Module['websocket']['on']('error', function(error) {err('Socket error ' + error);});
Module['websocket']['on']('open', function(fd) {out('Socket open fd = ' + fd);});
Module['websocket']['on']('listen', function(fd) {out('Socket listen fd = ' + fd);});
Module['websocket']['on']('connection', function(fd) {out('Socket connection fd = ' + fd);});
Module['websocket']['on']('message', function(fd) {out('Socket message fd = ' + fd);});
Module['websocket']['on']('close', function(fd) {out('Socket close fd = ' + fd);});
#endif
return FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */, 0);
},
createSocket: function(family, type, protocol) {
var streaming = type == {{{ cDefine('SOCK_STREAM') }}};
if (protocol) {
assert(streaming == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if SOCK_STREAM, must be tcp
}
// create our internal socket structure
var sock = {
family: family,
type: type,
protocol: protocol,
server: null,
error: null, // Used in getsockopt for SOL_SOCKET/SO_ERROR test
peers: {},
pending: [],
recv_queue: [],
#if SOCKET_WEBRTC
#else
sock_ops: SOCKFS.websocket_sock_ops
#endif
};
// create the filesystem node to store the socket structure
var name = SOCKFS.nextname();
var node = FS.createNode(SOCKFS.root, name, {{{ cDefine('S_IFSOCK') }}}, 0);
node.sock = sock;
// and the wrapping stream that enables library functions such
// as read and write to indirectly interact with the socket
var stream = FS.createStream({
path: name,
node: node,
flags: FS.modeStringToFlags('r+'),
seekable: false,
stream_ops: SOCKFS.stream_ops
});
// map the new stream to the socket structure (sockets have a 1:1
// relationship with a stream)
sock.stream = stream;
return sock;
},
getSocket: function(fd) {
var stream = FS.getStream(fd);
if (!stream || !FS.isSocket(stream.node.mode)) {
return null;
}
return stream.node.sock;
},
// node and stream ops are backend agnostic
stream_ops: {
poll: function(stream) {
var sock = stream.node.sock;
return sock.sock_ops.poll(sock);
},
ioctl: function(stream, request, varargs) {
var sock = stream.node.sock;
return sock.sock_ops.ioctl(sock, request, varargs);
},
read: function(stream, buffer, offset, length, position /* ignored */) {
var sock = stream.node.sock;
var msg = sock.sock_ops.recvmsg(sock, length);
if (!msg) {
// socket is closed
return 0;
}
buffer.set(msg.buffer, offset);
return msg.buffer.length;
},
write: function(stream, buffer, offset, length, position /* ignored */) {
var sock = stream.node.sock;
return sock.sock_ops.sendmsg(sock, buffer, offset, length);
},
close: function(stream) {
var sock = stream.node.sock;
sock.sock_ops.close(sock);
}
},
nextname: function() {
if (!SOCKFS.nextname.current) {
SOCKFS.nextname.current = 0;
}
return 'socket[' + (SOCKFS.nextname.current++) + ']';
},
// backend-specific stream ops
websocket_sock_ops: {
//
// peers are a small wrapper around a WebSocket to help in
// emulating dgram sockets
//
// these functions aren't actually sock_ops members, but we're
// abusing the namespace to organize them
//
createPeer: function(sock, addr, port) {
var ws;
if (typeof addr === 'object') {
ws = addr;
addr = null;
port = null;
}
if (ws) {
// for sockets that've already connected (e.g. we're the server)
// we can inspect the _socket property for the address
if (ws._socket) {
addr = ws._socket.remoteAddress;
port = ws._socket.remotePort;
}
// if we're just now initializing a connection to the remote,
// inspect the url property
else {
var result = /ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);
if (!result) {
throw new Error('WebSocket URL must be in the format ws(s)://address:port');
}
addr = result[1];
port = parseInt(result[2], 10);
}
} else {
// create the actual websocket object and connect
try {
// runtimeConfig gets set to true if WebSocket runtime configuration is available.
var runtimeConfig = (Module['websocket'] && ('object' === typeof Module['websocket']));
// The default value is 'ws://' the replace is needed because the compiler replaces '//' comments with '#'
// comments without checking context, so we'd end up with ws:#, the replace swaps the '#' for '//' again.
var url = '{{{ WEBSOCKET_URL }}}'.replace('#', '//');
if (runtimeConfig) {
if ('string' === typeof Module['websocket']['url']) {
url = Module['websocket']['url']; // Fetch runtime WebSocket URL config.
}
}
if (url === 'ws://' || url === 'wss://') { // Is the supplied URL config just a prefix, if so complete it.
var parts = addr.split('/');
url = url + parts[0] + ":" + port + "/" + parts.slice(1).join('/');
}
// Make the WebSocket subprotocol (Sec-WebSocket-Protocol) default to binary if no configuration is set.
var subProtocols = '{{{ WEBSOCKET_SUBPROTOCOL }}}'; // The default value is 'binary'
if (runtimeConfig) {
if ('string' === typeof Module['websocket']['subprotocol']) {
subProtocols = Module['websocket']['subprotocol']; // Fetch runtime WebSocket subprotocol config.
}
}
// The regex trims the string (removes spaces at the beginning and end, then splits the string by
// <any space>,<any space> into an Array. Whitespace removal is important for Websockify and ws.
subProtocols = subProtocols.replace(/^ +| +$/g,"").split(/ *, */);
// The node ws library API for specifying optional subprotocol is slightly different than the browser's.
var opts = ENVIRONMENT_IS_NODE ? {'protocol': subProtocols.toString()} : subProtocols;
// some webservers (azure) does not support subprotocol header
if (runtimeConfig && null === Module['websocket']['subprotocol']) {
subProtocols = 'null';
opts = undefined;
}
#if SOCKET_DEBUG
out('connect: ' + url + ', ' + subProtocols.toString());
#endif
// If node we use the ws library.
var WebSocketConstructor;
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
WebSocketConstructor = require('ws');
} else
#endif // ENVIRONMENT_MAY_BE_NODE
#if ENVIRONMENT_MAY_BE_WEB
if (ENVIRONMENT_IS_WEB) {
WebSocketConstructor = window['WebSocket'];
} else
#endif // ENVIRONMENT_MAY_BE_WEB
{
WebSocketConstructor = WebSocket;
}
ws = new WebSocketConstructor(url, opts);
ws.binaryType = 'arraybuffer';
} catch (e) {
throw new FS.ErrnoError(ERRNO_CODES.EHOSTUNREACH);
}
}
#if SOCKET_DEBUG
out('websocket adding peer: ' + addr + ':' + port);
#endif
var peer = {
addr: addr,
port: port,
socket: ws,
dgram_send_queue: []
};
SOCKFS.websocket_sock_ops.addPeer(sock, peer);
SOCKFS.websocket_sock_ops.handlePeerEvents(sock, peer);
// if this is a bound dgram socket, send the port number first to allow
// us to override the ephemeral port reported to us by remotePort on the
// remote end.
if (sock.type === {{{ cDefine('SOCK_DGRAM') }}} && typeof sock.sport !== 'undefined') {
#if SOCKET_DEBUG
out('websocket queuing port message (port ' + sock.sport + ')');
#endif
peer.dgram_send_queue.push(new Uint8Array([
255, 255, 255, 255,
'p'.charCodeAt(0), 'o'.charCodeAt(0), 'r'.charCodeAt(0), 't'.charCodeAt(0),
((sock.sport & 0xff00) >> 8) , (sock.sport & 0xff)
]));
}
return peer;
},
getPeer: function(sock, addr, port) {
return sock.peers[addr + ':' + port];
},
addPeer: function(sock, peer) {
sock.peers[peer.addr + ':' + peer.port] = peer;
},
removePeer: function(sock, peer) {
delete sock.peers[peer.addr + ':' + peer.port];
},
handlePeerEvents: function(sock, peer) {
var first = true;
var handleOpen = function () {
#if SOCKET_DEBUG
out('websocket handle open');
#endif
Module['websocket'].emit('open', sock.stream.fd);
try {
var queued = peer.dgram_send_queue.shift();
while (queued) {
#if SOCKET_DEBUG
out('websocket sending queued data (' + queued.byteLength + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(queued))]);
#endif
peer.socket.send(queued);
queued = peer.dgram_send_queue.shift();
}
} catch (e) {
// not much we can do here in the way of proper error handling as we've already
// lied and said this data was sent. shut it down.
peer.socket.close();
}
};
function handleMessage(data) {
assert(typeof data !== 'string' && data.byteLength !== undefined); // must receive an ArrayBuffer
// An empty ArrayBuffer will emit a pseudo disconnect event
// as recv/recvmsg will return zero which indicates that a socket
// has performed a shutdown although the connection has not been disconnected yet.
if (data.byteLength == 0) {
return;
}
data = new Uint8Array(data); // make a typed array view on the array buffer
#if SOCKET_DEBUG
out('websocket handle message (' + data.byteLength + ' bytes): ' + [Array.prototype.slice.call(data)]);
#endif
// if this is the port message, override the peer's port with it
var wasfirst = first;
first = false;
if (wasfirst &&
data.length === 10 &&
data[0] === 255 && data[1] === 255 && data[2] === 255 && data[3] === 255 &&
data[4] === 'p'.charCodeAt(0) && data[5] === 'o'.charCodeAt(0) && data[6] === 'r'.charCodeAt(0) && data[7] === 't'.charCodeAt(0)) {
// update the peer's port and it's key in the peer map
var newport = ((data[8] << 8) | data[9]);
SOCKFS.websocket_sock_ops.removePeer(sock, peer);
peer.port = newport;
SOCKFS.websocket_sock_ops.addPeer(sock, peer);
return;
}
sock.recv_queue.push({ addr: peer.addr, port: peer.port, data: data });
Module['websocket'].emit('message', sock.stream.fd);
};
if (ENVIRONMENT_IS_NODE) {
peer.socket.on('open', handleOpen);
peer.socket.on('message', function(data, flags) {
if (!flags.binary) {
return;
}
handleMessage((new Uint8Array(data)).buffer); // copy from node Buffer -> ArrayBuffer
});
peer.socket.on('close', function() {
Module['websocket'].emit('close', sock.stream.fd);
});
peer.socket.on('error', function(error) {
// Although the ws library may pass errors that may be more descriptive than
// ECONNREFUSED they are not necessarily the expected error code e.g.
// ENOTFOUND on getaddrinfo seems to be node.js specific, so using ECONNREFUSED
// is still probably the most useful thing to do.
sock.error = ERRNO_CODES.ECONNREFUSED; // Used in getsockopt for SOL_SOCKET/SO_ERROR test.
Module['websocket'].emit('error', [sock.stream.fd, sock.error, 'ECONNREFUSED: Connection refused']);
// don't throw
});
} else {
peer.socket.onopen = handleOpen;
peer.socket.onclose = function() {
Module['websocket'].emit('close', sock.stream.fd);
};
peer.socket.onmessage = function peer_socket_onmessage(event) {
handleMessage(event.data);
};
peer.socket.onerror = function(error) {
// The WebSocket spec only allows a 'simple event' to be thrown on error,
// so we only really know as much as ECONNREFUSED.
sock.error = ERRNO_CODES.ECONNREFUSED; // Used in getsockopt for SOL_SOCKET/SO_ERROR test.
Module['websocket'].emit('error', [sock.stream.fd, sock.error, 'ECONNREFUSED: Connection refused']);
};
}
},
//
// actual sock ops
//
poll: function(sock) {
if (sock.type === {{{ cDefine('SOCK_STREAM') }}} && sock.server) {
// listen sockets should only say they're available for reading
// if there are pending clients.
return sock.pending.length ? ({{{ cDefine('POLLRDNORM') }}} | {{{ cDefine('POLLIN') }}}) : 0;
}
var mask = 0;
var dest = sock.type === {{{ cDefine('SOCK_STREAM') }}} ? // we only care about the socket state for connection-based sockets
SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport) :
null;
if (sock.recv_queue.length ||
!dest || // connection-less sockets are always ready to read
(dest && dest.socket.readyState === dest.socket.CLOSING) ||
(dest && dest.socket.readyState === dest.socket.CLOSED)) { // let recv return 0 once closed
mask |= ({{{ cDefine('POLLRDNORM') }}} | {{{ cDefine('POLLIN') }}});
}
if (!dest || // connection-less sockets are always ready to write
(dest && dest.socket.readyState === dest.socket.OPEN)) {
mask |= {{{ cDefine('POLLOUT') }}};
}
if ((dest && dest.socket.readyState === dest.socket.CLOSING) ||
(dest && dest.socket.readyState === dest.socket.CLOSED)) {
mask |= {{{ cDefine('POLLHUP') }}};
}
return mask;
},
ioctl: function(sock, request, arg) {
switch (request) {
case {{{ cDefine('FIONREAD') }}}:
var bytes = 0;
if (sock.recv_queue.length) {
bytes = sock.recv_queue[0].data.length;
}
{{{ makeSetValue('arg', '0', 'bytes', 'i32') }}};
return 0;
default:
return ERRNO_CODES.EINVAL;
}
},
close: function(sock) {
// if we've spawned a listen server, close it
if (sock.server) {
try {
sock.server.close();
} catch (e) {
}
sock.server = null;
}
// close any peer connections
var peers = Object.keys(sock.peers);
for (var i = 0; i < peers.length; i++) {
var peer = sock.peers[peers[i]];
try {
peer.socket.close();
} catch (e) {
}
SOCKFS.websocket_sock_ops.removePeer(sock, peer);
}
return 0;
},
bind: function(sock, addr, port) {
if (typeof sock.saddr !== 'undefined' || typeof sock.sport !== 'undefined') {
throw new FS.ErrnoError(ERRNO_CODES.EINVAL); // already bound
}
sock.saddr = addr;
sock.sport = port;
// in order to emulate dgram sockets, we need to launch a listen server when
// binding on a connection-less socket
// note: this is only required on the server side
if (sock.type === {{{ cDefine('SOCK_DGRAM') }}}) {
// close the existing server if it exists
if (sock.server) {
sock.server.close();
sock.server = null;
}
// swallow error operation not supported error that occurs when binding in the
// browser where this isn't supported
try {
sock.sock_ops.listen(sock, 0);
} catch (e) {
if (!(e instanceof FS.ErrnoError)) throw e;
if (e.errno !== ERRNO_CODES.EOPNOTSUPP) throw e;
}
}
},
connect: function(sock, addr, port) {
if (sock.server) {
throw new FS.ErrnoError(ERRNO_CODES.EOPNOTSUPP);
}
// TODO autobind
// if (!sock.addr && sock.type == {{{ cDefine('SOCK_DGRAM') }}}) {
// }
// early out if we're already connected / in the middle of connecting
if (typeof sock.daddr !== 'undefined' && typeof sock.dport !== 'undefined') {
var dest = SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport);
if (dest) {
if (dest.socket.readyState === dest.socket.CONNECTING) {
throw new FS.ErrnoError(ERRNO_CODES.EALREADY);
} else {
throw new FS.ErrnoError(ERRNO_CODES.EISCONN);
}
}
}
// add the socket to our peer list and set our
// destination address / port to match
var peer = SOCKFS.websocket_sock_ops.createPeer(sock, addr, port);
sock.daddr = peer.addr;
sock.dport = peer.port;
// always "fail" in non-blocking mode
throw new FS.ErrnoError(ERRNO_CODES.EINPROGRESS);
},
listen: function(sock, backlog) {
if (!ENVIRONMENT_IS_NODE) {
throw new FS.ErrnoError(ERRNO_CODES.EOPNOTSUPP);
}
#if ENVIRONMENT_MAY_BE_NODE
if (sock.server) {
throw new FS.ErrnoError(ERRNO_CODES.EINVAL); // already listening
}
var WebSocketServer = require('ws').Server;
var host = sock.saddr;
#if SOCKET_DEBUG
console.log('listen: ' + host + ':' + sock.sport);
#endif
sock.server = new WebSocketServer({
host: host,
port: sock.sport
// TODO support backlog
});
Module['websocket'].emit('listen', sock.stream.fd); // Send Event with listen fd.
sock.server.on('connection', function(ws) {
#if SOCKET_DEBUG
console.log('received connection from: ' + ws._socket.remoteAddress + ':' + ws._socket.remotePort);
#endif
if (sock.type === {{{ cDefine('SOCK_STREAM') }}}) {
var newsock = SOCKFS.createSocket(sock.family, sock.type, sock.protocol);
// create a peer on the new socket
var peer = SOCKFS.websocket_sock_ops.createPeer(newsock, ws);
newsock.daddr = peer.addr;
newsock.dport = peer.port;
// push to queue for accept to pick up
sock.pending.push(newsock);
Module['websocket'].emit('connection', newsock.stream.fd);
} else {
// create a peer on the listen socket so calling sendto
// with the listen socket and an address will resolve
// to the correct client
SOCKFS.websocket_sock_ops.createPeer(sock, ws);
Module['websocket'].emit('connection', sock.stream.fd);
}
});
sock.server.on('closed', function() {
Module['websocket'].emit('close', sock.stream.fd);
sock.server = null;
});
sock.server.on('error', function(error) {
// Although the ws library may pass errors that may be more descriptive than
// ECONNREFUSED they are not necessarily the expected error code e.g.
// ENOTFOUND on getaddrinfo seems to be node.js specific, so using EHOSTUNREACH
// is still probably the most useful thing to do. This error shouldn't
// occur in a well written app as errors should get trapped in the compiled
// app's own getaddrinfo call.
sock.error = ERRNO_CODES.EHOSTUNREACH; // Used in getsockopt for SOL_SOCKET/SO_ERROR test.
Module['websocket'].emit('error', [sock.stream.fd, sock.error, 'EHOSTUNREACH: Host is unreachable']);
// don't throw
});
#endif // ENVIRONMENT_MAY_BE_NODE
},
accept: function(listensock) {
if (!listensock.server) {
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
}
var newsock = listensock.pending.shift();
newsock.stream.flags = listensock.stream.flags;
return newsock;
},
getname: function(sock, peer) {
var addr, port;
if (peer) {
if (sock.daddr === undefined || sock.dport === undefined) {
throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN);
}
addr = sock.daddr;
port = sock.dport;
} else {
// TODO saddr and sport will be set for bind()'d UDP sockets, but what
// should we be returning for TCP sockets that've been connect()'d?
addr = sock.saddr || 0;
port = sock.sport || 0;
}
return { addr: addr, port: port };
},
sendmsg: function(sock, buffer, offset, length, addr, port) {
if (sock.type === {{{ cDefine('SOCK_DGRAM') }}}) {
// connection-less sockets will honor the message address,
// and otherwise fall back to the bound destination address
if (addr === undefined || port === undefined) {
addr = sock.daddr;
port = sock.dport;
}
// if there was no address to fall back to, error out
if (addr === undefined || port === undefined) {
throw new FS.ErrnoError(ERRNO_CODES.EDESTADDRREQ);
}
} else {
// connection-based sockets will only use the bound
addr = sock.daddr;
port = sock.dport;
}
// find the peer for the destination address
var dest = SOCKFS.websocket_sock_ops.getPeer(sock, addr, port);
// early out if not connected with a connection-based socket
if (sock.type === {{{ cDefine('SOCK_STREAM') }}}) {
if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) {
throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN);
} else if (dest.socket.readyState === dest.socket.CONNECTING) {
throw new FS.ErrnoError(ERRNO_CODES.EAGAIN);
}
}
// create a copy of the incoming data to send, as the WebSocket API
// doesn't work entirely with an ArrayBufferView, it'll just send
// the entire underlying buffer
if (ArrayBuffer.isView(buffer)) {
offset += buffer.byteOffset;
buffer = buffer.buffer;
}
var data;
#if USE_PTHREADS
// WebSockets .send() does not allow passing a SharedArrayBuffer, so clone the portion of the SharedArrayBuffer as a regular
// ArrayBuffer that we want to send.
if (buffer instanceof SharedArrayBuffer) {
data = new Uint8Array(new Uint8Array(buffer.slice(offset, offset + length))).buffer;
} else {
#endif
data = buffer.slice(offset, offset + length);
#if USE_PTHREADS
}
#endif
// if we're emulating a connection-less dgram socket and don't have
// a cached connection, queue the buffer to send upon connect and
// lie, saying the data was sent now.
if (sock.type === {{{ cDefine('SOCK_DGRAM') }}}) {
if (!dest || dest.socket.readyState !== dest.socket.OPEN) {
// if we're not connected, open a new connection
if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) {
dest = SOCKFS.websocket_sock_ops.createPeer(sock, addr, port);
}
#if SOCKET_DEBUG
out('websocket queuing (' + length + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(data))]);
#endif
dest.dgram_send_queue.push(data);
return length;
}
}
try {
#if SOCKET_DEBUG
out('websocket send (' + length + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(data))]);
#endif
// send the actual data
dest.socket.send(data);
return length;
} catch (e) {
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
}
},
recvmsg: function(sock, length) {
// http://pubs.opengroup.org/onlinepubs/7908799/xns/recvmsg.html
if (sock.type === {{{ cDefine('SOCK_STREAM') }}} && sock.server) {
// tcp servers should not be recv()'ing on the listen socket
throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN);
}
var queued = sock.recv_queue.shift();
if (!queued) {
if (sock.type === {{{ cDefine('SOCK_STREAM') }}}) {
var dest = SOCKFS.websocket_sock_ops.getPeer(sock, sock.daddr, sock.dport);
if (!dest) {
// if we have a destination address but are not connected, error out
throw new FS.ErrnoError(ERRNO_CODES.ENOTCONN);
}
else if (dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) {
// return null if the socket has closed
return null;
}
else {
// else, our socket is in a valid state but truly has nothing available
throw new FS.ErrnoError(ERRNO_CODES.EAGAIN);
}
} else {
throw new FS.ErrnoError(ERRNO_CODES.EAGAIN);
}
}
// queued.data will be an ArrayBuffer if it's unadulterated, but if it's
// requeued TCP data it'll be an ArrayBufferView
var queuedLength = queued.data.byteLength || queued.data.length;
var queuedOffset = queued.data.byteOffset || 0;
var queuedBuffer = queued.data.buffer || queued.data;
var bytesRead = Math.min(length, queuedLength);
var res = {
buffer: new Uint8Array(queuedBuffer, queuedOffset, bytesRead),
addr: queued.addr,
port: queued.port
};
#if SOCKET_DEBUG
out('websocket read (' + bytesRead + ' bytes): ' + [Array.prototype.slice.call(res.buffer)]);
#endif
// push back any unread data for TCP connections
if (sock.type === {{{ cDefine('SOCK_STREAM') }}} && bytesRead < queuedLength) {
var bytesRemaining = queuedLength - bytesRead;
#if SOCKET_DEBUG
out('websocket read: put back ' + bytesRemaining + ' bytes');
#endif
queued.data = new Uint8Array(queuedBuffer, queuedOffset + bytesRead, bytesRemaining);
sock.recv_queue.unshift(queued);
}
return res;
}
}
},
/*
* Mechanism to register handlers for the various Socket Events from C code.
* The registration functions are mostly variations on a theme, so we use this
* generic handler. Most of the callback functions take a file descriptor as a
* parameter, which will get passed to them by the emitting call. The error
* callback also takes an int representing the errno and a char* representing the
* error message, which we extract from the data passed to _callback and convert
* to a char* string before calling the registered C callback.
* Passing a NULL callback function to a emscripten_set_socket_*_callback call
* will deregister the callback registered for that Event.
*/
__set_network_callback: function(event, userData, callback) {
function _callback(data) {
try {
if (event === 'error') {
var sp = stackSave();
var msg = allocate(intArrayFromString(data[2]), 'i8', ALLOC_STACK);
{{{ makeDynCall('viiii') }}}(callback, data[0], data[1], msg, userData);
stackRestore(sp);
} else {
{{{ makeDynCall('vii') }}}(callback, data, userData);
}
} catch (e) {
if (e instanceof ExitStatus) {
return;
} else {
if (e && typeof e === 'object' && e.stack) err('exception thrown: ' + [e, e.stack]);
throw e;
}
}
};
Module['noExitRuntime'] = true;
Module['websocket']['on'](event, callback ? _callback : null);
},
emscripten_set_socket_error_callback__deps: ['__set_network_callback'],
emscripten_set_socket_error_callback: function(userData, callback) {
___set_network_callback('error', userData, callback);
},
emscripten_set_socket_open_callback__deps: ['__set_network_callback'],
emscripten_set_socket_open_callback: function(userData, callback) {
___set_network_callback('open', userData, callback);
},
emscripten_set_socket_listen_callback__deps: ['__set_network_callback'],
emscripten_set_socket_listen_callback: function(userData, callback) {
___set_network_callback('listen', userData, callback);
},
emscripten_set_socket_connection_callback__deps: ['__set_network_callback'],
emscripten_set_socket_connection_callback: function(userData, callback) {
___set_network_callback('connection', userData, callback);
},
emscripten_set_socket_message_callback__deps: ['__set_network_callback'],
emscripten_set_socket_message_callback: function(userData, callback) {
___set_network_callback('message', userData, callback);
},
emscripten_set_socket_close_callback__deps: ['__set_network_callback'],
emscripten_set_socket_close_callback: function(userData, callback) {
___set_network_callback('close', userData, callback);
}
});