/* -*- 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.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() { do_print('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, false); } this.tcpPort = aTcpPort; } TestDescription.prototype = { QueryInterface: XPCOMUtils.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: function() { 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: function(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: function(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: function(aAnswer) { Assert.ok(false, 'get answer'); }, onIceCandidate: function(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: function() { Assert.equal(this.status, 'created', '0. presenterControlChannel: opened'); this.status = 'opened'; }, notifyDisconnected: function(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: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; }, onReconnectRequest: function(deviceInfo, url, presentationId, controlChannel) { Assert.equal(url, 'http://example.com', 'expected url'); Assert.equal(presentationId, 'testPresentationId', 'expected presentation id'); yayFuncs.presenterControlChannelReconnect(); }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlServerListener]), }; let presenterDeviceInfo = { id: 'presentatorID', address: '127.0.0.1', port: PRESENTER_CONTROL_CHANNEL_PORT, certFingerprint: pcs.certFingerprint, QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]), }; let controllerControlChannel = pcs.connect(presenterDeviceInfo); controllerControlChannel.listener = { status: 'created', onOffer: function(offer) { Assert.ok(false, 'get offer'); }, onAnswer: function(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: function(aCandidate) { Assert.ok(false, 'get ICE candidate'); }, notifyConnected: function() { 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: function(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: function() { reconnectControllerControlChannel.reconnect('testPresentationId', 'http://example.com'); }, notifyReconnected: function() { yayFuncs.controllerControlChannelReconnect(); }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; } function terminateRequest() { let yayFuncs = makeJointSuccess(['controllerControlChannelConnected', 'controllerControlChannelDisconnected', 'presenterControlChannelDisconnected', 'terminatedByController', 'terminatedByReceiver']); let controllerControlChannel; let terminatePhase = 'controller'; pcs.listener = { onTerminateRequest: function(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: function() { Assert.ok(true, 'control channel notify connected'); yayFuncs.controllerControlChannelConnected(); terminatePhase = 'receiver'; controllerControlChannel.terminate('testPresentationId'); }, notifyDisconnected: function(aReason) { Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'controllerControlChannel notify disconncted'); yayFuncs.controllerControlChannelDisconnected(); }, QueryInterface: XPCOMUtils.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: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]), }; let presenterDeviceInfo = { id: 'presentatorID', address: '127.0.0.1', port: PRESENTER_CONTROL_CHANNEL_PORT, certFingerprint: pcs.certFingerprint, QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]), }; let presenterControlChannel = pcs.connect(presenterDeviceInfo); presenterControlChannel.listener = { notifyConnected: function() { presenterControlChannel.terminate('testPresentationId'); }, notifyDisconnected: function(aReason) { Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify disconnected'); yayFuncs.presenterControlChannelDisconnected(); }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; } function terminateRequestAbnormal() { let yayFuncs = makeJointSuccess(['controllerControlChannelConnected', 'controllerControlChannelDisconnected', 'presenterControlChannelDisconnected']); let controllerControlChannel; pcs.listener = { onTerminateRequest: function(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: function() { Assert.ok(true, 'control channel notify connected'); yayFuncs.controllerControlChannelConnected(); }, notifyDisconnected: function(aReason) { Assert.equal(aReason, Cr.NS_ERROR_FAILURE, 'controllerControlChannel notify disconncted with error'); yayFuncs.controllerControlChannelDisconnected(); }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; }, QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]), }; let presenterDeviceInfo = { id: 'presentatorID', address: '127.0.0.1', port: PRESENTER_CONTROL_CHANNEL_PORT, certFingerprint: pcs.certFingerprint, QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]), }; let presenterControlChannel = pcs.connect(presenterDeviceInfo); presenterControlChannel.listener = { notifyConnected: function() { presenterControlChannel.terminate('testPresentationId'); }, notifyDisconnected: function(aReason) { Assert.equal(aReason, Cr.NS_ERROR_FAILURE, '4. presenterControlChannel notify disconnected with error'); yayFuncs.presenterControlChannelDisconnected(); }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), }; } function setOffline() { pcs.listener = { onServerReady: function(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: function() { 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: function(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); do_register_cleanup(() => { Services.prefs.clearUserPref("dom.presentation.tcp_server.debug"); }); run_next_test(); }