Bug 1217683 - Add sendIceCandidate and implement |close(aReason)| in TCPControlChannel. r=fabrice
authorJunior Hsu <juhsu@mozilla.com>
Mon, 25 May 2015 15:23:26 +0800
changeset 269510 4633017e40fbb7748c774a6a96f24fda1bf44cc0
parent 269509 3ede2a3f0399e61cff6202497384f3b98b24677e
child 269511 724ce80732b9b15b137123bfb01d4391a9abbf1b
push idunknown
push userunknown
push dateunknown
reviewersfabrice
bugs1217683
milestone44.0a1
Bug 1217683 - Add sendIceCandidate and implement |close(aReason)| in TCPControlChannel. r=fabrice
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/interfaces/nsIPresentationControlChannel.idl
dom/presentation/provider/TCPPresentationServer.js
dom/presentation/tests/xpcshell/test_tcp_control_channel.js
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -541,16 +541,23 @@ PresentationControllingInfo::GetAddress(
       this,
       &PresentationControllingInfo::OnGetAddress,
       EmptyCString()));
 #endif
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PresentationControllingInfo::OnIceCandidate(const nsAString& aCandidate)
+{
+  MOZ_ASSERT(false, "Should not receive ICE candidates.");
+  return NS_ERROR_FAILURE;
+}
+
 nsresult
 PresentationControllingInfo::OnGetAddress(const nsACString& aAddress)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Prepare and send the offer.
   int32_t port;
   nsresult rv = mServerSocket->GetPort(&port);
