Backed out changeset f594e6f00208 (bug 1940883) for causing crashes in bug 1941164.
[gecko.git] / dom / network / tests / test_tcpsocket_client_and_server_basics.js
blob32b290a9494dd97f801aa55b91c4503ca39272b6
1 "use strict";
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);
18   newArr.set(a, 0);
19   newArr.set(b, a.length);
20   return newArr;
23 function assertUint8ArraysEqual(a, b, comparingWhat) {
24   if (a.length !== b.length) {
25     ok(
26       false,
27       comparingWhat +
28         " arrays do not have the same length; " +
29         a.length +
30         " versus " +
31         b.length
32     );
33     return;
34   }
35   for (let i = 0; i < a.length; i++) {
36     if (a[i] !== b[i]) {
37       ok(
38         false,
39         comparingWhat +
40           " arrays differ at index " +
41           i +
42           a[i] +
43           " versus " +
44           b[i]
45       );
46       return;
47     }
48   }
49   ok(true, comparingWhat + " arrays were equivalent.");
52 /**
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
56  * invoked.
57  */
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;
69     } else {
70       receivedEvents.push(event);
71     }
72   };
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;
85     }
86   };
87   socket.ondata = function (event) {
88     dump(
89       "(" +
90         socketType +
91         " event: " +
92         event.type +
93         " length: " +
94         event.data.byteLength +
95         ")\n"
96     );
97     ok(
98       socketCompartmentInstanceOfArrayBuffer(event.data),
99       "payload is ArrayBuffer"
100     );
101     var arr = new Uint8Array(event.data);
102     if (receivedData === null) {
103       receivedData = arr;
104     } else {
105       receivedData = concatUint8Arrays(receivedData, arr);
106     }
107     if (wantDataLength !== null && receivedData.length >= wantDataLength) {
108       pendingResolve(receivedData);
109       pendingResolve = null;
110       receivedData = null;
111       wantDataLength = null;
112     }
113   };
115   return {
116     /**
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
120      * the event loop).
121      */
122     waitForEvent() {
123       if (pendingResolve) {
124         throw new Error("only one wait allowed at a time.");
125       }
127       if (receivedEvents.length) {
128         return Promise.resolve(receivedEvents.shift());
129       }
131       dump("(" + socketType + " waiting for event)\n");
132       return new Promise(function (resolve) {
133         pendingResolve = resolve;
134       });
135     },
136     /**
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.
141      */
142     waitForDataWithAtLeastLength(length) {
143       if (pendingResolve) {
144         throw new Error("only one wait allowed at a time.");
145       }
146       if (receivedData && receivedData.length >= length) {
147         let promise = Promise.resolve(receivedData);
148         receivedData = null;
149         return promise;
150       }
151       dump("(" + socketType + " waiting for " + length + " bytes)\n");
152       return new Promise(function (resolve) {
153         pendingResolve = resolve;
154         wantDataLength = length;
155       });
156     },
157     waitForAnyDataAndClose() {
158       if (pendingResolve) {
159         throw new Error("only one wait allowed at a time.");
160       }
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;
168       });
169     },
170   };
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.
178  */
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
186       // after this.
187       listeningServer.onconnect = function () {
188         ok(false, "Received a connection when not expecting one.");
189       };
190       ok(true, "Listening server accepted socket");
191       resolve({
192         socket: event.socket,
193         queue: listenForEventsOnSocket(event.socket, "server"),
194       });
195     };
196   });
199 function defer() {
200   var deferred = {};
201   deferred.promise = new Promise(function (resolve, reject) {
202     deferred.resolve = resolve;
203     deferred.reject = reject;
204   });
205   return deferred;
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
211   // test was using.
212   let serverPort = 8085;
214   // - Start up a listening socket.
215   let listeningServer = createServer(
216     serverPort,
217     { binaryType: "arraybuffer" },
218     SERVER_BACKLOG
219   );
221   let connectedPromise = waitForConnection(listeningServer);
223   // -- Open a connection to the server
224   let clientSocket = createSocket("127.0.0.1", serverPort, {
225     binaryType: "arraybuffer",
226   });
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;
243   }
244   is(
245     clientSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length),
246     true,
247     "Client sending less than 64k, buffer should not be full."
248   );
250   let serverReceived = await serverQueue.waitForDataWithAtLeastLength(256);
251   assertUint8ArraysEqual(
252     serverReceived,
253     smallUint8Array,
254     "Server received/client sent"
255   );
257   // - Send data from server to client
258   // (But not so much we cross the drain threshold.)
259   is(
260     serverSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length),
261     true,
262     "Server sending less than 64k, buffer should not be full."
263   );
265   let clientReceived = await clientQueue.waitForDataWithAtLeastLength(256);
266   assertUint8ArraysEqual(
267     clientReceived,
268     smallUint8Array,
269     "Client received/server sent"
270   );
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.)
275   is(
276     clientSocket.send(smallUint8Array.buffer, 0, 7),
277     true,
278     "Client sending less than 64k, buffer should not be full."
279   );
280   is(
281     clientSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7),
282     true,
283     "Client sending less than 64k, buffer should not be full."
284   );
286   serverReceived = await serverQueue.waitForDataWithAtLeastLength(256);
287   assertUint8ArraysEqual(
288     serverReceived,
289     smallUint8Array,
290     "Server received/client sent"
291   );
293   // - Send data from server to client
294   // (But not so much we cross the drain threshold.)
295   is(
296     serverSocket.send(smallUint8Array.buffer, 0, 7),
297     true,
298     "Server sending less than 64k, buffer should not be full."
299   );
300   is(
301     serverSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7),
302     true,
303     "Server sending less than 64k, buffer should not be full."
304   );
306   clientReceived = await clientQueue.waitForDataWithAtLeastLength(256);
307   assertUint8ArraysEqual(
308     clientReceived,
309     smallUint8Array,
310     "Client received/server sent"
311   );
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;
318   }
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;
323   is(
324     clientSocket.send(bigUint8Array.buffer, 0, lengthOfChunk1),
325     true,
326     "Client sending chunk1 should not result in the buffer being full."
327   );
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;
334     is(
335       clientSocket.send(bigUint8Array.buffer, offset, bigUint8Array.length),
336       false,
337       "Client sending more than 64k should result in the buffer being full."
338     );
339     is(
340       (await clientQueue.waitForEvent()).type,
341       "drain",
342       "The drain event should fire after a large send that indicated full."
343     );
345     serverReceived = await serverQueue.waitForDataWithAtLeastLength(
346       bigUint8Array.length
347     );
348     assertUint8ArraysEqual(
349       serverReceived,
350       bigUint8Array,
351       "server received/client sent"
352     );
354     if (iSend == 0) {
355       is(
356         serverSocket.send(bigUint8Array.buffer, 0, lengthOfChunk1),
357         true,
358         "Server sending chunk1 should not result in the buffer being full."
359       );
360     }
361     // - Send "big" data from the server to the client
362     is(
363       serverSocket.send(bigUint8Array.buffer, offset, bigUint8Array.length),
364       false,
365       "Server sending more than 64k should result in the buffer being full."
366     );
367     is(
368       (await serverQueue.waitForEvent()).type,
369       "drain",
370       "The drain event should fire after a large send that indicated full."
371     );
373     clientReceived = await clientQueue.waitForDataWithAtLeastLength(
374       bigUint8Array.length
375     );
376     assertUint8ArraysEqual(
377       clientReceived,
378       bigUint8Array,
379       "client received/server sent"
380     );
381   }
383   // -- Server closes the connection
384   serverSocket.close();
385   is(
386     serverSocket.readyState,
387     "closing",
388     "readyState should be closing immediately after calling close"
389   );
391   is(
392     (await clientQueue.waitForEvent()).type,
393     "close",
394     "The client should get a close event when the server closes."
395   );
396   is(
397     clientSocket.readyState,
398     "closed",
399     "client readyState should be closed after close event"
400   );
401   is(
402     (await serverQueue.waitForEvent()).type,
403     "close",
404     "The server should get a close event when it closes itself."
405   );
406   is(
407     serverSocket.readyState,
408     "closed",
409     "server readyState should be closed after close event"
410   );
412   // -- Re-establish connection
413   connectedPromise = waitForConnection(listeningServer);
414   clientSocket = createSocket("127.0.0.1", serverPort, {
415     binaryType: "arraybuffer",
416   });
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();
427   is(
428     clientSocket.readyState,
429     "closing",
430     "client readyState should be losing immediately after calling close"
431   );
433   is(
434     (await clientQueue.waitForEvent()).type,
435     "close",
436     "The client should get a close event when it closes itself."
437   );
438   is(
439     clientSocket.readyState,
440     "closed",
441     "client readyState should be closed after the close event is received"
442   );
443   is(
444     (await serverQueue.waitForEvent()).type,
445     "close",
446     "The server should get a close event when the client closes."
447   );
448   is(
449     serverSocket.readyState,
450     "closed",
451     "server readyState should be closed after the close event is received"
452   );
454   // -- Re-establish connection
455   connectedPromise = waitForConnection(listeningServer);
456   clientSocket = createSocket("127.0.0.1", serverPort, {
457     binaryType: "arraybuffer",
458   });
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.
469   is(
470     clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length),
471     false,
472     "Client sending more than 64k should result in the buffer being full."
473   );
474   clientSocket.close();
475   // The drain will still fire
476   is(
477     (await clientQueue.waitForEvent()).type,
478     "drain",
479     "The drain event should fire after a large send that returned true."
480   );
481   // Then we'll get a close
482   is(
483     (await clientQueue.waitForEvent()).type,
484     "close",
485     "The close event should fire after the drain event."
486   );
488   // The server will get its data
489   serverReceived = await serverQueue.waitForDataWithAtLeastLength(
490     bigUint8Array.length
491   );
492   assertUint8ArraysEqual(
493     serverReceived,
494     bigUint8Array,
495     "server received/client sent"
496   );
497   // And a close.
498   is(
499     (await serverQueue.waitForEvent()).type,
500     "close",
501     "The drain event should fire after a large send that returned true."
502   );
504   // -- Re-establish connection
505   connectedPromise = waitForConnection(listeningServer);
506   clientSocket = createSocket("127.0.0.1", serverPort, {
507     binaryType: "string",
508   });
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;
521   is(
522     clientSocket.send(bigUint8Array),
523     true,
524     "Client sending a large non-string should only send a small string."
525   );
526   clientSocket.close();
527   // The server will get its data
528   serverReceived = await serverQueue.waitForDataWithAtLeastLength(
529     bigUint8Array.toString().length
530   );
531   // Then we'll get a close
532   is(
533     (await clientQueue.waitForEvent()).type,
534     "close",
535     "The close event should fire after the drain event."
536   );
538   // -- Re-establish connection (Test for Close Immediately)
539   connectedPromise = waitForConnection(listeningServer);
540   clientSocket = createSocket("127.0.0.1", serverPort, {
541     binaryType: "arraybuffer",
542   });
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.
552   is(
553     clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length),
554     false,
555     "Server sending more than 64k should result in the buffer being full."
556   );
557   is(
558     clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length),
559     false,
560     "Server sending more than 64k should result in the buffer being full."
561   );
562   clientSocket.closeImmediately();
564   serverReceived = await serverQueue.waitForAnyDataAndClose();
566   is(
567     serverReceived.length < 2 * bigUint8Array.length,
568     true,
569     "Received array length less than sent array length"
570   );
572   // -- Close the listening server (and try to connect)
573   // We want to verify that the server actually closes / stops listening when
574   // we tell it to.
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",
582     });
583     clientQueue = listenForEventsOnSocket(clientSocket, "client");
584     is((await clientQueue.waitForEvent()).type, "error", "fail to connect");
585     is(
586       clientSocket.readyState,
587       "closed",
588       "client readyState should be closed after the failure to connect"
589     );
590   }
593 add_task(test_basics);
596  * Test that TCPSocket works with ipv6 address.
597  */
598 add_task(async function test_ipv6() {
599   const { HttpServer } = ChromeUtils.importESModule(
600     "resource://testing-common/httpd.sys.mjs"
601   );
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");
609     deferred.resolve();
610   };
611   clientSocket.onerror = () => {
612     ok(false, "Connect to ipv6 address failed");
613     deferred.reject();
614   };
615   await deferred.promise;
616   await httpServer.stop();