Bug 1146462: Close ICE transports when m-sections are disabled. r=drno
authorByron Campen [:bwc] <docfaraday@gmail.com>
Mon, 23 Mar 2015 16:56:08 -0700
changeset 266648 6dd46eb4124acaeec9c50000e950d7531dd6111d
parent 266647 d7f990fd97f61fce199489f7cc676f0221bf75f3
child 266649 54e20c1910611f9c3706e3125ff4eaa6417fe4e1
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdrno
bugs1146462
milestone39.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 1146462: Close ICE transports when m-sections are disabled. r=drno
media/mtransport/nricectx.cpp
media/mtransport/nricectx.h
media/mtransport/nricemediastream.cpp
media/mtransport/test/ice_unittest.cpp
media/mtransport/test/transport_unittests.cpp
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_peer_ctx.c
media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/jsep/JsepSessionImpl.h
media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
media/webrtc/signaling/test/jsep_session_unittest.cpp
media/webrtc/signaling/test/signaling_unittests.cpp
--- a/media/mtransport/nricectx.cpp
+++ b/media/mtransport/nricectx.cpp
@@ -526,24 +526,30 @@ NrIceCtx::~NrIceCtx() {
   nr_ice_peer_ctx_destroy(&peer_);
   nr_ice_ctx_destroy(&ctx_);
   delete ice_handler_vtbl_;
   delete ice_handler_;
 }
 
 RefPtr<NrIceMediaStream>
 NrIceCtx::CreateStream(const std::string& name, int components) {
-  RefPtr<NrIceMediaStream> stream =
-    NrIceMediaStream::Create(this, name, components);
+  return NrIceMediaStream::Create(this, name, components);
+}
 
