Bug 842549 - Part 1: Generate trickle candidates from nICEr, with testing r=abr
authorEKR <ekr@rtfm.com>
Thu, 19 Sep 2013 09:17:52 -0700
changeset 148719 e79cae6e6d1563c33ab61bd2fb996296a80cc1b2
parent 148718 2d0c10c47ac51106de993ea1da549b10f5337862
child 148720 3f5b5ef7db3085a754458ffcbe9ecf5dad7be9a4
push id34316
push useradam@nostrum.com
push dateThu, 26 Sep 2013 00:58:27 +0000
treeherdermozilla-inbound@fe35cf0e4c07 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersabr
bugs842549
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 842549 - Part 1: Generate trickle candidates from nICEr, with testing r=abr
media/mtransport/nricectx.cpp
media/mtransport/nricectx.h
media/mtransport/nricemediastream.cpp
media/mtransport/nricemediastream.h
media/mtransport/test/ice_unittest.cpp
media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
media/mtransport/third_party/nICEr/src/ice/ice_component.c
media/mtransport/third_party/nICEr/src/ice/ice_component.h
media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -247,17 +247,17 @@ int NrIceCtx::select_pair(void *obj,nr_i
             << potential_ct);
 
   return 0;
 }
 
 int NrIceCtx::stream_ready(void *obj, nr_ice_media_stream *stream) {
   MOZ_MTLOG(ML_DEBUG, "stream_ready called");
 
-  // Get the ICE ctx
+  // Get the ICE ctx.
   NrIceCtx *ctx = static_cast<NrIceCtx *>(obj);
 
   RefPtr<NrIceMediaStream> s = ctx->FindStream(stream);
 
   // Streams which do not exist should never be ready.
   MOZ_ASSERT(s);
 
   s->Ready();
@@ -301,16 +301,40 @@ int NrIceCtx::msg_recvd(void *obj, nr_ic
   // Streams which do not exist should never have packets.
   MOZ_ASSERT(s);
 
   s->SignalPacketReceived(s, component_id, msg, len);
 
   return 0;
 }
 
+void NrIceCtx::trickle_cb(void *arg, nr_ice_ctx *ice_ctx,
+                          nr_ice_media_stream *stream,
+                          int component_id,
+                          nr_ice_candidate *candidate) {
+  // Get the ICE ctx
+  NrIceCtx *ctx = static_cast<NrIceCtx *>(arg);
+  RefPtr<NrIceMediaStream> s = ctx->FindStream(stream);
+
+  // Streams which do not exist shouldn't have candidates.
+  MOZ_ASSERT(s);
+
+  // Format the candidate.
+  char candidate_str[NR_ICE_MAX_ATTRIBUTE_SIZE];
+  int r = nr_ice_format_candidate_attribute(candidate, candidate_str,
+                                            sizeof(candidate_str));
+  MOZ_ASSERT(!r);
+  if (r)
+    return;
+
+  MOZ_MTLOG(ML_INFO, "NrIceCtx(" << ctx->name_ << "): trickling candidate "
+            << candidate_str);
+
+  s->SignalCandidate(s, candidate_str);
+}
 
 RefPtr<NrIceCtx> NrIceCtx::Create(const std::string& name,
                                   bool offerer,
                                   bool set_interface_priorities) {
   RefPtr<NrIceCtx> ctx = new NrIceCtx(name, offerer);
 
   // Initialize the crypto callbacks
   if (!initialized) {
@@ -378,16 +402,24 @@ RefPtr<NrIceCtx> NrIceCtx::Create(const 
 
   r = nr_ice_ctx_set_interface_prioritizer(ctx->ctx_, prioritizer);
   if (r) {
     MOZ_MTLOG(PR_LOG_ERROR, "Couldn't set interface prioritizer.");
     return nullptr;
   }
 #endif  // USE_INTERFACE_PRIORITIZER
 
+  if (ctx->generating_trickle()) {
+    r = nr_ice_ctx_set_trickle_cb(ctx->ctx_, &NrIceCtx::trickle_cb, ctx);
+    if (r) {
+      MOZ_MTLOG(ML_ERROR, "Couldn't set trickle cb for '" << name << "'");
+      return nullptr;
+    }
+  }
+
   // Create the handler objects
   ctx->ice_handler_vtbl_ = new nr_ice_handler_vtbl();
   ctx->ice_handler_vtbl_->select_pair = &NrIceCtx::select_pair;
   ctx->ice_handler_vtbl_->stream_ready = &NrIceCtx::stream_ready;
   ctx->ice_handler_vtbl_->stream_failed = &NrIceCtx::stream_failed;
   ctx->ice_handler_vtbl_->ice_completed = &NrIceCtx::ice_completed;
   ctx->ice_handler_vtbl_->msg_recvd = &NrIceCtx::msg_recvd;
 
@@ -530,25 +562,16 @@ nsresult NrIceCtx::StartGathering() {
       return NS_ERROR_FAILURE;
   }
 
   SetState(ICE_CTX_GATHERING);
 
   return NS_OK;
 }
 
-void NrIceCtx::EmitAllCandidates() {
-  MOZ_MTLOG(ML_NOTICE, "Gathered all ICE candidates for '"
-            << name_ << "'");
-
-  for(size_t i=0; i<streams_.size(); ++i) {
-    streams_[i]->EmitAllCandidates();
-  }
-}
-
 RefPtr<NrIceMediaStream> NrIceCtx::FindStream(
     nr_ice_media_stream *stream) {
   for (size_t i=0; i<streams_.size(); ++i) {
     if (streams_[i]->stream() == stream) {
       return streams_[i];
     }
   }
 
@@ -625,18 +648,16 @@ nsresult NrIceCtx::StartChecks() {
 
   return NS_OK;
 }
 
 
 void NrIceCtx::initialized_cb(NR_SOCKET s, int h, void *arg) {
   NrIceCtx *ctx = static_cast<NrIceCtx *>(arg);
 
-  ctx->EmitAllCandidates();
-
   ctx->SetState(ICE_CTX_GATHERED);
 }
 
 nsresult NrIceCtx::Finalize() {
   int r = nr_ice_ctx_finalize(ctx_, peer_);
 
   if (r) {
     MOZ_MTLOG(ML_ERROR, "Couldn't finalize "
--- a/media/mtransport/nricectx.h
+++ b/media/mtransport/nricectx.h
@@ -64,16 +64,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 
 #include "m_cpp_utils.h"
 
 typedef struct nr_ice_ctx_ nr_ice_ctx;
 typedef struct nr_ice_peer_ctx_ nr_ice_peer_ctx;
 typedef struct nr_ice_media_stream_ nr_ice_media_stream;
 typedef struct nr_ice_handler_ nr_ice_handler;
 typedef struct nr_ice_handler_vtbl_ nr_ice_handler_vtbl;
+typedef struct nr_ice_candidate_ nr_ice_candidate;
 typedef struct nr_ice_cand_pair_ nr_ice_cand_pair;
 typedef struct nr_ice_stun_server_ nr_ice_stun_server;
 typedef struct nr_ice_turn_server_ nr_ice_turn_server;
 typedef struct nr_resolver_ nr_resolver;
 
 typedef void* NR_SOCKET;
 
 namespace mozilla {
@@ -216,16 +217,19 @@ class NrIceCtx {
 
   // Start checking
   nsresult StartChecks();
 
   // Finalize the ICE negotiation. I.e., there will be no
   // more forking.
   nsresult Finalize();
 
+  // Are we trickling?
+  bool generating_trickle() const { return trickle_; }
+
   // Signals to indicate events. API users can (and should)
   // register for these.
   // TODO(ekr@rtfm.com): refactor this to be state change instead
   // so we don't need to keep adding signals?
   sigslot::signal1<NrIceCtx *> SignalGatheringCompleted;  // Done gathering
   sigslot::signal1<NrIceCtx *> SignalCompleted;  // Done handshaking
   sigslot::signal1<NrIceCtx *> SignalFailed;  // Failure.
 
@@ -239,18 +243,18 @@ class NrIceCtx {
            bool offerer)
   : state_(ICE_CTX_INIT),
     name_(name),
     offerer_(offerer),
     streams_(),
     ctx_(nullptr),
     peer_(nullptr),
     ice_handler_vtbl_(nullptr),
-    ice_handler_(nullptr)
-  {
+    ice_handler_(nullptr),
+    trickle_(true) {
     // XXX: offerer_ will be used eventually;  placate clang in the meantime.
     (void)offerer_;
   }
 
   DISALLOW_COPY_ASSIGN(NrIceCtx);
 
   // Callbacks for nICEr
   static void initialized_cb(NR_SOCKET s, int h, void *arg);  // ICE initialized
@@ -260,33 +264,32 @@ class NrIceCtx {
                          int component_id, nr_ice_cand_pair **potentials,
                          int potential_ct);
   static int stream_ready(void *obj, nr_ice_media_stream *stream);
   static int stream_failed(void *obj, nr_ice_media_stream *stream);
   static int ice_completed(void *obj, nr_ice_peer_ctx *pctx);
   static int msg_recvd(void *obj, nr_ice_peer_ctx *pctx,
                        nr_ice_media_stream *stream, int component_id,
                        unsigned char *msg, int len);
-
-  // Iterate through all media streams and emit the candidates
-  // Note that we don't do trickle ICE yet
-  void EmitAllCandidates();
+  static void trickle_cb(void *arg, nr_ice_ctx *ctx, nr_ice_media_stream *stream,
+                         int component_id, nr_ice_candidate *candidate);
 
   // Find a media stream by stream ptr. Gross
   RefPtr<NrIceMediaStream> FindStream(nr_ice_media_stream *stream);
 
   // Set the state
   void SetState(State state);
 
   State state_;
   const std::string name_;
   bool offerer_;
   std::vector<RefPtr<NrIceMediaStream> > streams_;
   nr_ice_ctx *ctx_;
   nr_ice_peer_ctx *peer_;
   nr_ice_handler_vtbl* ice_handler_vtbl_;  // Must be pointer
   nr_ice_handler* ice_handler_;  // Must be pointer
+  bool trickle_;
   nsCOMPtr<nsIEventTarget> sts_target_; // The thread to run on
 };
 
 
 }  // close namespace
 #endif
--- a/media/mtransport/nricemediastream.cpp
+++ b/media/mtransport/nricemediastream.cpp
@@ -238,36 +238,16 @@ nsresult NrIceMediaStream::GetActivePair
     *localp = local.forget();
   if (remotep)
     *remotep = remote.forget();
 
   return NS_OK;
 }
 
 
-void NrIceMediaStream::EmitAllCandidates() {
-  char **attrs = 0;
-  int attrct;
-  int r;
-  r = nr_ice_media_stream_get_attributes(stream_,
-                                         &attrs, &attrct);
-  if (r) {
-    MOZ_MTLOG(ML_ERROR, "Couldn't get ICE candidates for '"
-         << name_ << "'");
-    return;
-  }
-
-  for (int i=0; i<attrct; i++) {
-    SignalCandidate(this, attrs[i]);
-    RFREE(attrs[i]);
-  }
-
-  RFREE(attrs);
-}
-
 nsresult NrIceMediaStream::GetCandidatePairs(std::vector<NrIceCandidatePair>*
                                              out_pairs) const {
   MOZ_ASSERT(out_pairs);
 
   // Get the check_list on the peer stream (this is where the check_list
   // actually lives, not in stream_)
   nr_ice_media_stream* peer_stream;
   int r = nr_ice_peer_ctx_find_pstream(ctx_->peer(), stream_, &peer_stream);
@@ -327,33 +307,45 @@ nsresult NrIceMediaStream::GetDefaultCan
                                                std::string *addrp,
                                                int *portp) {
   nr_ice_candidate *cand;
   int r;
 
   r = nr_ice_media_stream_get_default_candidate(stream_,
                                                 component, &cand);
   if (r) {
-    MOZ_MTLOG(ML_ERROR, "Couldn't get default ICE candidate for '"
-              << name_ << "'");
-    return NS_ERROR_NOT_AVAILABLE;
-  }
+    if (ctx_->generating_trickle()) {
+      // Generate default trickle candidates.
+      // draft-ivov-mmusic-trickle-ice-01.txt says to use port 9
+      // but "::" instead of "0.0.0.0". Since we don't do any
+      // IPv6 we are ignoring that for now.
+      *addrp = "0.0.0.0";
+      *portp = 9;
+    }
+    else {
+      MOZ_MTLOG(ML_ERROR, "Couldn't get default ICE candidate for '"
+                << name_ << "'");
 
-  char addr[64];  // Enough for IPv6 with colons.
-  r = nr_transport_addr_get_addrstring(&cand->addr,addr,sizeof(addr));
-  if (r)
-    return NS_ERROR_FAILURE;
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  }
+  else {
+    char addr[64];  // Enough for IPv6 with colons.
+    r = nr_transport_addr_get_addrstring(&cand->addr,addr,sizeof(addr));
+    if (r)
+      return NS_ERROR_FAILURE;
 
-  int port;
-  r=nr_transport_addr_get_port(&cand->addr,&port);
-  if (r)
-    return NS_ERROR_FAILURE;
+    int port;
+    r=nr_transport_addr_get_port(&cand->addr,&port);
+    if (r)
+      return NS_ERROR_FAILURE;
 
-  *addrp = addr;
-  *portp = port;
+    *addrp = addr;
+    *portp = port;
+  }
 
   return NS_OK;
 }
 
 std::vector<std::string> NrIceMediaStream::GetCandidates() const {
   char **attrs = 0;
   int attrct;
   int r;
--- a/media/mtransport/nricemediastream.h
+++ b/media/mtransport/nricemediastream.h
@@ -101,16 +101,22 @@ struct NrIceCandidatePair {
   // Set if this candidate pair has been selected. Note: Since we are using
   // aggressive nomination, this could change frequently as ICE runs.
   bool selected;
   NrIceCandidate local;
   NrIceCandidate remote;
   // TODO(bcampen@mozilla.com): Is it important to put the foundation in here?
 };
 
+// Abstract base class for opaque values.
+class NrIceOpaque {
+ public:
+  virtual ~NrIceOpaque() {}
+};
+
 class NrIceMediaStream {
  public:
   static RefPtr<NrIceMediaStream> Create(NrIceCtx *ctx,
                                          const std::string& name,
                                          int components);
   ~NrIceMediaStream();
 
   enum State { ICE_CONNECTING, ICE_OPEN, ICE_CLOSED};
@@ -159,42 +165,46 @@ class NrIceMediaStream {
   void Ready();
 
   // Close the stream. Called by the NrIceCtx.
   // Different from the destructor because other people
   // might be holding RefPtrs but we want those writes to fail once
   // the context has been destroyed.
   void Close();
 
+  // Set an opaque value. Owned by the media stream.
+  void SetOpaque(NrIceOpaque *opaque) { opaque_ = opaque; }
+
+  // Get the opaque
+  NrIceOpaque* opaque() const { return opaque_; }
+
   sigslot::signal2<NrIceMediaStream *, const std::string& >
   SignalCandidate;  // A new ICE candidate:
   sigslot::signal1<NrIceMediaStream *> SignalReady;  // Candidate pair ready.
   sigslot::signal1<NrIceMediaStream *> SignalFailed;  // Candidate pair failed.
   sigslot::signal4<NrIceMediaStream *, int, const unsigned char *, int>
   SignalPacketReceived;  // Incoming packet
 
-  // Emit all the ICE candidates. Note that this doesn't
-  // work for trickle ICE yet--called internally
-  void EmitAllCandidates();
-
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrIceMediaStream)
 
  private:
   NrIceMediaStream(NrIceCtx *ctx,  const std::string& name,
                    int components) :
       state_(ICE_CONNECTING),
       ctx_(ctx),
       name_(name),
       components_(components),
-      stream_(nullptr) {}
+      stream_(nullptr),
+      opaque_(nullptr) {}
 
   DISALLOW_COPY_ASSIGN(NrIceMediaStream);
 
   State state_;
   NrIceCtx *ctx_;
   const std::string name_;
   const int components_;
   nr_ice_media_stream *stream_;
+  ScopedDeletePtr<NrIceOpaque> opaque_;
 };
 
 
 }  // close namespace
 #endif
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -49,23 +49,26 @@ const std::string kDefaultStunServerAddr
 const std::string kDefaultStunServerHostname(
     (char *)"ec2-23-21-150-121.compute-1.amazonaws.com");
 const std::string kBogusStunServerHostname(
     (char *)"stun-server-nonexistent.invalid");
 const uint16_t kDefaultStunServerPort=3478;
 const std::string kBogusIceCandidate(
     (char *)"candidate:0 2 UDP 2113601790 192.168.178.20 50769 typ");
 
+std::string g_stun_server_address(kDefaultStunServerAddress);
+std::string g_stun_server_hostname(kDefaultStunServerHostname);
 std::string g_turn_server;
 std::string g_turn_user;
 std::string g_turn_password;
 
 namespace {
 
-enum TrickleMode { TRICKLE_NONE, TRICKLE_DEFERRED };
+enum TrickleMode { TRICKLE_NONE, TRICKLE_SIMULATE, TRICKLE_REAL };
+
 typedef bool (*CandidateFilter)(const std::string& candidate);
 
 static bool IsRelayCandidate(const std::string& candidate) {
   return candidate.find("typ relay") != std::string::npos;
 }
 
 bool ContainsSucceededPair(const std::vector<NrIceCandidatePair>& pairs) {
   for (size_t i = 0; i < pairs.size(); ++i) {
@@ -123,17 +126,19 @@ class IceTestPeer : public sigslot::has_
       ice_complete_(false),
       received_(0),
       sent_(0),
       fake_resolver_(),
       dns_resolver_(new NrIceResolver()),
       remote_(nullptr),
       candidate_filter_(nullptr),
       expected_local_type_(NrIceCandidate::ICE_HOST),
-      expected_remote_type_(NrIceCandidate::ICE_HOST) {
+      expected_remote_type_(NrIceCandidate::ICE_HOST),
+      trickle_mode_(TRICKLE_NONE),
+      trickled_(0) {
     ice_ctx_->SignalGatheringCompleted.connect(this,
                                                &IceTestPeer::GatheringComplete);
     ice_ctx_->SignalCompleted.connect(this, &IceTestPeer::IceCompleted);
   }
 
   ~IceTestPeer() {
     test_utils->sts_target()->Dispatch(WrapRunnable(this,
                                                     &IceTestPeer::Shutdown),
@@ -149,17 +154,17 @@ class IceTestPeer : public sigslot::has_
     snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(),
              (int)streams_.size());
 
     mozilla::RefPtr<NrIceMediaStream> stream =
         ice_ctx_->CreateStream(static_cast<char *>(name), components);
 
     ASSERT_TRUE(stream);
     streams_.push_back(stream);
-    stream->SignalCandidate.connect(this, &IceTestPeer::GotCandidate);
+    stream->SignalCandidate.connect(this, &IceTestPeer::CandidateInitialized);
     stream->SignalReady.connect(this, &IceTestPeer::StreamReady);
     stream->SignalFailed.connect(this, &IceTestPeer::StreamFailed);
     stream->SignalPacketReceived.connect(this, &IceTestPeer::PacketReceived);
   }
 
   void SetStunServer(const std::string addr, uint16_t port) {
     std::vector<NrIceStunServer> stun_servers;
     ScopedDeletePtr<NrIceStunServer> server(NrIceStunServer::Create(addr,
@@ -184,21 +189,21 @@ class IceTestPeer : public sigslot::has_
         addr, port, username, password));
     turn_servers.push_back(*server);
     ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetTurnServers(turn_servers)));
   }
 
   void SetFakeResolver() {
     ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init()));
     PRNetAddr addr;
-    PRStatus status = PR_StringToNetAddr(kDefaultStunServerAddress.c_str(),
-                                         &addr);
+    PRStatus status = PR_StringToNetAddr(g_stun_server_address.c_str(),
+                                          &addr);
     addr.inet.port = kDefaultStunServerPort;
     ASSERT_EQ(PR_SUCCESS, status);
-    fake_resolver_.SetAddr(kDefaultStunServerHostname, addr);
+    fake_resolver_.SetAddr(g_stun_server_hostname, addr);
     ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetResolver(
         fake_resolver_.AllocateResolver())));
   }
 
   void SetDNSResolver() {
     ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init()));
     ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetResolver(
         dns_resolver_->AllocateResolver())));
@@ -214,17 +219,28 @@ class IceTestPeer : public sigslot::has_
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
   // Get various pieces of state
   std::vector<std::string> GetGlobalAttributes() {
     return ice_ctx_->GetGlobalAttributes();
   }
 
-  std::vector<std::string> GetCandidates(size_t stream) {
+   std::vector<std::string> GetCandidates(size_t stream) {
+    std::vector<std::string> v;
+
+    RUN_ON_THREAD(
+        test_utils->sts_target(),
+        WrapRunnableRet(this, &IceTestPeer::GetCandidates_s, stream, &v),
+        NS_DISPATCH_SYNC);
+
+    return v;
+  }
+
+  std::vector<std::string> GetCandidates_s(size_t stream) {
     std::vector<std::string> candidates;
 
     if (stream >= streams_.size())
       return candidates;
 
     std::vector<std::string> candidates_in =
       streams_[stream]->GetCandidates();
 
@@ -234,71 +250,79 @@ class IceTestPeer : public sigslot::has_
         std::cerr << "Returning candidate: " << candidates_in[i] << std::endl;
         candidates.push_back(candidates_in[i]);
       }
     }
 
     return candidates;
   }
 
-  void SetExpectedTypes(NrIceCandidate::Type local, NrIceCandidate::Type remote) {
+  void SetExpectedTypes(NrIceCandidate::Type local,
+                        NrIceCandidate::Type remote) {
     expected_local_type_ = local;
     expected_remote_type_ = remote;
   }
 
   bool gathering_complete() { return gathering_complete_; }
   int ready_ct() { return ready_ct_; }
   bool is_ready(size_t stream) {
     return streams_[stream]->state() == NrIceMediaStream::ICE_OPEN;
   }
   bool ice_complete() { return ice_complete_; }
   size_t received() { return received_; }
   size_t sent() { return sent_; }
 
   // Start connecting to another peer
-  void Connect(IceTestPeer *remote, TrickleMode trickle_mode,
-               bool start = true) {
+  void Connect_s(IceTestPeer *remote, TrickleMode trickle_mode,
+                 bool start = true) {
     nsresult res;
 
     remote_ = remote;
 
-    test_utils->sts_target()->Dispatch(
-      WrapRunnableRet(ice_ctx_,
-        &NrIceCtx::ParseGlobalAttributes, remote->GetGlobalAttributes(), &res),
-      NS_DISPATCH_SYNC);
+    trickle_mode_ = trickle_mode;
+    res = ice_ctx_->ParseGlobalAttributes(remote->GetGlobalAttributes());
     ASSERT_TRUE(NS_SUCCEEDED(res));
 
-    if (trickle_mode == TRICKLE_NONE) {
+    if (trickle_mode == TRICKLE_NONE ||
+        trickle_mode == TRICKLE_REAL) {
       for (size_t i=0; i<streams_.size(); ++i) {
-        test_utils->sts_target()->Dispatch(
-            WrapRunnableRet(streams_[i], &NrIceMediaStream::ParseAttributes,
-                            remote->GetCandidates(i),
-                            &res), NS_DISPATCH_SYNC);
+        std::vector<std::string> candidates =
+            remote->GetCandidates(i);
 
+        for (size_t j=0; j<candidates.size(); ++j) {
+          std::cerr << "Candidate: " + candidates[j] << std::endl;
+        }
+        res = streams_[i]->ParseAttributes(candidates);
         ASSERT_TRUE(NS_SUCCEEDED(res));
       }
     } else {
       // Parse empty attributes and then trickle them out later
       for (size_t i=0; i<streams_.size(); ++i) {
         std::vector<std::string> empty_attrs;
-        test_utils->sts_target()->Dispatch(
-            WrapRunnableRet(streams_[i], &NrIceMediaStream::ParseAttributes,
-                            empty_attrs,
-                            &res), NS_DISPATCH_SYNC);
-
+        res = streams_[i]->ParseAttributes(empty_attrs);
         ASSERT_TRUE(NS_SUCCEEDED(res));
       }
     }
 
     if (start) {
-      StartChecks();
+      // Now start checks
+      res = ice_ctx_->StartChecks();
+      ASSERT_TRUE(NS_SUCCEEDED(res));
     }
   }
 
-  void DoTrickle(size_t stream) {
+  void Connect(IceTestPeer *remote, TrickleMode trickle_mode,
+               bool start = true) {
+    test_utils->sts_target()->Dispatch(
+        WrapRunnable(
+            this, &IceTestPeer::Connect_s, remote, trickle_mode, start),
+        NS_DISPATCH_SYNC);
+  }
+
+  void SimulateTrickle(size_t stream) {
     std::cerr << "Doing trickle for stream " << stream << std::endl;
     // If we are in trickle deferred mode, now trickle in the candidates
     // for |stream}
     nsresult res;
 
     ASSERT_GT(remote_->streams_.size(), stream);
 
     std::vector<std::string> candidates =
@@ -387,22 +411,51 @@ class IceTestPeer : public sigslot::has_
     test_utils->sts_target()->Dispatch(
         WrapRunnableRet(ice_ctx_, &NrIceCtx::StartChecks, &res),
         NS_DISPATCH_SYNC);
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
   // Handle events
   void GatheringComplete(NrIceCtx *ctx) {
+    std::cerr << "Gathering complete for " <<  name_ << std::endl;
     gathering_complete_ = true;
+
+    std::cerr << "CANDIDATES:" << std::endl;
+    for (size_t i=0; i<streams_.size(); ++i) {
+      std::cerr << "Stream " << name_ << std::endl;
+      std::vector<std::string> candidates =
+          streams_[i]->GetCandidates();
+
+      for(size_t j=0; j<candidates.size(); ++j) {
+        std::cerr << candidates[j] << std::endl;
+      }
+    }
+    std::cerr << std::endl;
+
   }
 
-  void GotCandidate(NrIceMediaStream *stream, const std::string &candidate) {
-    std::cerr << "Got candidate " << candidate << std::endl;
+  void CandidateInitialized(NrIceMediaStream *stream, const std::string &candidate) {
+    std::cerr << "Candidate initialized: " << candidate << std::endl;
     candidates_[stream->name()].push_back(candidate);
+
+    // If we are connected, then try to trickle to the
+    // other side.
+    if (remote_ && remote_->remote_) {
+      std::vector<mozilla::RefPtr<NrIceMediaStream> >::iterator it =
+          std::find(streams_.begin(), streams_.end(), stream);
+      ASSERT_NE(streams_.end(), it);
+      size_t index = it - streams_.begin();
+
+      ASSERT_GT(remote_->streams_.size(), index);
+      nsresult res = remote_->streams_[index]->ParseTrickleCandidate(
+          candidate);
+      ASSERT_TRUE(NS_SUCCEEDED(res));
+      ++trickled_;
+    }
   }
 
   nsresult GetCandidatePairs(size_t stream_index,
                              std::vector<NrIceCandidatePair>* pairs) {
     MOZ_ASSERT(pairs);
     if (stream_index >= streams_.size()) {
       // Is there a better error for "no such index"?
       ADD_FAILURE() << "No such media stream index: " << stream_index;
@@ -565,47 +618,57 @@ class IceTestPeer : public sigslot::has_
   }
 
   void DisableComponent(size_t stream, int component_id) {
     ASSERT_LT(stream, streams_.size());
     nsresult res = streams_[stream]->DisableComponent(component_id);
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
+  int trickled() { return trickled_; }
+
  private:
   std::string name_;
   nsRefPtr<NrIceCtx> ice_ctx_;
   std::vector<mozilla::RefPtr<NrIceMediaStream> > streams_;
   std::map<std::string, std::vector<std::string> > candidates_;
   bool gathering_complete_;
   int ready_ct_;
   bool ice_complete_;
   size_t received_;
   size_t sent_;
   NrIceResolverFake fake_resolver_;
   nsRefPtr<NrIceResolver> dns_resolver_;
   IceTestPeer *remote_;
   CandidateFilter candidate_filter_;
   NrIceCandidate::Type expected_local_type_;
   NrIceCandidate::Type expected_remote_type_;
+  TrickleMode trickle_mode_;
+  int trickled_;
 };
 
 class IceGatherTest : public ::testing::Test {
  public:
   void SetUp() {
     test_utils->sts_target()->Dispatch(WrapRunnable(TestStunServer::GetInstance(),
                                                     &TestStunServer::Reset),
                                        NS_DISPATCH_SYNC);
     peer_ = new IceTestPeer("P1", true, false);
     peer_->AddStream(1);
   }
 
-  void Gather() {
-    peer_->Gather();
+  void Gather(bool wait = true) {
+     peer_->Gather();
 
+    if (wait) {
+      WaitForGather();
+    }
+  }
+
+  void WaitForGather() {
     ASSERT_TRUE_WAIT(peer_->gathering_complete(), 10000);
   }
 
   void UseFakeStunServerWithResponse(const std::string& fake_addr,
                                      uint16_t fake_port) {
     TestStunServer::GetInstance()->SetResponseAddr(fake_addr, fake_port);
     // Sets an additional stun server
     peer_->SetStunServer(TestStunServer::GetInstance()->addr(),
@@ -650,28 +713,29 @@ class IceConnectTest : public ::testing:
       p1_ = new IceTestPeer("P1", true, set_priorities);
       p2_ = new IceTestPeer("P2", false, set_priorities);
     }
     initted_ = true;
   }
 
   bool Gather(bool wait) {
     Init(false);
-    p1_->SetStunServer(kDefaultStunServerAddress, kDefaultStunServerPort);
-    p2_->SetStunServer(kDefaultStunServerAddress, kDefaultStunServerPort);
+    p1_->SetStunServer(g_stun_server_address, kDefaultStunServerPort);
+    p2_->SetStunServer(g_stun_server_address, kDefaultStunServerPort);
     p1_->Gather();
     p2_->Gather();
 
-    EXPECT_TRUE_WAIT(p1_->gathering_complete(), 10000);
-    if (!p1_->gathering_complete())
-      return false;
-    EXPECT_TRUE_WAIT(p2_->gathering_complete(), 10000);
-    if (!p2_->gathering_complete())
-      return false;
-
+    if (wait) {
+      EXPECT_TRUE_WAIT(p1_->gathering_complete(), 10000);
+      if (!p1_->gathering_complete())
+        return false;
+      EXPECT_TRUE_WAIT(p2_->gathering_complete(), 10000);
+      if (!p2_->gathering_complete())
+        return false;
+    }
     return true;
   }
 
   void SetTurnServer(const std::string addr, uint16_t port,
                      const std::string username,
                      const std::string password) {
     p1_->SetTurnServer(addr, port, username, password);
     p2_->SetTurnServer(addr, port, username, password);
@@ -715,34 +779,39 @@ class IceConnectTest : public ::testing:
   }
 
   void WaitForComplete(int expected_streams = 1) {
     ASSERT_TRUE_WAIT(p1_->ready_ct() == expected_streams &&
                      p2_->ready_ct() == expected_streams, 5000);
     ASSERT_TRUE_WAIT(p1_->ice_complete() && p2_->ice_complete(), 5000);
   }
 
-  void ConnectTrickle() {
-    p1_->Connect(p2_, TRICKLE_DEFERRED);
-    p2_->Connect(p1_, TRICKLE_DEFERRED);
+  void WaitForGather() {
+    ASSERT_TRUE_WAIT(p1_->gathering_complete(), 10000);
+    ASSERT_TRUE_WAIT(p2_->gathering_complete(), 10000);
   }
 
-  void DoTrickle(size_t stream) {
-    p1_->DoTrickle(stream);
-    p2_->DoTrickle(stream);
+  void ConnectTrickle(TrickleMode trickle = TRICKLE_SIMULATE) {
+    p1_->Connect(p2_, trickle);
+    p2_->Connect(p1_, trickle);
+  }
+
+  void SimulateTrickle(size_t stream) {
+    p1_->SimulateTrickle(stream);
+    p2_->SimulateTrickle(stream);
     ASSERT_TRUE_WAIT(p1_->is_ready(stream), 5000);
     ASSERT_TRUE_WAIT(p2_->is_ready(stream), 5000);
   }
 
-  void DoTrickleP1(size_t stream) {
-    p1_->DoTrickle(stream);
+  void SimulateTrickleP1(size_t stream) {
+    p1_->SimulateTrickle(stream);
   }
 
-  void DoTrickleP2(size_t stream) {
-    p2_->DoTrickle(stream);
+  void SimulateTrickleP2(size_t stream) {
+    p2_->SimulateTrickle(stream);
   }
 
   void VerifyConnected() {
   }
 
   void CloseP1() {
     p1_->Close();
   }
@@ -823,47 +892,47 @@ class PrioritizerTest : public ::testing
 
  private:
   nr_interface_prioritizer *prioritizer_;
 };
 
 }  // end namespace
 
 TEST_F(IceGatherTest, TestGatherFakeStunServerHostnameNoResolver) {
-  peer_->SetStunServer(kDefaultStunServerHostname, kDefaultStunServerPort);
+  peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort);
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherFakeStunServerIpAddress) {
-  peer_->SetStunServer(kDefaultStunServerAddress, kDefaultStunServerPort);
+  peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort);
   peer_->SetFakeResolver();
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherFakeStunServerHostname) {
-  peer_->SetStunServer(kDefaultStunServerHostname, kDefaultStunServerPort);
+  peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort);
   peer_->SetFakeResolver();
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherFakeStunBogusHostname) {
   peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort);
   peer_->SetFakeResolver();
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherDNSStunServerIpAddress) {
-  peer_->SetStunServer(kDefaultStunServerAddress, kDefaultStunServerPort);
+  peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort);
   peer_->SetDNSResolver();
   Gather();
   // TODO(jib@mozilla.com): ensure we get server reflexive candidates Bug 848094
 }
 
 TEST_F(IceGatherTest, TestGatherDNSStunServerHostname) {
-  peer_->SetStunServer(kDefaultStunServerHostname, kDefaultStunServerPort);
+  peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort);
   peer_->SetDNSResolver();
   Gather();
 }
 
 TEST_F(IceGatherTest, TestGatherDNSStunBogusHostname) {
   peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort);
   peer_->SetDNSResolver();
   Gather();
@@ -918,16 +987,26 @@ TEST_F(IceGatherTest, TestStunServerRetu
 }
 
 TEST_F(IceGatherTest, TestStunServerReturnsLoopbackAddr) {
   UseFakeStunServerWithResponse("127.0.0.133", 3333);
   Gather();
   ASSERT_FALSE(StreamHasMatchingCandidate(0, " 127.0.0.133 "));
 }
 
+TEST_F(IceGatherTest, TestStunServerTrickle) {
+  UseFakeStunServerWithResponse("192.0.2.1", 3333);
+  TestStunServer::GetInstance()->SetActive(false);
+  Gather(false);
+  ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1"));
+  TestStunServer::GetInstance()->SetActive(true);
+  WaitForGather();
+  ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1"));
+}
+
 TEST_F(IceConnectTest, TestGather) {
   AddStream("first", 1);
   ASSERT_TRUE(Gather(true));
 }
 
 TEST_F(IceConnectTest, TestGatherAutoPrioritize) {
   Init(false);
   AddStream("first", 1);
@@ -965,63 +1044,72 @@ TEST_F(IceConnectTest, TestConnectP2Then
   WaitForComplete();
 }
 
 TEST_F(IceConnectTest, TestConnectP2ThenP1Trickle) {
   AddStream("first", 1);
   ASSERT_TRUE(Gather(true));
   ConnectP2();
   PR_Sleep(1000);
-  ConnectP1(TRICKLE_DEFERRED);
-  DoTrickleP1(0);
+  ConnectP1(TRICKLE_SIMULATE);
+  SimulateTrickleP1(0);
   WaitForComplete();
 }
 
 TEST_F(IceConnectTest, TestConnectP2ThenP1TrickleTwoComponents) {
   AddStream("first", 1);
   AddStream("second", 2);
   ASSERT_TRUE(Gather(true));
   ConnectP2();
   PR_Sleep(1000);
-  ConnectP1(TRICKLE_DEFERRED);
-  DoTrickleP1(0);
+  ConnectP1(TRICKLE_SIMULATE);
+  SimulateTrickleP1(0);
   std::cerr << "Sleeping between trickle streams" << std::endl;
   PR_Sleep(1000);  // Give this some time to settle but not complete
                    // all of ICE.
-  DoTrickleP1(1);
+  SimulateTrickleP1(1);
   WaitForComplete(2);
 }
 
 TEST_F(IceConnectTest, TestConnectAutoPrioritize) {
   Init(false);
   AddStream("first", 1);
   ASSERT_TRUE(Gather(true));
   Connect();
 }
 
 TEST_F(IceConnectTest, TestConnectTrickleOneStreamOneComponent) {
   AddStream("first", 1);
   ASSERT_TRUE(Gather(true));
   ConnectTrickle();
-  DoTrickle(0);
+  SimulateTrickle(0);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
 }
 
 TEST_F(IceConnectTest, TestConnectTrickleTwoStreamsOneComponent) {
   AddStream("first", 1);
   AddStream("second", 1);
   ASSERT_TRUE(Gather(true));
   ConnectTrickle();
-  DoTrickle(0);
-  DoTrickle(1);
+  SimulateTrickle(0);
+  SimulateTrickle(1);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
 }
 
+TEST_F(IceConnectTest, TestConnectRealTrickleOneStreamOneComponent) {
+  AddStream("first", 1);
+  AddStream("second", 1);
+  ASSERT_TRUE(Gather(false));
+  ConnectTrickle(TRICKLE_REAL);
+  ASSERT_TRUE_WAIT(p1_->ice_complete(), 5000);
+  ASSERT_TRUE_WAIT(p2_->ice_complete(), 5000);
+  WaitForGather();  // ICE can complete before we finish gathering.
+}
 
 TEST_F(IceConnectTest, TestSendReceive) {
   AddStream("first", 1);
   ASSERT_TRUE(Gather(true));
   Connect();
   SendReceive();
 }
 
@@ -1190,16 +1278,25 @@ int main(int argc, char **argv)
       g_turn_user.empty(),
       g_turn_password.empty()) {
     printf(
         "Set TURN_SERVER_ADDRESS, TURN_SERVER_USER, and TURN_SERVER_PASSWORD\n"
         "environment variables to run this test\n");
     g_turn_server="";
   }
 
+  std::string tmp = get_environment("STUN_SERVER_ADDRESS");
+  if (tmp != "")
+    g_stun_server_address = tmp;
+
+
+  tmp = get_environment("STUN_SERVER_HOSTNAME");
+  if (tmp != "")
+    g_stun_server_hostname = tmp;
+
   test_utils = new MtransportTestUtils();
   NSS_NoDB_Init(nullptr);
   NSS_SetDomesticPolicy();
 
   // Start the tests
   ::testing::InitGoogleTest(&argc, argv);
 
   test_utils->sts_target()->Dispatch(
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
@@ -411,18 +411,18 @@ int nr_ice_candidate_compute_priority(nr
   abort:
     return(_status);
   }
 
 static void nr_ice_candidate_fire_ready_cb(NR_SOCKET s, int how, void *cb_arg)
   {
     nr_ice_candidate *cand = cb_arg;
 
+    cand->ready_cb_timer = 0;
     cand->ready_cb(0, 0, cand->ready_cb_arg);
-    cand->ready_cb_timer = 0;
   }
 
 int nr_ice_candidate_initialize(nr_ice_candidate *cand, NR_async_cb ready_cb, void *cb_arg)
   {
     int r,_status;
     int protocol=NR_RESOLVE_PROTOCOL_STUN;
     cand->done_cb=ready_cb;
     cand->cb_arg=cb_arg;
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -244,17 +244,17 @@ int nr_ice_component_initialize(struct n
 
         /* srvrflx */
         if(r=nr_ice_candidate_create(ctx,component,
           isock,sock,SERVER_REFLEXIVE,
           &ctx->turn_servers[j].turn_server,component->component_id,&cand))
           ABORT(r);
         cand->state=NR_ICE_CAND_STATE_INITIALIZING; /* Don't start */
         cand->done_cb=nr_ice_initialize_finished_cb;
-        cand->cb_arg=ctx;
+        cand->cb_arg=cand;
 
         TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
         component->candidate_ct++;
         srvflx_cand=cand;
 
         /* relayed*/
         if(r=nr_socket_turn_create(sock, &turn_sock))
           ABORT(r);
@@ -310,99 +310,89 @@ int nr_ice_component_initialize(struct n
       ctx->uninitialized_candidates++;
       cand=TAILQ_NEXT(cand,entry_comp);
     }
 
     /* Now initialize all the candidates */
     cand=TAILQ_FIRST(&component->candidates);
     while(cand){
       if(cand->state!=NR_ICE_CAND_STATE_INITIALIZING){
-        if(r=nr_ice_candidate_initialize(cand,nr_ice_initialize_finished_cb,ctx)){
+        if(r=nr_ice_candidate_initialize(cand,nr_ice_initialize_finished_cb,cand)){
           if(r!=R_WOULDBLOCK){
             ctx->uninitialized_candidates--;
             cand->state=NR_ICE_CAND_STATE_FAILED;
           }
         }
       }
       cand=TAILQ_NEXT(cand,entry_comp);
     }
 
     _status=0;
   abort:
     return(_status);
   }
 
-/* Prune redundant candidates. We use an n^2 algorithm for now.
+/*
+  Compare this newly initialized candidate against the other initialized
+  candidates and discard the lower-priority one if they are redundant.
 
    This algorithm combined with the other algorithms, favors
    host > srflx > relay
 
    This actually won't prune relayed in the very rare
    case that relayed is the same. Not relevant in practice.
-*/
+ */
+int nr_ice_component_maybe_prune_candidate(nr_ice_ctx *ctx, nr_ice_component *comp, nr_ice_candidate *c1, int *was_pruned)
+  {
+    nr_ice_candidate *c2, *tmp = NULL;
 
-int nr_ice_component_prune_candidates(nr_ice_ctx *ctx, nr_ice_component *comp)
-  {
-    nr_ice_candidate *c1,*c1n,*c2;
+    *was_pruned = 0;
+    c2 = TAILQ_FIRST(&comp->candidates);
+    while(c2){
+      if((c1 != c2) &&
+         (c2->state == NR_ICE_CAND_STATE_INITIALIZED) &&
+         !nr_transport_addr_cmp(&c1->base,&c2->base,NR_TRANSPORT_ADDR_CMP_MODE_ALL) &&
+         !nr_transport_addr_cmp(&c1->addr,&c2->addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){
 
-    c1=TAILQ_FIRST(&comp->candidates);
-    while(c1){
-      c1n=TAILQ_NEXT(c1,entry_comp);
-      if(c1->state!=NR_ICE_CAND_STATE_INITIALIZED){
-        r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Removing non-initialized candidate %s",
-          ctx->label,c1->label);
-        if (c1->state == NR_ICE_CAND_STATE_INITIALIZING) {
-          r_log(LOG_ICE,LOG_NOTICE, "ICE(%s): Removing candidate %s which is in INITIALIZING state",
-            ctx->label, c1->label);
+        if((c1->type == c2->type) ||
+           (c1->type==HOST && c2->type == SERVER_REFLEXIVE) ||
+           (c2->type==HOST && c1->type == SERVER_REFLEXIVE)){
+
+          /*
+             These are redundant. Remove the lower pri one.
+
+             Since this algorithmis run whenever a new candidate
+             is initialized, there should at most one duplicate.
+           */
+          if (c1->priority < c2->priority) {
+            tmp = c1;
+            *was_pruned = 1;
+          }
+          else {
+            tmp = c2;
+          }
+          break;
         }
-        TAILQ_REMOVE(&comp->candidates,c1,entry_comp);
-        comp->candidate_ct--;
-        TAILQ_REMOVE(&c1->isock->candidates,c1,entry_sock);
-        /* schedule this delete for later as we don't want to delete the underlying
-         * objects while in the middle of a callback on one of those objects */
-        NR_ASYNC_SCHEDULE(nr_ice_candidate_destroy_cb,c1);
-        goto next_c1;
       }
 
-      c2=TAILQ_NEXT(c1,entry_comp);
-
-      while(c2){
-        nr_ice_candidate *tmp;
-
-        if(!nr_transport_addr_cmp(&c1->base,&c2->base,NR_TRANSPORT_ADDR_CMP_MODE_ALL) && !nr_transport_addr_cmp(&c1->addr,&c2->addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){
-
-          if((c1->type == c2->type) ||
-            (c1->type==HOST && c2->type == SERVER_REFLEXIVE) ||
-            (c2->type==HOST && c1->type == SERVER_REFLEXIVE)){
-
-            /* OK these are redundant. Remove the lower pri one */
-            tmp=c2;
-            c2=TAILQ_NEXT(c2,entry_comp);
-            if(c1n==tmp)
-              c1n=c2;
-
-            r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Removing redundant candidate %s",
-              ctx->label,tmp->label);
-
-            TAILQ_REMOVE(&comp->candidates,tmp,entry_comp);
-            comp->candidate_ct--;
-            TAILQ_REMOVE(&tmp->isock->candidates,tmp,entry_sock);
-
-            nr_ice_candidate_destroy(&tmp);
-          }
-        }
-        else{
-          c2=TAILQ_NEXT(c2,entry_comp);
-        }
-      }
-    next_c1:
-      c1=c1n;
+      c2=TAILQ_NEXT(c2,entry_comp);
     }
 
-    return(0);
+    if (tmp) {
+      r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Removing redundant candidate %s",
+            ctx->label,tmp->label);
+
+      TAILQ_REMOVE(&comp->candidates,tmp,entry_comp);
+      comp->candidate_ct--;
+      TAILQ_REMOVE(&tmp->isock->candidates,tmp,entry_sock);
+
+      nr_ice_candidate_destroy(&tmp);
+    }
+
+    return 0;
   }
 
 /* Section 7.2.1 */
 static int nr_ice_component_process_incoming_check(nr_ice_component *comp, nr_transport_addr *local_addr, nr_stun_server_request *req, int *error)
   {
     nr_ice_cand_pair *pair;
     nr_ice_candidate *pcand=0;
     nr_stun_message *sreq=req->request;
@@ -647,84 +637,95 @@ int nr_ice_component_service_pre_answer_
       }
     }
 
     _status=0;
  abort:
     return(_status);
   }
 
+int nr_ice_component_pair_candidate(nr_ice_peer_ctx *pctx, nr_ice_component *pcomp, nr_ice_candidate *lcand, int pair_all_remote)
+  {
+    int r, _status;
+    nr_ice_candidate *pcand;
+    nr_ice_cand_pair *pair=0;
+    char codeword[5];
+
+    nr_ice_compute_codeword(lcand->label,strlen(lcand->label),codeword);
+    r_log(LOG_ICE,LOG_DEBUG,"Pairing local candidate %s:%s",codeword,lcand->label);
+
+    switch(lcand->type){
+      case HOST:
+        break;
+      case SERVER_REFLEXIVE:
+      case PEER_REFLEXIVE:
+        /* Don't actually pair these candidates */
+        goto done;
+        break;
+      case RELAYED:
+        break;
+      default:
+        assert(0);
+        ABORT(R_INTERNAL);
+        break;
+    }
+
+    pcand=TAILQ_FIRST(&pcomp->candidates);
+    while(pcand){
+      /*
+        Two modes, depending on |pair_all_remote|
+
+        1. Pair remote candidates which have not been paired
+           (used in initial pairing or in processing the other side's
+           trickle candidates).
+        2. Pair any remote candidate (used when processing our own
+           trickle candidates).
+      */
+      if (pair_all_remote || (pcand->state == NR_ICE_CAND_PEER_CANDIDATE_UNPAIRED)) {
+        /* If we are pairing our own trickle candidates, the remote candidate should
+           all be paired */
+        if (pair_all_remote)
+          assert (pcand->state == NR_ICE_CAND_PEER_CANDIDATE_PAIRED);
+
+        nr_ice_compute_codeword(pcand->label,strlen(pcand->label),codeword);
+        r_log(LOG_ICE,LOG_DEBUG,"Pairing with peer candidate %s:%s",codeword,pcand->label);
+
+        if(r=nr_ice_candidate_pair_create(pctx,lcand,pcand,&pair))
+          ABORT(r);
+
+        if(r=nr_ice_candidate_pair_insert(&pcomp->stream->check_list,
+                                          pair))
+          ABORT(r);
+      }
+
+      pcand=TAILQ_NEXT(pcand,entry_comp);
+    }
+
+   done:
+    _status = 0;
+   abort:
+    return(_status);
+  }
+
 int nr_ice_component_pair_candidates(nr_ice_peer_ctx *pctx, nr_ice_component *lcomp,nr_ice_component *pcomp)
   {
-    nr_ice_candidate *lcand,*pcand;
-    nr_ice_cand_pair *pair=0;
+    nr_ice_candidate *lcand, *pcand;
     nr_ice_socket *isock;
     int r,_status;
-    char codeword[5];
 
     r_log(LOG_ICE,LOG_DEBUG,"Pairing candidates======");
+
     /* Create the candidate pairs */
     lcand=TAILQ_FIRST(&lcomp->candidates);
     while(lcand){
-      int was_paired = 0;
-
-      nr_ice_compute_codeword(lcand->label,strlen(lcand->label),codeword);
-      r_log(LOG_ICE,LOG_DEBUG,"Examining local candidate %s:%s",codeword,lcand->label);
-
-      switch(lcand->type){
-        case HOST:
-          break;
-        case SERVER_REFLEXIVE:
-        case PEER_REFLEXIVE:
-          /* Don't actually pair these candidates */
-          goto next_cand;
-          break;
-        case RELAYED:
-          break;
-        default:
-          assert(0);
-          ABORT(R_INTERNAL);
-          break;
+      if (lcand->state == NR_ICE_CAND_STATE_INITIALIZED) {
+        if ((r = nr_ice_component_pair_candidate(pctx, pcomp, lcand, 0)))
+          ABORT(r);
       }
 
-      /* PAIR with each peer*/
-      if(TAILQ_EMPTY(&pcomp->candidates)) {
-          /* can happen if our peer proposes no (or all bogus) candidates */
-          goto next_cand;
-      }
-      pcand=TAILQ_FIRST(&pcomp->candidates);
-      while(pcand){
-        /* Only pair peer candidates which have not yet been paired.
-           This allows "trickle ICE". (Not yet standardized, but
-           part of WebRTC).
-
-           TODO(ekr@rtfm.com): Add refernece to the spec when there
-           is one.
-         */
-        if (pcand->state == NR_ICE_CAND_PEER_CANDIDATE_UNPAIRED) {
-          nr_ice_compute_codeword(pcand->label,strlen(pcand->label),codeword);
-          r_log(LOG_ICE,LOG_DEBUG,"Examining peer candidate %s:%s",codeword,pcand->label);
-
-          if(r=nr_ice_candidate_pair_create(pctx,lcand,pcand,&pair))
-            ABORT(r);
-
-          if(r=nr_ice_candidate_pair_insert(&pcomp->stream->check_list,
-              pair))
-            ABORT(r);
-        }
-        else {
-          was_paired = 1;
-        }
-        pcand=TAILQ_NEXT(pcand,entry_comp);
-      }
-
-      if(!pair)
-        goto next_cand;
-
-    next_cand:
       lcand=TAILQ_NEXT(lcand,entry_comp);
     }
 
     /* Mark all peer candidates as paired */
     pcand=TAILQ_FIRST(&pcomp->candidates);
     while(pcand){
       pcand->state = NR_ICE_CAND_PEER_CANDIDATE_PAIRED;
 
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.h
@@ -77,18 +77,19 @@ struct nr_ice_component_ {
   STAILQ_ENTRY(nr_ice_component_)entry;
 };
 
 typedef STAILQ_HEAD(nr_ice_component_head_,nr_ice_component_) nr_ice_component_head;
 
 int nr_ice_component_create(struct nr_ice_media_stream_ *stream, int component_id, nr_ice_component **componentp);
 int nr_ice_component_destroy(nr_ice_component **componentp);
 int nr_ice_component_initialize(struct nr_ice_ctx_ *ctx,nr_ice_component *component);
-int nr_ice_component_prune_candidates(nr_ice_ctx *ctx, nr_ice_component *comp);
-int nr_ice_component_pair_candidates(nr_ice_peer_ctx *pctx, nr_ice_component *lcomp,nr_ice_component *pcomp);
+int nr_ice_component_maybe_prune_candidate(nr_ice_ctx *ctx, nr_ice_component *comp, nr_ice_candidate *c1, int *was_pruned);
+int nr_ice_component_pair_candidate(nr_ice_peer_ctx *pctx, nr_ice_component *pcomp, nr_ice_candidate *lcand, int pair_all_remote);
+int nr_ice_component_pair_candidates(nr_ice_peer_ctx *pctx, nr_ice_component *lcomp, nr_ice_component *pcomp);
 int nr_ice_component_service_pre_answer_requests(nr_ice_peer_ctx *pctx, nr_ice_component *pcomp, char *username, int *serviced);
 int nr_ice_component_nominated_pair(nr_ice_component *comp, nr_ice_cand_pair *pair);
 int nr_ice_component_failed_pair(nr_ice_component *comp, nr_ice_cand_pair *pair);
 int nr_ice_component_select_pair(nr_ice_peer_ctx *pctx, nr_ice_component *comp);
 int nr_ice_component_set_failed(nr_ice_component *comp);
 int nr_ice_component_finalize(nr_ice_component *lcomp, nr_ice_component *rcomp);
 
 #ifdef __cplusplus
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -59,16 +59,17 @@ static char *RCSSTRING __UNUSED__="$Id: 
 int LOG_ICE = 0;
 
 static int nr_ice_random_string(char *str, int len);
 static int nr_ice_fetch_stun_servers(int ct, nr_ice_stun_server **out);
 #ifdef USE_TURN
 static int nr_ice_fetch_turn_servers(int ct, nr_ice_turn_server **out);
 #endif /* USE_TURN */
 static void nr_ice_ctx_destroy_cb(NR_SOCKET s, int how, void *cb_arg);
+static int nr_ice_ctx_pair_new_trickle_candidates(nr_ice_ctx *ctx, nr_ice_candidate *cand);
 
 int nr_ice_fetch_stun_servers(int ct, nr_ice_stun_server **out)
   {
     int r,_status;
     nr_ice_stun_server *servers = 0;
     int i;
     NR_registry child;
     char *addr=0;
@@ -436,38 +437,85 @@ int nr_ice_ctx_destroy(nr_ice_ctx **ctxp
 
     *ctxp=0;
 
     return(0);
   }
 
 void nr_ice_initialize_finished_cb(NR_SOCKET s, int h, void *cb_arg)
   {
-    nr_ice_ctx *ctx=cb_arg;
+    int r,_status;
+    nr_ice_candidate *cand=cb_arg;
+    nr_ice_ctx *ctx;
+
+
+    assert(cb_arg);
+    if (!cb_arg)
+      return;
+    ctx = cand->ctx;
+
+    ctx->uninitialized_candidates--;
+
+    if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) {
+      int was_pruned = 0;
 
-/*    r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Candidate %s %s",ctx->label,
-      cand->label, cand->state==NR_ICE_CAND_STATE_INITIALIZED?"INITIALIZED":"FAILED");
-*/
-    ctx->uninitialized_candidates--;
+      if (r=nr_ice_component_maybe_prune_candidate(ctx, cand->component,
+                                                   cand, &was_pruned)) {
+          r_log(LOG_ICE, LOG_NOTICE, "ICE(%s): Problem pruning candidates",ctx->label);
+      }
+
+      /* If we are initialized, the candidate wasn't pruned,
+         and we have a trickle ICE callback fire the callback */
+      if (ctx->trickle_cb && !was_pruned) {
+        ctx->trickle_cb(ctx->trickle_cb_arg, ctx, cand->stream, cand->component_id, cand);
+
+        if (r==nr_ice_ctx_pair_new_trickle_candidates(ctx, cand)) {
+          r_log(LOG_ICE,LOG_ERR, "ICE(%s): All could not pair new trickle candidate",ctx->label);
+          /* But continue */
+        }
+      }
+    }
 
     if(ctx->uninitialized_candidates==0){
       r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): All candidates initialized",ctx->label);
       ctx->state=NR_ICE_STATE_INITIALIZED;
       if (ctx->done_cb) {
         ctx->done_cb(0,0,ctx->cb_arg);
       }
       else {
         r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): No done_cb. We were probably destroyed.",ctx->label);
       }
     }
     else {
       r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Waiting for %d candidates to be initialized",ctx->label, ctx->uninitialized_candidates);
     }
   }
 
+static int nr_ice_ctx_pair_new_trickle_candidates(nr_ice_ctx *ctx, nr_ice_candidate *cand)
+  {
+    int r,_status;
+    nr_ice_peer_ctx *pctx;
+
+    pctx=STAILQ_FIRST(&ctx->peers);
+    while(pctx){
+      if (pctx->state == NR_ICE_PEER_STATE_PAIRED) {
+        r = nr_ice_peer_ctx_pair_new_trickle_candidate(ctx, pctx, cand);
+        if (r)
+          ABORT(r);
+      }
+
+      pctx=STAILQ_NEXT(pctx,entry);
+    }
+
+    _status=0;
+ abort:
+    return(_status);
+  }
+
+
 #define MAXADDRS 100 // Ridiculously high
 int nr_ice_initialize(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg)
   {
     int r,_status;
     nr_ice_media_stream *stream;
     nr_local_addr addrs[MAXADDRS];
     int i,addr_ct;
 
@@ -684,8 +732,16 @@ int nr_ice_ctx_finalize(nr_ice_ctx *ctx,
       nr_ice_media_stream_finalize(lstr,rstr);
 
       lstr=STAILQ_NEXT(lstr,entry);
     }
 
     return(0);
   }
 
+
+int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, void *cb_arg)
+{
+  ctx->trickle_cb = cb;
+  ctx->trickle_cb_arg = cb_arg;
+
+  return 0;
+}
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
@@ -84,16 +84,19 @@ typedef STAILQ_HEAD(nr_ice_foundation_he
 typedef TAILQ_HEAD(nr_ice_candidate_head_,nr_ice_candidate_) nr_ice_candidate_head;
 typedef TAILQ_HEAD(nr_ice_cand_pair_head_,nr_ice_cand_pair_) nr_ice_cand_pair_head;
 typedef struct nr_ice_component_ nr_ice_component;
 typedef struct nr_ice_media_stream_ nr_ice_media_stream;
 typedef struct nr_ice_ctx_ nr_ice_ctx;
 typedef struct nr_ice_peer_ctx_ nr_ice_peer_ctx;
 typedef struct nr_ice_candidate_ nr_ice_candidate;
 typedef struct nr_ice_cand_pair_ nr_ice_cand_pair;
+typedef void (*nr_ice_trickle_candidate_cb) (void *cb_arg,
+  nr_ice_ctx *ctx, nr_ice_media_stream *stream, int component_id,
+  nr_ice_candidate *candidate);
 
 #include "ice_socket.h"
 #include "ice_component.h"
 #include "ice_media_stream.h"
 #include "ice_candidate.h"
 #include "ice_candidate_pair.h"
 #include "ice_handler.h"
 #include "ice_peer_ctx.h"
@@ -139,16 +142,19 @@ struct nr_ice_ctx_ {
   UINT4 gather_rto;
   UINT4 stun_delay;
 
   nr_ice_peer_ctx_head peers;
   nr_ice_stun_id_head ids;
 
   NR_async_cb done_cb;
   void *cb_arg;
+
+  nr_ice_trickle_candidate_cb trickle_cb;
+  void *trickle_cb_arg;
 };
 
 int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp);
 #define NR_ICE_CTX_FLAGS_OFFERER                           1
 #define NR_ICE_CTX_FLAGS_ANSWERER                          (1<<1)
 #define NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION             (1<<2)
 #define NR_ICE_CTX_FLAGS_LITE                              (1<<3)
 
@@ -161,16 +167,19 @@ int nr_ice_get_global_attributes(nr_ice_
 int nr_ice_ctx_deliver_packet(nr_ice_ctx *ctx, nr_ice_component *comp, nr_transport_addr *source_addr, UCHAR *data, int len);
 int nr_ice_ctx_is_known_id(nr_ice_ctx *ctx, UCHAR id[12]);
 int nr_ice_ctx_remember_id(nr_ice_ctx *ctx, nr_stun_message *msg);
 int nr_ice_ctx_finalize(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx);
 int nr_ice_ctx_set_stun_servers(nr_ice_ctx *ctx,nr_ice_stun_server *servers, int ct);
 int nr_ice_ctx_set_turn_servers(nr_ice_ctx *ctx,nr_ice_turn_server *servers, int ct);
 int nr_ice_ctx_set_resolver(nr_ice_ctx *ctx, nr_resolver *resolver);
 int nr_ice_ctx_set_interface_prioritizer(nr_ice_ctx *ctx, nr_interface_prioritizer *prioritizer);
+int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, void *cb_arg);
+
+#define NR_ICE_MAX_ATTRIBUTE_SIZE 256
 
 extern int LOG_ICE;
 
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
 #endif
 
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
@@ -135,69 +135,74 @@ int nr_ice_media_stream_initialize(nr_ic
       comp=STAILQ_NEXT(comp,entry);
     }
 
     _status=0;
   abort:
     return(_status);
   }
 
-#define MAX_ATTRIBUTE_SIZE 256
-
 int nr_ice_media_stream_get_attributes(nr_ice_media_stream *stream, char ***attrsp, int *attrctp)
   {
     int attrct=0;
     nr_ice_component *comp;
     char **attrs=0;
     int index=0;
+    nr_ice_candidate *cand;
     int r,_status;
 
     *attrctp=0;
 
     /* First find out how many attributes we need */
     comp=STAILQ_FIRST(&stream->components);
     while(comp){
       if (comp->state != NR_ICE_COMPONENT_DISABLED) {
-        if(r=nr_ice_component_prune_candidates(stream->ctx,comp))
-          ABORT(r);
+        cand = TAILQ_FIRST(&comp->candidates);
+        while(cand){
+          if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) {
+            ++attrct;
+          }
 
-        attrct+=comp->candidate_ct;
+          cand = TAILQ_NEXT(cand, entry_comp);
+        }
       }
-
       comp=STAILQ_NEXT(comp,entry);
     }
 
     if(attrct < 1){
       r_log(LOG_ICE,LOG_WARNING,"ICE-STREAM(%s): Failed to find any components for stream",stream->label);
       ABORT(R_FAILED);
     }
 
     /* Make the array we'll need */
     if(!(attrs=RCALLOC(sizeof(char *)*attrct)))
       ABORT(R_NO_MEMORY);
     for(index=0;index<attrct;index++){
-      if(!(attrs[index]=RMALLOC(MAX_ATTRIBUTE_SIZE)))
+      if(!(attrs[index]=RMALLOC(NR_ICE_MAX_ATTRIBUTE_SIZE)))
         ABORT(R_NO_MEMORY);
     }
 
     index=0;
     /* Now format the attributes */
     comp=STAILQ_FIRST(&stream->components);
     while(comp){
       if (comp->state != NR_ICE_COMPONENT_DISABLED) {
         nr_ice_candidate *cand;
 
         cand=TAILQ_FIRST(&comp->candidates);
         while(cand){
-          assert(index < attrct);
+          if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) {
+            assert(index < attrct);
+
 
-          if(r=nr_ice_format_candidate_attribute(cand, attrs[index],MAX_ATTRIBUTE_SIZE))
-            ABORT(r);
+            if(r=nr_ice_format_candidate_attribute(cand, attrs[index],NR_ICE_MAX_ATTRIBUTE_SIZE))
+              ABORT(r);
 
-          index++;
+            index++;
+          }
 
           cand=TAILQ_NEXT(cand,entry_comp);
         }
       }
       comp=STAILQ_NEXT(comp,entry);
     }
 
     *attrsp=attrs;
@@ -238,25 +243,27 @@ int nr_ice_media_stream_get_default_cand
 
     /* We have the component. Now find the "best" candidate, making
        use of the fact that more "reliable" candidate types have
        higher numbers. So, we sort by type and then priority within
        type
     */
     cand=TAILQ_FIRST(&comp->candidates);
     while(cand){
-      if (!best_cand) {
-        best_cand = cand;
-      }
-      else {
-        if (best_cand->type < cand->type) {
+      if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) {
+        if (!best_cand) {
           best_cand = cand;
-        } else if (best_cand->type == cand->type) {
-          if (best_cand->priority < cand->priority)
+        }
+        else {
+          if (best_cand->type < cand->type) {
             best_cand = cand;
+          } else if (best_cand->type == cand->type) {
+            if (best_cand->priority < cand->priority)
+              best_cand = cand;
+          }
         }
       }
 
       cand=TAILQ_NEXT(cand,entry_comp);
     }
 
     /* No candidates */
     if (!best_cand)
@@ -806,16 +813,31 @@ int nr_ice_media_stream_finalize(nr_ice_
       if(rcomp){
         rcomp=STAILQ_NEXT(rcomp,entry);
       }
     }
 
     return(0);
   }
 
+int nr_ice_media_stream_pair_new_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, nr_ice_candidate *cand)
+  {
+    int r,_status;
+    nr_ice_component *comp;
+
+    if ((r=nr_ice_media_stream_find_component(pstream, cand->component_id, &comp)))
+      ABORT(R_NOT_FOUND);
+
+    if (r=nr_ice_component_pair_candidate(pctx, comp, cand, 1))
+      ABORT(r);
+
+    _status=0;
+ abort:
+    return(_status);
+  }
 
 int nr_ice_media_stream_disable_component(nr_ice_media_stream *stream, int component_id)
   {
     int r,_status;
     nr_ice_component *comp;
 
     if (stream->ice_state != NR_ICE_MEDIA_STREAM_UNPAIRED)
       ABORT(R_FAILED);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
@@ -90,15 +90,15 @@ int nr_ice_media_stream_set_state(nr_ice
 int nr_ice_media_stream_get_best_candidate(nr_ice_media_stream *str, int component, nr_ice_candidate **candp);
 int nr_ice_media_stream_send(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, UCHAR *data, int len);
 int nr_ice_media_stream_get_active(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_ice_candidate **local, nr_ice_candidate **remote);
 int nr_ice_media_stream_find_component(nr_ice_media_stream *str, int comp_id, nr_ice_component **compp);
 int nr_ice_media_stream_addrs(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_transport_addr *local, nr_transport_addr *remote);
 int
 nr_ice_peer_ctx_parse_media_stream_attribute(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *attr);
 int nr_ice_media_stream_disable_component(nr_ice_media_stream *stream, int component_id);
-
+int nr_ice_media_stream_pair_new_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, nr_ice_candidate *cand);
 
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
 #endif
 
--- a/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
@@ -51,16 +51,18 @@ static int nr_ice_ctx_parse_candidate(nr
 int nr_ice_peer_ctx_create(nr_ice_ctx *ctx, nr_ice_handler *handler,char *label, nr_ice_peer_ctx **pctxp)
   {
     int r,_status;
     nr_ice_peer_ctx *pctx=0;
 
     if(!(pctx=RCALLOC(sizeof(nr_ice_peer_ctx))))
       ABORT(R_NO_MEMORY);
 
+    pctx->state = NR_ICE_PEER_STATE_UNPAIRED;
+
     if(!(pctx->label=r_strdup(label)))
       ABORT(R_NO_MEMORY);
 
     pctx->ctx=ctx;
     pctx->handler=handler;
 
     /* Decide controlling vs. controlled */
     if(ctx->flags & NR_ICE_CTX_FLAGS_LITE){
@@ -252,35 +254,24 @@ int nr_ice_peer_ctx_find_pstream(nr_ice_
 
     _status=0;
  abort:
     return(_status);
   }
 
 int nr_ice_peer_ctx_parse_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *candidate)
   {
-    /* First need to find the stream. Because we don't have forward pointers,
-       iterate through all the peer streams to find one that matches us */
     nr_ice_media_stream *pstream;
     int r,_status;
     int needs_pairing = 0;
 
     r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s) parsing trickle ICE candidate %s",pctx->ctx->label,pctx->label,candidate);
-
-    pstream=STAILQ_FIRST(&pctx->peer_streams);
-    while(pstream) {
-      if (pstream->local_stream == stream)
-        break;
-
-      pstream = STAILQ_NEXT(pstream, entry);
-    }
-    if (!pstream) {
-      r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s) has no stream matching stream %s",pctx->ctx->label,pctx->label,stream->label);
-      ABORT(R_NOT_FOUND);
-    }
+    r = nr_ice_peer_ctx_find_pstream(pctx, stream, &pstream);
+    if (r)
+      ABORT(r);
 
     switch(pstream->ice_state) {
       case NR_ICE_MEDIA_STREAM_UNPAIRED:
         break;
       case NR_ICE_MEDIA_STREAM_CHECKS_FROZEN:
       case NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE:
         needs_pairing = 1;
         break;
@@ -349,27 +340,46 @@ int nr_ice_peer_ctx_pair_candidates(nr_i
     while(stream){
       if(r=nr_ice_media_stream_pair_candidates(pctx, stream->local_stream,
         stream))
         ABORT(r);
 
       stream=STAILQ_NEXT(stream,entry);
     }
 
+    pctx->state = NR_ICE_PEER_STATE_PAIRED;
+
     _status=0;
   abort:
     return(_status);
   }
 
+
+int nr_ice_peer_ctx_pair_new_trickle_candidate(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx, nr_ice_candidate *cand)
+  {
+    int r, _status;
+    nr_ice_media_stream *pstream;
+
+    r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s) pairing local trickle ICE candidate %s",pctx->ctx->label,pctx->label,cand->label);
+    if ((r = nr_ice_peer_ctx_find_pstream(pctx, cand->stream, &pstream)))
+      ABORT(r);
+
+    if ((r = nr_ice_media_stream_pair_new_trickle_candidate(pctx, pstream, cand)))
+      ABORT(r);
+
+    _status=0;
+ abort:
+    return _status;
+  }
+
 int nr_ice_peer_ctx_disable_component(nr_ice_peer_ctx *pctx, nr_ice_media_stream *lstream, int component_id)
   {
     int r, _status;
     nr_ice_media_stream *pstream;
     nr_ice_component *component;
-    int j;
 
     if ((r=nr_ice_peer_ctx_find_pstream(pctx, lstream, &pstream)))
       ABORT(r);
 
     /* We shouldn't be calling this after we have started pairing */
     if (pstream->ice_state != NR_ICE_MEDIA_STREAM_UNPAIRED)
       ABORT(R_FAILED);
 
@@ -538,17 +548,16 @@ static void nr_ice_peer_ctx_fire_done(NR
     pctx->done_cb_timer=0;
 
     /* Fire the handler callback to say we're done */
     if (pctx->handler) {
       pctx->handler->vtbl->ice_completed(pctx->handler->obj, pctx);
     }
   }
 
-
 /* OK, a stream just went ready. Examine all the streams to see if we're
    maybe miraculously done */
 int nr_ice_peer_ctx_stream_done(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream)
   {
     int _status;
     nr_ice_media_stream *str;
     int failed=0;
     int succeeded=0;
--- a/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
@@ -35,16 +35,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 #ifndef _ice_peer_ctx_h
 #define _ice_peer_ctx_h
 #ifdef __cplusplus
 using namespace std;
 extern "C" {
 #endif /* __cplusplus */
 
 struct nr_ice_peer_ctx_ {
+  int state;
+#define NR_ICE_PEER_STATE_UNPAIRED 1
+#define NR_ICE_PEER_STATE_PAIRED   2
+
   char *label;
   nr_ice_ctx *ctx;
   nr_ice_handler *handler;
 
   UCHAR controlling; /* 1 for controlling, 0 for controlled */
   UINT8 tiebreaker;
 
   char *peer_ufrag;
@@ -75,14 +79,15 @@ int nr_ice_peer_ctx_parse_global_attribu
 int nr_ice_peer_ctx_start_checks(nr_ice_peer_ctx *pctx);
 int nr_ice_peer_ctx_start_checks2(nr_ice_peer_ctx *pctx, int allow_non_first);
 int nr_ice_peer_ctx_dump_state(nr_ice_peer_ctx *pctx,FILE *out);
 int nr_ice_peer_ctx_log_state(nr_ice_peer_ctx *pctx);
 int nr_ice_peer_ctx_stream_done(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream);
 int nr_ice_peer_ctx_find_component(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component_id, nr_ice_component **compp);
 int nr_ice_peer_ctx_deliver_packet_maybe(nr_ice_peer_ctx *pctx, nr_ice_component *comp, nr_transport_addr *source_addr, UCHAR *data, int len);
 int nr_ice_peer_ctx_disable_component(nr_ice_peer_ctx *pctx, nr_ice_media_stream *lstream, int component_id);
+int nr_ice_peer_ctx_pair_new_trickle_candidate(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx, nr_ice_candidate *cand);
 
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
 #endif