diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/test/unit/test_unix_domain.js | 702 |
1 files changed, 702 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_unix_domain.js b/netwerk/test/unit/test_unix_domain.js new file mode 100644 index 0000000000..86f2f0da11 --- /dev/null +++ b/netwerk/test/unit/test_unix_domain.js @@ -0,0 +1,702 @@ +// Exercise Unix domain sockets. +"use strict"; + +var CC = Components.Constructor; + +const UnixServerSocket = CC( + "@mozilla.org/network/server-socket;1", + "nsIServerSocket", + "initWithFilename" +); +const UnixAbstractServerSocket = CC( + "@mozilla.org/network/server-socket;1", + "nsIServerSocket", + "initWithAbstractAddress" +); + +const ScriptableInputStream = CC( + "@mozilla.org/scriptableinputstream;1", + "nsIScriptableInputStream", + "init" +); + +const socketTransportService = Cc[ + "@mozilla.org/network/socket-transport-service;1" +].getService(Ci.nsISocketTransportService); + +const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(); + +const allPermissions = parseInt("777", 8); + +function run_test() { + // If we're on Windows, simply check for graceful failure. + if (mozinfo.os == "win") { + test_not_supported(); + return; + } + + // The xpcshell temp directory on Android doesn't seem to let us create + // Unix domain sockets. (Perhaps it's a FAT filesystem?) + if (mozinfo.os != "android") { + add_test(test_echo); + add_test(test_name_too_long); + add_test(test_no_directory); + add_test(test_no_such_socket); + add_test(test_address_in_use); + add_test(test_file_in_way); + add_test(test_create_permission); + add_test(test_connect_permission); + add_test(test_long_socket_name); + add_test(test_keep_when_offline); + } + + if (mozinfo.os == "android" || mozinfo.os == "linux") { + add_test(test_abstract_address_socket); + } + + run_next_test(); +} + +// Check that creating a Unix domain socket fails gracefully on Windows. +function test_not_supported() { + let socketName = do_get_tempdir(); + socketName.append("socket"); + info("creating socket: " + socketName.path); + + do_check_throws_nsIException( + () => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED" + ); + + do_check_throws_nsIException( + () => socketTransportService.createUnixDomainTransport(socketName), + "NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED" + ); +} + +// Actually exchange data with Unix domain sockets. +function test_echo() { + let log = ""; + + let socketName = do_get_tempdir(); + socketName.append("socket"); + + // Create a server socket, listening for connections. + info("creating socket: " + socketName.path); + let server = new UnixServerSocket(socketName, allPermissions, -1); + server.asyncListen({ + onSocketAccepted(aServ, aTransport) { + info("called test_echo's onSocketAccepted"); + log += "a"; + + Assert.equal(aServ, server); + + let connection = aTransport; + + // Check the server socket's self address. + let connectionSelfAddr = connection.getScriptableSelfAddr(); + Assert.equal(connectionSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); + Assert.equal(connectionSelfAddr.address, socketName.path); + + // The client socket is anonymous, so the server transport should + // have an empty peer address. + Assert.equal(connection.host, ""); + Assert.equal(connection.port, 0); + let connectionPeerAddr = connection.getScriptablePeerAddr(); + Assert.equal(connectionPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); + Assert.equal(connectionPeerAddr.address, ""); + + let serverAsyncInput = connection + .openInputStream(0, 0, 0) + .QueryInterface(Ci.nsIAsyncInputStream); + let serverOutput = connection.openOutputStream(0, 0, 0); + + serverAsyncInput.asyncWait( + function(aStream) { + info("called test_echo's server's onInputStreamReady"); + let serverScriptableInput = new ScriptableInputStream(aStream); + + // Receive data from the client, and send back a response. + Assert.equal( + serverScriptableInput.readBytes(17), + "Mervyn Murgatroyd" + ); + info("server has read message from client"); + serverOutput.write("Ruthven Murgatroyd", 18); + info("server has written to client"); + }, + 0, + 0, + threadManager.currentThread + ); + }, + + onStopListening(aServ, aStatus) { + info("called test_echo's onStopListening"); + log += "s"; + + Assert.equal(aServ, server); + Assert.equal(log, "acs"); + + run_next_test(); + }, + }); + + // Create a client socket, and connect to the server. + let client = socketTransportService.createUnixDomainTransport(socketName); + Assert.equal(client.host, socketName.path); + Assert.equal(client.port, 0); + + let clientAsyncInput = client + .openInputStream(0, 0, 0) + .QueryInterface(Ci.nsIAsyncInputStream); + let clientInput = new ScriptableInputStream(clientAsyncInput); + let clientOutput = client.openOutputStream(0, 0, 0); + + clientOutput.write("Mervyn Murgatroyd", 17); + info("client has written to server"); + + clientAsyncInput.asyncWait( + function(aStream) { + info("called test_echo's client's onInputStreamReady"); + log += "c"; + + Assert.equal(aStream, clientAsyncInput); + + // Now that the connection has been established, we can check the + // transport's self and peer addresses. + let clientSelfAddr = client.getScriptableSelfAddr(); + Assert.equal(clientSelfAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); + Assert.equal(clientSelfAddr.address, ""); + + Assert.equal(client.host, socketName.path); // re-check, but hey + let clientPeerAddr = client.getScriptablePeerAddr(); + Assert.equal(clientPeerAddr.family, Ci.nsINetAddr.FAMILY_LOCAL); + Assert.equal(clientPeerAddr.address, socketName.path); + + Assert.equal(clientInput.readBytes(18), "Ruthven Murgatroyd"); + info("client has read message from server"); + + server.close(); + }, + 0, + 0, + threadManager.currentThread + ); +} + +// Create client and server sockets using a path that's too long. +function test_name_too_long() { + let socketName = do_get_tempdir(); + // The length limits on all the systems NSPR supports are a bit past 100. + socketName.append(new Array(1000).join("x")); + + // The length must be checked before we ever make any system calls --- we + // have to create the sockaddr first --- so it's unambiguous which error + // we should get here. + + do_check_throws_nsIException( + () => new UnixServerSocket(socketName, 0, -1), + "NS_ERROR_FILE_NAME_TOO_LONG" + ); + + // Unlike most other client socket errors, this one gets reported + // immediately, as we can't even initialize the sockaddr with the given + // name. + do_check_throws_nsIException( + () => socketTransportService.createUnixDomainTransport(socketName), + "NS_ERROR_FILE_NAME_TOO_LONG" + ); + + run_next_test(); +} + +// Try creating a socket in a directory that doesn't exist. +function test_no_directory() { + let socketName = do_get_tempdir(); + socketName.append("missing"); + socketName.append("socket"); + + do_check_throws_nsIException( + () => new UnixServerSocket(socketName, 0, -1), + "NS_ERROR_FILE_NOT_FOUND" + ); + + run_next_test(); +} + +// Try connecting to a server socket that isn't there. +function test_no_such_socket() { + let socketName = do_get_tempdir(); + socketName.append("nonexistent-socket"); + + let client = socketTransportService.createUnixDomainTransport(socketName); + let clientAsyncInput = client + .openInputStream(0, 0, 0) + .QueryInterface(Ci.nsIAsyncInputStream); + clientAsyncInput.asyncWait( + function(aStream) { + info("called test_no_such_socket's onInputStreamReady"); + + Assert.equal(aStream, clientAsyncInput); + + // nsISocketTransport puts off actually creating sockets as long as + // possible, so the error in connecting doesn't actually show up until + // this point. + do_check_throws_nsIException( + () => clientAsyncInput.available(), + "NS_ERROR_FILE_NOT_FOUND" + ); + + clientAsyncInput.close(); + client.close(Cr.NS_OK); + + run_next_test(); + }, + 0, + 0, + threadManager.currentThread + ); +} + +// Creating a socket with a name that another socket is already using is an +// error. +function test_address_in_use() { + let socketName = do_get_tempdir(); + socketName.append("socket-in-use"); + + // Create one server socket. + new UnixServerSocket(socketName, allPermissions, -1); + + // Now try to create another with the same name. + do_check_throws_nsIException( + () => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_SOCKET_ADDRESS_IN_USE" + ); + + run_next_test(); +} + +// Creating a socket with a name that is already a file is an error. +function test_file_in_way() { + let socketName = do_get_tempdir(); + socketName.append("file_in_way"); + + // Create a file with the given name. + socketName.create(Ci.nsIFile.NORMAL_FILE_TYPE, allPermissions); + + // Try to create a socket with the same name. + do_check_throws_nsIException( + () => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_SOCKET_ADDRESS_IN_USE" + ); + + // Try to create a socket under a name that uses that as a parent directory. + socketName.append("socket"); + do_check_throws_nsIException( + () => new UnixServerSocket(socketName, 0, -1), + "NS_ERROR_FILE_NOT_DIRECTORY" + ); + + run_next_test(); +} + +// It is not permitted to create a socket in a directory which we are not +// permitted to execute, or create files in. +function test_create_permission() { + let dirName = do_get_tempdir(); + dirName.append("unfriendly"); + + let socketName = dirName.clone(); + socketName.append("socket"); + + // The test harness has difficulty cleaning things up if we don't make + // everything writable before we're done. + try { + // Create a directory which we are not permitted to search. + dirName.create(Ci.nsIFile.DIRECTORY_TYPE, 0); + + // Try to create a socket in that directory. Because Linux returns EACCES + // when a 'connect' fails because of a local firewall rule, + // nsIServerSocket returns NS_ERROR_CONNECTION_REFUSED in this case. + do_check_throws_nsIException( + () => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_CONNECTION_REFUSED" + ); + + // Grant read and execute permission, but not write permission on the directory. + dirName.permissions = parseInt("0555", 8); + + // This should also fail; we need write permission. + do_check_throws_nsIException( + () => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_CONNECTION_REFUSED" + ); + } finally { + // Make the directory writable, so the test harness can clean it up. + dirName.permissions = allPermissions; + } + + // This should succeed, since we now have all the permissions on the + // directory we could want. + do_check_instanceof( + new UnixServerSocket(socketName, allPermissions, -1), + Ci.nsIServerSocket + ); + + run_next_test(); +} + +// To connect to a Unix domain socket, we need search permission on the +// directories containing it, and some kind of permission or other on the +// socket itself. +function test_connect_permission() { + // This test involves a lot of callbacks, but they're written out so that + // the actual control flow proceeds from top to bottom. + let log = ""; + + // Create a directory which we are permitted to search - at first. + let dirName = do_get_tempdir(); + dirName.append("inhospitable"); + dirName.create(Ci.nsIFile.DIRECTORY_TYPE, allPermissions); + + let socketName = dirName.clone(); + socketName.append("socket"); + + // Create a server socket in that directory, listening for connections, + // and accessible. + let server = new UnixServerSocket(socketName, allPermissions, -1); + server.asyncListen({ + onSocketAccepted: socketAccepted, + onStopListening: stopListening, + }); + + // Make the directory unsearchable. + dirName.permissions = 0; + + let client3; + + let client1 = socketTransportService.createUnixDomainTransport(socketName); + let client1AsyncInput = client1 + .openInputStream(0, 0, 0) + .QueryInterface(Ci.nsIAsyncInputStream); + client1AsyncInput.asyncWait( + function(aStream) { + info("called test_connect_permission's client1's onInputStreamReady"); + log += "1"; + + // nsISocketTransport puts off actually creating sockets as long as + // possible, so the error doesn't actually show up until this point. + do_check_throws_nsIException( + () => client1AsyncInput.available(), + "NS_ERROR_CONNECTION_REFUSED" + ); + + client1AsyncInput.close(); + client1.close(Cr.NS_OK); + + // Make the directory searchable, but make the socket inaccessible. + dirName.permissions = allPermissions; + socketName.permissions = 0; + + let client2 = socketTransportService.createUnixDomainTransport( + socketName + ); + let client2AsyncInput = client2 + .openInputStream(0, 0, 0) + .QueryInterface(Ci.nsIAsyncInputStream); + client2AsyncInput.asyncWait( + function(aStream) { + info("called test_connect_permission's client2's onInputStreamReady"); + log += "2"; + + do_check_throws_nsIException( + () => client2AsyncInput.available(), + "NS_ERROR_CONNECTION_REFUSED" + ); + + client2AsyncInput.close(); + client2.close(Cr.NS_OK); + + // Now make everything accessible, and try one last time. + socketName.permissions = allPermissions; + + client3 = socketTransportService.createUnixDomainTransport( + socketName + ); + + let client3Output = client3.openOutputStream(0, 0, 0); + client3Output.write("Hanratty", 8); + + let client3AsyncInput = client3 + .openInputStream(0, 0, 0) + .QueryInterface(Ci.nsIAsyncInputStream); + client3AsyncInput.asyncWait( + client3InputStreamReady, + 0, + 0, + threadManager.currentThread + ); + }, + 0, + 0, + threadManager.currentThread + ); + }, + 0, + 0, + threadManager.currentThread + ); + + function socketAccepted(aServ, aTransport) { + info("called test_connect_permission's onSocketAccepted"); + log += "a"; + + let serverInput = aTransport + .openInputStream(0, 0, 0) + .QueryInterface(Ci.nsIAsyncInputStream); + let serverOutput = aTransport.openOutputStream(0, 0, 0); + + serverInput.asyncWait( + function(aStream) { + info( + "called test_connect_permission's socketAccepted's onInputStreamReady" + ); + log += "i"; + + // Receive data from the client, and send back a response. + let serverScriptableInput = new ScriptableInputStream(serverInput); + Assert.equal(serverScriptableInput.readBytes(8), "Hanratty"); + serverOutput.write("Ferlingatti", 11); + }, + 0, + 0, + threadManager.currentThread + ); + } + + function client3InputStreamReady(aStream) { + info("called client3's onInputStreamReady"); + log += "3"; + + let client3Input = new ScriptableInputStream(aStream); + + Assert.equal(client3Input.readBytes(11), "Ferlingatti"); + + client3.close(Cr.NS_OK); + server.close(); + } + + function stopListening(aServ, aStatus) { + info("called test_connect_permission's server's stopListening"); + log += "s"; + + Assert.equal(log, "12ai3s"); + + run_next_test(); + } +} + +// Creating a socket with a long filename doesn't crash. +function test_long_socket_name() { + let socketName = do_get_tempdir(); + socketName.append(new Array(10000).join("long")); + + // Try to create a server socket with the long name. + do_check_throws_nsIException( + () => new UnixServerSocket(socketName, allPermissions, -1), + "NS_ERROR_FILE_NAME_TOO_LONG" + ); + + // Try to connect to a socket with the long name. + do_check_throws_nsIException( + () => socketTransportService.createUnixDomainTransport(socketName), + "NS_ERROR_FILE_NAME_TOO_LONG" + ); + + run_next_test(); +} + +// Going offline should not shut down Unix domain sockets. +function test_keep_when_offline() { + let log = ""; + + let socketName = do_get_tempdir(); + socketName.append("keep-when-offline"); + + // Create a listening socket. + let listener = new UnixServerSocket(socketName, allPermissions, -1); + listener.asyncListen({ onSocketAccepted: onAccepted, onStopListening }); + + // Connect a client socket to the listening socket. + let client = socketTransportService.createUnixDomainTransport(socketName); + let clientOutput = client.openOutputStream(0, 0, 0); + let clientInput = client.openInputStream(0, 0, 0); + clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread); + let clientScriptableInput = new ScriptableInputStream(clientInput); + + let server, serverInput, serverScriptableInput, serverOutput; + + // How many times has the server invited the client to go first? + let count = 0; + + // The server accepted connection callback. + function onAccepted(aListener, aServer) { + info("test_keep_when_offline: onAccepted called"); + log += "a"; + Assert.equal(aListener, listener); + server = aServer; + + // Prepare to receive messages from the client. + serverInput = server.openInputStream(0, 0, 0); + serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread); + serverScriptableInput = new ScriptableInputStream(serverInput); + + // Start a conversation with the client. + serverOutput = server.openOutputStream(0, 0, 0); + serverOutput.write("After you, Alphonse!", 20); + count++; + } + + // The client has seen its end of the socket close. + function clientReady(aStream) { + log += "c"; + info("test_keep_when_offline: clientReady called: " + log); + Assert.equal(aStream, clientInput); + + // If the connection has been closed, end the conversation and stop listening. + let available; + try { + available = clientInput.available(); + } catch (ex) { + do_check_instanceof(ex, Ci.nsIException); + Assert.equal(ex.result, Cr.NS_BASE_STREAM_CLOSED); + + info("client received end-of-stream; closing client output stream"); + log += ")"; + + client.close(Cr.NS_OK); + + // Now both output streams have been closed, and both input streams + // have received the close notification. Stop listening for + // connections. + listener.close(); + } + + if (available) { + // Check the message from the server. + Assert.equal(clientScriptableInput.readBytes(20), "After you, Alphonse!"); + + // Write our response to the server. + clientOutput.write("No, after you, Gaston!", 22); + + // Ask to be called again, when more input arrives. + clientInput.asyncWait(clientReady, 0, 0, threadManager.currentThread); + } + } + + function serverReady(aStream) { + log += "s"; + info("test_keep_when_offline: serverReady called: " + log); + Assert.equal(aStream, serverInput); + + // Check the message from the client. + Assert.equal(serverScriptableInput.readBytes(22), "No, after you, Gaston!"); + + // This should not shut things down: Unix domain sockets should + // remain open in offline mode. + if (count == 5) { + Services.io.offline = true; + log += "o"; + } + + if (count < 10) { + // Insist. + serverOutput.write("After you, Alphonse!", 20); + count++; + + // As long as the input stream is open, always ask to be called again + // when more input arrives. + serverInput.asyncWait(serverReady, 0, 0, threadManager.currentThread); + } else if (count == 10) { + // After sending ten times and receiving ten replies, we're not + // going to send any more. Close the server's output stream; the + // client's input stream should see this. + info("closing server transport"); + server.close(Cr.NS_OK); + log += "("; + } + } + + // We have stopped listening. + function onStopListening(aServ, aStatus) { + info("test_keep_when_offline: onStopListening called"); + log += "L"; + Assert.equal(log, "acscscscscsocscscscscs(c)L"); + + Assert.equal(aServ, listener); + Assert.equal(aStatus, Cr.NS_BINDING_ABORTED); + + run_next_test(); + } +} + +function test_abstract_address_socket() { + const socketname = "abstractsocket"; + let server = new UnixAbstractServerSocket(socketname, -1); + server.asyncListen({ + onSocketAccepted: (aServ, aTransport) => { + let serverInput = aTransport + .openInputStream(0, 0, 0) + .QueryInterface(Ci.nsIAsyncInputStream); + let serverOutput = aTransport.openOutputStream(0, 0, 0); + + serverInput.asyncWait( + aStream => { + info( + "called test_abstract_address_socket's onSocketAccepted's onInputStreamReady" + ); + + // Receive data from the client, and send back a response. + let serverScriptableInput = new ScriptableInputStream(serverInput); + Assert.equal(serverScriptableInput.readBytes(9), "ping ping"); + serverOutput.write("pong", 4); + }, + 0, + 0, + threadManager.currentThread + ); + }, + onStopListening: (aServ, aTransport) => {}, + }); + + let client = socketTransportService.createUnixDomainAbstractAddressTransport( + socketname + ); + Assert.equal(client.host, socketname); + Assert.equal(client.port, 0); + let clientInput = client + .openInputStream(0, 0, 0) + .QueryInterface(Ci.nsIAsyncInputStream); + let clientOutput = client.openOutputStream(0, 0, 0); + + clientOutput.write("ping ping", 9); + + clientInput.asyncWait( + aStream => { + let clientScriptInput = new ScriptableInputStream(clientInput); + let available = clientScriptInput.available(); + if (available) { + Assert.equal(clientScriptInput.readBytes(4), "pong"); + + client.close(Cr.NS_OK); + server.close(Cr.NS_OK); + + run_next_test(); + } + }, + 0, + 0, + threadManager.currentThread + ); +} |