Bug 825703: Stun configuration from JS for PeerConnections (IP only) r=bz,jesup
authorJan-Ivar Bruaroey <jib@mozilla.com>
Wed, 23 Jan 2013 14:21:25 -0500
changeset 119727 6b5016ab9ebb
parent 119726 0eda7bbdfedf
child 119728 f8f4c3163cd9
push id24219
push userryanvm@gmail.com
push date2013-01-24 17:36 +0000
treeherdermozilla-central@fa969919b1bb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, jesup
bugs825703
milestone21.0a1
Bug 825703: Stun configuration from JS for PeerConnections (IP only) r=bz,jesup
dom/media/PeerConnection.js
dom/media/bridge/IPeerConnection.idl
dom/media/tests/mochitest/Makefile.in
dom/media/tests/mochitest/test_peerConnection_bug825703.html
dom/webidl/RTCIceServer.webidl
dom/webidl/WebIDL.mk
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
media/webrtc/signaling/test/signaling_unittests.cpp
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
 const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
 const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
 const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
@@ -42,17 +42,17 @@ GlobalPCList.prototype = {
                                       Ci.nsIObserver,
                                       Ci.nsISupportsWeakReference,
                                       Ci.IPeerConnectionManager
                                     ]}),
 
   _xpcom_factory: {
     createInstance: function(outer, iid) {
       if (outer) {
-        throw Components.results.NS_ERROR_NO_AGGREGATION;
+        throw Cr.NS_ERROR_NO_AGGREGATION;
       }
       return _globalPCList.QueryInterface(iid);
     }
   },
 
   addPC: function(pc) {
     let winID = pc._winID;
     if (this._list[winID]) {
@@ -249,35 +249,41 @@ PeerConnection.prototype = {
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIDOMRTCPeerConnection,
     Ci.nsIDOMGlobalObjectConstructor,
     Ci.nsISupportsWeakReference
   ]),
 
   // Constructor is an explicit function, because of nsIDOMGlobalObjectConstructor.
-  constructor: function(win) {
+  constructor: function(win, rtcConfig) {
     if (!Services.prefs.getBoolPref("media.peerconnection.enabled")) {
       throw new Error("PeerConnection not enabled (did you set the pref?)");
     }
     if (this._win) {
-      throw new Error("Constructor already called");
+      throw new Error("RTCPeerConnection constructor already called");
     }
+    if (!rtcConfig) {
+      // TODO(jib@mozilla.com): Hardcoded server pending Mozilla one. Bug 807494
+      rtcConfig = [{ url: "stun:23.21.150.121" }];
+    }
+    this._mustValidateRTCConfiguration(rtcConfig,
+        "RTCPeerConnection constructor passed invalid RTCConfiguration");
     if (_globalPCList._networkdown) {
-      throw new Error("Can't create RTPPeerConnections when the network is down");
+      throw new Error("Can't create RTCPeerConnections when the network is down");
     }
 
     this._pc = Cc["@mozilla.org/peerconnection;1"].
              createInstance(Ci.IPeerConnection);
     this._observer = new PeerConnectionObserver(this);
 
     // Nothing starts until ICE gathering completes.
     this._queueOrRun({
       func: this._pc.initialize,
-      args: [this._observer, win, Services.tm.currentThread],
+      args: [this._observer, win, rtcConfig, Services.tm.currentThread],
       wait: true
     });
 
     this._win = win;
     this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
 
     // Add a reference to the PeerConnection to global list.
@@ -317,16 +323,59 @@ PeerConnection.prototype = {
         this._executeNext();
       }
     } else {
       this._pending = false;
     }
   },
 
   /**
+   * An RTCConfiguration looks like this:
+   *
+   *   [ { url:"stun:23.21.150.121" },
+   *     { url:"turn:user@turn.example.org", credential:"myPassword"} ]
+   *
+   * We check for basic structure and well-formed stun/turn urls, but not
+   * validity of servers themselves, before passing along to C++.
+   * ErrorMsg is passed in to detail which array-entry failed, if any.
+   */
+  _mustValidateRTCConfiguration: function(rtcConfig, errorMsg) {
+    function isObject(obj) {
+      return obj && (typeof obj === "object");
+    }
+    function isArray(obj) {
+      return isObject(obj) &&
+        (Object.prototype.toString.call(obj) === "[object Array]");
+    }
+    function nicerNewURI(uriStr, errorMsg) {
+      let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
+      try {
+        return ios.newURI(uriStr, null, null);
+      } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) {
+        throw new Error(errorMsg + " - malformed URI: " + uriStr);
+      }
+    }
+    function mustValidateServer(server) {
+      let url = nicerNewURI(server.url, errorMsg);
+      if (!(url.scheme in { stun:1, stuns:1, turn:1 })) {
+        throw new Error (errorMsg + " - improper scheme: " + url.scheme);
+      }
+      if (server.credential && isObject(server.credential)) {
+        throw new Error (errorMsg + " - invalid credential");
+      }
+    }
+    if (!isArray(rtcConfig)) {
+      throw new Error (errorMsg);
+    }
+    for (let i=0; i < rtcConfig.length; i++) {
+      mustValidateServer (rtcConfig[i], errorMsg);
+    }
+  },
+
+  /**
    * Constraints look like this:
    *
    * {
    *   mandatory: {"foo": true, "bar": 10, "baz": "boo"},
    *   optional: [{"foo": true}, {"bar": 10}]
    * }
    *
    * We check for basic structure but not the validity of the constraints
@@ -476,21 +525,21 @@ PeerConnection.prototype = {
   },
 
   updateIce: function(config, constraints, restart) {
     return Cr.NS_ERROR_NOT_IMPLEMENTED;
   },
 
   addIceCandidate: function(cand) {
     if (!cand) {
-      throw "NULL candidate passed to addIceCandidate!";
+      throw new Error ("NULL candidate passed to addIceCandidate!");
     }
 
     if (!cand.candidate || !cand.sdpMLineIndex) {
-      throw "Invalid candidate passed to addIceCandidate!";
+      throw new Error ("Invalid candidate passed to addIceCandidate!");
     }
 
     this._queueOrRun({
       func: this._pc.addIceCandidate,
       args: [cand.candidate, cand.sdpMid || "", cand.sdpMLineIndex],
       wait: false
     });
   },
--- a/dom/media/bridge/IPeerConnection.idl
+++ b/dom/media/bridge/IPeerConnection.idl
@@ -60,17 +60,17 @@ interface IPeerConnectionObserver : nsIS
   /* When SDP is parsed and a candidate line is found this method is called.
    * It should hook back into the media transport to notify it of ICE candidates
    * listed in the SDP PeerConnectionImpl does not parse ICE candidates, just
    * pulls them out of the SDP.
    */
   void foundIceCandidate(in string candidate);
 };
 
