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