Bug 905150 - Don't cancel running ICE candidate pairs on nomination. r=abr
authorEKR <ekr@rtfm.com>
Sat, 31 Aug 2013 07:35:38 -0700
changeset 158100 6eb4d1008076eb454944a92c63b3c47c5293f482
parent 158099 12a2620f14c6d429ca4d99c2f74fffacc2ac4d59
child 158101 3290e40c2a5e59d2ab600adfecf9df05622d8185
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersabr
bugs905150
milestone26.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 905150 - Don't cancel running ICE candidate pairs on nomination. r=abr
media/mtransport/nricectx.cpp
media/mtransport/nricemediastream.cpp
media/mtransport/nricemediastream.h
media/mtransport/test/ice_unittest.cpp
media/mtransport/third_party/nICEr/src/ice/ice_component.c
media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -313,22 +313,22 @@ RefPtr<NrIceCtx> NrIceCtx::Create(const 
   RefPtr<NrIceCtx> ctx = new NrIceCtx(name, offerer);
 
   // Initialize the crypto callbacks
   if (!initialized) {
     NR_reg_init(NR_REG_MODE_LOCAL);
     nr_crypto_vtbl = &nr_ice_crypto_nss_vtbl;
     initialized = true;
 
-    // Set the priorites for candidate type preferences
-    NR_reg_set_uchar((char *)"ice.pref.type.srv_rflx",100);
-    NR_reg_set_uchar((char *)"ice.pref.type.peer_rflx",105);
-    NR_reg_set_uchar((char *)"ice.pref.type.prflx",99);
-    NR_reg_set_uchar((char *)"ice.pref.type.host",125);
-    NR_reg_set_uchar((char *)"ice.pref.type.relayed",126);
+    // Set the priorites for candidate type preferences.
+    // These numbers come from RFC 5245 S. 4.1.2.2
+    NR_reg_set_uchar((char *)"ice.pref.type.srv_rflx", 100);
+    NR_reg_set_uchar((char *)"ice.pref.type.peer_rflx", 110);
+    NR_reg_set_uchar((char *)"ice.pref.type.host", 126);
+    NR_reg_set_uchar((char *)"ice.pref.type.relayed", 0);
 
     if (set_interface_priorities) {
       NR_reg_set_uchar((char *)"ice.pref.interface.rl0", 255);
       NR_reg_set_uchar((char *)"ice.pref.interface.wi0", 254);
       NR_reg_set_uchar((char *)"ice.pref.interface.lo0", 253);
       NR_reg_set_uchar((char *)"ice.pref.interface.en1", 252);
       NR_reg_set_uchar((char *)"ice.pref.interface.en0", 251);
       NR_reg_set_uchar((char *)"ice.pref.interface.eth0", 252);
--- a/media/mtransport/nricemediastream.cpp
+++ b/media/mtransport/nricemediastream.cpp
@@ -41,16 +41,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 */
 
 
 #include <string>
 #include <vector>
 
 #include "logging.h"
 #include "nsError.h"
+#include "mozilla/Scoped.h"
 
 // nICEr includes
 extern "C" {
 #include "nr_api.h"
 #include "registry.h"
 #include "async_timer.h"
 #include "ice_util.h"
 #include "transport_addr.h"
@@ -67,16 +68,62 @@ extern "C" {
 // Local includes
 #include "nricectx.h"
 #include "nricemediastream.h"
 
 namespace mozilla {
 
 MOZ_MTLOG_MODULE("mtransport")
 
+// Make an NrIceCandidate from the candidate |cand|.
+// This is not a member fxn because we want to hide the
+// defn of nr_ice_candidate but we pass by reference.
+static NrIceCandidate* MakeNrIceCandidate(const nr_ice_candidate& candc) {
+  ScopedDeletePtr<NrIceCandidate> out(new NrIceCandidate());
+
+  int r;
+  // Const-cast because the internal nICEr code isn't const-correct.
+  nr_ice_candidate *cand = const_cast<nr_ice_candidate *>(&candc);
+  char addr[INET6_ADDRSTRLEN + 1];
+
+  r = nr_transport_addr_get_addrstring(&cand->addr, addr, sizeof(addr));
+  if (r)
+    return nullptr;
+
+  int port;
+  r=nr_transport_addr_get_port(&cand->addr, &port);
+  if (r)
+    return nullptr;
+
+  NrIceCandidate::Type type;
+
+  switch(cand->type) {
+    case HOST:
+      type = NrIceCandidate::ICE_HOST;
+      break;
+    case SERVER_REFLEXIVE:
+      type = NrIceCandidate::ICE_SERVER_REFLEXIVE;
+      break;
+    case PEER_REFLEXIVE:
+      type = NrIceCandidate::ICE_PEER_REFLEXIVE;
+      break;
+    case RELAYED:
+      type = NrIceCandidate::ICE_RELAYED;
+      break;
+    default:
+      return nullptr;
+  }
+
+  out->host = addr;
+  out->port = port;
+  out->type = type;
+
+  return out.forget();
+}
+
 // NrIceMediaStream
 RefPtr<NrIceMediaStream>
 NrIceMediaStream::Create(NrIceCtx *ctx,
                          const std::string& name,
                          int components) {
   RefPtr<NrIceMediaStream> stream =
     new NrIceMediaStream(ctx, name, components);
 
@@ -145,16 +192,48 @@ nsresult NrIceMediaStream::ParseTrickleC
                 << name_ << "'");
       return NS_ERROR_FAILURE;
     }
   }
 
   return NS_OK;
 }
 
+nsresult NrIceMediaStream::GetActivePair(int component,
+                                         NrIceCandidate **localp,
+                                         NrIceCandidate **remotep) {
+  int r;
+  nr_ice_candidate *local_int;
+  nr_ice_candidate *remote_int;
+
+  r = nr_ice_media_stream_get_active(ctx_->peer(),
+                                     stream_,
+                                     component,
+                                     &local_int, &remote_int);
+  if (r)
+    return NS_ERROR_FAILURE;
+
+  ScopedDeletePtr<NrIceCandidate> local(
+      MakeNrIceCandidate(*local_int));
+  if (!local)
+    return NS_ERROR_FAILURE;
+
+  ScopedDeletePtr<NrIceCandidate> remote(
+      MakeNrIceCandidate(*remote_int));
+  if (!remote)
+    return NS_ERROR_FAILURE;
+
+  if (localp)
+    *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) {
@@ -243,19 +322,26 @@ nsresult NrIceMediaStream::SendPacket(in
     return NS_BASE_STREAM_OSERROR;
   }
 
   return NS_OK;
 }
 
 
 void NrIceMediaStream::Ready() {
-  MOZ_MTLOG(ML_DEBUG, "Marking stream ready '" << name_ << "'");
-  state_ = ICE_OPEN;
-  SignalReady(this);
+  // This function is called whenever a stream becomes ready, but it
+  // gets fired multiple times when a stream gets nominated repeatedly.
+  if (state_ != ICE_OPEN) {
+    MOZ_MTLOG(ML_DEBUG, "Marking stream ready '" << name_ << "'");
+    state_ = ICE_OPEN;
+    SignalReady(this);
+  }
+  else {
+    MOZ_MTLOG(ML_DEBUG, "Stream ready callback fired again for '" << name_ << "'");
+  }
 }
 
 void NrIceMediaStream::Close() {
   MOZ_MTLOG(ML_DEBUG, "Marking stream closed '" << name_ << "'");
   state_ = ICE_CLOSED;
   stream_ = nullptr;
 }
 }  // close namespace
--- a/media/mtransport/nricemediastream.h
+++ b/media/mtransport/nricemediastream.h
@@ -58,16 +58,31 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 
 
 namespace mozilla {
 
 typedef struct nr_ice_media_stream_ nr_ice_media_stream;
 
 class NrIceCtx;
 
+/* A summary of a candidate, for use in asking which candidate
+   pair is active */
+struct NrIceCandidate {
+  enum Type {
+    ICE_HOST,
+    ICE_SERVER_REFLEXIVE,
+    ICE_PEER_REFLEXIVE,
+    ICE_RELAYED
+  };
+
+  std::string host;
+  uint16_t port;
+  Type type;
+};
+
 class NrIceMediaStream {
  public:
   static RefPtr<NrIceMediaStream> Create(NrIceCtx *ctx,
                                          const std::string& name,
                                          int components);
   ~NrIceMediaStream();
 
   enum State { ICE_CONNECTING, ICE_OPEN, ICE_CLOSED};
@@ -84,16 +99,24 @@ class NrIceMediaStream {
   nsresult GetDefaultCandidate(int component, std::string *host, int *port);
 
   // Parse remote attributes
   nsresult ParseAttributes(std::vector<std::string>& candidates);
 
   // Parse trickle ICE candidate
   nsresult ParseTrickleCandidate(const std::string& candidate);
 
+  // Get the candidate pair currently active. It's the
+  // caller's responsibility to free these.
+  nsresult GetActivePair(int component,
+                         NrIceCandidate** local, NrIceCandidate** remote);
+
+  // The number of components
+  int components() const { return components_; }
+
   // The underlying nICEr stream
   nr_ice_media_stream *stream() { return stream_; }
   // Signals to indicate events. API users can (and should)
   // register for these.
 
   // Send a packet
   nsresult SendPacket(int component_id, const unsigned char *data, size_t len);
 
@@ -121,21 +144,17 @@ class NrIceMediaStream {
 
  private:
   NrIceMediaStream(NrIceCtx *ctx,  const std::string& name,
                    int components) :
       state_(ICE_CONNECTING),
       ctx_(ctx),
       name_(name),
       components_(components),
-      stream_(nullptr)
-  {
-    // XXX: components_ will be used eventually;  placate clang in the meantime.
-    (void)components_;
-  }
+      stream_(nullptr) {}
 
   DISALLOW_COPY_ASSIGN(NrIceMediaStream);
 
   State state_;
   NrIceCtx *ctx_;
   const std::string name_;
   const int components_;
   nr_ice_media_stream *stream_;
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -72,17 +72,19 @@ class IceTestPeer : public sigslot::has_
       gathering_complete_(false),
       ready_ct_(0),
       ice_complete_(false),
       received_(0),
       sent_(0),
       fake_resolver_(),
       dns_resolver_(new NrIceResolver()),
       remote_(nullptr),
-      candidate_filter_(nullptr) {
+      candidate_filter_(nullptr),
+      expected_local_type_(NrIceCandidate::ICE_HOST),
+      expected_remote_type_(NrIceCandidate::ICE_HOST) {
     ice_ctx_->SignalGatheringCompleted.connect(this,
                                                &IceTestPeer::GatheringComplete);
     ice_ctx_->SignalCompleted.connect(this, &IceTestPeer::IceCompleted);
   }
 
   ~IceTestPeer() {
     test_utils->sts_target()->Dispatch(WrapRunnable(this,
                                                     &IceTestPeer::Shutdown),
@@ -175,16 +177,21 @@ 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) {
+    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_; }
@@ -250,16 +257,67 @@ class IceTestPeer : public sigslot::has_
                         &NrIceMediaStream::ParseTrickleCandidate,
                         candidates[j],
                         &res), NS_DISPATCH_SYNC);
 
       ASSERT_TRUE(NS_SUCCEEDED(res));
     }
   }
 
+  void DumpCandidate(std::string which, const NrIceCandidate& cand) {
+    std::string type;
+
+    switch(cand.type) {
+      case NrIceCandidate::ICE_HOST:
+        type = "host";
+        break;
+      case NrIceCandidate::ICE_SERVER_REFLEXIVE:
+        type = "srflx";
+        break;
+      case NrIceCandidate::ICE_PEER_REFLEXIVE:
+        type = "prflx";
+        break;
+      case NrIceCandidate::ICE_RELAYED:
+        type = "relay";
+        break;
+      default:
+        FAIL();
+    };
+
+    std::cerr << which
+              << " --> "
+              << type
+              << " "
+              << cand.host
+              << ":"
+              << cand.port
+              << std::endl;
+  }
+
+  void DumpAndCheckActiveCandidates() {
+    std::cerr << "Active candidates:" << std::endl;
+    for (size_t i=0; i < streams_.size(); ++i) {
+      for (int j=0; j < streams_[i]->components(); ++j) {
+        std::cerr << "Stream " << i << " component " << j+1 << std::endl;
+
+        NrIceCandidate *local;
+        NrIceCandidate *remote;
+
+        nsresult res = streams_[i]->GetActivePair(j+1, &local, &remote);
+        ASSERT_TRUE(NS_SUCCEEDED(res));
+        DumpCandidate("Local  ", *local);
+        ASSERT_EQ(expected_local_type_, local->type);
+        DumpCandidate("Remote ", *remote);
+        ASSERT_EQ(expected_remote_type_, remote->type);
+        delete local;
+        delete remote;
+      }
+    }
+  }
+
   void Close() {
     test_utils->sts_target()->Dispatch(
       WrapRunnable(ice_ctx_, &NrIceCtx::destroy_peer_ctx),
       NS_DISPATCH_SYNC);
   }
 
   void Shutdown() {
     ice_ctx_ = nullptr;
@@ -281,18 +339,18 @@ class IceTestPeer : public sigslot::has_
   }
 
   void GotCandidate(NrIceMediaStream *stream, const std::string &candidate) {
     std::cerr << "Got candidate " << candidate << std::endl;
     candidates_[stream->name()].push_back(candidate);
   }
 
   void StreamReady(NrIceMediaStream *stream) {
-    std::cerr << "Stream ready " << stream->name() << std::endl;
     ++ready_ct_;
+    std::cerr << "Stream ready " << stream->name() << " ct=" << ready_ct_ << std::endl;
   }
 
   void IceCompleted(NrIceCtx *ctx) {
     std::cerr << "ICE completed " << name_ << std::endl;
     ice_complete_ = true;
   }
 
   void PacketReceived(NrIceMediaStream *stream, int component, const unsigned char *data,
@@ -330,16 +388,18 @@ class IceTestPeer : public sigslot::has_
   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_;
 };
 
 class IceGatherTest : public ::testing::Test {
  public:
   void SetUp() {
     peer_ = new IceTestPeer("P1", true, false);
     peer_->AddStream(1);
   }
@@ -396,29 +456,44 @@ class IceConnectTest : public ::testing:
 
   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);
   }
 
-  void SetCandidateFilter(CandidateFilter filter) {
+  void SetCandidateFilter(CandidateFilter filter, bool both=true) {
     p1_->SetCandidateFilter(filter);
-    p2_->SetCandidateFilter(filter);
+    if (both) {
+      p2_->SetCandidateFilter(filter);
+    }
   }
 
   void Connect() {
     p1_->Connect(p2_, TRICKLE_NONE);
     p2_->Connect(p1_, TRICKLE_NONE);
 
     ASSERT_TRUE_WAIT(p1_->ready_ct() == 1 && p2_->ready_ct() == 1, 5000);
     ASSERT_TRUE_WAIT(p1_->ice_complete() && p2_->ice_complete(), 5000);
+
+    p1_->DumpAndCheckActiveCandidates();
+    p2_->DumpAndCheckActiveCandidates();
   }
 
+  void SetExpectedTypes(NrIceCandidate::Type local, NrIceCandidate::Type remote) {
+    p1_->SetExpectedTypes(local, remote);
+    p2_->SetExpectedTypes(local, remote);
+  }
+
+  void SetExpectedTypes(NrIceCandidate::Type local1, NrIceCandidate::Type remote1,
+                        NrIceCandidate::Type local2, NrIceCandidate::Type remote2) {
+    p1_->SetExpectedTypes(local1, remote1);
+    p2_->SetExpectedTypes(local2, remote2);
+  }
 
   void ConnectP1(TrickleMode mode = TRICKLE_NONE) {
     p1_->Connect(p2_, mode);
   }
 
   void ConnectP2(TrickleMode mode = TRICKLE_NONE) {
     p2_->Connect(p1_, mode);
   }
@@ -695,28 +770,32 @@ TEST_F(IceConnectTest, TestConnectTurnOn
   if (g_turn_server.empty())
     return;
 
   AddStream("first", 1);
   SetTurnServer(g_turn_server, kDefaultStunServerPort,
                 g_turn_user, g_turn_password);
   ASSERT_TRUE(Gather(true));
   SetCandidateFilter(IsRelayCandidate);
+  SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+                   NrIceCandidate::Type::ICE_RELAYED);
   Connect();
 }
 
 TEST_F(IceConnectTest, TestSendReceiveTurnOnly) {
   if (g_turn_server.empty())
     return;
 
   AddStream("first", 1);
   SetTurnServer(g_turn_server, kDefaultStunServerPort,
                 g_turn_user, g_turn_password);
   ASSERT_TRUE(Gather(true));
   SetCandidateFilter(IsRelayCandidate);
+  SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+                   NrIceCandidate::Type::ICE_RELAYED);
   Connect();
   SendReceive();
 }
 
 TEST_F(IceConnectTest, TestConnectShutdownOneSide) {
   AddStream("first", 1);
   ASSERT_TRUE(Gather(true));
   ConnectThenDelete();
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -794,20 +794,23 @@ int nr_ice_component_nominated_pair(nr_i
     /* Set the new nominated pair */
     r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/comp(%d): nominated pair is %s (0x%p)",comp->stream->pctx->label,comp->stream->label,comp->component_id,pair->as_string,pair);
     comp->state=NR_ICE_COMPONENT_NOMINATED;
     comp->nominated=pair;
     comp->active=pair;
 
     r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/comp(%d): cancelling all pairs but %s (0x%p)",comp->stream->pctx->label,comp->stream->label,comp->component_id,pair->as_string,pair);
 
-    /* OK, we need to cancel off everything on this component */
+    /* Cancel checks in WAITING and FROZEN per ICE S 8.1.2 */
     p2=TAILQ_FIRST(&comp->stream->check_list);
     while(p2){
-      if((p2 != pair) && (p2->remote->component->component_id == comp->component_id)){
+      if((p2 != pair) &&
+         (p2->remote->component->component_id == comp->component_id) &&
+         ((p2->state == NR_ICE_PAIR_STATE_FROZEN) ||
+	  (p2->state == NR_ICE_PAIR_STATE_WAITING))) {
         r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/comp(%d): cancelling pair %s (0x%p)",comp->stream->pctx->label,comp->stream->label,comp->component_id,p2->as_string,p2);
 
         if(r=nr_ice_candidate_pair_cancel(pair->pctx,p2))
           ABORT(r);
       }
 
       p2=TAILQ_NEXT(p2,entry);
     }
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c
@@ -290,17 +290,16 @@ int nr_ice_media_stream_pair_candidates(
     return(_status);
   }
 
 int nr_ice_media_stream_service_pre_answer_requests(nr_ice_peer_ctx *pctx, nr_ice_media_stream *lstream, nr_ice_media_stream *pstream, int *serviced)
   {
     nr_ice_component *pcomp;
     int r,_status;
     char *user = 0;
-    char *lufrag, *rufrag;
 
     if (serviced)
       *serviced = 0;
 
     pcomp=STAILQ_FIRST(&pstream->components);
     while(pcomp){
       int serviced_inner=0;
 
@@ -693,44 +692,62 @@ int nr_ice_media_stream_find_component(n
 
     *compp=comp;
 
     _status=0;
   abort:
     return(_status);
   }
 
-
 int nr_ice_media_stream_send(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, UCHAR *data, int len)
   {
     int r,_status;
     nr_ice_component *comp;
 
     /* First find the peer component */
     if(r=nr_ice_peer_ctx_find_component(pctx, str, component, &comp))
       ABORT(r);
 
     /* Do we have an active pair yet? We should... */
     if(!comp->active)
-      ABORT(R_BAD_ARGS);
+      ABORT(R_NOT_FOUND);
 
     /* OK, write to that pair, which means:
        1. Use the socket on our local side.
        2. Use the address on the remote side
     */
     comp->keepalive_needed=0; /* Suppress keepalives */
     if(r=nr_socket_sendto(comp->active->local->osock,data,len,0,
-      &comp->active->remote->addr))
+                          &comp->active->remote->addr))
       ABORT(r);
 
     _status=0;
   abort:
     return(_status);
   }
 
+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 r,_status;
+    nr_ice_component *comp;
+
+    /* First find the peer component */
+    if(r=nr_ice_peer_ctx_find_component(pctx, str, component, &comp))
+      ABORT(r);
+
+    if(!comp->active)
+      ABORT(R_NOT_FOUND);
+
+    if (local) *local = comp->active->local;
+    if (remote) *remote = comp->active->remote;
+
+    _status=0;
+  abort:
+    return(_status);
+  }
 
 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 r,_status;
     nr_ice_component *comp;
 
     /* First find the peer component */
     if(r=nr_ice_peer_ctx_find_component(pctx, str, component, &comp))
--- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h
@@ -83,17 +83,18 @@ int nr_ice_media_stream_start_checks(nr_
 int nr_ice_media_stream_service_pre_answer_requests(nr_ice_peer_ctx *pctx,nr_ice_media_stream *lstream,nr_ice_media_stream *pstream, int *serviced);
 int nr_ice_media_stream_unfreeze_pairs(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream);
 int nr_ice_media_stream_unfreeze_pairs_foundation(nr_ice_media_stream *stream, char *foundation);
 int nr_ice_media_stream_dump_state(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream,FILE *out);
 int nr_ice_media_stream_component_nominated(nr_ice_media_stream *stream,nr_ice_component *component);
 int nr_ice_media_stream_component_failed(nr_ice_media_stream *stream,nr_ice_component *component);
 int nr_ice_media_stream_set_state(nr_ice_media_stream *str, int state);
 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_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);
 
 
 #ifdef __cplusplus
 }