Bug 904598 - Fix TURN long-term auth for Permissions Requests. r=abr
☠☠ backed out by 12a2620f14c6 ☠ ☠
authorEKR <ekr@rtfm.com>
Sat, 31 Aug 2013 07:35:38 -0700
changeset 145191 c6907a76e9258f2de2422e99e5b085a7c5f0a9ce
parent 145190 34de49884318eed6b4ae6370873952cc2d332fb6
child 145192 12a2620f14c6d429ca4d99c2f74fffacc2ac4d59
push id2472
push userryanvm@gmail.com
push dateSat, 31 Aug 2013 20:52:57 +0000
treeherderfx-team@b6c29e434519 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersabr
bugs904598
milestone26.0a1
Bug 904598 - Fix TURN long-term auth for Permissions Requests. 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
 }