@@ -847,16 +854,23 @@ PresentationPresentingInfo::OnOffer(nsIP
 NS_IMETHODIMP
 PresentationPresentingInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
 {
   MOZ_ASSERT(false, "Receiver side should not receive answer.");
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
+PresentationPresentingInfo::OnIceCandidate(const nsAString& aCandidate)
+{
+  MOZ_ASSERT(false, "Should not receive ICE candidates.");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
 PresentationPresentingInfo::NotifyOpened()
 {
   // Do nothing.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationPresentingInfo::NotifyClosed(nsresult aReason)
--- a/dom/presentation/interfaces/nsIPresentationControlChannel.idl
+++ b/dom/presentation/interfaces/nsIPresentationControlChannel.idl
@@ -27,74 +27,82 @@ interface nsIPresentationChannelDescript
   // SDP for Data Channel.
   // Should only be used while type == TYPE_DATACHANNEL.
   readonly attribute DOMString dataChannelSDP;
 };
 
 /*
  * The callbacks for events on control channel.
  */
-[scriptable, uuid(d0cdc638-a9d5-4bcd-838c-3aed7c3f2a6b)]
+[scriptable, uuid(96dd548f-7d0f-43c1-b1ad-28e666cf1e82)]
 interface nsIPresentationControlChannelListener: nsISupports
 {
   /*
    * Callback for receiving offer from remote endpoint.
    * @param offer The received offer.
    */
   void onOffer(in nsIPresentationChannelDescription offer);
 
   /*
    * Callback for receiving answer from remote endpoint.
    * @param answer The received answer.
    */
   void onAnswer(in nsIPresentationChannelDescription answer);
 
   /*
+   * Callback for receiving ICE candidate from remote endpoint.
+   * @param answer The received answer.
+   */
+  void onIceCandidate(in DOMString candidate);
+
+  /*
    * The callback for notifying channel opened.
    */
   void notifyOpened();
 
   /*
    * The callback for notifying channel closed.
    * @param reason The reason of channel close, NS_OK represents normal close.
    */
   void notifyClosed(in nsresult reason);
 };
 
 /*
  * The control channel for establishing RTCPeerConnection for a presentation
  * session. SDP Offer/Answer will be exchanged through this interface.
  */
-[scriptable, uuid(2c8ec493-4e5b-4df7-bedc-7ab25af323f0)]
+[scriptable, uuid(e60e208c-a9f5-4bc6-9a3e-47f3e4ae9c57)]
 interface nsIPresentationControlChannel: nsISupports
 {
   // The listener for handling events of this control channel.
   // All the events should be pending until listener is assigned.
   attribute nsIPresentationControlChannelListener listener;
 
   /*
-   * Send offer to remote endpiont. |onOffer| should be invoked
-   * on remote endpoint.
+   * Send offer to remote endpoint. |onOffer| should be invoked on remote
+   * endpoint.
    * @param offer The offer to send.
    * @throws  NS_ERROR_FAILURE on failure
    */
   void sendOffer(in nsIPresentationChannelDescription offer);
 
   /*
-   * Send answer to remote endpiont. |onAnswer| should
-   * be invoked on remote endpoint.
+   * Send answer to remote endpoint. |onAnswer| should be invoked on remote
+   * endpoint.
    * @param answer The answer to send.
    * @throws  NS_ERROR_FAILURE on failure
    */
   void sendAnswer(in nsIPresentationChannelDescription answer);
 
   /*
-   * Notify the app-to-app connection is fully established. (Only used at the
-   * receiver side.)
+   * Send ICE candidate to remote endpoint. |onIceCandidate| should be invoked
+   * on remote endpoint.
+   * @param candidate The candidate to send
+   * @throws NS_ERROR_FAILURE on failure
    */
-  void sendReceiverReady();
+  void sendIceCandidate(in DOMString candidate);
 
   /*
    * Close the transport channel.
    * @param reason The reason of channel close; NS_OK represents normal.
    */
   void close(in nsresult reason);
 };
--- a/dom/presentation/provider/TCPPresentationServer.js
+++ b/dom/presentation/provider/TCPPresentationServer.js
@@ -369,16 +369,24 @@ TCPControlChannel.prototype = {
     let msg = {
       type: "requestSession:Answer",
       presentationId: this.presentationId,
       answer: discriptionAsJson(aAnswer),
     };
     this._sendMessage("answer", msg);
   },
 
+  sendIceCandidate: function(aCandidate) {
+    let msg = {
+      type: "requestSession:IceCandidate",
+      presentationId: this.presentationId,
+      iceCandidate: aCandidate,
+    };
+    this._sendMessage("iceCandidate", msg);
+  },
   // may throw an exception
   _send: function(aMsg) {
     DEBUG && log("TCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2));
 
     /**
      * XXX In TCP streaming, it is possible that more than one message in one
      * TCP packet. We use line delimited JSON to identify where one JSON encoded
      * object ends and the next begins. Therefore, we do not allow newline
@@ -433,17 +441,17 @@ TCPControlChannel.prototype = {
     DEBUG && log("TCPControlChannel - onStartRequest with role: "
                  + this._direction);
   },
 
   // nsIRequestObserver (Triggered by nsIInputStreamPump.asyncRead)
   onStopRequest: function(aRequest, aContext, aStatus) {
     DEBUG && log("TCPControlChannel - onStopRequest: " + aStatus
                  + " with role: " + this._direction);
-    this.close();
+    this.close(Cr.NS_OK);
     this._notifyClosed(aStatus);
   },
 
   // nsIStreamListener (Triggered by nsIInputStreamPump.asyncRead)
   onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
     let data = NetUtil.readInputStreamToString(aInputStream,
                                                aInputStream.available());
     DEBUG && log("TCPControlChannel - onDataAvailable: " + data);
@@ -491,16 +499,24 @@ TCPControlChannel.prototype = {
       case "requestSession:Offer": {
         this._onOffer(aMsg.offer);
         break;
       }
       case "requestSession:Answer": {
         this._onAnswer(aMsg.answer);
         break;
       }
+      case "requestSession:IceCandidate": {
+        this._listener.onIceCandidate(aMsg.iceCandidate);
+        break;
+      }
+      case "requestSession:CloseReason": {
+        this._pendingCloseReason = aMsg.reason;
+        break;
+      }
     }
   },
 
   get listener() {
     return this._listener;
   },
 
   set listener(aListener) {
@@ -568,17 +584,17 @@ TCPControlChannel.prototype = {
     DEBUG && log("TCPControlChannel - notify answer: "
                  + JSON.stringify(aAnswer));
     this._listener.onAnswer(new ChannelDescription(aAnswer));
   },
 
   _notifyOpened: function() {
     this._connected = true;
     this._pendingClose = false;
-    this._pendingCloseReason = null;
+    this._pendingCloseReason = Cr.NS_OK;
 
     if (!this._listener) {
       this._pendingOpen = true;
       return;
     }
 
     DEBUG && log("TCPControlChannel - notify opened with role: "
                  + this._direction);
@@ -586,30 +602,47 @@ TCPControlChannel.prototype = {
   },
 
   _notifyClosed: function(aReason) {
     this._connected = false;
     this._pendingOpen = false;
     this._pendingOffer = null;
     this._pendingAnswer = null;
 
+    // Remote endpoint closes the control channel with abnormal reason.
+    if (aReason == Cr.NS_OK && this._pendingCloseReason != Cr.NS_OK) {
+      aReason = this._pendingCloseReason;
+    }
+
     if (!this._listener) {
-     this._pendingClose = true;
-     this._pendingCloseReason = aReason;
-     return;
+      this._pendingClose = true;
+      this._pendingCloseReason = aReason;
+      return;
     }
 
     DEBUG && log("TCPControlChannel - notify closed with role: "
                  + this._direction);
     this._listener.notifyClosed(aReason);
   },
 
-  close: function() {
-    DEBUG && log("TCPControlChannel - close");
+  close: function(aReason) {
+    DEBUG && log("TCPControlChannel - close with reason: " + aReason);
+
     if (this._connected) {
+      // default reason is NS_OK
+      if (typeof aReason !== "undefined" && aReason !== Cr.NS_OK) {
+        let msg = {
+          type: "requestSession:CloseReason",
+          presentationId: this.presentationId,
+          reason: aReason,
+        };
+        this._sendMessage("close", msg);
+        this._pendingCloseReason = aReason;
+      }
+
       this._transport.setEventSink(null, null);
       this._pump = null;
 
       this._input.close();
       this._output.close();
       this._presentationServer.releaseControlChannel(this);
 
       this._connected = false;
--- a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
+++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js
@@ -39,16 +39,19 @@ function TestDescription(aType, aTcpAddr
 
 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;
 
@@ -93,23 +96,33 @@ function testPresentationServer() {
             controllerControlChannel.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. controllerControlChannel: 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.");
+            }
+          }
+          controllerControlChannel.close(CLOSE_CONTROL_CHANNEL_REASON);
+        },
         notifyOpened: function() {
           Assert.equal(this.status, 'created', '0. controllerControlChannel: opened');
           this.status = 'opened';
         },
         notifyClosed: function(aReason) {
-          Assert.equal(this.status, 'onOffer', '3. controllerControlChannel: closed');
-          Assert.equal(aReason, Cr.NS_OK, 'presenterControlChannel notify closed NS_OK');
+          Assert.equal(this.status, 'onOffer', '4. controllerControlChannel: closed');
+          Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'presenterControlChannel notify closed');
           this.status = 'closed';
           yayFuncs.controllerControlChannelClose();
         },
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
       };
     },
 
     QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]),
@@ -127,40 +140,47 @@ function testPresentationServer() {
                                                    'testPresentationId');
 
   presenterControlChannel.listener = {
     status: 'created',
     onOffer: function(offer) {
       Assert.ok(false, 'get offer');
     },
     onAnswer: function(aAnswer) {
-      Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, close channel');
+      Assert.equal(this.status, 'opened', '2. presenterControlChannel: 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');
-
-      presenterControlChannel.close(Cr.NS_OK);
+      candidate = {
+        candidate: "1 1 UDP 1 127.0.0.1 34567 type host",
+        sdpMid: "helloworld",
+        sdpMLineIndex: 1
+      };
+      presenterControlChannel.sendIceCandidate(JSON.stringify(candidate));
+    },
+    onIceCandidate: function(aCandidate) {
+      Assert.ok(false, 'get ICE candidate');
     },
     notifyOpened: function() {
       Assert.equal(this.status, 'created', '0. presenterControlChannel: opened, send offer');
       this.status = 'opened';
       try {
         let tcpType = Ci.nsIPresentationChannelDescription.TYPE_TCP;
         let offer = new TestDescription(tcpType, [OFFER_ADDRESS], OFFER_PORT)
         presenterControlChannel.sendOffer(offer);
       } catch (e) {
         Assert.ok(false, 'sending offer fails:' + e);
       }
     },
     notifyClosed: function(aReason) {
       this.status = 'closed';
-      Assert.equal(aReason, Cr.NS_OK, '3. presenterControlChannel notify closed NS_OK');
+      Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify closed');
       yayFuncs.presenterControlChannelClose();
     },
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
   };
 }
 
 function setOffline() {
   let expectedReason;
@@ -193,18 +213,25 @@ function shutdown()
     onClose: function(aReason) {
       Assert.equal(aReason, Cr.NS_OK, 'TCPPresentationServer close success');
       run_next_test();
     },
   }
   tps.close();
 }
 
+// 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(setOffline);
+add_test(changeCloseReason);
 add_test(oneMoreLoop);
 add_test(shutdown);
 
 function run_test() {
   Services.prefs.setBoolPref("dom.presentation.tcp_server.debug", true);
 
   do_register_cleanup(() => {
     Services.prefs.clearUserPref("dom.presentation.tcp_server.debug");