diff --git a/package.json b/package.json index 2006d229..cb4f83a0 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "bindings": "~1.2.0" }, "devDependencies": { + "tap": "git+https://github.com/tcr/node-tap.git#4f96b1", "tape": "~2.3.2", - "tap": "git+https://github.com/tcr/node-tap.git#4f96b1" + "tinytap": "^0.2.0" } } diff --git a/src/colony/lua_tm.c b/src/colony/lua_tm.c index 76dc9c70..f46c61c9 100644 --- a/src/colony/lua_tm.c +++ b/src/colony/lua_tm.c @@ -188,13 +188,10 @@ static int l_tm_tcp_close (lua_State* L) static int l_tm_tcp_connect (lua_State* L) { tm_socket_t socket = (tm_socket_t) lua_tonumber(L, 1); - uint8_t ip0 = (uint8_t) lua_tonumber(L, 2); - uint8_t ip1 = (uint8_t) lua_tonumber(L, 3); - uint8_t ip2 = (uint8_t) lua_tonumber(L, 4); - uint8_t ip3 = (uint8_t) lua_tonumber(L, 5); - uint16_t port = (uint16_t) lua_tonumber(L, 6); + uint32_t addr = (uint32_t) lua_tonumber(L, 2); + uint16_t port = (uint16_t) lua_tonumber(L, 3); - lua_pushnumber(L, tm_tcp_connect(socket, ip0, ip1, ip2, ip3, port)); + lua_pushnumber(L, tm_tcp_connect(socket, addr, port)); return 1; } @@ -247,11 +244,13 @@ static int l_tm_tcp_listen (lua_State* L) static int l_tm_tcp_accept (lua_State* L) { uint32_t addr; + uint16_t port; tm_socket_t socket = (tm_socket_t) lua_tonumber(L, 1); - lua_pushnumber(L, tm_tcp_accept(socket, &addr)); + lua_pushnumber(L, tm_tcp_accept(socket, &addr, &port)); lua_pushnumber(L, addr); - return 2; + lua_pushnumber(L, port); + return 3; } #ifdef ENABLE_TLS diff --git a/src/colony/modules/net.js b/src/colony/modules/net.js index fff32982..c5e0b68b 100644 --- a/src/colony/modules/net.js +++ b/src/colony/modules/net.js @@ -17,6 +17,42 @@ var dns = require('dns'); var Stream = require('stream'); var tls = require('tls'); + +/** + * ip/helpers + */ +function isIPv4 (host) { + // via http://stackoverflow.com/a/5284410/179583 + modified to disallow leading 0s + return /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.|$)){4}/.test(host); +} + +function isIPv6 (host) { + // via http://stackoverflow.com/a/17871737/179583 + var itIs = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/.test(host); + if (!itIs && typeof host === 'string') { + // HACK: regex above doesn't handle all IPv4-suffixed-IPv6 addresses, and, well…do you really blame me for not fixing it? + var parts = host.split(':'); + if (isIPv4(parts[parts.length-1])) { + parts.pop(); + parts.push('FFFF:FFFF'); + itIs = isIPv6(parts.join(':')); + } + } + return itIs; +} + +function isIP (host) { + if (isIPv6(host)) return 6; + else if (isIPv4(host)) return 4; + else return 0; +} + +function isPipeName(s) { + return util.isString(s) && toNumber(s) === false; +} + +function toNumber(x) { return (x = Number(x)) >= 0 ? x : false; } + /** * ssl */ @@ -39,13 +75,34 @@ function ensureSSLCtx () { function TCPSocket (socket, _secure) { Stream.Duplex.call(this); - this.socket = socket; + + if (typeof socket === 'object') { + this.socket = socket.fd; + // TODO: respect readable/writable flags + if (socket.allowHalfOpen) console.warn("Ignoring allowHalfOpen option."); + } else if (socket == null) { + if (_secure) ensureSSLCtx(); + this.socket = tm.tcp_open(); + } else { + this.socket = socket; + } this._secure = _secure; this._outgoing = []; this._sending = false; this._queueEnd = false; var self = this; + if (this.socket < 0) { + setImmediate(function () { + self.emit('error', new Error("ENOENT: Cannot open another socket.")); + }); + return; + } + self.on('finish', function () { + // this is called when writing is ended + // TODO: support allowHalfOpen (if firmware can?) + self.close(); + }) self._closehandler = function (buf) { var socket = buf.readUInt32LE(0); if (socket == self.socket) { @@ -60,15 +117,17 @@ function TCPSocket (socket, _secure) { util.inherits(TCPSocket, Stream.Duplex); -function isIP (host) { - return host.match(/^[0-9.]+$/); -} +TCPSocket._portsUsed = Object.create(null); -function isPipeName(s) { - return util.isString(s) && toNumber(s) === false; -} - -function toNumber(x) { return (x = Number(x)) >= 0 ? x : false; } +TCPSocket._requestPort = function (port) { + // NOTE: only supports _automatic_ port assignment; we track (but not *check*) manually requested ports + if (port === 0) { + port = 1024; // NOTE: could optimize, e.g. by starting from last-granted or assuming only 7 sockets… + while (port in TCPSocket._portsUsed) ++port; + } + TCPSocket._portsUsed[port] = true; + return port; +}; function normalizeConnectArgs(args) { var options = {}; @@ -94,18 +153,24 @@ function normalizeConnectArgs(args) { TCPSocket.prototype.connect = function (/*options | [port], [host], [cb]*/) { var self = this; var args = normalizeConnectArgs(arguments); - var port = args[0].port; - var host = args[0].host; + var opts = args[0]; + if (opts.allowHalfOpen) console.warn("Ignoring allowHalfOpen option."); + var port = +opts.port; + var host = opts.host || "127.0.0.1"; var cb = args[1]; - self._port = port; - self._address = host; + self.remotePort = port; + self.remoteAddress = host; + // TODO: proper value for these? + self.localPort = 0; + self.localAddress = "0.0.0.0"; if (cb) { self.once('connect', cb); } setImmediate(function () { + self._restartTimeout(); if (isIP(host)) { doConnect(host); } else { @@ -113,29 +178,31 @@ TCPSocket.prototype.connect = function (/*options | [port], [host], [cb]*/) { if (err) { return self.emit('error', err); } - doConnect(ips[0]); + self._restartTimeout(); + self.remoteAddress = ips[0]; + doConnect(self.remoteAddress); }) } var retries = 0; function doConnect(ip) { - var unsplitIp = ip; - ip = ip.split('.').map(Number); + var addr = ip.split('.').map(Number); + addr = (addr[0] << 24) + (addr[1] << 16) + (addr[2] << 8) + addr[3]; - var ret = tm.tcp_connect(self.socket, ip[0], ip[1], ip[2], ip[3], Number(port)); + var ret = tm.tcp_connect(self.socket, addr, port); if (ret >= 1) { // we're not connected to the internet - throw new Error("Lost connection"); + return self.emit('error', new Error("Lost connection")); } if (ret < 0) { tm.tcp_close(self.socket); // -57 if (retries > 3) { - throw new Error('ENOENT Cannot connect to ' + ip.join('.') + ' Got: err'+ret); + return self.emit('error', new Error('ENOENT Cannot connect to ' + ip + ' Got: err'+ret)); } else { retries++; setTimeout(function(){ // wait for tcp socket to actually close self.socket = tm.tcp_open(); - doConnect(unsplitIp); + doConnect(ip); }, 100); return; } @@ -151,11 +218,11 @@ TCPSocket.prototype.connect = function (/*options | [port], [host], [cb]*/) { , ret = _[1] if (ret != 0) { if (ret == -517) { - throw new Error('CERT_HAS_EXPIRED'); + return self.emit('error', new Error('CERT_HAS_EXPIRED')); } else if (ret == -516) { - throw new Error('CERT_NOT_YET_VALID'); + return self.emit('error', new Error('CERT_NOT_YET_VALID')); } else { - throw new Error('Could not validate SSL request (error ' + ret + ')'); + return self.emit('error', new Error('Could not validate SSL request (error ' + ret + ')')); } } @@ -179,12 +246,13 @@ TCPSocket.prototype.connect = function (/*options | [port], [host], [cb]*/) { } if (!tls.checkServerIdentity(host, cert)) { - throw new Error('Hostname/IP doesn\'t match certificate\'s altnames'); + return self.emit('error', new Error('Hostname/IP doesn\'t match certificate\'s altnames')); } self._ssl = ssl; } + self._restartTimeout(); self.__listen(); self.connected = true; self.emit('connect'); @@ -201,6 +269,10 @@ TCPSocket.prototype.__listen = function () { var self = this; this.__listenid = setTimeout(function loop () { self.__listenid = null; + // ~HACK: set a watchdog to fire end event if not re-polled + var failsafeEnd = setImmediate(function () { + self.emit('end'); + }); if (self._sending) { return; @@ -227,28 +299,25 @@ TCPSocket.prototype.__listen = function () { } if (buf.length) { + self._restartTimeout(); self.push(buf); // TODO: stop polling if this returns false } self.__listenid = setTimeout(loop, 10); + clearImmediate(failsafeEnd); }, 10); }; -TCPSocket.prototype.address = function () { - return { - port: this._port, - family: 'IPv4', - address: this._address - }; -}; +TCPSocket.prototype.localFamily = 'IPv4'; +TCPSocket.prototype.remoteFamily = 'IPv4'; // Maximum packet size CC can handle. var WRITE_PACKET_SIZE = 1024; TCPSocket.prototype._write = function (buf, encoding, cb) { var self = this; - + if (!Buffer.isBuffer(buf)) { buf = new Buffer(buf); } @@ -292,7 +361,7 @@ TCPSocket.prototype.__send = function (cb) { } if (ret == null) { - throw new Error('Never sent data over socket'); + return self.emit('error', new Error('Never sent data over socket')); } else if (ret == -2) { // cc3000 ran out of buffers. wait until a buffer clears up to send this packet. setTimeout(function() { @@ -304,9 +373,9 @@ TCPSocket.prototype.__send = function (cb) { // EWOULDBLOCK / EAGAIN setTimeout(send, 100); } else if (ret < 0) { - // Error. - throw new Error(-ret); + return self.emit('error', new Error("Socket write failed unexpectedly! ("+ret+")")); } else { + self._restartTimeout(); // Next buffer. self._sending = false; self.__send(cb); @@ -328,6 +397,7 @@ TCPSocket.prototype.destroy = TCPSocket.prototype.close = function () { if (self.__listenid != null) { clearInterval(self.__listenid); self.__listenid = null + self.emit('end') } if (self.socket != null) { @@ -342,20 +412,30 @@ TCPSocket.prototype.destroy = TCPSocket.prototype.close = function () { }); }; -TCPSocket.prototype.setTimeout = function () { /* noop */ }; -TCPSocket.prototype.setNoDelay = function () { /* noop */ }; - -function connect (port, host, callback, _secure) { - if (_secure) { - ensureSSLCtx(); +TCPSocket.prototype.setTimeout = function (msecs, cb) { + this._timeout = msecs; + this._restartTimeout(); + if (cb) { + if (msecs) this.once('timeout', cb); + else this.removeListener('timeout', cb); // not documented, but node.js does this } +}; +TCPSocket.prototype._restartTimeout = function () { + var self = this; + clearTimeout(self._timeoutWatchdog); + this._timeoutWatchdog = (self._timeout) ? setTimeout(function () { + self.emit('timeout'); + }, self._timeout) : null; +} - var sock = tm.tcp_open(); - if (sock == -1) { - throw 'ENOENT: Cannot connect to new socket.' - } - var client = new TCPSocket(sock, _secure); +// NOTE: CC3K may not support? http://e2e.ti.com/support/wireless_connectivity/f/851/p/349461/1223801.aspx#1223801 +TCPSocket.prototype.setNoDelay = function (val) { + if (val) console.warn("Ignoring call to setNoDelay. TCP_NODELAY socket option not supported."); +}; + +function connect (port, host, callback, _secure) { + var client = new TCPSocket(null, _secure); client.connect(port, host, callback); return client; }; @@ -371,38 +451,78 @@ function TCPServer (socket) { util.inherits(TCPServer, TCPSocket); -TCPServer.prototype.listen = function (port, ip) { - var self = this; - var res = tm.tcp_listen(this.socket, port); - if (res < 0) { - throw "Error listening on TCP socket (port " + port + ", ip " + ip + ")" +TCPServer.prototype.listen = function (port, host, backlog, cb) { + if (typeof port === 'string') { + throw Error("UNIX sockets not supported"); } - - self._port = port; - self._address = ip; - + + if (typeof host === 'function') { + cb = host; + host = null; // NOTE: would be INADDR_ANY, but we ignore… + backlog = 511; // NOTE: also ignored + } else if (typeof host === 'number') { + backlog = host; + host = null; + } + + if (typeof backlog === 'function') { + backlog = 511; + cb = backlog; + } + + this.localPort = TCPSocket._requestPort(port); + this.localAddress = host || "0.0.0.0"; + if (cb) this.once('listening', cb); + + var self = this, + res = tm.tcp_listen(this.socket, this.localPort); + if (res < 0) setImmediate(function () { + self.emit('error', new Error("Listen on TCP socket failed ("+res+")")); + }); else setImmediate(function () { + self.emit('listening'); + poll(); + }); + function poll(){ - if(self.socket == null){ return false; } - var _ = tm.tcp_accept(self.socket) + // stop polling if we get closed + if (self.socket === null) return; + + var _ = tm.tcp_accept(self.socket) , client = _[0] - , ip = _[1]; + , addr = _[1] + , port = _[2]; if (client >= 0) { var clientsocket = new TCPSocket(client); clientsocket.connected = true; + clientsocket.localAddress = self.localAddress; // TODO: https://forums.tessel.io/t/get-ip-address-of-tessel-in-code/203 + clientsocket.localPort = self.localPort; + clientsocket.remoteAddress = [addr >>> 24, (addr >>> 16) & 0xFF, (addr >>> 8) & 0xFF, addr & 0xFF].join('.'); + clientsocket.remotePort = port; clientsocket.__listen(); - self.emit('socket', clientsocket); + self.emit('connection', clientsocket); } setTimeout(poll, 10); } - - poll(); }; -function createServer (onsocket) { +TCPServer.prototype.address = function () { + return { + port: this.localPort, + family: this.localFamily, + address: this.localAddress + }; +}; + +function createServer (opts, onsocket) { + if (typeof opts === 'function') { + onsocket = opts; + opts = null; + } + if (opts && opts.allowHalfOpen) console.warn("Ignoring allowHalfOpen option."); var server = new TCPServer(tm.tcp_open()); - onsocket && server.on('socket', onsocket); + onsocket && server.on('connection', onsocket); return server; }; @@ -412,6 +532,8 @@ function createServer (onsocket) { */ exports.isIP = isIP; +exports.isIPv4 = isIPv4; +exports.isIPv6 = isIPv6; exports.connect = exports.createConnection = connect; exports.createServer = createServer; exports.Socket = TCPSocket; diff --git a/src/posix/tm_net.c b/src/posix/tm_net.c index df4036bb..5b0f25d0 100644 --- a/src/posix/tm_net.c +++ b/src/posix/tm_net.c @@ -125,11 +125,11 @@ int tm_tcp_close (tm_socket_t sock) // return close(sock); } -int tm_tcp_connect (tm_socket_t sock, uint8_t ip0, uint8_t ip1, uint8_t ip2, uint8_t ip3, uint16_t port) +int tm_tcp_connect (tm_socket_t sock, uint32_t addr, uint16_t port) { struct sockaddr_in server; - server.sin_addr.s_addr = htonl(ip0 << 24 | ip1 << 16 | ip2 << 8 | ip3); // inet_addr("74.125.235.20"); server.sin_family = AF_INET; + server.sin_addr.s_addr = htonl(addr); server.sin_port = htons(port); // printf("server: %p, %d, %d\n", server.sin_addr.s_addr, server.sin_family, server.sin_port); return connect(sock, (struct sockaddr *) &server, sizeof(server)); @@ -166,19 +166,15 @@ int tm_tcp_listen (tm_socket_t sock, uint16_t port) { // CC3000_START; - struct sockaddr localSocketAddr; - localSocketAddr.sa_family = AF_INET; - localSocketAddr.sa_data[0] = (port & 0xFF00) >> 8; //ascii_to_char(0x01, 0x01); - localSocketAddr.sa_data[1] = (port & 0x00FF); //ascii_to_char(0x05, 0x0c); - localSocketAddr.sa_data[2] = 0; - localSocketAddr.sa_data[3] = 0; - localSocketAddr.sa_data[4] = 0; - localSocketAddr.sa_data[5] = 0; + struct sockaddr_in localSocketAddr; + localSocketAddr.sin_family = AF_INET; + localSocketAddr.sin_port = htons(port); + localSocketAddr.sin_addr.s_addr = 0; // Bind socket // TM_COMMAND('w', "Binding local socket..."); int sockStatus; - if ((sockStatus = bind(sock, &localSocketAddr, sizeof(struct sockaddr))) != 0) { + if ((sockStatus = bind(sock, (struct sockaddr *) &localSocketAddr, sizeof(localSocketAddr))) != 0) { // TM_COMMAND('w', "binding failed: %d", sockStatus); // CC3000_END; return -1; @@ -204,11 +200,12 @@ int tm_tcp_listen (tm_socket_t sock, uint16_t port) // Returns -1 on error or no socket. // Returns -2 on pending connection. // Returns >= 0 for socket descriptor. -tm_socket_t tm_tcp_accept (tm_socket_t sock, uint32_t *ip) +tm_socket_t tm_tcp_accept (tm_socket_t sock, uint32_t *addr, uint16_t *port) { - struct sockaddr addrClient; - socklen_t addrlen; - int res = accept(sock, &addrClient, &addrlen); - *ip = ((struct sockaddr_in *) &addrClient)->sin_addr.s_addr; + struct sockaddr_in addrClient; + socklen_t addrlen = sizeof(addrClient); + int res = accept(sock, (struct sockaddr *) &addrClient, &addrlen); + *addr = ntohl(addrClient.sin_addr.s_addr); + *port = ntohs(addrClient.sin_addr.s_addr); return res; } \ No newline at end of file diff --git a/src/tm.h b/src/tm.h index f778fe98..6d062ecd 100644 --- a/src/tm.h +++ b/src/tm.h @@ -123,12 +123,12 @@ int tm_udp_send (int ulSocket, uint8_t ip0, uint8_t ip1, uint8_t ip2, uint8_t ip tm_socket_t tm_tcp_open (); int tm_tcp_close (); -int tm_tcp_connect (tm_socket_t sock, uint8_t ip0, uint8_t ip1, uint8_t ip2, uint8_t ip3, uint16_t port); +int tm_tcp_connect (tm_socket_t sock, uint32_t addr, uint16_t port); int tm_tcp_write (tm_socket_t sock, const uint8_t *buf, size_t buflen); int tm_tcp_read (tm_socket_t sock, uint8_t *buf, size_t buflen); int tm_tcp_readable (tm_socket_t sock); int tm_tcp_listen (tm_socket_t sock, uint16_t port); -tm_socket_t tm_tcp_accept (tm_socket_t sock, uint32_t *ip); +tm_socket_t tm_tcp_accept (tm_socket_t sock, uint32_t *addr, uint16_t *port); // DNS diff --git a/test/suite/net.js b/test/suite/net.js new file mode 100644 index 00000000..2075ce1a --- /dev/null +++ b/test/suite/net.js @@ -0,0 +1,167 @@ +// NOTE: see https://github.com/tcr/tinytap/issues/4 — not all tests get applied?! +var test = require('tinytap'), + net = require('net'); + +test('addresses', function (t) { + // API checks + t.ok(net.isIP); + t.ok(net.isIPv4); + t.ok(net.isIPv6); + + // some samples from http://publib.boulder.ibm.com/infocenter/ts3500tl/v1r0/index.jsp?topic=%2Fcom.ibm.storage.ts3500.doc%2Fopg_3584_IPv4_IPv6_addresses.html + // c.f. http://tools.ietf.org/id/draft-main-ipaddr-text-rep-02.txt for some grammar discussion + + var validIPv4 = ["0.0.0.0", "127.0.0.1", "255.255.255.255", "1.2.3.4", "101.203.111.200"/*, "01.102.103.104"*/], + validIPv6 = [ + "2001:db8:3333:4444:5555:6666:7777:8888", "2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF", + "::", "2001:db8::", "::1234:5678", "2001:db8::1234:5678", + "2001:0db8:0001:0000:0000:0ab9:C0A8:0102", "2001:db8:1::ab9:C0A8:102", + "2001:db8:3333:4444:5555:6666:1.2.3.4", + "::11.22.33.44", "2001:db8::123.123.123.123", "::1234:5678:91.123.4.56", "::1234:5678:1.2.3.4", "2001:db8::1234:5678:5.6.7.8", + ], + totalBunk = [ + "255.255.255.256", "0xFF.0xFF.0xFF.0xFF", "0.0.A.0", "-1.0.0.0", "123.45.67.89zzz", "01.102.103.104", + "::11.22.33.044", "::255.255.255.256", "::FG", "hello world", void 0, 42, "" + ]; + + // isIP + validIPv4.forEach(function (v) { + t.equal(net.isIP(v), 4, v); + }); + validIPv6.forEach(function (v) { + t.equal(net.isIP(v), 6, v); + }); + totalBunk.forEach(function (v) { + t.equal(net.isIP(v), 0, v); + }); + + // isIPv4 + validIPv4.forEach(function (v) { + t.equal(net.isIPv4(v), true, v); + }); + validIPv6.forEach(function (v) { + t.equal(net.isIPv4(v), false, v); + }); + totalBunk.forEach(function (v) { + t.equal(net.isIPv4(v), false, v); + }); + + // isIPv6 + validIPv4.forEach(function (v) { + t.equal(net.isIPv6(v), false, v); + }); + validIPv6.forEach(function (v) { + t.equal(net.isIPv6(v), true, v); + }); + totalBunk.forEach(function (v) { + t.equal(net.isIPv6(v), false, v); + }); + + t.end(); +}); + +test('client-basic', function (t) { + // API checks + t.ok(net.createConnection, "method available"); + t.ok(net.connect, "method available"); + + // see http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener + + // connects + var client = net.connect(80, "ipcalf.com", function () { + t.pass("callback called"); + }); + t.ok(client instanceof net.Socket, "returned socket"); + client.on('connect', function () { + t.pass("socket connected"); + client.write("GET / HTTP/1.1\nHost: ipcalf.com\nAccept: text/plain\n\n"); + }); + client.on('error', function () { + t.fail("socket error"); + }); + + // lives/dies + client.on('data', function (d) { + t.equal(d.slice(0,8).toString(), "HTTP/1.1", "got response"); + client.end(); + }); + client.on('end', function () { + t.ok(true, "socket closed"); + t.end(); + }); +}); + +test('server-basic', function (t) { + // API checks + t.ok(net.createServer, "method available"); + + // see http://nodejs.org/api/net.html#net_net_createserver_options_connectionlistener + + // listening + var server = net.createServer(function (c) { + t.pass("connection callback called"); + }); + server.listen(0, function () { + t.pass("listening callback called"); + }); + server.on('listening', function () { + t.pass("got listening event"); + t.ok(server.address().port, "port assigned"); + testConnection(server.address().port); + }); + + // connecting + server.on('connection', function (c) { + t.ok(c instanceof net.Socket, "got connection"); + c.on('end', function () { + t.pass("disconnected"); + }); + c.end("«§»"); + }); + function testConnection(port) { + net.connect(port).on('data', function (d) { + t.equal(d.toString(), "«§»"); + t.end(); + }); + } +}); + +test('server-binding', function (t) { + var firstServer = net.createServer(), + otherServer = net.createServer(), + conflicting = net.createServer(); + firstServer.listen(0, function () { + var firstPort = firstServer.address().port; + t.ok(firstPort, "assigned a port"); + otherServer.listen(0, function () { + t.notEqual(otherServer.address().port, firstPort, "assigned a different port"); + }); + conflicting.listen(firstPort, function () { + t.fail("this should not be called!"); + }); + conflicting.on('error', function (e) { + t.ok(e, "got error as expected"); + t.end(); + }); + }); +}); + +test('client-errors', function (t) { + net.connect(1, "0.0.0.0").on('error', function (e) { + t.ok(e, "got expected error"); + t.end(); + }); +}); + +test('client-timeout', function (t) { + var client = net.connect(80, "ipcalf.com", function () { + client.setTimeout(100, function () { + t.pass("timeout callback called"); + }); + client.on('timeout', function () { + t.pass("timeout event fired"); + client.destroy(); + t.end(); + }); + }); +});