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 });