tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_tcpsocket_client_and_server_basics.js (19109B)


      1 "use strict";
      2 
      3 // These are defined in test_tcpsocket_client_and_server_basics.html
      4 /* global createServer, createSocket, socketCompartmentInstanceOfArrayBuffer */
      5 
      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;
     11 
     12 const SERVER_BACKLOG = -1;
     13 
     14 const SOCKET_EVENTS = ["open", "data", "drain", "error", "close"];
     15 
     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;
     21 }
     22 
     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.");
     50 }
     51 
     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  };
     73 
     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  };
    114 
    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      }
    126 
    127      if (receivedEvents.length) {
    128        return Promise.resolve(receivedEvents.shift());
    129      }
    130 
    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      }
    161 
    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  };
    171 }
    172 
    173 /**
    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  });
    197 }
    198 
    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;
    206 }
    207 
    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;
    213 
    214  // - Start up a listening socket.
    215  let listeningServer = createServer(
    216    serverPort,
    217    { binaryType: "arraybuffer" },
    218    SERVER_BACKLOG
    219  );
    220 
    221  let connectedPromise = waitForConnection(listeningServer);
    222 
    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");
    228 
    229  // (the client connects)
    230  is((await clientQueue.waitForEvent()).type, "open", "got open event");
    231  is(clientSocket.readyState, "open", "client readyState is open");
    232 
    233  // (the server connected)
    234  let { socket: serverSocket, queue: serverQueue } = await connectedPromise;
    235  is(serverSocket.readyState, "open", "server readyState is open");
    236 
    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  );
    249 
    250  let serverReceived = await serverQueue.waitForDataWithAtLeastLength(256);
    251  assertUint8ArraysEqual(
    252    serverReceived,
    253    smallUint8Array,
    254    "Server received/client sent"
    255  );
    256 
    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  );
    264 
    265  let clientReceived = await clientQueue.waitForDataWithAtLeastLength(256);
    266  assertUint8ArraysEqual(
    267    clientReceived,
    268    smallUint8Array,
    269    "Client received/server sent"
    270  );
    271 
    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  );
    285 
    286  serverReceived = await serverQueue.waitForDataWithAtLeastLength(256);
    287  assertUint8ArraysEqual(
    288    serverReceived,
    289    smallUint8Array,
    290    "Server received/client sent"
    291  );
    292 
    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  );
    305 
    306  clientReceived = await clientQueue.waitForDataWithAtLeastLength(256);
    307  assertUint8ArraysEqual(
    308    clientReceived,
    309    smallUint8Array,
    310    "Client received/server sent"
    311  );
    312 
    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    );
    344 
    345    serverReceived = await serverQueue.waitForDataWithAtLeastLength(
    346      bigUint8Array.length
    347    );
    348    assertUint8ArraysEqual(
    349      serverReceived,
    350      bigUint8Array,
    351      "server received/client sent"
    352    );
    353 
    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    );
    372 
    373    clientReceived = await clientQueue.waitForDataWithAtLeastLength(
    374      bigUint8Array.length
    375    );
    376    assertUint8ArraysEqual(
    377      clientReceived,
    378      bigUint8Array,
    379      "client received/server sent"
    380    );
    381  }
    382 
    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  );
    390 
    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  );
    411 
    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");
    419 
    420  let connectedResult = await connectedPromise;
    421  // destructuring assignment is not yet ES6 compliant, must manually unpack
    422  serverSocket = connectedResult.socket;
    423  serverQueue = connectedResult.queue;
    424 
    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  );
    432 
    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  );
    453 
    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");
    461 
    462  connectedResult = await connectedPromise;
    463  // destructuring assignment is not yet ES6 compliant, must manually unpack
    464  serverSocket = connectedResult.socket;
    465  serverQueue = connectedResult.queue;
    466 
    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  );
    487 
    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  );
    503 
    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");
    511 
    512  connectedResult = await connectedPromise;
    513  // destructuring assignment is not yet ES6 compliant, must manually unpack
    514  serverSocket = connectedResult.socket;
    515  serverQueue = connectedResult.queue;
    516 
    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  );
    537 
    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");
    545 
    546  connectedResult = await connectedPromise;
    547  // destructuring assignment is not yet ES6 compliant, must manually unpack
    548  serverSocket = connectedResult.socket;
    549  serverQueue = connectedResult.queue;
    550 
    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();
    563 
    564  serverReceived = await serverQueue.waitForAnyDataAndClose();
    565 
    566  is(
    567    serverReceived.length < 2 * bigUint8Array.length,
    568    true,
    569    "Received array length less than sent array length"
    570  );
    571 
    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();
    576 
    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  }
    591 }
    592 
    593 add_task(test_basics);
    594 
    595 /**
    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);
    605 
    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();
    617 });