/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); var pcs; // Call |run_next_test| if all functions in |names| are called function makeJointSuccess(names) { let funcs = {}, successCount = 0; names.forEach(function(name) { funcs[name] = function() { info("got expected: " + name); if (++successCount === names.length) run_next_test(); }; }); return funcs; } function TestDescription(aType, aTcpAddress, aTcpPort) { this.type = aType; this.tcpAddress = Cc["@mozilla.org/array;1"] .createInstance(Ci.nsIMutableArray); for (let address of aTcpAddress) { let wrapper = Cc["@mozilla.org/supports-cstring;1"] .createInstance(Ci.nsISupportsCString); wrapper.data = address; this.tcpAddress.appendElement(wrapper); } this.tcpPort = aTcpPort; } TestDescription.prototype = { QueryInterface: ChromeUtils.generateQI([Ci.nsIPresentationChannelDescription]), }; const CONTROLLER_CONTROL_CHANNEL_PORT = 36777; const PRESENTER_CONTROL_CHANNEL_PORT = 36888; var CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_OK; var candidate; // presenter's presentation channel description const OFFER_ADDRESS = "192.168.123.123"; const OFFER_PORT = 123; // controller's presentation channel description const ANSWER_ADDRESS = "192.168.321.321"; const ANSWER_PORT = 321; function loopOfferAnser() { pcs = Cc["@mozilla.org/presentation/control-service;1"] .createInstance(Ci.nsIPresentationControlService); pcs.id = "controllerID"; pcs.listener = { onServerReady() { testPresentationServer(); }, }; // First run with TLS enabled. pcs.startServer(true, PRESENTER_CONTROL_CHANNEL_PORT); } function testPresentationServer() { let yayFuncs = makeJointSuccess(["controllerControlChannelClose", "presenterControlChannelClose", "controllerControlChannelReconnect", "presenterControlChannelReconnect"]); let presenterControlChannel; pcs.listener = { onSessionRequest(deviceInfo, url, presentationId, controlChannel) { presenterControlChannel = controlChannel; Assert.equal(deviceInfo.id, pcs.id, "expected device id"); Assert.equal(deviceInfo.address, "127.0.0.1", "expected device address"); Assert.equal(url, "http://example.com", "expected url"); Assert.equal(presentationId, "testPresentationId", "expected presentation id"); presenterControlChannel.listener = { status: "created", onOffer(aOffer) { Assert.equal(this.status, "opened", "1. presenterControlChannel: get offer, send answer"); this.status = "onOffer"; let offer = aOffer.QueryInterface(Ci.nsIPresentationChannelDescription); Assert.strictEqual(offer.tcpAddress.queryElementAt(0, Ci.nsISupportsCString).data, OFFER_ADDRESS, "expected offer address array"); Assert.equal(offer.tcpPort, OFFER_PORT, "expected offer port"); try { let tcpType = Ci.nsIPresentationChannelDescription.TYPE_TCP; let answer = new TestDescription(tcpType, [ANSWER_ADDRESS], ANSWER_PORT); presenterControlChannel.sendAnswer(answer); } catch (e) { Assert.ok(false, "sending answer fails" + e); } }, onAnswer(aAnswer) { Assert.ok(false, "get answer"); }, onIceCandidate(aCandidate) { Assert.ok(true, "3. presenterControlChannel: get ice candidate, close channel"); let recvCandidate = JSON.parse(aCandidate); for (let key in recvCandidate) { if (typeof(recvCandidate[key]) !== "function") { Assert.equal(recvCandidate[key], candidate[key], "key " + key + " should match."); } } presenterControlChannel.disconnect(CLOSE_CONTROL_CHANNEL_REASON); }, notifyConnected() { Assert.equal(this.status, "created", "0. presenterControlChannel: opened"); this.status = "opened"; }, notifyDisconnected(aReason) { Assert.equal(this.status, "onOffer", "4. presenterControlChannel: closed"); Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, "presenterControlChannel notify closed"); this.status = "closed"; yayFuncs.controllerControlChannelClose(); }, QueryInterface: ChromeUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; }, onReconnectRequest(deviceInfo, url, presentationId, controlChannel) { Assert.equal(url, "http://example.com", "expected url"); Assert.equal(presentationId, "testPresentationId", "expected presentation id"); yayFuncs.presenterControlChannelReconnect(); }, QueryInterface: ChromeUtils.generateQI([Ci.nsIPresentationControlServerListener]), }; let presenterDeviceInfo = { id: "presentatorID", address: "127.0.0.1", port: PRESENTER_CONTROL_CHANNEL_PORT, certFingerprint: pcs.certFingerprint, QueryInterface: ChromeUtils.generateQI([Ci.nsITCPDeviceInfo]), }; let controllerControlChannel = pcs.connect(presenterDeviceInfo); controllerControlChannel.listener = { status: "created", onOffer(offer) { Assert.ok(false, "get offer"); }, onAnswer(aAnswer) { Assert.equal(this.status, "opened", "2. controllerControlChannel: get answer, send ICE candidate"); let answer = aAnswer.QueryInterface(Ci.nsIPresentationChannelDescription); Assert.strictEqual(answer.tcpAddress.queryElementAt(0, Ci.nsISupportsCString).data, ANSWER_ADDRESS, "expected answer address array"); Assert.equal(answer.tcpPort, ANSWER_PORT, "expected answer port"); candidate = { candidate: "1 1 UDP 1 127.0.0.1 34567 type host", sdpMid: "helloworld", sdpMLineIndex: 1, }; controllerControlChannel.sendIceCandidate(JSON.stringify(candidate)); }, onIceCandidate(aCandidate) { Assert.ok(false, "get ICE candidate"); }, notifyConnected() { Assert.equal(this.status, "created", "0. controllerControlChannel: opened, send offer"); controllerControlChannel.launch("testPresentationId", "http://example.com"); this.status = "opened"; try { let tcpType = Ci.nsIPresentationChannelDescription.TYPE_TCP; let offer = new TestDescription(tcpType, [OFFER_ADDRESS], OFFER_PORT); controllerControlChannel.sendOffer(offer); } catch (e) { Assert.ok(false, "sending offer fails:" + e); } }, notifyDisconnected(aReason) { this.status = "closed"; Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, "4. controllerControlChannel notify closed"); yayFuncs.presenterControlChannelClose(); let reconnectControllerControlChannel = pcs.connect(presenterDeviceInfo); reconnectControllerControlChannel.listener = { notifyConnected() { reconnectControllerControlChannel.reconnect("testPresentationId", "http://example.com"); }, notifyReconnected() { yayFuncs.controllerControlChannelReconnect(); }, QueryInterface: ChromeUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; }, QueryInterface: ChromeUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; } function terminateRequest() { let yayFuncs = makeJointSuccess(["controllerControlChannelConnected", "controllerControlChannelDisconnected", "presenterControlChannelDisconnected", "terminatedByController", "terminatedByReceiver"]); let controllerControlChannel; let terminatePhase = "controller"; pcs.listener = { onTerminateRequest(deviceInfo, presentationId, controlChannel, isFromReceiver) { Assert.equal(deviceInfo.address, "127.0.0.1", "expected device address"); Assert.equal(presentationId, "testPresentationId", "expected presentation id"); controlChannel.terminate(presentationId); // Reply terminate ack. if (terminatePhase === "controller") { controllerControlChannel = controlChannel; Assert.equal(deviceInfo.id, pcs.id, "expected controller device id"); Assert.equal(isFromReceiver, false, "expected request from controller"); yayFuncs.terminatedByController(); controllerControlChannel.listener = { notifyConnected() { Assert.ok(true, "control channel notify connected"); yayFuncs.controllerControlChannelConnected(); terminatePhase = "receiver"; controllerControlChannel.terminate("testPresentationId"); }, notifyDisconnected(aReason) { Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, "controllerControlChannel notify disconncted"); yayFuncs.controllerControlChannelDisconnected(); }, QueryInterface: ChromeUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; } else { Assert.equal(deviceInfo.id, presenterDeviceInfo.id, "expected presenter device id"); Assert.equal(isFromReceiver, true, "expected request from receiver"); yayFuncs.terminatedByReceiver(); presenterControlChannel.disconnect(CLOSE_CONTROL_CHANNEL_REASON); } }, QueryInterface: ChromeUtils.generateQI([Ci.nsITCPPresentationServerListener]), }; let presenterDeviceInfo = { id: "presentatorID", address: "127.0.0.1", port: PRESENTER_CONTROL_CHANNEL_PORT, certFingerprint: pcs.certFingerprint, QueryInterface: ChromeUtils.generateQI([Ci.nsITCPDeviceInfo]), }; let presenterControlChannel = pcs.connect(presenterDeviceInfo); presenterControlChannel.listener = { notifyConnected() { presenterControlChannel.terminate("testPresentationId"); }, notifyDisconnected(aReason) { Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, "4. presenterControlChannel notify disconnected"); yayFuncs.presenterControlChannelDisconnected(); }, QueryInterface: ChromeUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; } function terminateRequestAbnormal() { let yayFuncs = makeJointSuccess(["controllerControlChannelConnected", "controllerControlChannelDisconnected", "presenterControlChannelDisconnected"]); let controllerControlChannel; pcs.listener = { onTerminateRequest(deviceInfo, presentationId, controlChannel, isFromReceiver) { Assert.equal(deviceInfo.id, pcs.id, "expected controller device id"); Assert.equal(deviceInfo.address, "127.0.0.1", "expected device address"); Assert.equal(presentationId, "testPresentationId", "expected presentation id"); Assert.equal(isFromReceiver, false, "expected request from controller"); controlChannel.terminate("unmatched-presentationId"); // Reply abnormal terminate ack. controllerControlChannel = controlChannel; controllerControlChannel.listener = { notifyConnected() { Assert.ok(true, "control channel notify connected"); yayFuncs.controllerControlChannelConnected(); }, notifyDisconnected(aReason) { Assert.equal(aReason, Cr.NS_ERROR_FAILURE, "controllerControlChannel notify disconncted with error"); yayFuncs.controllerControlChannelDisconnected(); }, QueryInterface: ChromeUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; }, QueryInterface: ChromeUtils.generateQI([Ci.nsITCPPresentationServerListener]), }; let presenterDeviceInfo = { id: "presentatorID", address: "127.0.0.1", port: PRESENTER_CONTROL_CHANNEL_PORT, certFingerprint: pcs.certFingerprint, QueryInterface: ChromeUtils.generateQI([Ci.nsITCPDeviceInfo]), }; let presenterControlChannel = pcs.connect(presenterDeviceInfo); presenterControlChannel.listener = { notifyConnected() { presenterControlChannel.terminate("testPresentationId"); }, notifyDisconnected(aReason) { Assert.equal(aReason, Cr.NS_ERROR_FAILURE, "4. presenterControlChannel notify disconnected with error"); yayFuncs.presenterControlChannelDisconnected(); }, QueryInterface: ChromeUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; } function setOffline() { pcs.listener = { onServerReady(aPort, aCertFingerprint) { Assert.notEqual(aPort, 0, "TCPPresentationServer port changed and the port should be valid"); pcs.close(); run_next_test(); }, }; // Let the server socket restart automatically. Services.io.offline = true; Services.io.offline = false; } function oneMoreLoop() { try { pcs.listener = { onServerReady() { testPresentationServer(); }, }; // Second run with TLS disabled. pcs.startServer(false, PRESENTER_CONTROL_CHANNEL_PORT); } catch (e) { Assert.ok(false, "TCP presentation init fail:" + e); run_next_test(); } } function shutdown() { pcs.listener = { onServerReady(aPort, aCertFingerprint) { Assert.ok(false, "TCPPresentationServer port changed"); }, }; pcs.close(); Assert.equal(pcs.port, 0, "TCPPresentationServer closed"); run_next_test(); } // Test manually close control channel with NS_ERROR_FAILURE function changeCloseReason() { CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_ERROR_FAILURE; run_next_test(); } add_test(loopOfferAnser); add_test(terminateRequest); add_test(terminateRequestAbnormal); add_test(setOffline); add_test(changeCloseReason); add_test(oneMoreLoop); add_test(shutdown); function run_test() { // Need profile dir to store the key / cert do_get_profile(); // Ensure PSM is initialized Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); Services.prefs.setBoolPref("dom.presentation.tcp_server.debug", true); registerCleanupFunction(() => { Services.prefs.clearUserPref("dom.presentation.tcp_server.debug"); }); run_next_test(); }