Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
1 // Exercise Unix domain sockets.
3 const CC = Components.Constructor;
5 const UnixServerSocket = CC("@mozilla.org/network/server-socket;1",
6 "nsIServerSocket",
7 "initWithFilename");
9 const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
10 "nsIScriptableInputStream",
11 "init");
13 const IOService = Cc["@mozilla.org/network/io-service;1"]
14 .getService(Ci.nsIIOService);
15 const socketTransportService = Cc["@mozilla.org/network/socket-transport-service;1"]
16 .getService(Ci.nsISocketTransportService);
18 const threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
20 const allPermissions = parseInt("777", 8);
22 function run_test()
23 {
24 // If we're on Windows, simply check for graceful failure.
25 if ("@mozilla.org/windows-registry-key;1" in Cc) {
26 test_not_supported();
27 return;
28 }
30 add_test(test_echo);
31 add_test(test_name_too_long);
32 add_test(test_no_directory);
33 add_test(test_no_such_socket);
34 add_test(test_address_in_use);
35 add_test(test_file_in_way);
36 add_test(test_create_permission);
37 add_test(test_connect_permission);
38 add_test(test_long_socket_name);
39 add_test(test_keep_when_offline);
41 run_next_test();
42 }
44 // Check that creating a Unix domain socket fails gracefully on Windows.
45 function test_not_supported()
46 {
47 let socketName = do_get_tempdir();
48 socketName.append('socket');
49 do_print("creating socket: " + socketName.path);
51 do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
52 "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED");
54 do_check_throws_nsIException(() => socketTransportService.createUnixDomainTransport(socketName),
55 "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED");
56 }
58 // Actually exchange data with Unix domain sockets.
59 function test_echo()
60 {
61 let log = '';
63 let socketName = do_get_tempdir();
64 socketName.append('socket');
66 // Create a server socket, listening for connections.
67 do_print("creating socket: " + socketName.path);
68 let server = new UnixServerSocket(socketName, allPermissions, -1);
69 server.asyncListen({
70 onSocketAccepted: function(aServ, aTransport) {
71 do_print("called test_echo's onSocketAccepted");
72 log += 'a';
74 do_check_eq(aServ, server);
76 let connection = aTransport;
78 // Check the server socket's self address.
79 let connectionSelfAddr = connection.getScriptableSelfAddr();
80 do_check_eq(connectionSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
81 do_check_eq(connectionSelfAddr.address, socketName.path);
83 // The client socket is anonymous, so the server transport should
84 // have an empty peer address.
85 do_check_eq(connection.host, '');
86 do_check_eq(connection.port, 0);
87 let connectionPeerAddr = connection.getScriptablePeerAddr();
88 do_check_eq(connectionPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
89 do_check_eq(connectionPeerAddr.address, '');
91 let serverAsyncInput = connection.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
92 let serverOutput = connection.openOutputStream(0, 0, 0);
94 serverAsyncInput.asyncWait(function (aStream) {
95 do_print("called test_echo's server's onInputStreamReady");
96 let serverScriptableInput = new ScriptableInputStream(aStream);
98 // Receive data from the client, and send back a response.
99 do_check_eq(serverScriptableInput.readBytes(17), "Mervyn Murgatroyd");
100 do_print("server has read message from client");
101 serverOutput.write("Ruthven Murgatroyd", 18);
102 do_print("server has written to client");
103 }, 0, 0, threadManager.currentThread);
104 },
106 onStopListening: function(aServ, aStatus) {
107 do_print("called test_echo's onStopListening");
108 log += 's';
110 do_check_eq(aServ, server);
111 do_check_eq(log, 'acs');
113 run_next_test();
114 }
115 });
117 // Create a client socket, and connect to the server.
118 let client = socketTransportService.createUnixDomainTransport(socketName);
119 do_check_eq(client.host, socketName.path);
120 do_check_eq(client.port, 0);
122 let clientAsyncInput = client.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
123 let clientInput = new ScriptableInputStream(clientAsyncInput);
124 let clientOutput = client.openOutputStream(0, 0, 0);
126 clientOutput.write("Mervyn Murgatroyd", 17);
127 do_print("client has written to server");
129 clientAsyncInput.asyncWait(function (aStream) {
130 do_print("called test_echo's client's onInputStreamReady");
131 log += 'c';
133 do_check_eq(aStream, clientAsyncInput);
135 // Now that the connection has been established, we can check the
136 // transport's self and peer addresses.
137 let clientSelfAddr = client.getScriptableSelfAddr();
138 do_check_eq(clientSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
139 do_check_eq(clientSelfAddr.address, '');
141 do_check_eq(client.host, socketName.path); // re-check, but hey
142 let clientPeerAddr = client.getScriptablePeerAddr();
143 do_check_eq(clientPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL);
144 do_check_eq(clientPeerAddr.address, socketName.path);
146 do_check_eq(clientInput.readBytes(18), "Ruthven Murgatroyd");
147 do_print("client has read message from server");
149 server.close();
150 }, 0, 0, threadManager.currentThread);
151 }
153 // Create client and server sockets using a path that's too long.
154 function test_name_too_long()
155 {
156 let socketName = do_get_tempdir();
157 // The length limits on all the systems NSPR supports are a bit past 100.
158 socketName.append(new Array(1000).join('x'));
160 // The length must be checked before we ever make any system calls --- we
161 // have to create the sockaddr first --- so it's unambiguous which error
162 // we should get here.
164 do_check_throws_nsIException(() => new UnixServerSocket(socketName, 0, -1),
165 "NS_ERROR_FILE_NAME_TOO_LONG");
167 // Unlike most other client socket errors, this one gets reported
168 // immediately, as we can't even initialize the sockaddr with the given
169 // name.
170 do_check_throws_nsIException(() => socketTransportService.createUnixDomainTransport(socketName),
171 "NS_ERROR_FILE_NAME_TOO_LONG");
173 run_next_test();
174 }
176 // Try creating a socket in a directory that doesn't exist.
177 function test_no_directory()
178 {
179 let socketName = do_get_tempdir();
180 socketName.append('directory-that-does-not-exist');
181 socketName.append('socket');
183 do_check_throws_nsIException(() => new UnixServerSocket(socketName, 0, -1),
184 "NS_ERROR_FILE_NOT_FOUND");
186 run_next_test();
187 }
189 // Try connecting to a server socket that isn't there.
190 function test_no_such_socket()
191 {
192 let socketName = do_get_tempdir();
193 socketName.append('nonexistent-socket');
195 let client = socketTransportService.createUnixDomainTransport(socketName);
196 let clientAsyncInput = client.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
197 clientAsyncInput.asyncWait(function (aStream) {
198 do_print("called test_no_such_socket's onInputStreamReady");
200 do_check_eq(aStream, clientAsyncInput);
202 // nsISocketTransport puts off actually creating sockets as long as
203 // possible, so the error in connecting doesn't actually show up until
204 // this point.
205 do_check_throws_nsIException(() => clientAsyncInput.available(),
206 "NS_ERROR_FILE_NOT_FOUND");
208 clientAsyncInput.close();
209 client.close(Cr.NS_OK);
211 run_next_test();
212 }, 0, 0, threadManager.currentThread);
213 }
215 // Creating a socket with a name that another socket is already using is an
216 // error.
217 function test_address_in_use()
218 {
219 let socketName = do_get_tempdir();
220 socketName.append('socket-in-use');
222 // Create one server socket.
223 let server = new UnixServerSocket(socketName, allPermissions, -1);
225 // Now try to create another with the same name.
226 do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
227 "NS_ERROR_SOCKET_ADDRESS_IN_USE");
229 run_next_test();
230 }
232 // Creating a socket with a name that is already a file is an error.
233 function test_file_in_way()
234 {
235 let socketName = do_get_tempdir();
236 socketName.append('file_in_way');
238 // Create a file with the given name.
239 socketName.create(Ci.nsIFile.NORMAL_FILE_TYPE, allPermissions);
241 // Try to create a socket with the same name.
242 do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
243 "NS_ERROR_SOCKET_ADDRESS_IN_USE");
245 // Try to create a socket under a name that uses that as a parent directory.
246 socketName.append('socket');
247 do_check_throws_nsIException(() => new UnixServerSocket(socketName, 0, -1),
248 "NS_ERROR_FILE_NOT_DIRECTORY");
250 run_next_test();
251 }
253 // It is not permitted to create a socket in a directory which we are not
254 // permitted to execute, or create files in.
255 function test_create_permission()
256 {
257 let dirName = do_get_tempdir();
258 dirName.append('unfriendly');
260 let socketName = dirName.clone();
261 socketName.append('socket');
263 // The test harness has difficulty cleaning things up if we don't make
264 // everything writable before we're done.
265 try {
266 // Create a directory which we are not permitted to search.
267 dirName.create(Ci.nsIFile.DIRECTORY_TYPE, 0);
269 // Try to create a socket in that directory. Because Linux returns EACCES
270 // when a 'connect' fails because of a local firewall rule,
271 // nsIServerSocket returns NS_ERROR_CONNECTION_REFUSED in this case.
272 do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
273 "NS_ERROR_CONNECTION_REFUSED");
275 // Grant read and execute permission, but not write permission on the directory.
276 dirName.permissions = parseInt("0555", 8);
278 // This should also fail; we need write permission.
279 do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
280 "NS_ERROR_CONNECTION_REFUSED");
282 } finally {
283 // Make the directory writable, so the test harness can clean it up.
284 dirName.permissions = allPermissions;
285 }
287 // This should succeed, since we now have all the permissions on the
288 // directory we could want.
289 do_check_instanceof(new UnixServerSocket(socketName, allPermissions, -1),
290 Ci.nsIServerSocket);
292 run_next_test();
293 }
295 // To connect to a Unix domain socket, we need search permission on the
296 // directories containing it, and some kind of permission or other on the
297 // socket itself.
298 function test_connect_permission()
299 {
300 // This test involves a lot of callbacks, but they're written out so that
301 // the actual control flow proceeds from top to bottom.
302 let log = '';
304 // Create a directory which we are permitted to search - at first.
305 let dirName = do_get_tempdir();
306 dirName.append('inhospitable');
307 dirName.create(Ci.nsIFile.DIRECTORY_TYPE, allPermissions);
309 let socketName = dirName.clone();
310 socketName.append('socket');
312 // Create a server socket in that directory, listening for connections,
313 // and accessible.
314 let server = new UnixServerSocket(socketName, allPermissions, -1);
315 server.asyncListen({ onSocketAccepted: socketAccepted, onStopListening: stopListening });
317 // Make the directory unsearchable.
318 dirName.permissions = 0;
320 let client3;
322 let client1 = socketTransportService.createUnixDomainTransport(socketName);
323 let client1AsyncInput = client1.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
324 client1AsyncInput.asyncWait(function (aStream) {
325 do_print("called test_connect_permission's client1's onInputStreamReady");
326 log += '1';
328 // nsISocketTransport puts off actually creating sockets as long as
329 // possible, so the error doesn't actually show up until this point.
330 do_check_throws_nsIException(() => client1AsyncInput.available(),
331 "NS_ERROR_CONNECTION_REFUSED");
333 client1AsyncInput.close();
334 client1.close(Cr.NS_OK);
336 // Make the directory searchable, but make the socket inaccessible.
337 dirName.permissions = allPermissions;
338 socketName.permissions = 0;
340 let client2 = socketTransportService.createUnixDomainTransport(socketName);
341 let client2AsyncInput = client2.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
342 client2AsyncInput.asyncWait(function (aStream) {
343 do_print("called test_connect_permission's client2's onInputStreamReady");
344 log += '2';
346 do_check_throws_nsIException(() => client2AsyncInput.available(),
347 "NS_ERROR_CONNECTION_REFUSED");
349 client2AsyncInput.close();
350 client2.close(Cr.NS_OK);
352 // Now make everything accessible, and try one last time.
353 socketName.permissions = allPermissions;
355 client3 = socketTransportService.createUnixDomainTransport(socketName);
357 let client3Output = client3.openOutputStream(0, 0, 0);
358 client3Output.write("Hanratty", 8);
360 let client3AsyncInput = client3.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
361 client3AsyncInput.asyncWait(client3InputStreamReady, 0, 0, threadManager.currentThread);
362 }, 0, 0, threadManager.currentThread);
363 }, 0, 0, threadManager.currentThread);
365 function socketAccepted(aServ, aTransport) {
366 do_print("called test_connect_permission's onSocketAccepted");
367 log += 'a';
369 let serverInput = aTransport.openInputStream(0, 0, 0).QueryInterface(Ci.nsIAsyncInputStream);
370 let serverOutput = aTransport.openOutputStream(0, 0, 0);
372 serverInput.asyncWait(function (aStream) {
373 do_print("called test_connect_permission's socketAccepted's onInputStreamReady");
374 log += 'i';
376 // Receive data from the client, and send back a response.
377 let serverScriptableInput = new ScriptableInputStream(serverInput);
378 do_check_eq(serverScriptableInput.readBytes(8), "Hanratty");
379 serverOutput.write("Ferlingatti", 11);
380 }, 0, 0, threadManager.currentThread);
381 }
383 function client3InputStreamReady(aStream) {
384 do_print("called client3's onInputStreamReady");
385 log += '3';
387 let client3Input = new ScriptableInputStream(aStream);
389 do_check_eq(client3Input.readBytes(11), "Ferlingatti");
391 client3.close(Cr.NS_OK);
392 server.close();
393 }
395 function stopListening(aServ, aStatus) {
396 do_print("called test_connect_permission's server's stopListening");
397 log += 's';
399 do_check_eq(log, '12ai3s');
401 run_next_test();
402 }
403 }
405 // Creating a socket with a long filename doesn't crash.
406 function test_long_socket_name()
407 {
408 let socketName = do_get_tempdir();
409 socketName.append(new Array(10000).join('long'));
411 // Try to create a server socket with the long name.
412 do_check_throws_nsIException(() => new UnixServerSocket(socketName, allPermissions, -1),
413 "NS_ERROR_FILE_NAME_TOO_LONG");
415 // Try to connect to a socket with the long name.
416 do_check_throws_nsIException(() => socketTransportService.createUnixDomainTransport(socketName),
417 "NS_ERROR_FILE_NAME_TOO_LONG");
419 run_next_test();
420 }
422 // Going offline should not shut down Unix domain sockets.
423 function test_keep_when_offline()
424 {
425 let log = '';
427 let socketName = do_get_tempdir();
428 socketName.append('keep-when-offline');
430 // Create a listening socket.
431 let listener = new UnixServerSocket(socketName, allPermissions, -1);
432 listener.asyncListen({ onSocketAccepted: onAccepted, onStopListening: onStopListening });
434 // Connect a client socket to the listening socket.
435 let client = socketTransportService.createUnixDomainTransport(socketName);
436 let clientOutput = client.openOutputStream(0, 0, 0);
437 let clientInput = client.openInputStream(0, 0, 0);
438 clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread);
439 let clientScriptableInput = new ScriptableInputStream(clientInput);
441 let server, serverInput, serverScriptableInput, serverOutput;
443 // How many times has the server invited the client to go first?
444 let count = 0;
446 // The server accepted connection callback.
447 function onAccepted(aListener, aServer) {
448 do_print("test_keep_when_offline: onAccepted called");
449 log += 'a';
450 do_check_eq(aListener, listener);
451 server = aServer;
453 // Prepare to receive messages from the client.
454 serverInput = server.openInputStream(0, 0, 0);
455 serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread);
456 serverScriptableInput = new ScriptableInputStream(serverInput);
458 // Start a conversation with the client.
459 serverOutput = server.openOutputStream(0, 0, 0);
460 serverOutput.write("After you, Alphonse!", 20);
461 count++;
462 }
464 // The client has seen its end of the socket close.
465 function clientReady(aStream) {
466 log += 'c';
467 do_print("test_keep_when_offline: clientReady called: " + log);
468 do_check_eq(aStream, clientInput);
470 // If the connection has been closed, end the conversation and stop listening.
471 let available;
472 try {
473 available = clientInput.available();
474 } catch (ex) {
475 do_check_instanceof(ex, Ci.nsIException);
476 do_check_eq(ex.result, Cr.NS_BASE_STREAM_CLOSED);
478 do_print("client received end-of-stream; closing client output stream");
479 log += ')';
481 client.close(Cr.NS_OK);
483 // Now both output streams have been closed, and both input streams
484 // have received the close notification. Stop listening for
485 // connections.
486 listener.close();
487 }
489 if (available) {
490 // Check the message from the server.
491 do_check_eq(clientScriptableInput.readBytes(20), "After you, Alphonse!");
493 // Write our response to the server.
494 clientOutput.write("No, after you, Gaston!", 22);
496 // Ask to be called again, when more input arrives.
497 clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread);
498 }
499 }
501 function serverReady(aStream) {
502 log += 's';
503 do_print("test_keep_when_offline: serverReady called: " + log);
504 do_check_eq(aStream, serverInput);
506 // Check the message from the client.
507 do_check_eq(serverScriptableInput.readBytes(22), "No, after you, Gaston!");
509 // This should not shut things down: Unix domain sockets should
510 // remain open in offline mode.
511 if (count == 5) {
512 IOService.offline = true;
513 log += 'o';
514 }
516 if (count < 10) {
517 // Insist.
518 serverOutput.write("After you, Alphonse!", 20);
519 count++;
521 // As long as the input stream is open, always ask to be called again
522 // when more input arrives.
523 serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread);
524 } else if (count == 10) {
525 // After sending ten times and receiving ten replies, we're not
526 // going to send any more. Close the server's output stream; the
527 // client's input stream should see this.
528 do_print("closing server transport");
529 server.close(Cr.NS_OK);
530 log += '(';
531 }
532 }
534 // We have stopped listening.
535 function onStopListening(aServ, aStatus) {
536 do_print("test_keep_when_offline: onStopListening called");
537 log += 'L';
538 do_check_eq(log, 'acscscscscsocscscscscs(c)L');
540 do_check_eq(aServ, listener);
541 do_check_eq(aStatus, Cr.NS_BINDING_ABORTED);
543 run_next_test();
544 }
545 }