-  if (stream) {
-    streams_.push_back(stream);
+void
+NrIceCtx::SetStream(size_t index, NrIceMediaStream* stream) {
+  if (index >= streams_.size()) {
+    streams_.resize(index + 1);
   }
 
-  return stream;
+  if (streams_[index]) {
+    streams_[index]->Close();
+  }
+
+  streams_[index] = stream;
 }
 
 std::string NrIceCtx::ufrag() const {
   return ctx_->ufrag;
 }
 
 std::string NrIceCtx::pwd() const {
   return ctx_->pwd;
--- a/media/mtransport/nricectx.h
+++ b/media/mtransport/nricectx.h
@@ -222,26 +222,29 @@ class NrIceCtx {
 
   // Testing only.
   void destroy_peer_ctx();
 
   // Create a media stream
   RefPtr<NrIceMediaStream> CreateStream(const std::string& name,
                                         int components);
 
+  void SetStream(size_t index, NrIceMediaStream* stream);
+
   RefPtr<NrIceMediaStream> GetStream(size_t index) {
     if (index < streams_.size()) {
       return streams_[index];
     }
     return nullptr;
   }
 
-  void RemoveStream(size_t index)
+  // Some might be null
+  size_t GetStreamCount() const
   {
-    streams_[index] = nullptr;
+    return streams_.size();
   }
 
   // The name of the ctx
   const std::string& name() const { return name_; }
 
   // Get ufrag and password.
   std::string ufrag() const;
   std::string pwd() const;
--- a/media/mtransport/nricemediastream.cpp
+++ b/media/mtransport/nricemediastream.cpp
@@ -475,11 +475,16 @@ void NrIceMediaStream::Ready() {
   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;
+
+  int r = nr_ice_remove_media_stream(ctx_->ctx(), &stream_);
+  if (r) {
+    MOZ_ASSERT(false, "Failed to remove stream");
+    MOZ_MTLOG(ML_ERROR, "Failed to remove stream, error=" << r);
+  }
 }
 }  // close namespace
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -267,30 +267,41 @@ class IceTestPeer : public sigslot::has_
 
   void AddStream_s(int components) {
     char name[100];
     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);
+    ice_ctx_->SetStream(streams_.size(), stream);
 
     ASSERT_TRUE(stream);
     streams_.push_back(stream);
     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 AddStream(int components)
   {
-    test_utils->sts_target()->Dispatch(WrapRunnable(this,
-                                                    &IceTestPeer::AddStream_s,
-                                                    components),
+    test_utils->sts_target()->Dispatch(
+        WrapRunnable(this, &IceTestPeer::AddStream_s, components),
+        NS_DISPATCH_SYNC);
+  }
+
+  void RemoveStream_s(size_t index) {
+    streams_[index] = nullptr;
+    ice_ctx_->SetStream(index, nullptr);
+  }
+
+  void RemoveStream(size_t index) {
+    test_utils->sts_target()->Dispatch(
+        WrapRunnable(this, &IceTestPeer::RemoveStream_s, index),
         NS_DISPATCH_SYNC);
   }
 
   void SetStunServer(const std::string addr, uint16_t port) {
     if (addr.empty()) {
       // Happens when MOZ_DISABLE_NONLOCAL_CONNECTIONS is set
       return;
     }
@@ -380,18 +391,20 @@ class IceTestPeer : public sigslot::has_
       return candidate_filter_(candidate);
     }
     return candidate;
   }
 
   std::vector<std::string> GetCandidates_s(size_t stream) {
     std::vector<std::string> candidates;
 
-    if (stream >= streams_.size())
+    if (stream >= streams_.size() || !streams_[stream]) {
+      EXPECT_TRUE(false) << "No such stream " << stream;
       return candidates;
+    }
 
     std::vector<std::string> candidates_in =
       streams_[stream]->GetCandidates();
 
 
     for (size_t i=0; i < candidates_in.size(); i++) {
       std::string candidate(FilterCandidate(candidates_in[i]));
       if (!candidate.empty()) {
@@ -413,16 +426,20 @@ class IceTestPeer : public sigslot::has_
 
   void SetExpectedRemoteCandidateAddr(const std::string& addr) {
     expected_remote_addr_ = addr;
   }
 
   bool gathering_complete() { return gathering_complete_; }
   int ready_ct() { return ready_ct_; }
   bool is_ready_s(size_t stream) {
+    if (!streams_[stream]) {
+      EXPECT_TRUE(false) << "No such stream " << stream;
+      return false;
+    }
     return streams_[stream]->state() == NrIceMediaStream::ICE_OPEN;
   }
   bool is_ready(size_t stream)
   {
     bool result;
     test_utils->sts_target()->Dispatch(
         WrapRunnableRet(this, &IceTestPeer::is_ready_s, stream, &result),
         NS_DISPATCH_SYNC);
@@ -443,32 +460,32 @@ class IceTestPeer : public sigslot::has_
     trickle_mode_ = trickle_mode;
     ice_complete_ = false;
     res = ice_ctx_->ParseGlobalAttributes(remote->GetGlobalAttributes());
     ASSERT_TRUE(NS_SUCCEEDED(res));
 
     if (trickle_mode == TRICKLE_NONE ||
         trickle_mode == TRICKLE_REAL) {
       for (size_t i=0; i<streams_.size(); ++i) {
-        if (streams_[i]->HasParsedAttributes()) {
+        if (!streams_[i] || streams_[i]->HasParsedAttributes()) {
           continue;
         }
         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) {
-        if (streams_[i]->HasParsedAttributes()) {
+        if (!streams_[i] || streams_[i]->HasParsedAttributes()) {
           continue;
         }
         std::vector<std::string> empty_attrs;
         std::cout << "Calling ParseAttributes on stream " << i << std::endl;
         res = streams_[i]->ParseAttributes(empty_attrs);
         ASSERT_TRUE(NS_SUCCEEDED(res));
       }
     }
@@ -491,16 +508,17 @@ class IceTestPeer : public sigslot::has_
   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|
 
     // The size of streams_ is not going to change out from under us, so should
     // be safe here.
     ASSERT_GT(remote_->streams_.size(), stream);
+    ASSERT_TRUE(remote_->streams_[stream]);
 
     std::vector<SchedulableTrickleCandidate*>& candidates =
       ControlTrickle(stream);
 
     for (auto i = candidates.begin(); i != candidates.end(); ++i) {
       (*i)->Schedule(0);
     }
   }
@@ -518,16 +536,20 @@ class IceTestPeer : public sigslot::has_
       controlled_trickle_candidates_[stream].push_back(
           new SchedulableTrickleCandidate(this, stream, candidates[j]));
     }
 
     return controlled_trickle_candidates_[stream];
   }
 
   nsresult TrickleCandidate_s(const std::string &candidate, size_t stream) {
+    if (!streams_[stream]) {
+      // stream might have gone away before the trickle timer popped
+      return NS_OK;
+    }
     return streams_[stream]->ParseTrickleCandidate(candidate);
   }
 
   void DumpCandidate(std::string which, const NrIceCandidate& cand) {
     std::string type;
 
     std::string addr;
     int port;
@@ -571,16 +593,20 @@ class IceTestPeer : public sigslot::has_
               << " codeword="
               << cand.codeword
               << std::endl;
   }
 
   void DumpAndCheckActiveCandidates_s() {
     std::cerr << "Active candidates:" << std::endl;
     for (size_t i=0; i < streams_.size(); ++i) {
+      if (!streams_[i]) {
+        continue;
+      }
+
       for (size_t 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);
         if (res == NS_ERROR_NOT_AVAILABLE) {
@@ -655,16 +681,22 @@ class IceTestPeer : public sigslot::has_
     }
 
     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;
+
+      if (!streams_[i]) {
+        std::cerr << "DISABLED" << std::endl;
+        continue;
+      }
+
       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;
@@ -694,17 +726,17 @@ class IceTestPeer : public sigslot::has_
       ++trickled_;
     }
   }
 
   nsresult GetCandidatePairs_s(size_t stream_index,
                                std::vector<NrIceCandidatePair>* pairs)
   {
     MOZ_ASSERT(pairs);
-    if (stream_index >= streams_.size()) {
+    if (stream_index >= streams_.size() || !streams_[stream_index]) {
       // Is there a better error for "no such index"?
       ADD_FAILURE() << "No such media stream index: " << stream_index;
       return NS_ERROR_INVALID_ARG;
     }
 
     return streams_[stream_index]->GetCandidatePairs(pairs);
   }
 
@@ -744,16 +776,19 @@ class IceTestPeer : public sigslot::has_
       DumpCandidatePair(*p);
     }
     std::cerr << "]" << std::endl;
   }
 
   void DumpCandidatePairs_s() {
     std::cerr << "Dumping candidate pairs for all streams [" << std::endl;
     for (size_t s = 0; s < streams_.size(); ++s) {
+      if (!streams_[s]) {
+        continue;
+      }
       DumpCandidatePairs_s(streams_[s]);
     }
     std::cerr << "]" << std::endl;
   }
 
   bool CandidatePairsPriorityDescending(const std::vector<NrIceCandidatePair>&
                                         pairs) {
     // Verify that priority is descending
@@ -858,27 +893,34 @@ class IceTestPeer : public sigslot::has_
   void PacketReceived(NrIceMediaStream *stream, int component, const unsigned char *data,
                       int len) {
     std::cerr << "Received " << len << " bytes" << std::endl;
     ++received_;
   }
 
   void SendPacket(int stream, int component, const unsigned char *data,
                   int len) {
+    if (!streams_[stream]) {
+      ADD_FAILURE() << "No such stream " << stream;
+      return;
+    }
+
     ASSERT_TRUE(NS_SUCCEEDED(streams_[stream]->SendPacket(component, data, len)));
 
     ++sent_;
     std::cerr << "Sent " << len << " bytes" << std::endl;
   }
 
   void SetCandidateFilter(CandidateFilter filter) {
     candidate_filter_ = filter;
   }
 
   void ParseCandidate_s(size_t i, const std::string& candidate) {
+    ASSERT_TRUE(streams_[i]) << "No such stream " << i;
+
     std::vector<std::string> attributes;
 
     attributes.push_back(candidate);
     streams_[i]->ParseAttributes(attributes);
   }
 
   void ParseCandidate(size_t i, const std::string& candidate)
   {
@@ -887,16 +929,17 @@ class IceTestPeer : public sigslot::has_
                         &IceTestPeer::ParseCandidate_s,
                         i,
                         candidate),
         NS_DISPATCH_SYNC);
   }
 
   void DisableComponent_s(size_t stream, int component_id) {
     ASSERT_LT(stream, streams_.size());
+    ASSERT_TRUE(streams_[stream]) << "No such stream " << stream;
     nsresult res = streams_[stream]->DisableComponent(component_id);
     ASSERT_TRUE(NS_SUCCEEDED(res));
   }
 
   void DisableComponent(size_t stream, int component_id)
   {
     test_utils->sts_target()->Dispatch(
         WrapRunnable(this,
@@ -1059,16 +1102,21 @@ class IceConnectTest : public ::testing:
   }
 
   void AddStream(const std::string& name, int components) {
     Init(false, false);
     p1_->AddStream(components);
     p2_->AddStream(components);
   }
 
+  void RemoveStream(size_t index) {
+    p1_->RemoveStream(index);
+    p2_->RemoveStream(index);
+  }
+
   void Init(bool set_priorities, bool allow_loopback) {
     if (!initted_) {
       p1_ = new IceTestPeer("P1", true, set_priorities, allow_loopback);
       p2_ = new IceTestPeer("P2", false, set_priorities, allow_loopback);
     }
     initted_ = true;
   }
 
@@ -1722,16 +1770,55 @@ TEST_F(IceConnectTest, TestConnectTrickl
   ConnectTrickle();
   RealisticTrickleDelay(p1_->ControlTrickle(1));
   RealisticTrickleDelay(p2_->ControlTrickle(1));
   ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
   AssertCheckingReached();
 }
 
+TEST_F(IceConnectTest, RemoveStream) {
+  AddStream("first", 1);
+  AddStream("second", 1);
+  ASSERT_TRUE(Gather());
+  ConnectTrickle();
+  RealisticTrickleDelay(p1_->ControlTrickle(0));
+  RealisticTrickleDelay(p2_->ControlTrickle(0));
+  RealisticTrickleDelay(p1_->ControlTrickle(1));
+  RealisticTrickleDelay(p2_->ControlTrickle(1));
+  ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
+  ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
+
+  RemoveStream(0);
+  ASSERT_TRUE(Gather());
+  ConnectTrickle();
+}
+
+TEST_F(IceConnectTest, RemoveAndAddStream) {
+  AddStream("first", 1);
+  AddStream("second", 1);
+  ASSERT_TRUE(Gather());
+  ConnectTrickle();
+  RealisticTrickleDelay(p1_->ControlTrickle(0));
+  RealisticTrickleDelay(p2_->ControlTrickle(0));
+  RealisticTrickleDelay(p1_->ControlTrickle(1));
+  RealisticTrickleDelay(p2_->ControlTrickle(1));
+  ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000);
+  ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000);
+
+  RemoveStream(0);
+  AddStream("third", 1);
+  ASSERT_TRUE(Gather());
+  ConnectTrickle();
+  RealisticTrickleDelay(p1_->ControlTrickle(2));
+  RealisticTrickleDelay(p2_->ControlTrickle(2));
+  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(0));
   ConnectTrickle(TRICKLE_REAL);
   ASSERT_TRUE_WAIT(p1_->ice_complete(), kDefaultTimeout);
   ASSERT_TRUE_WAIT(p2_->ice_complete(), kDefaultTimeout);
   WaitForGather();  // ICE can complete before we finish gathering.
--- a/media/mtransport/test/transport_unittests.cpp
+++ b/media/mtransport/test/transport_unittests.cpp
@@ -594,17 +594,19 @@ class TransportTestPeer : public sigslot
 
     char name[100];
     snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(),
              (int)streams_.size());
 
     // Create the media stream
     mozilla::RefPtr<NrIceMediaStream> stream =
         ice_ctx_->CreateStream(static_cast<char *>(name), 1);
+
     ASSERT_TRUE(stream != nullptr);
+    ice_ctx_->SetStream(streams_.size(), stream);
     streams_.push_back(stream);
 
     // Listen for candidates
     stream->SignalCandidate.
         connect(this, &TransportTestPeer::GotCandidate);
 
     // Create the transport layer
     ice_ = new TransportLayerIce(name);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c
@@ -600,16 +600,43 @@ int nr_ice_add_media_stream(nr_ice_ctx *
 
     STAILQ_INSERT_TAIL(&ctx->streams,*streamp,entry);
 
     _status=0;
   abort:
     return(_status);
   }
 
+int nr_ice_remove_media_stream(nr_ice_ctx *ctx,nr_ice_media_stream **streamp)
+  {
+    int r,_status;
+    nr_ice_peer_ctx *pctx;
+    nr_ice_media_stream *peer_stream;
+
+    pctx=STAILQ_FIRST(&ctx->peers);
+    while(pctx){
+      if(!nr_ice_peer_ctx_find_pstream(pctx, *streamp, &peer_stream)) {
+        if(r=nr_ice_peer_ctx_remove_pstream(pctx, &peer_stream)) {
+          ABORT(r);
+        }
+      }
+
+      pctx=STAILQ_NEXT(pctx,entry);
+    }
+
+    STAILQ_REMOVE(&ctx->streams,*streamp,nr_ice_media_stream_,entry);
+    if(r=nr_ice_media_stream_destroy(streamp)) {
+      ABORT(r);
+    }
+
+    _status=0;
+  abort:
+    return(_status);
+  }
+
 int nr_ice_get_global_attributes(nr_ice_ctx *ctx,char ***attrsp, int *attrctp)
   {
     char **attrs=0;
     int _status;
     char *tmp=0;
 
     if(!(attrs=RCALLOC(sizeof(char *)*2)))
       ABORT(R_NO_MEMORY);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h
@@ -157,16 +157,17 @@ int nr_ice_ctx_create(char *label, UINT4
 #define NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION             (1<<2)
 #define NR_ICE_CTX_FLAGS_LITE                              (1<<3)
 
 int nr_ice_ctx_destroy(nr_ice_ctx **ctxp);
 int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg);
 int nr_ice_add_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand);
 void nr_ice_gather_finished_cb(NR_SOCKET s, int h, void *cb_arg);
 int nr_ice_add_media_stream(nr_ice_ctx *ctx,char *label,int components, nr_ice_media_stream **streamp);
+int nr_ice_remove_media_stream(nr_ice_ctx *ctx,nr_ice_media_stream **streamp);
 int nr_ice_get_global_attributes(nr_ice_ctx *ctx,char ***attrsp, int *attrctp);
 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);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
@@ -252,16 +252,31 @@ int nr_ice_peer_ctx_find_pstream(nr_ice_
 
     *pstreamp = pstream;
 
     _status=0;
  abort:
     return(_status);
   }
 
+int nr_ice_peer_ctx_remove_pstream(nr_ice_peer_ctx *pctx, nr_ice_media_stream **pstreamp)
+  {
+    int r,_status;
+
+    STAILQ_REMOVE(&pctx->peer_streams,*pstreamp,nr_ice_media_stream_,entry);
+
+    if(r=nr_ice_media_stream_destroy(pstreamp)) {
+      ABORT(r);
+    }
+
+    _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)
   {
     nr_ice_media_stream *pstream;
     int r,_status;
     int needs_pairing = 0;
 
     r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): peer (%s) parsing trickle ICE candidate %s",pctx->ctx->label,pctx->label,candidate);
     r = nr_ice_peer_ctx_find_pstream(pctx, stream, &pstream);
--- a/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.h
@@ -70,16 +70,17 @@ struct nr_ice_peer_ctx_ {
 };
 
 typedef STAILQ_HEAD(nr_ice_peer_ctx_head_, nr_ice_peer_ctx_) nr_ice_peer_ctx_head;
 
 int nr_ice_peer_ctx_create(nr_ice_ctx *ctx, nr_ice_handler *handler,char *label, nr_ice_peer_ctx **pctxp);
 int nr_ice_peer_ctx_destroy(nr_ice_peer_ctx **pctxp);
 int nr_ice_peer_ctx_parse_stream_attributes(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char **attrs, int attr_ct);
 int nr_ice_peer_ctx_find_pstream(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, nr_ice_media_stream **pstreamp);
+int nr_ice_peer_ctx_remove_pstream(nr_ice_peer_ctx *pctx, nr_ice_media_stream **pstreamp);
 int nr_ice_peer_ctx_parse_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *cand);
 
 int nr_ice_peer_ctx_pair_candidates(nr_ice_peer_ctx *pctx);
 int nr_ice_peer_ctx_parse_global_attributes(nr_ice_peer_ctx *pctx, char **attrs, int attr_ct);
 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);
 void nr_ice_peer_ctx_stream_started_checks(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream);
 int nr_ice_peer_ctx_dump_state(nr_ice_peer_ctx *pctx,FILE *out);
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -503,75 +503,76 @@ JsepSessionImpl::AddRecvonlyMsections(Sd
 // (ie; all m-sections are inactive).
 nsresult
 JsepSessionImpl::CreateReoffer(const Sdp& oldLocalSdp,
                                const Sdp& oldAnswer,
                                Sdp* newSdp)
 {
   nsresult rv;
 
-  // Figure out which mids were bundled before we begin, so we know how to
-  // populate candidate attributes and other transport info properly.
-  std::set<std::string> bundleMids;
-  const SdpMediaSection* bundleMsection;
-  rv = GetBundleInfo(oldAnswer, &bundleMids, &bundleMsection);
-  if (NS_FAILED(rv)) {
-    MOZ_ASSERT(false);
-    mLastError += " (This should have been caught sooner!)";
-    return NS_ERROR_FAILURE;
-  }
-
   for (size_t i = 0; i < oldLocalSdp.GetMediaSectionCount(); ++i) {
     // We do not set the direction in this function (or disable when previously
     // disabled), that happens in |AddOfferMSectionsByType|
     rv = CreateOfferMSection(oldLocalSdp.GetMediaSection(i).GetMediaType(),
                              SdpDirectionAttribute::kInactive,
                              newSdp);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = CopyStickyParams(oldAnswer.GetMediaSection(i),
                           &newSdp->GetMediaSection(i));
     NS_ENSURE_SUCCESS(rv, rv);
-
-    const SdpMediaSection* msectionWithTransportParams =
-      &oldLocalSdp.GetMediaSection(i);
-
-    auto& answerAttrs = oldAnswer.GetMediaSection(i).GetAttributeList();
-    if (answerAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
-        bundleMids.count(answerAttrs.GetMid())) {
-      msectionWithTransportParams = bundleMsection;
-    }
-
-    rv = CopyTransportParams(*msectionWithTransportParams,
-                             &newSdp->GetMediaSection(i));
-    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 void
 JsepSessionImpl::SetupBundle(Sdp* sdp) const
 {
   std::vector<std::string> mids;
 
+  // This has the effect of changing the bundle level if the first m-section
+  // goes from disabled to enabled. This is kinda inefficient.
+
   for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
     auto& attrs = sdp->GetMediaSection(i).GetAttributeList();
     if (attrs.HasAttribute(SdpAttribute::kMidAttribute)) {
       mids.push_back(attrs.GetMid());
     }
   }
 
   if (!mids.empty()) {
     UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
     groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids);
     sdp->GetAttributeList().SetAttribute(groupAttr.release());
   }
 }
 
+nsresult
+JsepSessionImpl::SetupTransportAttributes(Sdp* offer)
+{
+  const Sdp* oldAnswer = GetAnswer();
+
+  if (oldAnswer) {
+    // Renegotiation, we might have transport attributes to copy over
+    for (size_t i = 0; i < oldAnswer->GetMediaSectionCount(); ++i) {
+      if (!MsectionIsDisabled(offer->GetMediaSection(i)) &&
+          !MsectionIsDisabled(oldAnswer->GetMediaSection(i)) &&
+          !IsBundleSlave(*oldAnswer, i)) {
+        nsresult rv = CopyTransportParams(
+            mCurrentLocalDescription->GetMediaSection(i),
+            &offer->GetMediaSection(i));
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+    }
+  }
+
+  return NS_OK;
+}
+
 void
 JsepSessionImpl::SetupMsidSemantic(const std::vector<std::string>& msids,
                                    Sdp* sdp) const
 {
   if (!msids.empty()) {
     UniquePtr<SdpMsidSemanticAttributeList> msidSemantics(
         new SdpMsidSemanticAttributeList);
     msidSemantics->PushEntry("WMS", msids);
@@ -740,37 +741,30 @@ JsepSessionImpl::ParseMsid(const std::st
 }
 
 nsresult
 JsepSessionImpl::CreateOffer(const JsepOfferOptions& options,
                              std::string* offer)
 {
   mLastError.clear();
 
-  switch (mState) {
-    case kJsepStateStable:
-      break;
-    default:
-      JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
-      return NS_ERROR_UNEXPECTED;
+  if (mState != kJsepStateStable) {
+    JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
+    return NS_ERROR_UNEXPECTED;
   }
 
   UniquePtr<Sdp> sdp;
 
   // Make the basic SDP that is common to offer/answer.
   nsresult rv = CreateGenericSDP(&sdp);
   NS_ENSURE_SUCCESS(rv, rv);
   ++mSessionVersion;
 
   if (mCurrentLocalDescription) {
-    rv = CreateReoffer(*mCurrentLocalDescription,
-                       mIsOfferer ?
-                         *mCurrentRemoteDescription :
-                         *mCurrentLocalDescription,
-                       sdp.get());
+    rv = CreateReoffer(*mCurrentLocalDescription, *GetAnswer(), sdp.get());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Get rid of all m-line assignments that have not been executed by a call
   // to SetLocalDescription.
   if (NS_SUCCEEDED(rv)) {
     for (auto i = mLocalTracks.begin(); i != mLocalTracks.end(); ++i) {
       if (!i->mSetInLocalDescription) {
@@ -780,16 +774,19 @@ JsepSessionImpl::CreateOffer(const JsepO
   }
 
   // Now add all the m-lines that we are attempting to negotiate.
   rv = AddOfferMSections(options, sdp.get());
   NS_ENSURE_SUCCESS(rv, rv);
 
   SetupBundle(sdp.get());
 
+  rv = SetupTransportAttributes(sdp.get());
+  NS_ENSURE_SUCCESS(rv,rv);
+
   *offer = sdp->ToString();
   mGeneratedLocalDescription = Move(sdp);
 
   return NS_OK;
 }
 
 std::string
 JsepSessionImpl::GetLocalDescription() const
@@ -967,22 +964,19 @@ JsepSessionImpl::AddCommonExtmaps(const 
 }
 
 nsresult
 JsepSessionImpl::CreateAnswer(const JsepAnswerOptions& options,
                               std::string* answer)
 {
   mLastError.clear();
 
-  switch (mState) {
-    case kJsepStateHaveRemoteOffer:
-      break;
-    default:
-      JSEP_SET_ERROR("Cannot create answer in state " << GetStateStr(mState));
-      return NS_ERROR_UNEXPECTED;
+  if (mState != kJsepStateHaveRemoteOffer) {
+    JSEP_SET_ERROR("Cannot create answer in state " << GetStateStr(mState));
+    return NS_ERROR_UNEXPECTED;
   }
 
   // This is the heart of the negotiation code. Depressing that it's
   // so bad.
   //
   // Here's the current algorithm:
   // 1. Walk through all the m-lines on the other side.
   // 2. For each m-line, walk through all of our local tracks
@@ -2179,23 +2173,16 @@ JsepSessionImpl::ValidateRemoteDescripti
       continue;
     }
 
     const SdpAttributeList& newAttrs(
         description.GetMediaSection(i).GetAttributeList());
     const SdpAttributeList& oldAttrs(
         mCurrentRemoteDescription->GetMediaSection(i).GetAttributeList());
 
-    if (oldBundleMids.count(oldAttrs.GetMid()) &&
-        !newBundleMids.count(newAttrs.GetMid())) {
-      JSEP_SET_ERROR("Removing m-sections from a bundle group is unsupported "
-                     "at this time.");
-      return NS_ERROR_INVALID_ARG;
-    }
-
     if ((newAttrs.GetIceUfrag() != oldAttrs.GetIceUfrag()) ||
         (newAttrs.GetIcePwd() != oldAttrs.GetIcePwd())) {
       JSEP_SET_ERROR("ICE restart is unsupported at this time "
                      "(new remote description changes either the ice-ufrag "
                      "or ice-pwd)" <<
                      "ice-ufrag (old): " << oldAttrs.GetIceUfrag() <<
                      "ice-ufrag (new): " << newAttrs.GetIceUfrag() <<
                      "ice-pwd (old): " << oldAttrs.GetIcePwd() <<
@@ -2543,21 +2530,24 @@ JsepSessionImpl::AddLocalIceCandidate(co
   }
 
   if (sdp->GetMediaSectionCount() <= level) {
     // mainly here to make some testing less complicated, but also just in case
     *skipped = true;
     return NS_OK;
   }
 
-  if (IsBundleSlave(*sdp, level)) {
-    // We do not add candidate attributes to bundled m-sections unless they
-    // are the "master" bundle m-section.
-    *skipped = true;
-    return NS_OK;
+  if (mState == kJsepStateStable) {
+    const Sdp* answer(GetAnswer());
+    if (IsBundleSlave(*answer, level)) {
+      // We do not add candidate attributes to bundled m-sections unless they
+      // are the "master" bundle m-section.
+      *skipped = true;
+      return NS_OK;
+    }
   }
 
   *skipped = false;
 
   return AddCandidateToSdp(sdp, candidate, mid, level);
 }
 
 nsresult
@@ -2727,40 +2717,39 @@ JsepSessionImpl::GetBundleInfo(const Sdp
       return NS_ERROR_INVALID_ARG;
     }
   }
 
   return NS_OK;
 }
 
 bool
-JsepSessionImpl::IsBundleSlave(const Sdp& localSdp, uint16_t level)
+JsepSessionImpl::IsBundleSlave(const Sdp& sdp, uint16_t level)
 {
-  auto& localMsection = localSdp.GetMediaSection(level);
-
-  if (!localMsection.GetAttributeList().HasAttribute(
-        SdpAttribute::kMidAttribute)) {
+  auto& msection = sdp.GetMediaSection(level);
+
+  if (!msection.GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) {
     // No mid, definitely no bundle for this m-section
     return false;
   }
 
   std::set<std::string> bundleMids;
   const SdpMediaSection* bundleMsection;
-  nsresult rv = GetNegotiatedBundleInfo(&bundleMids, &bundleMsection);
+  nsresult rv = GetBundleInfo(sdp, &bundleMids, &bundleMsection);
   if (NS_FAILED(rv)) {
     // Should have been caught sooner.
     MOZ_ASSERT(false);
     return false;
   }
 
   if (!bundleMsection) {
     return false;
   }
 
-  std::string mid(localMsection.GetAttributeList().GetMid());
+  std::string mid(msection.GetAttributeList().GetMid());
 
   if (bundleMids.count(mid) && level != bundleMsection->GetLevel()) {
     // mid is bundled, and isn't the bundle m-section
     return true;
   }
 
   return false;
 }
@@ -2932,16 +2921,25 @@ JsepSessionImpl::GetCNAME(const SdpMedia
 bool
 JsepSessionImpl::MsectionIsDisabled(const SdpMediaSection& msection) const
 {
   return !msection.GetPort() &&
          !msection.GetAttributeList().HasAttribute(
              SdpAttribute::kBundleOnlyAttribute);
 }
 
+const Sdp*
+JsepSessionImpl::GetAnswer() const
+{
+  MOZ_ASSERT(mState == kJsepStateStable);
+
+  return mIsOfferer ? mCurrentRemoteDescription.get()
+                    : mCurrentLocalDescription.get();
+}
+
 nsresult
 JsepSessionImpl::Close()
 {
   mLastError.clear();
   SetState(kJsepStateClosed);
   return NS_OK;
 }
 
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -231,16 +231,17 @@ private:
                                     size_t* offerToRecv);
   nsresult AddRecvonlyMsections(SdpMediaSection::MediaType mediatype,
                                 size_t count,
                                 Sdp* sdp);
   nsresult CreateReoffer(const Sdp& oldLocalSdp,
                          const Sdp& oldAnswer,
                          Sdp* newSdp);
   void SetupBundle(Sdp* sdp) const;
+  nsresult SetupTransportAttributes(Sdp* sdp);
   void SetupMsidSemantic(const std::vector<std::string>& msids, Sdp* sdp) const;
   nsresult GetIdsFromMsid(const Sdp& sdp,
                           const SdpMediaSection& msection,
                           std::string* streamId,
                           std::string* trackId);
   nsresult GetRemoteIds(const Sdp& sdp,
                         const SdpMediaSection& msection,
                         std::string* streamId,
@@ -307,16 +308,17 @@ private:
   void DisableMsection(Sdp* sdp, SdpMediaSection* msection) const;
   nsresult EnableMsection(SdpMediaSection* msection);
 
   nsresult SetUniquePayloadTypes();
   nsresult GetAllPayloadTypes(const JsepTrackNegotiatedDetails& trackDetails,
                               std::vector<uint8_t>* payloadTypesOut);
   std::string GetCNAME(const SdpMediaSection& msection) const;
   bool MsectionIsDisabled(const SdpMediaSection& msection) const;
+  const Sdp* GetAnswer() const;
 
   std::vector<JsepSendingTrack> mLocalTracks;
   std::vector<JsepReceivingTrack> mRemoteTracks;
   // By the most recent SetRemoteDescription
   std::vector<JsepReceivingTrack> mRemoteTracksAdded;
   std::vector<JsepReceivingTrack> mRemoteTracksRemoved;
   std::vector<RefPtr<JsepTransport> > mTransports;
   std::vector<JsepTrackPair> mNegotiatedTrackPairs;
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -366,17 +366,17 @@ nsresult MediaPipeline::SendPacket(Trans
   TransportResult res = dtls->downward()->
       SendPacket(static_cast<const unsigned char *>(data), len);
 
   if (res != len) {
     // Ignore blocking indications
     if (res == TE_WOULDBLOCK)
       return NS_OK;
 
-    MOZ_MTLOG(ML_ERROR, "Failed write on stream");
+    MOZ_MTLOG(ML_ERROR, "Failed write on stream " << description_);
     return NS_BASE_STREAM_CLOSED;
   }
 
   return NS_OK;
 }
 
 void MediaPipeline::increment_rtp_packets_sent(int32_t bytes) {
   ++rtp_packets_sent_;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -26,16 +26,17 @@
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsICancelable.h"
 #include "nsIDocument.h"
 #include "nsILoadInfo.h"
 #include "nsIContentPolicy.h"
 #include "nsIProxyInfo.h"
 #include "nsIProtocolProxyService.h"
+#include "nsProxyRelease.h"
 
 #ifdef MOZILLA_INTERNAL_API
 #include "MediaStreamList.h"
 #include "nsIScriptGlobalObject.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
 #include "MediaStreamTrack.h"
 #include "VideoStreamTrack.h"
@@ -395,22 +396,25 @@ nsresult PeerConnectionMedia::UpdateMedi
   return NS_OK;
 }
 
 void
 PeerConnectionMedia::StartIceChecks(const JsepSession& session) {
 
   std::vector<size_t> numComponentsByLevel;
   auto transports = session.GetTransports();
-  for (auto i = transports.begin(); i != transports.end(); ++i) {
-    RefPtr<JsepTransport> transport = *i;
+  for (size_t i = 0; i < transports.size(); ++i) {
+    RefPtr<JsepTransport> transport = transports[i];
     if (transport->mState == JsepTransport::kJsepTransportClosed) {
       CSFLogDebug(logTag, "Transport %s is disabled",
                           transport->mTransportId.c_str());
       numComponentsByLevel.push_back(0);
+      // Make sure the MediaPipelineFactory doesn't try to use these.
+      RemoveTransportFlow(i, false);
+      RemoveTransportFlow(i, true);
     } else {
       CSFLogDebug(logTag, "Transport %s has %u components",
                           transport->mTransportId.c_str(),
                           static_cast<unsigned>(transport->mComponents));
       numComponentsByLevel.push_back(transport->mComponents);
     }
   }
 
@@ -458,19 +462,20 @@ PeerConnectionMedia::StartIceChecks_s(
                           NrIceCtx::ICE_CONTROLLED);
 
   for (size_t i = 0; i < aComponentCountByLevel.size(); ++i) {
     RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(i));
     if (!stream) {
       continue;
     }
 
-    if (!stream->HasParsedAttributes()) {
+    if (!aComponentCountByLevel[i]) {
       // Inactive stream. Remove.
-      mIceCtx->RemoveStream(i);
+      mIceCtx->SetStream(i, nullptr);
+      continue;
     }
 
     for (size_t c = aComponentCountByLevel[i]; c < stream->components(); ++c) {
       // components are 1-indexed
       stream->DisableComponent(c + 1);
     }
   }
 
@@ -489,26 +494,27 @@ PeerConnectionMedia::AddIceCandidate(con
                     std::string(mid),
                     aMLine),
                 NS_DISPATCH_NORMAL);
 }
 void
 PeerConnectionMedia::AddIceCandidate_s(const std::string& aCandidate,
                                        const std::string& aMid,
                                        uint32_t aMLine) {
-  if (aMLine >= mIceStreams.size()) {
-    CSFLogError(logTag, "Couldn't process ICE candidate for bogus level %u",
-                aMLine);
+  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aMLine));
+  if (!stream) {
+    CSFLogError(logTag, "No ICE stream for candidate at level %u: %s",
+                        static_cast<unsigned>(aMLine), aCandidate.c_str());
     return;
   }
 
-  nsresult rv = mIceStreams[aMLine]->ParseTrickleCandidate(aCandidate);
+  nsresult rv = stream->ParseTrickleCandidate(aCandidate);
   if (NS_FAILED(rv)) {
     CSFLogError(logTag, "Couldn't process ICE candidate at level %u",
-                aMLine);
+                static_cast<unsigned>(aMLine));
     return;
   }
 }
 
 void
 PeerConnectionMedia::FlushIceCtxOperationQueueIfReady()
 {
   ASSERT_ON_THREAD(mMainThread);
@@ -557,52 +563,39 @@ PeerConnectionMedia::EnsureIceGathering_
 void
 PeerConnectionMedia::UpdateIceMediaStream_s(size_t aMLine,
                                             size_t aComponentCount,
                                             bool aHasAttrs,
                                             const std::string& aUfrag,
                                             const std::string& aPassword,
                                             const std::vector<std::string>&
                                             aCandidateList) {
-  if (aMLine > mIceStreams.size()) {
-    CSFLogError(logTag, "Missing stream for previous m-line %u, this can "
-                        "happen if we failed to create a stream earlier.",
-                        static_cast<unsigned>(aMLine - 1));
-    return;
-  }
+  RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aMLine));
+  if (!stream) {
+    CSFLogDebug(logTag, "%s: Creating ICE media stream=%u components=%u",
+                mParentHandle.c_str(),
+                static_cast<unsigned>(aMLine),
+                static_cast<unsigned>(aComponentCount));
 
-  CSFLogDebug(logTag, "%s: Creating ICE media stream=%u components=%u",
-              mParentHandle.c_str(),
-              static_cast<unsigned>(aMLine),
-              static_cast<unsigned>(aComponentCount));
-  RefPtr<NrIceMediaStream> stream;
-
-  if (mIceStreams.size() == aMLine) {
-    mIceStreams.push_back(nullptr);
-  }
-
-  if (!mIceStreams[aMLine]) {
     std::ostringstream os;
     os << mParentName << " level=" << aMLine;
     stream = mIceCtx->CreateStream(os.str().c_str(),
                                    aComponentCount);
 
     if (!stream) {
       CSFLogError(logTag, "Failed to create ICE stream.");
       return;
     }
 
     stream->SetLevel(aMLine);
     stream->SignalReady.connect(this, &PeerConnectionMedia::IceStreamReady_s);
     stream->SignalCandidate.connect(this,
                                     &PeerConnectionMedia::OnCandidateFound_s);
 
-    mIceStreams[aMLine] = stream;
-  } else {
-    stream = mIceStreams[aMLine];
+    mIceCtx->SetStream(aMLine, stream);
   }
 
   if (aHasAttrs && !stream->HasParsedAttributes()) {
     std::vector<std::string> attrs;
     for (auto i = aCandidateList.begin(); i != aCandidateList.end(); ++i) {
       attrs.push_back("candidate:" + *i);
     }
     attrs.push_back("ice-ufrag:" + aUfrag);
@@ -762,17 +755,16 @@ PeerConnectionMedia::ShutdownMediaTransp
   }
 
   for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) {
     mRemoteSourceStreams[i]->DetachTransport_s();
   }
 
   disconnect_all();
   mTransportFlows.clear();
-  mIceStreams.clear();
   mIceCtx = nullptr;
 
   mMainThread->Dispatch(WrapRunnable(this, &PeerConnectionMedia::SelfDestruct_m),
                         NS_DISPATCH_NORMAL);
 }
 
 LocalSourceStreamInfo*
 PeerConnectionMedia::GetLocalStreamByIndex(int aIndex)
@@ -986,27 +978,37 @@ PeerConnectionMedia::DtlsConnected_m(con
     pc->SetDtlsConnected(aPrivacyRequested);
   }
 }
 
 void
 PeerConnectionMedia::AddTransportFlow(int aIndex, bool aRtcp,
                                       const RefPtr<TransportFlow> &aFlow)
 {
-  int index_inner = aIndex * 2 + (aRtcp ? 1 : 0);
+  int index_inner = GetTransportFlowIndex(aIndex, aRtcp);
 
   MOZ_ASSERT(!mTransportFlows[index_inner]);
   mTransportFlows[index_inner] = aFlow;
 
   GetSTSThread()->Dispatch(
     WrapRunnable(this, &PeerConnectionMedia::ConnectDtlsListener_s, aFlow),
     NS_DISPATCH_NORMAL);
 }
 
 void
+PeerConnectionMedia::RemoveTransportFlow(int aIndex, bool aRtcp)
+{
+  int index_inner = GetTransportFlowIndex(aIndex, aRtcp);
+  TransportFlow* flow = mTransportFlows[index_inner].forget().take();
+  if (flow) {
+    NS_ProxyRelease(GetSTSThread(), flow);
+  }
+}
+
+void
 PeerConnectionMedia::ConnectDtlsListener_s(const RefPtr<TransportFlow>& aFlow)
 {
   TransportLayer* dtls = aFlow->GetLayer(TransportLayerDtls::ID());
   if (dtls) {
     dtls->SignalStateChange.connect(this, &PeerConnectionMedia::DtlsConnected_s);
   }
 }
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -231,26 +231,21 @@ class PeerConnectionMedia : public sigsl
   void SelfDestruct();
 
   // Configure the ability to use localhost.
   void SetAllowIceLoopback(bool val) { mAllowIceLoopback = val; }
 
   RefPtr<NrIceCtx> ice_ctx() const { return mIceCtx; }
 
   RefPtr<NrIceMediaStream> ice_media_stream(size_t i) const {
-    // TODO(ekr@rtfm.com): If someone asks for a value that doesn't exist,
-    // make one.
-    if (i >= mIceStreams.size()) {
-      return nullptr;
-    }
-    return mIceStreams[i];
+    return mIceCtx->GetStream(i);
   }
 
   size_t num_ice_media_streams() const {
-    return mIceStreams.size();
+    return mIceCtx->GetStreamCount();
   }
 
   // Create and modify transports in response to negotiation events.
   void UpdateTransports(const JsepSession& session);
 
   // Start ICE checks.
   void StartIceChecks(const JsepSession& session);
 
@@ -314,31 +309,36 @@ class PeerConnectionMedia : public sigsl
   void UpdateRemoteStreamPrincipals_m(nsIPrincipal* aPrincipal);
 #endif
 
   bool AnyCodecHasPluginID(uint64_t aPluginID);
 
   const nsCOMPtr<nsIThread>& GetMainThread() const { return mMainThread; }
   const nsCOMPtr<nsIEventTarget>& GetSTSThread() const { return mSTSThread; }
 
+  static size_t GetTransportFlowIndex(int aStreamIndex, bool aRtcp)
+  {
+    return aStreamIndex * 2 + (aRtcp ? 1 : 0);
+  }
+
   // Get a transport flow either RTP/RTCP for a particular stream
   // A stream can be of audio/video/datachannel/budled(?) types
-  RefPtr<TransportFlow> GetTransportFlow(int aStreamIndex,
-                                                           bool aIsRtcp) {
-    int index_inner = aStreamIndex * 2 + (aIsRtcp ? 1 : 0);
+  RefPtr<TransportFlow> GetTransportFlow(int aStreamIndex, bool aIsRtcp) {
+    int index_inner = GetTransportFlowIndex(aStreamIndex, aIsRtcp);
 
     if (mTransportFlows.find(index_inner) == mTransportFlows.end())
       return nullptr;
 
     return mTransportFlows[index_inner];
   }
 
   // Add a transport flow
   void AddTransportFlow(int aIndex, bool aRtcp,
                         const RefPtr<TransportFlow> &aFlow);
+  void RemoveTransportFlow(int aIndex, bool aRtcp);
   void ConnectDtlsListener_s(const RefPtr<TransportFlow>& aFlow);
   void DtlsConnected_s(TransportLayer* aFlow,
                        TransportLayer::State state);
   static void DtlsConnected_m(const std::string& aParentHandle,
                               bool aPrivacyRequested);
 
   RefPtr<AudioSessionConduit> GetAudioConduit(size_t level) {
     auto it = mConduits.find(level);
@@ -474,17 +474,16 @@ class PeerConnectionMedia : public sigsl
 
   std::map<size_t, std::pair<bool, RefPtr<MediaSessionConduit>>> mConduits;
 
   // Allow loopback for ICE.
   bool mAllowIceLoopback;
 
   // ICE objects
   RefPtr<NrIceCtx> mIceCtx;
-  std::vector<RefPtr<NrIceMediaStream> > mIceStreams;
 
   // DNS
   nsRefPtr<NrIceResolver> mDNSResolver;
 
   // Transport flows: even is RTP, odd is RTCP
   std::map<int, RefPtr<TransportFlow> > mTransportFlows;
 
   // UUID Generator
--- a/media/webrtc/signaling/test/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/test/jsep_session_unittest.cpp
@@ -1875,16 +1875,24 @@ TEST_P(JsepSessionTest, RenegotiationOff
     ASSERT_EQ(newAnswererPairs[0].mRtcpTransport.get(),
               newAnswererPairs[i].mRtcpTransport.get());
   }
 
   ASSERT_NE(newOffererPairs[0].mRtpTransport.get(),
             offererPairs[0].mRtpTransport.get());
   ASSERT_NE(newAnswererPairs[0].mRtpTransport.get(),
             answererPairs[0].mRtpTransport.get());
+
+  ASSERT_LE(1U, mSessionOff.GetTransports().size());
+  ASSERT_LE(1U, mSessionAns.GetTransports().size());
+
+  ASSERT_EQ(JsepTransport::kJsepTransportClosed,
+            mSessionOff.GetTransports()[0]->mState);
+  ASSERT_EQ(JsepTransport::kJsepTransportClosed,
+            mSessionAns.GetTransports()[0]->mState);
 }
 
 TEST_P(JsepSessionTest, RenegotiationAnswererDisablesBundleTransport)
 {
   AddTracks(mSessionOff);
   AddTracks(mSessionAns);
 
   if (types.size() < 2) {
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -2367,16 +2367,32 @@ TEST_P(SignalingTest, RenegotiationOffer
 
   a1_->RemoveTrack(0, false);
 
   OfferAnswer(options, OFFER_NONE);
 
   CloseStreams();
 }
 
+TEST_P(SignalingTest, RenegotiationBothRemoveThenAddTrack)
+{
+  OfferOptions options;
+  OfferAnswer(options, OFFER_AV | ANSWER_AV);
+
+  a1_->RemoveTrack(0, false);
+  a2_->RemoveTrack(0, false);
+
+  OfferAnswer(options, OFFER_NONE);
+
+  // OFFER_AUDIO causes a new audio track to be added on both sides
+  OfferAnswer(options, OFFER_AUDIO);
+
+  CloseStreams();
+}
+
 TEST_P(SignalingTest, RenegotiationOffererReplacesTrack)
 {
   OfferOptions options;
   OfferAnswer(options, OFFER_AV | ANSWER_AV);
 
   a1_->RemoveTrack(0, false);
 
   // OFFER_AUDIO causes a new audio track to be added on both sides