3 // These are defined in test_tcpsocket_client_and_server_basics.html
4 /* global createServer, createSocket, socketCompartmentInstanceOfArrayBuffer */
6 // Bug 788960 and later bug 1329245 have taught us that attempting to connect to
7 // a port that is not listening or is no longer listening fails to consistently
8 // result in the error (or any) event we expect on Darwin/OSX/"OS X".
9 const isOSX = Services.appinfo.OS === "Darwin";
10 const testConnectingToNonListeningPort = !isOSX;
12 const SERVER_BACKLOG = -1;
14 const SOCKET_EVENTS = ["open", "data", "drain", "error", "close"];
16 function concatUint8Arrays(a, b) {
17 let newArr = new Uint8Array(a.length + b.length);
19 newArr.set(b, a.length);
23 function assertUint8ArraysEqual(a, b, comparingWhat) {
24 if (a.length !== b.length) {
28 " arrays do not have the same length; " +
35 for (let i = 0; i < a.length; i++) {
40 " arrays differ at index " +
49 ok(true, comparingWhat + " arrays were equivalent.");
53 * Helper method to add event listeners to a socket and provide two Promise-returning
54 * helpers (see below for docs on them). This *must* be called during the turn of
55 * the event loop where TCPSocket's constructor is called or the onconnect method is being
58 function listenForEventsOnSocket(socket, socketType) {
59 let wantDataLength = null;
60 let wantDataAndClose = false;
61 let pendingResolve = null;
62 let receivedEvents = [];
63 let receivedData = null;
64 let handleGenericEvent = function (event) {
65 dump("(" + socketType + " event: " + event.type + ")\n");
66 if (pendingResolve && wantDataLength === null) {
67 pendingResolve(event);
68 pendingResolve = null;
70 receivedEvents.push(event);
74 socket.onopen = handleGenericEvent;
75 socket.ondrain = handleGenericEvent;
76 socket.onerror = handleGenericEvent;
77 socket.onclose = function (event) {
78 if (!wantDataAndClose) {
79 handleGenericEvent(event);
80 } else if (pendingResolve) {
81 dump("(" + socketType + " event: close)\n");
82 pendingResolve(receivedData);
83 pendingResolve = null;
84 wantDataAndClose = false;
87 socket.ondata = function (event) {
94 event.data.byteLength +
98 socketCompartmentInstanceOfArrayBuffer(event.data),
99 "payload is ArrayBuffer"
101 var arr = new Uint8Array(event.data);
102 if (receivedData === null) {
105 receivedData = concatUint8Arrays(receivedData, arr);
107 if (wantDataLength !== null && receivedData.length >= wantDataLength) {
108 pendingResolve(receivedData);
109 pendingResolve = null;
111 wantDataLength = null;
117 * Return a Promise that will be resolved with the next (non-data) event
118 * received by the socket. If there are queued events, the Promise will
119 * be immediately resolved (but you won't see that until a future turn of
123 if (pendingResolve) {
124 throw new Error("only one wait allowed at a time.");
127 if (receivedEvents.length) {
128 return Promise.resolve(receivedEvents.shift());
131 dump("(" + socketType + " waiting for event)\n");
132 return new Promise(function (resolve) {
133 pendingResolve = resolve;
137 * Return a Promise that will be resolved with a Uint8Array of at least the
138 * given length. We buffer / accumulate received data until we have enough
139 * data. Data is buffered even before you call this method, so be sure to
140 * explicitly wait for any and all data sent by the other side.
142 waitForDataWithAtLeastLength(length) {
143 if (pendingResolve) {
144 throw new Error("only one wait allowed at a time.");
146 if (receivedData && receivedData.length >= length) {
147 let promise = Promise.resolve(receivedData);
151 dump("(" + socketType + " waiting for " + length + " bytes)\n");
152 return new Promise(function (resolve) {
153 pendingResolve = resolve;
154 wantDataLength = length;
157 waitForAnyDataAndClose() {
158 if (pendingResolve) {
159 throw new Error("only one wait allowed at a time.");
162 return new Promise(function (resolve) {
163 pendingResolve = resolve;
164 // we may receive no data before getting close, in which case we want to
165 // return an empty array
166 receivedData = new Uint8Array();
167 wantDataAndClose = true;
174 * Return a promise that is resolved when the server receives a connection. The
175 * promise is resolved with { socket, queue } where `queue` is the result of
176 * calling listenForEventsOnSocket(socket). This must be done because we need
177 * to add the event listener during the connection.
179 function waitForConnection(listeningServer) {
180 return new Promise(function (resolve) {
181 // Because of the event model of sockets, we can't use the
182 // listenForEventsOnSocket mechanism; we need to hook up listeners during
183 // the connect event.
184 listeningServer.onconnect = function (event) {
185 // Clobber the listener to get upset if it receives any more connections
187 listeningServer.onconnect = function () {
188 ok(false, "Received a connection when not expecting one.");
190 ok(true, "Listening server accepted socket");
192 socket: event.socket,
193 queue: listenForEventsOnSocket(event.socket, "server"),
201 deferred.promise = new Promise(function (resolve, reject) {
202 deferred.resolve = resolve;
203 deferred.reject = reject;
208 async function test_basics() {
209 // See bug 903830; in e10s mode we never get to find out the localPort if we
210 // let it pick a free port by choosing 0. This is the same port the xpcshell
212 let serverPort = 8085;
214 // - Start up a listening socket.
215 let listeningServer = createServer(
217 { binaryType: "arraybuffer" },
221 let connectedPromise = waitForConnection(listeningServer);
223 // -- Open a connection to the server
224 let clientSocket = createSocket("127.0.0.1", serverPort, {
225 binaryType: "arraybuffer",
227 let clientQueue = listenForEventsOnSocket(clientSocket, "client");
229 // (the client connects)
230 is((await clientQueue.waitForEvent()).type, "open", "got open event");
231 is(clientSocket.readyState, "open", "client readyState is open");
233 // (the server connected)
234 let { socket: serverSocket, queue: serverQueue } = await connectedPromise;
235 is(serverSocket.readyState, "open", "server readyState is open");
237 // -- Simple send / receive
238 // - Send data from client to server
239 // (But not so much we cross the drain threshold.)
240 let smallUint8Array = new Uint8Array(256);
241 for (let i = 0; i < smallUint8Array.length; i++) {
242 smallUint8Array[i] = i;
245 clientSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length),
247 "Client sending less than 64k, buffer should not be full."
250 let serverReceived = await serverQueue.waitForDataWithAtLeastLength(256);
251 assertUint8ArraysEqual(
254 "Server received/client sent"
257 // - Send data from server to client
258 // (But not so much we cross the drain threshold.)
260 serverSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length),
262 "Server sending less than 64k, buffer should not be full."
265 let clientReceived = await clientQueue.waitForDataWithAtLeastLength(256);
266 assertUint8ArraysEqual(
269 "Client received/server sent"
272 // -- Perform sending multiple times with different buffer slices
273 // - Send data from client to server
274 // (But not so much we cross the drain threshold.)
276 clientSocket.send(smallUint8Array.buffer, 0, 7),
278 "Client sending less than 64k, buffer should not be full."
281 clientSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7),
283 "Client sending less than 64k, buffer should not be full."
286 serverReceived = await serverQueue.waitForDataWithAtLeastLength(256);
287 assertUint8ArraysEqual(
290 "Server received/client sent"
293 // - Send data from server to client
294 // (But not so much we cross the drain threshold.)
296 serverSocket.send(smallUint8Array.buffer, 0, 7),
298 "Server sending less than 64k, buffer should not be full."
301 serverSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7),
303 "Server sending less than 64k, buffer should not be full."
306 clientReceived = await clientQueue.waitForDataWithAtLeastLength(256);
307 assertUint8ArraysEqual(
310 "Client received/server sent"
313 // -- Send "big" data in both directions
314 // (Enough to cross the buffering/drain threshold; 64KiB)
315 let bigUint8Array = new Uint8Array(65536 + 3);
316 for (let i = 0; i < bigUint8Array.length; i++) {
317 bigUint8Array[i] = i % 256;
319 // This can be anything from 1 to 65536. The idea is spliting and sending
320 // bigUint8Array in two chunks should trigger ondrain the same as sending
321 // bigUint8Array in one chunk.
322 let lengthOfChunk1 = 65536;
324 clientSocket.send(bigUint8Array.buffer, 0, lengthOfChunk1),
326 "Client sending chunk1 should not result in the buffer being full."
328 // Do this twice so we have confidence that the 'drain' event machinery
329 // doesn't break after the first use. The first time we send bigUint8Array in
330 // two chunks, the second time we send bigUint8Array in one chunk.
331 for (let iSend = 0; iSend < 2; iSend++) {
332 // - Send "big" data from the client to the server
333 let offset = iSend == 0 ? lengthOfChunk1 : 0;
335 clientSocket.send(bigUint8Array.buffer, offset, bigUint8Array.length),
337 "Client sending more than 64k should result in the buffer being full."
340 (await clientQueue.waitForEvent()).type,
342 "The drain event should fire after a large send that indicated full."
345 serverReceived = await serverQueue.waitForDataWithAtLeastLength(
348 assertUint8ArraysEqual(
351 "server received/client sent"
356 serverSocket.send(bigUint8Array.buffer, 0, lengthOfChunk1),
358 "Server sending chunk1 should not result in the buffer being full."
361 // - Send "big" data from the server to the client
363 serverSocket.send(bigUint8Array.buffer, offset, bigUint8Array.length),
365 "Server sending more than 64k should result in the buffer being full."
368 (await serverQueue.waitForEvent()).type,
370 "The drain event should fire after a large send that indicated full."
373 clientReceived = await clientQueue.waitForDataWithAtLeastLength(
376 assertUint8ArraysEqual(
379 "client received/server sent"
383 // -- Server closes the connection
384 serverSocket.close();
386 serverSocket.readyState,
388 "readyState should be closing immediately after calling close"
392 (await clientQueue.waitForEvent()).type,
394 "The client should get a close event when the server closes."
397 clientSocket.readyState,
399 "client readyState should be closed after close event"
402 (await serverQueue.waitForEvent()).type,
404 "The server should get a close event when it closes itself."
407 serverSocket.readyState,
409 "server readyState should be closed after close event"
412 // -- Re-establish connection
413 connectedPromise = waitForConnection(listeningServer);
414 clientSocket = createSocket("127.0.0.1", serverPort, {
415 binaryType: "arraybuffer",
417 clientQueue = listenForEventsOnSocket(clientSocket, "client");
418 is((await clientQueue.waitForEvent()).type, "open", "got open event");
420 let connectedResult = await connectedPromise;
421 // destructuring assignment is not yet ES6 compliant, must manually unpack
422 serverSocket = connectedResult.socket;
423 serverQueue = connectedResult.queue;
425 // -- Client closes the connection
426 clientSocket.close();
428 clientSocket.readyState,
430 "client readyState should be losing immediately after calling close"
434 (await clientQueue.waitForEvent()).type,
436 "The client should get a close event when it closes itself."
439 clientSocket.readyState,
441 "client readyState should be closed after the close event is received"
444 (await serverQueue.waitForEvent()).type,
446 "The server should get a close event when the client closes."
449 serverSocket.readyState,
451 "server readyState should be closed after the close event is received"
454 // -- Re-establish connection
455 connectedPromise = waitForConnection(listeningServer);
456 clientSocket = createSocket("127.0.0.1", serverPort, {
457 binaryType: "arraybuffer",
459 clientQueue = listenForEventsOnSocket(clientSocket, "client");
460 is((await clientQueue.waitForEvent()).type, "open", "got open event");
462 connectedResult = await connectedPromise;
463 // destructuring assignment is not yet ES6 compliant, must manually unpack
464 serverSocket = connectedResult.socket;
465 serverQueue = connectedResult.queue;
467 // -- Call close after enqueueing a lot of data, make sure it goes through.
468 // We'll have the client send and close.
470 clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length),
472 "Client sending more than 64k should result in the buffer being full."
474 clientSocket.close();
475 // The drain will still fire
477 (await clientQueue.waitForEvent()).type,
479 "The drain event should fire after a large send that returned true."
481 // Then we'll get a close
483 (await clientQueue.waitForEvent()).type,
485 "The close event should fire after the drain event."
488 // The server will get its data
489 serverReceived = await serverQueue.waitForDataWithAtLeastLength(
492 assertUint8ArraysEqual(
495 "server received/client sent"
499 (await serverQueue.waitForEvent()).type,
501 "The drain event should fire after a large send that returned true."
504 // -- Re-establish connection
505 connectedPromise = waitForConnection(listeningServer);
506 clientSocket = createSocket("127.0.0.1", serverPort, {
507 binaryType: "string",
509 clientQueue = listenForEventsOnSocket(clientSocket, "client");
510 is((await clientQueue.waitForEvent()).type, "open", "got open event");
512 connectedResult = await connectedPromise;
513 // destructuring assignment is not yet ES6 compliant, must manually unpack
514 serverSocket = connectedResult.socket;
515 serverQueue = connectedResult.queue;
517 // -- Attempt to send non-string data.
518 // Restore the original behavior by replacing toString with
519 // Object.prototype.toString. (bug 1121938)
520 bigUint8Array.toString = Object.prototype.toString;
522 clientSocket.send(bigUint8Array),
524 "Client sending a large non-string should only send a small string."
526 clientSocket.close();
527 // The server will get its data
528 serverReceived = await serverQueue.waitForDataWithAtLeastLength(
529 bigUint8Array.toString().length
531 // Then we'll get a close
533 (await clientQueue.waitForEvent()).type,
535 "The close event should fire after the drain event."
538 // -- Re-establish connection (Test for Close Immediately)
539 connectedPromise = waitForConnection(listeningServer);
540 clientSocket = createSocket("127.0.0.1", serverPort, {
541 binaryType: "arraybuffer",
543 clientQueue = listenForEventsOnSocket(clientSocket, "client");
544 is((await clientQueue.waitForEvent()).type, "open", "got open event");
546 connectedResult = await connectedPromise;
547 // destructuring assignment is not yet ES6 compliant, must manually unpack
548 serverSocket = connectedResult.socket;
549 serverQueue = connectedResult.queue;
551 // -- Attempt to send two non-string data.
553 clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length),
555 "Server sending more than 64k should result in the buffer being full."
558 clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length),
560 "Server sending more than 64k should result in the buffer being full."
562 clientSocket.closeImmediately();
564 serverReceived = await serverQueue.waitForAnyDataAndClose();
567 serverReceived.length < 2 * bigUint8Array.length,
569 "Received array length less than sent array length"
572 // -- Close the listening server (and try to connect)
573 // We want to verify that the server actually closes / stops listening when
575 listeningServer.close();
577 // (We don't run this check on OS X where it's flakey; see definition up top.)
578 if (testConnectingToNonListeningPort) {
579 // - try and connect, get an error
580 clientSocket = createSocket("127.0.0.1", serverPort, {
581 binaryType: "arraybuffer",
583 clientQueue = listenForEventsOnSocket(clientSocket, "client");
584 is((await clientQueue.waitForEvent()).type, "error", "fail to connect");
586 clientSocket.readyState,
588 "client readyState should be closed after the failure to connect"
593 add_task(test_basics);
596 * Test that TCPSocket works with ipv6 address.
598 add_task(async function test_ipv6() {
599 const { HttpServer } = ChromeUtils.importESModule(
600 "resource://testing-common/httpd.sys.mjs"
602 let deferred = defer();
603 let httpServer = new HttpServer();
604 httpServer.start_ipv6(-1);
606 let clientSocket = new TCPSocket("::1", httpServer.identity.primaryPort);
607 clientSocket.onopen = () => {
608 ok(true, "Connect to ipv6 address succeeded");
611 clientSocket.onerror = () => {
612 ok(false, "Connect to ipv6 address failed");
615 await deferred.promise;
616 await httpServer.stop();