-[scriptable, uuid(c86903c7-0a2e-42b4-903c-1518f03b9ba5)]
+[scriptable, uuid(cc8327f5-66f4-42f4-820d-9a9db0474b6e)]
 interface IPeerConnection : nsISupports
 {
   const unsigned long kHintAudio = 0x00000001;
   const unsigned long kHintVideo = 0x00000002;
 
   const long kActionNone = -1;
   const long kActionOffer = 0;
   const long kActionAnswer = 1;
@@ -83,17 +83,18 @@ interface IPeerConnection : nsISupports
   const long kIceFailed = 4;
 
   /* for 'type' in DataChannelInit dictionary */
   const unsigned short kDataChannelReliable = 0;
   const unsigned short kDataChannelPartialReliableRexmit = 1;
   const unsigned short kDataChannelPartialReliableTimed = 2;
 
   /* Must be called first. Observer events will be dispatched on the thread provided */
-  void initialize(in IPeerConnectionObserver observer, in nsIDOMWindow window,
+  [implicit_jscontext] void initialize(in IPeerConnectionObserver observer, in nsIDOMWindow window,
+                  [optional] in jsval iceServers,
                   [optional] in nsIThread thread);
 
   /* JSEP calls */
   [implicit_jscontext] void createOffer(in jsval constraints);
   [implicit_jscontext] void createAnswer(in jsval constraints);
   void setLocalDescription(in long action, in string sdp);
   void setRemoteDescription(in long action, in string sdp);
 
--- a/dom/media/tests/mochitest/Makefile.in
+++ b/dom/media/tests/mochitest/Makefile.in
@@ -15,16 +15,17 @@ MOCHITEST_FILES = \
   test_getUserMedia_basicAudio.html \
   test_getUserMedia_basicVideo.html \
   test_getUserMedia_basicVideoAudio.html \
   test_peerConnection_basicAudio.html \
   test_peerConnection_basicAudioVideo.html \
   test_peerConnection_basicAudioVideoCombined.html \
   test_peerConnection_basicVideo.html \
   test_peerConnection_bug827843.html \
+  test_peerConnection_bug825703.html \
   head.js \
   mediaStreamPlayback.js \
   pc.js \
   $(NULL)
 
 # The following tests are leaking and cannot be run by default yet
 ifdef MOZ_WEBRTC_LEAKING_TESTS
 MOCHITEST_FILES += \
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_bug825703.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=825703
+-->
+<head>
+  <meta charset="utf-8">
+    <title>Bug 825703: RTCConfiguration valid/invalid permutations</title>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="head.js"></script>
+  </meta>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=825703">RTCConfiguration valid/invalid permutations</a>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+runTest(function () {
+  var pc;
+  var pcs;
+  var exception = null;
+  try { pcs = new mozRTCPeerConnection(); } catch (e) { exception = e; }
+  ok(!exception, "mozRTCPeerConnection() succeeds");
+  exception = null;
+  try { pc = new mozRTCPeerConnection(1); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection(1) throws");
+  exception = null;
+  try { pc = new mozRTCPeerConnection({}); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection({}) throws");
+  exception = null;
+  try { pcs = new mozRTCPeerConnection([]); } catch (e) { exception = e; }
+  ok(!exception, "mozRTCPeerConnection([]) succeeds");
+  exception = null;
+  try { pc = new mozRTCPeerConnection([{}]); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection([{}]) throws");
+  exception = null;
+  try { pc = new mozRTCPeerConnection([{ url:"" }]); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection([{ url:\"\" }]) throws");
+  exception = null;
+  try { pc = new mozRTCPeerConnection([{ url:"http:0.0.0.0" }]); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection([{ url:\"http:0.0.0.0\" }]) throws");
+  exception = null;
+  try { pcs = new mozRTCPeerConnection([{ url:"stun:0.0.0.0" }, { url:"stuns:x.net", foo:"" }, { url:"turn:user@x.org:42", credential:"p" }]); } catch (e) { exception = e; }
+  ok(!exception, "mozRTCPeerConnection([{ url:\"stun:0.0.0.0\" }, { url:\"stuns:x.net\", foo:\"\" }, { url:\"turn:user@x.org:42\", credential:\"p\" }]) succeeds");
+  exception = null;
+  try { pc = new mozRTCPeerConnection([{ url:"stun:0.0.0.0", credential:{}}]); } catch (e) { exception = e; }
+  ok(exception, "mozRTCPeerConnection([{ url:\"stun:0.0.0.0\", credential:{}}]) throws");
+  pc = null;
+  pcs = null;
+  SimpleTest.finish();
+}, true);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/webidl/RTCIceServer.webidl
@@ -0,0 +1,10 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+dictionary RTCIceServer {
+    DOMString  url;
+    DOMString? credential = null;
+};
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -186,16 +186,17 @@ ifdef MOZ_WEBGL
 webidl_files += \
   WebGLRenderingContext.webidl \
   $(NULL)
 endif
 
 ifdef MOZ_WEBRTC
 webidl_files += \
   MediaStreamList.webidl \
+  RTCIceServer.webidl \
   $(NULL)
 endif
 
 ifdef MOZ_B2G_RIL
 webidl_files += \
   USSDReceivedEvent.webidl \
   $(NULL)
 endif
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -21,36 +21,45 @@
 
 #include "nsNetCID.h"
 #include "nsIProperty.h"
 #include "nsIPropertyBag2.h"
 #include "nsIServiceManager.h"
 #include "nsISimpleEnumerator.h"
 #include "nsServiceManagerUtils.h"
 #include "nsISocketTransportService.h"
-
+#include "nsIConsoleService.h"
 #include "nsThreadUtils.h"
 #include "nsProxyRelease.h"
 
 #include "runnable_utils.h"
 #include "PeerConnectionCtx.h"
 #include "PeerConnectionImpl.h"
-
 #include "nsPIDOMWindow.h"
 #include "nsDOMDataChannel.h"
 #ifdef MOZILLA_INTERNAL_API
+#include "nsContentUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsIScriptError.h"
+#include "nsPrintfCString.h"
+#include "nsURLHelper.h"
+#include "nsNetUtil.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/RTCIceServerBinding.h"
 #include "MediaStreamList.h"
 #include "nsIScriptGlobalObject.h"
 #include "jsapi.h"
 #endif
 
 #ifndef USE_FAKE_MEDIA_STREAMS
 #include "MediaSegment.h"
 #endif
 
+#define ICE_PARSING "In RTCConfiguration passed to RTCPeerConnection constructor"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace mozilla {
   class DataChannel;
 }
 
 class nsIDOMDataChannel;
@@ -286,39 +295,155 @@ PeerConnectionImpl::CreateRemoteSourceSt
 
   nsRefPtr<RemoteSourceStreamInfo> remote;
   remote = new RemoteSourceStreamInfo(comstream);
   *aInfo = remote;
 
   return NS_OK;
 }
 
+#ifdef MOZILLA_INTERNAL_API
+static void
+Warn(JSContext* aCx, const nsCString& aMsg) {
+  CSFLogErrorS(logTag, "Warning: " << aMsg.get());
+  nsIScriptContext* sc = GetScriptContextFromJSContext(aCx);
+  if (sc) {
+    nsCOMPtr<nsIDocument> doc;
+    doc = nsContentUtils::GetDocumentFromScriptContext(sc);
+    if (doc) {
+      // Passing in line-# 1 hides peerconnection.js (shows document instead)
+      nsContentUtils::ReportToConsoleNonLocalized(NS_ConvertUTF8toUTF16 (aMsg),
+          nsIScriptError::warningFlag, logTag, doc, nullptr, EmptyString(), 1);
+    }
+  }
+}
+#endif
+
+/**
+ * In JS, an RTCConfiguration looks like this:
+ *
+ *    [ { url:"stun:23.21.150.121" },
+ *      { url:"turn:user@turn.example.org", credential:"myPassword"} ]
+ *
+ * This function converts an already-validated jsval that looks like the above
+ * into an RTCConfiguration object.
+ */
+nsresult
+PeerConnectionImpl::ConvertRTCConfiguration(const JS::Value& aSrc,
+                                            RTCConfiguration *aDst,
+                                            JSContext* aCx)
+{
+#ifdef MOZILLA_INTERNAL_API
+  if (!aSrc.isObject()) {
+    return NS_ERROR_FAILURE;
+  }
+  JSObject& servers = aSrc.toObject();
+  JSAutoCompartment ac(aCx, &servers);
+  uint32_t len;
+  if (!(IsArrayLike(aCx, &servers) && JS_GetArrayLength(aCx, &servers, &len))) {
+    return NS_ERROR_FAILURE;
+  }
+  for (uint32_t i = 0; i < len; i++) {
+    nsresult rv;
+    RTCIceServer server;
+    {
+      JS::Value v;
+      if (!(JS_GetElement(aCx, &servers, i, &v) && server.Init(aCx, nullptr, v))) {
+        return NS_ERROR_FAILURE;
+      }
+    }
+    if (!server.mUrl.WasPassed()) {
+      return NS_ERROR_FAILURE;
+    }
+    nsRefPtr<nsIURI> url;
+    rv = NS_NewURI(getter_AddRefs(url), server.mUrl.Value());
+    NS_ENSURE_SUCCESS(rv, rv);
+    bool isStun = false, isStuns = false, isTurn = false, isTurns = false;
+    url->SchemeIs("stun", &isStun);
+    url->SchemeIs("stuns", &isStuns);
+    url->SchemeIs("turn", &isTurn);
+    url->SchemeIs("turns", &isTurns);
+    if (!(isStun || isStuns || isTurn || isTurns)) {
+      return NS_ERROR_FAILURE;
+    }
+    nsAutoCString spec;
+    rv = url->GetSpec(spec);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (!server.mCredential.IsEmpty()) {
+      // TODO(jib@mozilla.com): Support username, credentials & TURN servers
+      Warn(aCx, nsPrintfCString(ICE_PARSING
+          ": Credentials not yet implemented. Omitting \"%s\"", spec.get()));
+      continue;
+    }
+    if (isTurn || isTurns) {
+      Warn(aCx, nsPrintfCString(ICE_PARSING
+          ": TURN servers not yet supported. Treating as STUN: \"%s\"", spec.get()));
+    }
+    // TODO(jib@mozilla.com): Revisit once nsURI supports host and port on STUN
+    int32_t port;
+    nsAutoCString host;
+    {
+      uint32_t hostPos;
+      int32_t hostLen;
+      nsAutoCString path;
+      rv = url->GetPath(path);
+      NS_ENSURE_SUCCESS(rv, rv);
+      rv = net_GetAuthURLParser()->ParseAuthority(path.get(), path.Length(),
+                                                  nullptr,  nullptr,
+                                                  nullptr,  nullptr,
+                                                  &hostPos,  &hostLen, &port);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (!hostLen) {
+        return NS_ERROR_FAILURE;
+      }
+      path.Mid(host, hostPos, hostLen);
+    }
+    if (port == -1)
+      port = (isStuns || isTurns)? 5349 : 3478;
+    if (!aDst->addServer(host.get(), port)) {
+      Warn(aCx, nsPrintfCString(ICE_PARSING
+          ": FQDN not yet implemented (only IP-#s). Omitting \"%s\"", spec.get()));
+    }
+  }
+#endif
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 PeerConnectionImpl::Initialize(IPeerConnectionObserver* aObserver,
                                nsIDOMWindow* aWindow,
-                               nsIThread* aThread) {
+                               const JS::Value &aRTCConfiguration,
+                               nsIThread* aThread,
+                               JSContext* aCx)
+{
+  return Initialize(aObserver, aWindow, nullptr, &aRTCConfiguration, aThread, aCx);
+}
+
+nsresult
+PeerConnectionImpl::Initialize(IPeerConnectionObserver* aObserver,
+                               nsIDOMWindow* aWindow,
+                               const RTCConfiguration* aConfiguration,
+                               const JS::Value* aRTCConfiguration,
+                               nsIThread* aThread,
+                               JSContext* aCx)
+{
 #ifdef MOZILLA_INTERNAL_API
   MOZ_ASSERT(NS_IsMainThread());
 #endif
   MOZ_ASSERT(aObserver);
   MOZ_ASSERT(aThread);
+  mThread = aThread;
 
   mPCObserver = do_GetWeakReference(aObserver);
 
   nsresult res;
-
 #ifdef MOZILLA_INTERNAL_API
   // This code interferes with the C++ unit test startup code.
   nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &res);
   NS_ENSURE_SUCCESS(res, res);
-#endif
-
-  mThread = aThread;
-
-#ifdef MOZILLA_INTERNAL_API
   // Currently no standalone unit tests for DataChannel,
   // which is the user of mWindow
   MOZ_ASSERT(aWindow);
   mWindow = do_QueryInterface(aWindow);
   NS_ENSURE_STATE(mWindow);
 #endif
 
   res = PeerConnectionCtx::InitializeGlobal(mThread);
@@ -333,17 +458,24 @@ PeerConnectionImpl::Initialize(IPeerConn
     return NS_ERROR_FAILURE;
   }
 
   // Connect ICE slots.
   mMedia->SignalIceGatheringCompleted.connect(this, &PeerConnectionImpl::IceGatheringCompleted);
   mMedia->SignalIceCompleted.connect(this, &PeerConnectionImpl::IceCompleted);
 
   // Initialize the media object.
-  res = mMedia->Init();
+  if (aRTCConfiguration) {
+    RTCConfiguration ic;
+    res = ConvertRTCConfiguration(*aRTCConfiguration, &ic, aCx);
+    NS_ENSURE_SUCCESS(res, res);
+    res = mMedia->Init(ic.getServers());
+  } else {
+    res = mMedia->Init(aConfiguration->getServers());
+  }
   if (NS_FAILED(res)) {
     CSFLogErrorS(logTag, __FUNCTION__ << ": Couldn't initialize media object");
     return res;
   }
 
   // Generate a random handle
   unsigned char handle_bin[8];
   PK11_GenerateRandom(handle_bin, sizeof(handle_bin));
@@ -610,34 +742,34 @@ PeerConnectionImpl::NotifyDataChannel(al
  *
  * Optional constraints are ordered, and hence in an array. This function
  * converts a jsval that looks like the above into a MediaConstraints object.
  */
 nsresult
 PeerConnectionImpl::ConvertConstraints(
   const JS::Value& aConstraints, MediaConstraints* aObj, JSContext* aCx)
 {
-  jsval mandatory, optional;
+  JS::Value mandatory, optional;
   JSObject& constraints = aConstraints.toObject();
 
   // Mandatory constraints.  Note that we only care if the constraint array exists
   if (!JS_GetProperty(aCx, &constraints, "mandatory", &mandatory)) {
     return NS_ERROR_FAILURE;
   }
   if (!mandatory.isNullOrUndefined()) {
     if (!mandatory.isObject()) {
       return NS_ERROR_FAILURE;
     }
 
     JSObject* opts = JSVAL_TO_OBJECT(mandatory);
     JS::AutoIdArray mandatoryOpts(aCx, JS_Enumerate(aCx, opts));
 
     // Iterate over each property.
     for (size_t i = 0; i < mandatoryOpts.length(); i++) {
-      jsval option, optionName;
+      JS::Value option, optionName;
       if (!JS_GetPropertyById(aCx, opts, mandatoryOpts[i], &option) ||
           !JS_IdToValue(aCx, mandatoryOpts[i], &optionName) ||
           // We only support boolean constraints for now.
           !JSVAL_IS_BOOLEAN(option)) {
         return NS_ERROR_FAILURE;
       }
       JSString* optionNameString = JS_ValueToString(aCx, optionName);
       if (!optionNameString) {
@@ -658,18 +790,18 @@ PeerConnectionImpl::ConvertConstraints(
     }
 
     JSObject* opts = JSVAL_TO_OBJECT(optional);
     uint32_t length;
     if (!JS_IsArrayObject(aCx, opts) ||
         !JS_GetArrayLength(aCx, opts, &length)) {
       return NS_ERROR_FAILURE;
     }
-    for (size_t i = 0; i < length; i++) {
-      jsval val;
+    for (uint32_t i = 0; i < length; i++) {
+      JS::Value val;
       if (!JS_GetElement(aCx, opts, i, &val) ||
           !val.isObject()) {
         return NS_ERROR_FAILURE;
       }
       // Extract name & value and store.
       // FIXME: MediaConstraints does not support optional constraints?
     }
   }
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -41,32 +41,52 @@ namespace mozilla {
 #endif
 
 using namespace mozilla;
 
 namespace sipcc {
 
 class PeerConnectionWrapper;
 
-struct ConstraintInfo {
+struct ConstraintInfo
+{
   std::string  value;
   bool         mandatory;
 };
 typedef std::map<std::string, ConstraintInfo> constraints_map;
 
-class MediaConstraints {
+class MediaConstraints
+{
 public:
   void setBooleanConstraint(const std::string& constraint, bool enabled, bool mandatory);
 
   void buildArray(cc_media_constraints_t** constraintarray);
 
 private:
   constraints_map  mConstraints;
 };
 
+class RTCConfiguration
+{
+public:
+  bool addServer(const std::string& addr, uint16_t port)
+  {
+    NrIceStunServer* server(NrIceStunServer::Create(addr, port));
+    if (!server) {
+      return false;
+    }
+    addServer(*server);
+    return true;
+  }
+  void addServer(const NrIceStunServer& server) { mServers.push_back (server); }
+  const std::vector<NrIceStunServer>& getServers() const { return mServers; }
+private:
+  std::vector<NrIceStunServer> mServers;
+};
+
 class PeerConnectionWrapper;
 
 // Enter an API call and check that the state is OK,
 // the PC isn't closed, etc.
 #define PC_AUTO_ENTER_API_CALL(assert_ice_ready) \
     do { \
       /* do/while prevents res from conflicting with locals */    \
       nsresult res = CheckApiState(assert_ice_ready);             \
@@ -74,17 +94,18 @@ class PeerConnectionWrapper;
     } while(0)
 #define PC_AUTO_ENTER_API_CALL_NO_CHECK() CheckThread()
 
 
 class PeerConnectionImpl MOZ_FINAL : public IPeerConnection,
 #ifdef MOZILLA_INTERNAL_API
                                      public mozilla::DataChannelConnection::DataConnectionListener,
 #endif
-                                     public sigslot::has_slots<> {
+                                     public sigslot::has_slots<>
+{
 public:
   PeerConnectionImpl();
   ~PeerConnectionImpl();
 
   enum ReadyState {
     kNew,
     kNegotiating,
     kActive,
@@ -112,16 +133,18 @@ public:
     kRoleOfferer,
     kRoleAnswerer
   };
 
   NS_DECL_ISUPPORTS
   NS_DECL_IPEERCONNECTION
 
   static PeerConnectionImpl* CreatePeerConnection();
+  static nsresult ConvertRTCConfiguration(const JS::Value& aSrc,
+    RTCConfiguration *aDst, JSContext* aCx);
   static nsresult ConvertConstraints(
     const JS::Value& aConstraints, MediaConstraints* aObj, JSContext* aCx);
   static nsresult MakeMediaStream(uint32_t aHint, nsIDOMMediaStream** aStream);
 
   Role GetRole() const {
     PC_AUTO_ENTER_API_CALL_NO_CHECK();
     return mRole;
   }
@@ -179,25 +202,38 @@ public:
   // Create a fake media stream
   nsresult CreateFakeMediaStream(uint32_t hint, nsIDOMMediaStream** retval);
 
   nsPIDOMWindow* GetWindow() const {
     PC_AUTO_ENTER_API_CALL_NO_CHECK();
     return mWindow;
   }
 
+  // Initialize PeerConnection from an RTCConfiguration object.
+  nsresult Initialize(IPeerConnectionObserver* aObserver,
+                      nsIDOMWindow* aWindow,
+                      const RTCConfiguration& aConfiguration,
+                      nsIThread* aThread) {
+    return Initialize(aObserver, aWindow, &aConfiguration, nullptr, aThread, nullptr);
+  }
+
   // Validate constraints and construct a MediaConstraints object
   // from a JS::Value.
   NS_IMETHODIMP CreateOffer(MediaConstraints& aConstraints);
   NS_IMETHODIMP CreateAnswer(MediaConstraints& aConstraints);
 
 private:
   PeerConnectionImpl(const PeerConnectionImpl&rhs);
   PeerConnectionImpl& operator=(PeerConnectionImpl);
-
+  nsresult Initialize(IPeerConnectionObserver* aObserver,
+                      nsIDOMWindow* aWindow,
+                      const RTCConfiguration* aConfiguration,
+                      const JS::Value* aRTCConfiguration,
+                      nsIThread* aThread,
+                      JSContext* aCx);
   NS_IMETHODIMP CreateOfferInt(MediaConstraints& constraints);
   NS_IMETHODIMP CreateAnswerInt(MediaConstraints& constraints);
 
   nsresult CloseInt(bool aIsSynchronous);
   void ChangeReadyState(ReadyState aReadyState);
   nsresult CheckApiState(bool assert_ice_ready) const;
   void CheckThread() const {
     NS_ABORT_IF_FALSE(CheckThreadInt(), "Wrong thread");
@@ -267,17 +303,18 @@ private:
 public:
   //these are temporary until the DataChannel Listen/Connect API is removed
   unsigned short listenPort;
   unsigned short connectPort;
   char *connectStr; // XXX ownership/free
 };
 
 // This is what is returned when you acquire on a handle
-class PeerConnectionWrapper {
+class PeerConnectionWrapper
+{
  public:
   PeerConnectionWrapper(const std::string& handle);
 
   PeerConnectionImpl *impl() { return impl_; }
 
  private:
   nsRefPtr<PeerConnectionImpl> impl_;
 };
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -63,43 +63,29 @@ PeerConnectionImpl* PeerConnectionImpl::
   PeerConnectionImpl *pc = new PeerConnectionImpl();
 
   CSFLogDebugS(logTag, "Created PeerConnection: " << static_cast<void*>(pc));
 
   return pc;
 }
 
 
-nsresult PeerConnectionMedia::Init()
+nsresult PeerConnectionMedia::Init(const std::vector<NrIceStunServer>& stun_servers)
 {
   // TODO(ekr@rtfm.com): need some way to set not offerer later
   // Looks like a bug in the NrIceCtx API.
   mIceCtx = NrIceCtx::Create("PC:" + mParent->GetHandle(), true);
   if(!mIceCtx) {
     CSFLogErrorS(logTag, __FUNCTION__ << ": Failed to create Ice Context");
     return NS_ERROR_FAILURE;
   }
-
-  // Temporarily hardwire the ICE server.
-  // TODO(ekr@rtfm.com): Remove this when we have ICE configuration
-  // settings.
-  std::vector<NrIceStunServer> stun_servers;
-  ScopedDeletePtr<NrIceStunServer> server(NrIceStunServer::Create(
-      std::string((char *)"216.93.246.14"), 3478));
-  MOZ_ASSERT(server);
-  if (!server) {
-    CSFLogErrorS(logTag, __FUNCTION__ << ": Could not parse STUN server string");
-    return NS_ERROR_FAILURE;
+  nsresult rv = mIceCtx->SetStunServers(stun_servers);
+  if (NS_FAILED(rv)) {
+    return rv;
   }
-    
-  stun_servers.push_back(*server);
-  nsresult rv = mIceCtx->SetStunServers(stun_servers);
-  if (NS_FAILED(rv))
-    return rv;
-
   mIceCtx->SignalGatheringCompleted.connect(this,
                                             &PeerConnectionMedia::IceGatheringCompleted);
   mIceCtx->SignalCompleted.connect(this,
                                    &PeerConnectionMedia::IceCompleted);
 
   // Create three streams to start with.
   // One each for audio, video and DataChannel
   // TODO: this will be re-visited
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -231,17 +231,17 @@ class PeerConnectionMedia : public sigsl
       : mParent(parent),
       mLocalSourceStreamsLock(PR_NewLock()),
       mIceCtx(NULL) {}
 
   ~PeerConnectionMedia() {
     PR_DestroyLock(mLocalSourceStreamsLock);
   }
 
-  nsresult Init();
+  nsresult Init(const std::vector<mozilla::NrIceStunServer>& stun_servers);
 
   // WARNING: This destroys the object!
   void SelfDestruct();
 
   mozilla::RefPtr<mozilla::NrIceCtx> ice_ctx() const { return mIceCtx; }
 
   mozilla::RefPtr<mozilla::NrIceMediaStream> ice_media_stream(size_t i) const {
     // TODO(ekr@rtfm.com): If someone asks for a value that doesn't exist,
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -23,16 +23,17 @@ using namespace std;
 #include "FakeMediaStreams.h"
 #include "FakeMediaStreamsImpl.h"
 #include "PeerConnectionImpl.h"
 #include "PeerConnectionCtx.h"
 #include "runnable_utils.h"
 #include "nsStaticComponents.h"
 #include "nsIDOMRTCPeerConnection.h"
 #include "nsWeakReference.h"
+#include "nricectx.h"
 
 #include "mtransport_test_utils.h"
 MtransportTestUtils *test_utils;
 
 
 
 static int kDefaultTimeout = 5000;
 
@@ -500,17 +501,19 @@ class SignalingAgent {
     ASSERT_TRUE(found > 0);
 
     pc = sipcc::PeerConnectionImpl::CreatePeerConnection();
     ASSERT_TRUE(pc);
 
     pObserver = new TestObserver(pc);
     ASSERT_TRUE(pObserver);
 
-    ASSERT_EQ(pc->Initialize(pObserver, nullptr, thread), NS_OK);
+    sipcc::RTCConfiguration cfg;
+    cfg.addServer("23.21.150.121", 3478);
+    ASSERT_EQ(pc->Initialize(pObserver, nullptr, cfg, thread), NS_OK);
 
   }
 
   void Init(nsCOMPtr<nsIThread> thread)
   {
     thread->Dispatch(
       WrapRunnable(this, &SignalingAgent::Init_m, thread),
       NS_DISPATCH_SYNC);