Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /**
2 * Test TCPSocket.js by creating an XPCOM-style server socket, then sending
3 * data in both directions and making sure each side receives their data
4 * correctly and with the proper events.
5 *
6 * This test is derived from netwerk/test/unit/test_socks.js, except we don't
7 * involve a subprocess.
8 *
9 * Future work:
10 * - SSL. see https://bugzilla.mozilla.org/show_bug.cgi?id=466524
11 * https://bugzilla.mozilla.org/show_bug.cgi?id=662180
12 * Alternatively, mochitests could be used.
13 * - Testing overflow logic.
14 *
15 **/
17 const Cc = Components.classes;
18 const Ci = Components.interfaces;
19 const Cr = Components.results;
20 const Cu = Components.utils;
21 const CC = Components.Constructor;
23 /**
24 *
25 * Constants
26 *
27 */
29 // Some binary data to send.
30 const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0],
31 DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length),
32 TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER),
33 HELLO_WORLD = "hlo wrld. ",
34 BIG_ARRAY = new Array(65539),
35 BIG_ARRAY_2 = new Array(65539);
37 TYPED_DATA_ARRAY.set(DATA_ARRAY, 0);
39 for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) {
40 BIG_ARRAY[i_big] = Math.floor(Math.random() * 256);
41 BIG_ARRAY_2[i_big] = Math.floor(Math.random() * 256);
42 }
44 const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length),
45 BIG_ARRAY_BUFFER_2 = new ArrayBuffer(BIG_ARRAY_2.length);
46 const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER),
47 BIG_TYPED_ARRAY_2 = new Uint8Array(BIG_ARRAY_BUFFER_2);
48 BIG_TYPED_ARRAY.set(BIG_ARRAY);
49 BIG_TYPED_ARRAY_2.set(BIG_ARRAY_2);
51 const ServerSocket = CC("@mozilla.org/network/server-socket;1",
52 "nsIServerSocket",
53 "init"),
54 InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
55 "nsIInputStreamPump",
56 "init"),
57 BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
58 "nsIBinaryInputStream",
59 "setInputStream"),
60 BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
61 "nsIBinaryOutputStream",
62 "setOutputStream"),
63 TCPSocket = new (CC("@mozilla.org/tcp-socket;1",
64 "nsIDOMTCPSocket"))();
66 const gInChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
67 .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
69 Cu.import("resource://gre/modules/Services.jsm");
71 /**
72 *
73 * Helper functions
74 *
75 */
77 function get_platform() {
78 var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
79 .getService(Components.interfaces.nsIXULRuntime);
80 return xulRuntime.OS;
81 }
83 function is_content() {
84 return this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
85 .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
86 }
88 /**
89 * Spin up a listening socket and associate at most one live, accepted socket
90 * with ourselves.
91 */
92 function TestServer() {
93 this.listener = ServerSocket(-1, true, -1);
94 do_print('server: listening on', this.listener.port);
95 this.listener.asyncListen(this);
97 this.binaryInput = null;
98 this.input = null;
99 this.binaryOutput = null;
100 this.output = null;
102 this.onconnect = null;
103 this.ondata = null;
104 this.onclose = null;
105 }
107 TestServer.prototype = {
108 onSocketAccepted: function(socket, trans) {
109 if (this.input)
110 do_throw("More than one live connection!?");
112 do_print('server: got client connection');
113 this.input = trans.openInputStream(0, 0, 0);
114 this.binaryInput = new BinaryInputStream(this.input);
115 this.output = trans.openOutputStream(0, 0, 0);
116 this.binaryOutput = new BinaryOutputStream(this.output);
118 new InputStreamPump(this.input, -1, -1, 0, 0, false).asyncRead(this, null);
120 if (this.onconnect)
121 this.onconnect();
122 else
123 do_throw("Received unexpected connection!");
124 },
126 onStopListening: function(socket) {
127 },
129 onDataAvailable: function(request, context, inputStream, offset, count) {
130 var readData = this.binaryInput.readByteArray(count);
131 if (this.ondata) {
132 try {
133 this.ondata(readData);
134 } catch(ex) {
135 // re-throw if this is from do_throw
136 if (ex === Cr.NS_ERROR_ABORT)
137 throw ex;
138 // log if there was a test problem
139 do_print('Caught exception: ' + ex + '\n' + ex.stack);
140 do_throw('test is broken; bad ondata handler; see above');
141 }
142 } else {
143 do_throw('Received ' + count + ' bytes of unexpected data!');
144 }
145 },
147 onStartRequest: function(request, context) {
148 },
150 onStopRequest: function(request, context, status) {
151 if (this.onclose)
152 this.onclose();
153 else
154 do_throw("Received unexpected close!");
155 },
157 close: function() {
158 this.binaryInput.close();
159 this.binaryOutput.close();
160 },
162 /**
163 * Forget about the socket we knew about before.
164 */
165 reset: function() {
166 this.binaryInput = null;
167 this.input = null;
168 this.binaryOutput = null;
169 this.output = null;
170 },
171 };
173 function makeSuccessCase(name) {
174 return function() {
175 do_print('got expected: ' + name);
176 run_next_test();
177 };
178 }
180 function makeJointSuccess(names) {
181 let funcs = {}, successCount = 0;
182 names.forEach(function(name) {
183 funcs[name] = function() {
184 do_print('got expected: ' + name);
185 if (++successCount === names.length)
186 run_next_test();
187 };
188 });
189 return funcs;
190 }
192 function makeFailureCase(name) {
193 return function() {
194 let argstr;
195 if (arguments.length) {
196 argstr = '(args: ' +
197 Array.map(arguments, function(x) { return x.data + ""; }).join(" ") + ')';
198 }
199 else {
200 argstr = '(no arguments)';
201 }
202 do_throw('got unexpected: ' + name + ' ' + argstr);
203 };
204 }
206 function makeExpectData(name, expectedData, fromEvent, callback) {
207 let dataBuffer = fromEvent ? null : [], done = false;
208 let dataBufferView = null;
209 return function(receivedData) {
210 if (receivedData.data) {
211 receivedData = receivedData.data;
212 }
213 let recvLength = receivedData.byteLength !== undefined ?
214 receivedData.byteLength : receivedData.length;
216 if (fromEvent) {
217 if (dataBuffer) {
218 let newBuffer = new ArrayBuffer(dataBuffer.byteLength + recvLength);
219 let newBufferView = new Uint8Array(newBuffer);
220 newBufferView.set(dataBufferView, 0);
221 newBufferView.set(receivedData, dataBuffer.byteLength);
222 dataBuffer = newBuffer;
223 dataBufferView = newBufferView;
224 }
225 else {
226 dataBuffer = receivedData;
227 dataBufferView = new Uint8Array(dataBuffer);
228 }
229 }
230 else {
231 dataBuffer = dataBuffer.concat(receivedData);
232 }
233 do_print(name + ' received ' + recvLength + ' bytes');
235 if (done)
236 do_throw(name + ' Received data event when already done!');
238 let dataView = dataBuffer.byteLength !== undefined ? new Uint8Array(dataBuffer) : dataBuffer;
239 if (dataView.length >= expectedData.length) {
240 // check the bytes are equivalent
241 for (let i = 0; i < expectedData.length; i++) {
242 if (dataView[i] !== expectedData[i]) {
243 do_throw(name + ' Received mismatched character at position ' + i);
244 }
245 }
246 if (dataView.length > expectedData.length)
247 do_throw(name + ' Received ' + dataView.length + ' bytes but only expected ' +
248 expectedData.length + ' bytes.');
250 done = true;
251 if (callback) {
252 callback();
253 } else {
254 run_next_test();
255 }
256 }
257 };
258 }
260 var server = null, sock = null, failure_drain = null;
262 /**
263 *
264 * Test functions
265 *
266 */
268 /**
269 * Connect the socket to the server. This test is added as the first
270 * test, and is also added after every test which results in the socket
271 * being closed.
272 */
274 function connectSock() {
275 server.reset();
276 var yayFuncs = makeJointSuccess(['serveropen', 'clientopen']);
278 sock = TCPSocket.open(
279 '127.0.0.1', server.listener.port,
280 { binaryType: 'arraybuffer' });
282 sock.onopen = yayFuncs.clientopen;
283 sock.ondrain = null;
284 sock.ondata = makeFailureCase('data');
285 sock.onerror = makeFailureCase('error');
286 sock.onclose = makeFailureCase('close');
288 server.onconnect = yayFuncs.serveropen;
289 server.ondata = makeFailureCase('serverdata');
290 server.onclose = makeFailureCase('serverclose');
291 }
293 /**
294 * Test that sending a small amount of data works, and that buffering
295 * does not take place for this small amount of data.
296 */
298 function sendData() {
299 server.ondata = makeExpectData('serverdata', DATA_ARRAY);
300 if (!sock.send(DATA_ARRAY_BUFFER)) {
301 do_throw("send should not have buffered such a small amount of data");
302 }
303 }
305 /**
306 * Test that sending a large amount of data works, that buffering
307 * takes place (send returns true), and that ondrain is called once
308 * the data has been sent.
309 */
311 function sendBig() {
312 var yays = makeJointSuccess(['serverdata', 'clientdrain']),
313 amount = 0;
315 server.ondata = function (data) {
316 amount += data.length;
317 if (amount === BIG_TYPED_ARRAY.length) {
318 yays.serverdata();
319 }
320 };
321 sock.ondrain = function(evt) {
322 if (sock.bufferedAmount) {
323 do_throw("sock.bufferedAmount was > 0 in ondrain");
324 }
325 yays.clientdrain(evt);
326 }
327 if (sock.send(BIG_ARRAY_BUFFER)) {
328 do_throw("expected sock.send to return false on large buffer send");
329 }
330 }
332 /**
333 * Test that data sent from the server correctly fires the ondata
334 * callback on the client side.
335 */
337 function receiveData() {
338 server.ondata = makeFailureCase('serverdata');
339 sock.ondata = makeExpectData('data', DATA_ARRAY, true);
341 server.binaryOutput.writeByteArray(DATA_ARRAY, DATA_ARRAY.length);
342 }
344 /**
345 * Test that when the server closes the connection, the onclose callback
346 * is fired on the client side.
347 */
349 function serverCloses() {
350 // we don't really care about the server's close event, but we do want to
351 // make sure it happened for sequencing purposes.
352 var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']);
353 sock.ondata = makeFailureCase('data');
354 sock.onclose = yayFuncs.clientclose;
355 server.onclose = yayFuncs.serverclose;
357 server.close();
358 }
360 /**
361 * Test that when the client closes the connection, the onclose callback
362 * is fired on the server side.
363 */
365 function clientCloses() {
366 // we want to make sure the server heard the close and also that the client's
367 // onclose event fired for consistency.
368 var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']);
369 server.onclose = yayFuncs.serverclose;
370 sock.onclose = yayFuncs.clientclose;
372 sock.close();
373 }
375 /**
376 * Send a large amount of data and immediately call close
377 */
379 function bufferedClose() {
380 var yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
381 server.ondata = makeExpectData(
382 "ondata", BIG_TYPED_ARRAY, false, yays.serverdata);
383 server.onclose = yays.serverclose;
384 sock.onclose = yays.clientclose;
385 sock.send(BIG_ARRAY_BUFFER);
386 sock.close();
387 }
389 /**
390 * Connect to a port we know is not listening so an error is assured,
391 * and make sure that onerror and onclose are fired on the client side.
392 */
394 function badConnect() {
395 // There's probably nothing listening on tcp port 2.
396 sock = TCPSocket.open('127.0.0.1', 2);
398 sock.onopen = makeFailureCase('open');
399 sock.ondata = makeFailureCase('data');
401 let success = makeSuccessCase('error');
402 let gotError = false;
403 sock.onerror = function(event) {
404 do_check_eq(event.data.name, 'ConnectionRefusedError');
405 gotError = true;
406 };
407 sock.onclose = function() {
408 if (!gotError)
409 do_throw('got close without error!');
410 else
411 success();
412 };
413 }
415 /**
416 * Test that calling send with enough data to buffer causes ondrain to
417 * be invoked once the data has been sent, and then test that calling send
418 * and buffering again causes ondrain to be fired again.
419 */
421 function drainTwice() {
422 let yays = makeJointSuccess(
423 ['ondrain', 'ondrain2',
424 'ondata', 'ondata2',
425 'serverclose', 'clientclose']);
426 let ondrainCalled = false,
427 ondataCalled = false;
429 function maybeSendNextData() {
430 if (!ondrainCalled || !ondataCalled) {
431 // make sure server got data and client got ondrain.
432 return;
433 }
435 server.ondata = makeExpectData(
436 "ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2);
438 sock.ondrain = yays.ondrain2;
440 if (sock.send(BIG_ARRAY_BUFFER_2)) {
441 do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering");
442 }
444 sock.close();
445 }
447 function clientOndrain() {
448 yays.ondrain();
449 ondrainCalled = true;
450 maybeSendNextData();
451 }
453 function serverSideCallback() {
454 yays.ondata();
455 ondataCalled = true;
456 maybeSendNextData();
457 }
459 server.onclose = yays.serverclose;
460 server.ondata = makeExpectData(
461 "ondata", BIG_TYPED_ARRAY, false, serverSideCallback);
463 sock.onclose = yays.clientclose;
464 sock.ondrain = clientOndrain;
466 if (sock.send(BIG_ARRAY_BUFFER)) {
467 throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
468 }
469 }
471 function cleanup() {
472 do_print("Cleaning up");
473 sock.close();
474 if (!gInChild)
475 Services.prefs.clearUserPref('dom.mozTCPSocket.enabled');
476 run_next_test();
477 }
479 /**
480 * Test that calling send with enough data to buffer twice in a row without
481 * waiting for ondrain still results in ondrain being invoked at least once.
482 */
484 function bufferTwice() {
485 let yays = makeJointSuccess(
486 ['ondata', 'ondrain', 'serverclose', 'clientclose']);
488 let double_array = new Uint8Array(BIG_ARRAY.concat(BIG_ARRAY_2));
489 server.ondata = makeExpectData(
490 "ondata", double_array, false, yays.ondata);
492 server.onclose = yays.serverclose;
493 sock.onclose = yays.clientclose;
495 sock.ondrain = function () {
496 sock.close();
497 yays.ondrain();
498 }
500 if (sock.send(BIG_ARRAY_BUFFER)) {
501 throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
502 }
503 if (sock.send(BIG_ARRAY_BUFFER_2)) {
504 throw new Error("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering on second synchronous call to send");
505 }
506 }
508 // Test child behavior when child thinks it's buffering but parent doesn't
509 // buffer.
510 // 1. set bufferedAmount of content socket to a value that will make next
511 // send() call return false.
512 // 2. send a small data to make send() return false, but it won't make
513 // parent buffer.
514 // 3. we should get a ondrain.
515 function childbuffered() {
516 let yays = makeJointSuccess(['ondrain', 'serverdata',
517 'clientclose', 'serverclose']);
518 sock.ondrain = function() {
519 yays.ondrain();
520 sock.close();
521 };
523 server.ondata = makeExpectData(
524 'ondata', DATA_ARRAY, false, yays.serverdata);
526 let internalSocket = sock.QueryInterface(Ci.nsITCPSocketInternal);
527 internalSocket.updateBufferedAmount(65535, // almost reach buffering threshold
528 0);
529 if (sock.send(DATA_ARRAY_BUFFER)) {
530 do_throw("expected sock.send to return false.");
531 }
533 sock.onclose = yays.clientclose;
534 server.onclose = yays.serverclose;
535 }
537 // Test child's behavior when send() of child return true but parent buffers
538 // data.
539 // 1. send BIG_ARRAY to make parent buffer. This would make child wait for
540 // drain as well.
541 // 2. set child's bufferedAmount to zero, so child will no longer wait for
542 // drain but parent will dispatch a drain event.
543 // 3. wait for 1 second, to make sure there's no ondrain event dispatched in
544 // child.
545 function childnotbuffered() {
546 let yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
547 server.ondata = makeExpectData('ondata', BIG_ARRAY, false, yays.serverdata);
548 if (sock.send(BIG_ARRAY_BUFFER)) {
549 do_throw("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
550 }
551 let internalSocket = sock.QueryInterface(Ci.nsITCPSocketInternal);
552 internalSocket.updateBufferedAmount(0, // setting zero will clear waitForDrain in sock.
553 1);
555 // shouldn't get ondrain, even after parent have cleared its buffer.
556 sock.ondrain = makeFailureCase('drain');
557 sock.onclose = yays.clientclose;
558 server.onclose = yays.serverclose;
559 do_timeout(1000, function() {
560 sock.close();
561 });
562 };
564 // - connect, data and events work both ways
565 add_test(connectSock);
566 add_test(sendData);
567 add_test(sendBig);
568 add_test(receiveData);
569 // - server closes on us
570 add_test(serverCloses);
572 // - connect, we close on the server
573 add_test(connectSock);
574 add_test(clientCloses);
576 // - connect, buffer, close
577 add_test(connectSock);
578 add_test(bufferedClose);
580 if (get_platform() !== "Darwin") {
581 // This test intermittently fails way too often on OS X, for unknown reasons.
582 // Please, diagnose and fix it if you can.
583 // - get an error on an attempt to connect to a non-listening port
584 add_test(badConnect);
585 }
587 // send a buffer, get a drain, send a buffer, get a drain
588 add_test(connectSock);
589 add_test(drainTwice);
591 // send a buffer, get a drain, send a buffer, get a drain
592 add_test(connectSock);
593 add_test(bufferTwice);
595 if (is_content()) {
596 add_test(connectSock);
597 add_test(childnotbuffered);
599 add_test(connectSock);
600 add_test(childbuffered);
601 }
603 // clean up
604 add_test(cleanup);
606 function run_test() {
607 if (!gInChild)
608 Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true);
610 server = new TestServer();
612 run_next_test();
613 }