Bug 1133866: Some refactoring and simplification in JsepSessionImpl. r=mt a=sylvestre
authorByron Campen [:bwc] <docfaraday@gmail.com>
Sat, 28 Mar 2015 08:06:07 -0700
changeset 248509 27402eebc291f38293a0f0212cba738683b8a76d
parent 248508 bce8a0df6baf35eb7b0b69a1f04585ba549ffbea
child 248510 21ee0f42cf6eba78946d18e0f8a53da867b0597f
push id7850
push userbcampen@mozilla.com
push dateSun, 29 Mar 2015 14:59:47 +0000
treeherdermozilla-aurora@7d33ef433d9e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmt, sylvestre
bugs1133866
milestone38.0a2
Bug 1133866: Some refactoring and simplification in JsepSessionImpl. r=mt a=sylvestre
media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
media/webrtc/signaling/src/jsep/JsepSessionImpl.h
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -46,35 +46,18 @@ JsepSessionImpl::~JsepSessionImpl()
 
 nsresult
 JsepSessionImpl::Init()
 {
   mLastError.clear();
 
   MOZ_ASSERT(!mSessionId, "Init called more than once");
 
-  SECStatus rv = PK11_GenerateRandom(
-      reinterpret_cast<unsigned char*>(&mSessionId), sizeof(mSessionId));
-  // RFC 3264 says that session-ids MUST be representable as a _signed_
-  // 64 bit number, meaning the MSB cannot be set.
-  mSessionId = mSessionId >> 1;
-  if (rv != SECSuccess) {
-    JSEP_SET_ERROR("Failed to generate session id: " << rv);
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!mUuidGen->Generate(&mDefaultRemoteStreamId)) {
-    JSEP_SET_ERROR("Failed to generate default uuid for streams");
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!mUuidGen->Generate(&mCNAME)) {
-    JSEP_SET_ERROR("Failed to generate CNAME");
-    return NS_ERROR_FAILURE;
-  }
+  nsresult rv = SetupIds();
+  NS_ENSURE_SUCCESS(rv, rv);
 
   SetupDefaultCodecs();
   SetupDefaultRtpExtensions();
 
   return NS_OK;
 }
 
 // Helper function to find the track for a given m= section.
@@ -312,23 +295,95 @@ GetProtocolForMediaType(SdpMediaSection:
     return SdpMediaSection::kDtlsSctp;
   }
 
   // TODO(bug 1094447): Use kUdpTlsRtpSavpf once it interops well
   return SdpMediaSection::kRtpSavpf;
 }
 
 nsresult
+JsepSessionImpl::AddOfferMSections(const JsepOfferOptions& options, Sdp* sdp)
+{
+  // First audio, then video, then datachannel, for interop
+  // TODO(bug 1121756): We need to group these by stream-id, _then_ by media
+  // type, according to the spec. However, this is not going to interop with
+  // older versions of Firefox if a video-only stream is added before an
+  // audio-only stream.
+  // We should probably wait until 38 is ESR before trying to do this.
+  nsresult rv = AddOfferMSectionsByType(
+      SdpMediaSection::kAudio, options.mOfferToReceiveAudio, sdp);
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = AddOfferMSectionsByType(
+      SdpMediaSection::kVideo, options.mOfferToReceiveVideo, sdp);
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!(options.mDontOfferDataChannel.isSome() &&
+        *options.mDontOfferDataChannel)) {
+    rv = AddOfferMSectionsByType(
+        SdpMediaSection::kApplication, Maybe<size_t>(), sdp);
+
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (!sdp->GetMediaSectionCount()) {
+    JSEP_SET_ERROR("Cannot create an offer with no local tracks, "
+                   "no offerToReceiveAudio/Video, and no DataChannel.");
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return NS_OK;
+}
+
+nsresult
 JsepSessionImpl::AddOfferMSectionsByType(SdpMediaSection::MediaType mediatype,
-                                         Maybe<size_t> offerToReceiveCount,
+                                         Maybe<size_t> offerToReceiveMaybe,
                                          Sdp* sdp)
 {
-  size_t numRecv = 0;
+  // Convert the Maybe into a size_t*, since that is more readable, especially
+  // when using it as an in/out param.
+  size_t offerToReceiveCount;
+  size_t* offerToReceiveCountPtr = nullptr;
+
+  if (offerToReceiveMaybe) {
+    offerToReceiveCount = *offerToReceiveMaybe;
+    offerToReceiveCountPtr = &offerToReceiveCount;
+  }
 
   // Make sure every local track has an m-section
+  nsresult rv = BindLocalTracks(mediatype, sdp);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Make sure that m-sections that previously had a remote track have the
+  // recv bit set. Only matters for renegotiation.
+  rv = EnsureRecvForRemoteTracks(mediatype, sdp, offerToReceiveCountPtr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // If we need more recv sections, start setting the recv bit on other
+  // msections. If not, disable msections that have no tracks.
+  rv = SetRecvAsNeededOrDisable(mediatype,
+                                sdp,
+                                offerToReceiveCountPtr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // If we still don't have enough recv m-sections, add some.
+  if (offerToReceiveCountPtr && *offerToReceiveCountPtr) {
+    rv = AddRecvonlyMsections(mediatype, *offerToReceiveCountPtr, sdp);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::BindLocalTracks(SdpMediaSection::MediaType mediatype,
+                                 Sdp* sdp)
+{
   for (auto track = mLocalTracks.begin(); track != mLocalTracks.end();
        ++track) {
     if (mediatype != track->mTrack->GetMediaType()) {
       continue;
     }
 
     SdpMediaSection* msection;
 
@@ -340,19 +395,39 @@ JsepSessionImpl::AddOfferMSectionsByType
                                            sdp,
                                            &msection);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     nsresult rv = BindTrackToMsection(&(*track), msection);
     NS_ENSURE_SUCCESS(rv, rv);
   }
-
-  // Make sure that m-sections that previously had a remote track have the
-  // recv bit set.
+  return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::BindTrackToMsection(
+    JsepSendingTrack* track,
+    SdpMediaSection* msection)
+{
+  if (msection->GetMediaType() != SdpMediaSection::kApplication) {
+    AddLocalSsrcs(*track->mTrack, msection);
+    AddLocalIds(*track->mTrack, msection);
+  }
+  msection->SetSending(true);
+  track->mAssignedMLine = Some(msection->GetLevel());
+  track->mSetInLocalDescription = false;
+  return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::EnsureRecvForRemoteTracks(SdpMediaSection::MediaType mediatype,
+                                           Sdp* sdp,
+                                           size_t* offerToReceive)
+{
   for (auto track = mRemoteTracks.begin(); track != mRemoteTracks.end();
        ++track) {
     if (mediatype != track->mTrack->GetMediaType()) {
       continue;
     }
 
     if (!track->mAssignedMLine.isSome()) {
       MOZ_ASSERT(false);
@@ -363,73 +438,69 @@ JsepSessionImpl::AddOfferMSectionsByType
 
     if (MsectionIsDisabled(msection)) {
       // TODO(bug 1095226) Content probably disabled this? Should we allow
       // content to do this?
       continue;
     }
 
     msection.SetReceiving(true);
-    ++numRecv;
+    if (offerToReceive && *offerToReceive) {
+      --(*offerToReceive);
+    }
   }
 
-  // If we need more recv sections, start setting the recv bit on other
-  // msections. If not, disable msections that have no tracks.
+  return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype,
+                                          Sdp* sdp,
+                                          size_t* offerToRecv)
+{
   for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
     auto& msection = sdp->GetMediaSection(i);
 
     if (MsectionIsDisabled(msection) ||
         msection.GetMediaType() != mediatype ||
         msection.IsReceiving()) {
       continue;
     }
 
-    if (offerToReceiveCount.isSome() && numRecv < *offerToReceiveCount) {
+    if (offerToRecv) {
+      if (*offerToRecv) {
+        msection.SetReceiving(true);
+        --(*offerToRecv);
+        continue;
+      }
+    } else if (msection.IsSending()) {
       msection.SetReceiving(true);
-      ++numRecv;
       continue;
     }
 
-    if (msection.IsSending()) {
-      if (!offerToReceiveCount.isSome()) {
-        // When offerToReceiveX is not specified, we offer to recv on all lines
-        // that we offer to send on.
-        msection.SetReceiving(true);
-        ++numRecv;
-      }
-    } else {
+    if (!msection.IsSending()) {
       // Unused m-section, and no reason to offer to recv on it
       DisableMsection(sdp, &msection);
     }
   }
 
-  // If we still don't have enough recv m-sections, add some
-  while (offerToReceiveCount.isSome() && numRecv < *offerToReceiveCount) {
-    nsresult rv = CreateOfferMSection(
-        mediatype, SdpDirectionAttribute::kRecvonly, sdp);
-
-    NS_ENSURE_SUCCESS(rv, rv);
-    ++numRecv;
-  }
-
   return NS_OK;
 }
 
 nsresult
-JsepSessionImpl::BindTrackToMsection(
-    JsepSendingTrack* track,
-    SdpMediaSection* msection)
+JsepSessionImpl::AddRecvonlyMsections(SdpMediaSection::MediaType mediatype,
+                                      size_t count,
+                                      Sdp* sdp)
 {
-  if (msection->GetMediaType() != SdpMediaSection::kApplication) {
-    AddLocalSsrcs(*track->mTrack, msection);
-    AddLocalIds(*track->mTrack, msection);
+  while (count--) {
+    nsresult rv = CreateOfferMSection(
+        mediatype, SdpDirectionAttribute::kRecvonly, sdp);
+
+    NS_ENSURE_SUCCESS(rv, rv);
   }
-  msection->SetSending(true);
-  track->mAssignedMLine = Some(msection->GetLevel());
-  track->mSetInLocalDescription = false;
   return NS_OK;
 }
 
 // This function creates a skeleton SDP based on the old descriptions
 // (ie; all m-sections are inactive).
 nsresult
 JsepSessionImpl::CreateReoffer(const Sdp& oldLocalSdp,
                                const Sdp& oldAnswer,
@@ -444,82 +515,39 @@ JsepSessionImpl::CreateReoffer(const Sdp
   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) {
-    auto& msection = oldLocalSdp.GetMediaSection(i);
-
-    if (MsectionIsDisabled(oldAnswer.GetMediaSection(i))) {
-        rv = CreateOfferMSection(
-            msection.GetMediaType(),
-            msection.GetAttributeList().GetDirection(),
-            newSdp);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        DisableMsection(newSdp, &newSdp->GetMediaSection(i));
-        continue;
-    }
-
-    // We do not set the direction in this function, that happens in
-    // |AddOfferMSectionsByType|
-    rv = CreateOfferMSection(msection.GetMediaType(),
+    // 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);
 
-    auto& newMsection = newSdp->GetMediaSection(i);
+    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();
-    auto& newAttrs = newMsection.GetAttributeList();
-
-    const SdpMediaSection* msectionWithTransportParams = &msection;
-
     if (answerAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
         bundleMids.count(answerAttrs.GetMid())) {
       msectionWithTransportParams = bundleMsection;
     }
 
-    auto& transportAttrs = msectionWithTransportParams->GetAttributeList();
-
-    // Copy over m-section details
-    newMsection.SetPort(msectionWithTransportParams->GetPort());
-    newMsection.GetConnection() = msectionWithTransportParams->GetConnection();
-
-    // Now we copy over attributes that won't be added by the usual logic
-    if (transportAttrs.HasAttribute(SdpAttribute::kCandidateAttribute)) {
-      auto* candidateAttrs =
-        new SdpMultiStringAttribute(SdpAttribute::kCandidateAttribute);
-      candidateAttrs->mValues = transportAttrs.GetCandidate();
-      newAttrs.SetAttribute(candidateAttrs);
-    }
-
-    if (transportAttrs.HasAttribute(SdpAttribute::kEndOfCandidatesAttribute)) {
-      newAttrs.SetAttribute(
-          new SdpFlagAttribute(SdpAttribute::kEndOfCandidatesAttribute));
-    }
-
-    // rtcp-mux, based on _answer_
-    if (answerAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
-      newAttrs.SetAttribute(
-          new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
-    } else if (transportAttrs.HasAttribute(SdpAttribute::kRtcpAttribute)) {
-      // copy rtcp attribute
-      newAttrs.SetAttribute(new SdpRtcpAttribute(transportAttrs.GetRtcp()));
-    }
-
-    // mid, based on _answer_
-    if (answerAttrs.HasAttribute(SdpAttribute::kMidAttribute)) {
-      newAttrs.SetAttribute(
-          new SdpStringAttribute(SdpAttribute::kMidAttribute,
-                                 answerAttrs.GetMid()));
-    }
+    rv = CopyTransportParams(*msectionWithTransportParams,
+                             &newSdp->GetMediaSection(i));
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 void
 JsepSessionImpl::SetupBundle(Sdp* sdp) const
 {
@@ -652,56 +680,71 @@ JsepSessionImpl::GetMsids(
 
   // Can we find some additional msids in ssrc attributes?
   // (Chrome does not put plain-old msid attributes in its SDP)
   if (msection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
     auto& ssrcs = msection.GetAttributeList().GetSsrc().mSsrcs;
 
     for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) {
       if (i->attribute.find("msid:") == 0) {
-        // Would be nice if SdpSsrcAttributeList could parse out the contained
-        // attribute, but at least the parse here is simple.
-        size_t streamIdStart = i->attribute.find_first_not_of(" \t", 5);
-        // We do not assume the appdata token is here, since this is not
-        // necessarily a webrtc msid
-        if (streamIdStart == std::string::npos) {
-          JSEP_SET_ERROR("Malformed source-level msid attribute: "
-                         << i->attribute);
-          return NS_ERROR_INVALID_ARG;
-        }
-
-        size_t streamIdEnd = i->attribute.find_first_of(" \t", streamIdStart);
-        if (streamIdEnd == std::string::npos) {
-          streamIdEnd = i->attribute.size();
-        }
-
-        size_t trackIdStart =
-          i->attribute.find_first_not_of(" \t", streamIdEnd);
-        if (trackIdStart == std::string::npos) {
-          trackIdStart = i->attribute.size();
-        }
-
-        size_t trackIdEnd = i->attribute.find_first_of(" \t", trackIdStart);
-        if (trackIdEnd == std::string::npos) {
-          trackIdEnd = i->attribute.size();
-        }
-
-        size_t streamIdSize = streamIdEnd - streamIdStart;
-        size_t trackIdSize = trackIdEnd - trackIdStart;
-
-        msids->push_back({i->attribute.substr(streamIdStart, streamIdSize),
-                          i->attribute.substr(trackIdStart, trackIdSize)});
+        std::string streamId;
+        std::string trackId;
+        nsresult rv = ParseMsid(i->attribute, &streamId, &trackId);
+        NS_ENSURE_SUCCESS(rv, rv);
+        msids->push_back({streamId, trackId});
       }
     }
   }
 
   return NS_OK;
 }
 
 nsresult
+JsepSessionImpl::ParseMsid(const std::string& msidAttribute,
+                           std::string* streamId,
+                           std::string* trackId)
+{
+  // Would be nice if SdpSsrcAttributeList could parse out the contained
+  // attribute, but at least the parse here is simple.
+  // We are being very forgiving here wrt whitespace; tabs are not actually
+  // allowed, nor is leading/trailing whitespace.
+  size_t streamIdStart = msidAttribute.find_first_not_of(" \t", 5);
+  // We do not assume the appdata token is here, since this is not
+  // necessarily a webrtc msid
+  if (streamIdStart == std::string::npos) {
+    JSEP_SET_ERROR("Malformed source-level msid attribute: "
+        << msidAttribute);
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  size_t streamIdEnd = msidAttribute.find_first_of(" \t", streamIdStart);
+  if (streamIdEnd == std::string::npos) {
+    streamIdEnd = msidAttribute.size();
+  }
+
+  size_t trackIdStart =
+    msidAttribute.find_first_not_of(" \t", streamIdEnd);
+  if (trackIdStart == std::string::npos) {
+    trackIdStart = msidAttribute.size();
+  }
+
+  size_t trackIdEnd = msidAttribute.find_first_of(" \t", trackIdStart);
+  if (trackIdEnd == std::string::npos) {
+    trackIdEnd = msidAttribute.size();
+  }
+
+  size_t streamIdSize = streamIdEnd - streamIdStart;
+  size_t trackIdSize = trackIdEnd - trackIdStart;
+
+  *streamId = msidAttribute.substr(streamIdStart, streamIdSize);
+  *trackId = msidAttribute.substr(trackIdStart, trackIdSize);
+  return NS_OK;
+}
+
+nsresult
 JsepSessionImpl::CreateOffer(const JsepOfferOptions& options,
                              std::string* offer)
 {
   mLastError.clear();
 
   switch (mState) {
     case kJsepStateStable:
       break;
@@ -732,46 +775,19 @@ JsepSessionImpl::CreateOffer(const JsepO
     for (auto i = mLocalTracks.begin(); i != mLocalTracks.end(); ++i) {
       if (!i->mSetInLocalDescription) {
         i->mAssignedMLine.reset();
       }
     }
   }
 
   // Now add all the m-lines that we are attempting to negotiate.
-  // First audio, then video, then datachannel, for interop
-  // TODO(bug 1121756): We need to group these by stream-id, _then_ by media
-  // type, according to the spec. However, this is not going to interop with
-  // older versions of Firefox if a video-only stream is added before an
-  // audio-only stream.
-  // We should probably wait until 38 is ESR before trying to do this.
-  rv = AddOfferMSectionsByType(
-      SdpMediaSection::kAudio, options.mOfferToReceiveAudio, sdp.get());
-
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = AddOfferMSectionsByType(
-      SdpMediaSection::kVideo, options.mOfferToReceiveVideo, sdp.get());
-
+  rv = AddOfferMSections(options, sdp.get());
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!(options.mDontOfferDataChannel.isSome() &&
-        *options.mDontOfferDataChannel)) {
-    rv = AddOfferMSectionsByType(
-        SdpMediaSection::kApplication, Maybe<size_t>(), sdp.get());
-
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  if (!sdp->GetMediaSectionCount()) {
-    JSEP_SET_ERROR("Cannot create an offer with no local tracks, "
-                   "no offerToReceiveAudio/Video, and no DataChannel.");
-    return NS_ERROR_INVALID_ARG;
-  }
-
   SetupBundle(sdp.get());
 
   *offer = sdp->ToString();
   mGeneratedLocalDescription = Move(sdp);
 
   return NS_OK;
 }
 
@@ -992,30 +1008,31 @@ JsepSessionImpl::CreateAnswer(const Jsep
     if (!i->mAssignedMLine.isSome()) {
       continue;
     }
 
     // Get rid of all m-line assignments that have not been executed by a call
     // to SetLocalDescription.
     if (!i->mSetInLocalDescription) {
       i->mAssignedMLine.reset();
+      continue;
     }
 
     if (!offer.GetMediaSection(*i->mAssignedMLine).IsReceiving()) {
       i->mAssignedMLine.reset();
     }
   }
 
   size_t numMsections = offer.GetMediaSectionCount();
 
   for (size_t i = 0; i < numMsections; ++i) {
     const SdpMediaSection& remoteMsection = offer.GetMediaSection(i);
     SdpMediaSection& msection =
         sdp->AddMediaSection(remoteMsection.GetMediaType(),
-                             SdpDirectionAttribute::kSendrecv,
+                             SdpDirectionAttribute::kInactive,
                              9,
                              remoteMsection.GetProtocol(),
                              sdp::kIPv4,
                              "0.0.0.0");
 
     rv = CreateAnswerMSection(options, i, remoteMsection, &msection, sdp.get());
     NS_ENSURE_SUCCESS(rv, rv);
   }
@@ -1142,92 +1159,80 @@ JsepSessionImpl::CreateAnswerMSection(co
 
   SdpSetupAttribute::Role role;
   nsresult rv = DetermineAnswererSetupRole(remoteMsection, &role);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = AddTransportAttributes(msection, role);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  SdpDirectionAttribute::Direction localDirection =
-      SdpDirectionAttribute::kInactive;
-
   // Only attempt to match up local tracks if the offerer has elected to
   // receive traffic.
   if (remoteMsection.IsReceiving()) {
-    auto track = FindTrackByLevel(mLocalTracks, mlineIndex);
-
-    if (track == mLocalTracks.end()) {
-      track = FindUnassignedTrackByType(mLocalTracks,
-                                        remoteMsection.GetMediaType());
-    }
-
-    if (track == mLocalTracks.end() &&
-        remoteMsection.GetMediaType() == SdpMediaSection::kApplication) {
-      // If we are offered datachannel, we need to play along even if no track
-      // for it has been added yet.
-      std::string streamId;
-      std::string trackId;
-
-      if (!mUuidGen->Generate(&streamId) || !mUuidGen->Generate(&trackId)) {
-        JSEP_SET_ERROR("Failed to generate UUIDs for datachannel track");
-        return NS_ERROR_FAILURE;
-      }
-
-      AddTrack(RefPtr<JsepTrack>(
-          new JsepTrack(SdpMediaSection::kApplication, streamId, trackId)));
-      track = FindUnassignedTrackByType(mLocalTracks,
-                                        remoteMsection.GetMediaType());
-      MOZ_ASSERT(track != mLocalTracks.end());
-    }
-
-    if (track != mLocalTracks.end()) {
-      localDirection = SdpDirectionAttribute::kSendonly;
-      rv = BindTrackToMsection(&(*track), msection);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
+    rv = BindMatchingLocalTrackForAnswer(msection);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (remoteMsection.IsSending()) {
-    localDirection = static_cast<SdpDirectionAttribute::Direction>(
-        localDirection | SdpDirectionAttribute::kRecvFlag);
+    msection->SetReceiving(true);
   }
 
-  auto& remoteAttrs = remoteMsection.GetAttributeList();
-  auto& localAttrs = msection->GetAttributeList();
-
-  localAttrs.SetAttribute(new SdpDirectionAttribute(localDirection));
-
-  if (remoteAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
-    // If we aren't using a protocol with RTCP, just smile and nod.
-    localAttrs.SetAttribute(
-        new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
-  }
-
-  // Reflect mid
-  if (remoteAttrs.HasAttribute(SdpAttribute::kMidAttribute)) {
-    localAttrs.SetAttribute(new SdpStringAttribute(SdpAttribute::kMidAttribute,
-                                                   remoteAttrs.GetMid()));
-  }
+  rv = CopyStickyParams(remoteMsection, msection);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // Now add the codecs.
   AddCommonCodecs(remoteMsection, msection);
 
   // Add extmap attributes.
   AddCommonExtmaps(remoteMsection, msection);
 
   if (msection->GetFormats().empty()) {
     // Could not negotiate anything. Disable m-section.
     DisableMsection(sdp, msection);
   }
 
   return NS_OK;
 }
 
 nsresult
+JsepSessionImpl::BindMatchingLocalTrackForAnswer(SdpMediaSection* msection)
+{
+  auto track = FindTrackByLevel(mLocalTracks, msection->GetLevel());
+
+  if (track == mLocalTracks.end()) {
+    track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType());
+  }
+
+  if (track == mLocalTracks.end() &&
+      msection->GetMediaType() == SdpMediaSection::kApplication) {
+    // If we are offered datachannel, we need to play along even if no track
+    // for it has been added yet.
+    std::string streamId;
+    std::string trackId;
+
+    if (!mUuidGen->Generate(&streamId) || !mUuidGen->Generate(&trackId)) {
+      JSEP_SET_ERROR("Failed to generate UUIDs for datachannel track");
+      return NS_ERROR_FAILURE;
+    }
+
+    AddTrack(RefPtr<JsepTrack>(
+          new JsepTrack(SdpMediaSection::kApplication, streamId, trackId)));
+    track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType());
+    MOZ_ASSERT(track != mLocalTracks.end());
+  }
+
+  if (track != mLocalTracks.end()) {
+    nsresult rv = BindTrackToMsection(&(*track), msection);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
 JsepSessionImpl::DetermineAnswererSetupRole(
     const SdpMediaSection& remoteMsection,
     SdpSetupAttribute::Role* rolep)
 {
   // Determine the role.
   // RFC 5763 says:
   //
   //   The endpoint MUST use the setup attribute defined in [RFC4145].
@@ -1364,18 +1369,22 @@ JsepSessionImpl::SetLocalDescriptionOffe
 
 nsresult
 JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type,
                                            UniquePtr<Sdp> answer)
 {
   MOZ_ASSERT(mState == kJsepStateHaveRemoteOffer);
   mPendingLocalDescription = Move(answer);
 
-  nsresult rv = HandleNegotiatedSession(mPendingLocalDescription,
-                                        mPendingRemoteDescription);
+  nsresult rv = ValidateAnswer(*mPendingRemoteDescription,
+                               *mPendingLocalDescription);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = HandleNegotiatedSession(mPendingLocalDescription,
+                               mPendingRemoteDescription);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mCurrentRemoteDescription = Move(mPendingRemoteDescription);
   mCurrentLocalDescription = Move(mPendingLocalDescription);
 
   SetState(kJsepStateStable);
   return NS_OK;
 }
@@ -1446,203 +1455,86 @@ JsepSessionImpl::SetRemoteDescription(Js
 
   return rv;
 }
 
 nsresult
 JsepSessionImpl::HandleNegotiatedSession(const UniquePtr<Sdp>& local,
                                          const UniquePtr<Sdp>& remote)
 {
-  if (local->GetMediaSectionCount() != remote->GetMediaSectionCount()) {
-    JSEP_SET_ERROR("Local and remote SDP have different number of m-lines "
-                   << "(" << local->GetMediaSectionCount() << " vs "
-                   << remote->GetMediaSectionCount() << ")");
-    return NS_ERROR_INVALID_ARG;
-  }
-
   bool remoteIceLite =
       remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
 
   mIceControlling = remoteIceLite || mIsOfferer;
 
-  const Sdp& offer = mIsOfferer ? *local : *remote;
   const Sdp& answer = mIsOfferer ? *remote : *local;
 
   std::set<std::string> bundleMids;
   const SdpMediaSection* bundleMsection = nullptr;
   // TODO(bug 1112692): Support more than one bundle group
   nsresult rv = GetBundleInfo(answer, &bundleMids, &bundleMsection);
   NS_ENSURE_SUCCESS(rv, rv);
 
   std::vector<JsepTrackPair> trackPairs;
 
   // Now walk through the m-sections, make sure they match, and create
   // track pairs that describe the media to be set up.
   for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) {
-    const SdpMediaSection& lm = local->GetMediaSection(i);
-    const SdpMediaSection& rm = remote->GetMediaSection(i);
-    const SdpMediaSection& offerMsection = offer.GetMediaSection(i);
-    const SdpMediaSection& answerMsection = answer.GetMediaSection(i);
-
-    if (lm.GetMediaType() != rm.GetMediaType()) {
-      JSEP_SET_ERROR(
-          "Answer and offerMsection have different media types at m-line "
-          << i);
-      return NS_ERROR_INVALID_ARG;
-    }
-
     // Skip disabled m-sections.
-    if (answerMsection.GetPort() == 0) {
-      // Transports start out in closed, so we don't need to do anything here.
+    if (answer.GetMediaSection(i).GetPort() == 0) {
       continue;
     }
 
-    if (!offerMsection.IsSending() && answerMsection.IsReceiving()) {
-      JSEP_SET_ERROR("Answer tried to set recv when offer did not set send");
-      return NS_ERROR_INVALID_ARG;
-    }
-
-    if (!offerMsection.IsReceiving() && answerMsection.IsSending()) {
-      JSEP_SET_ERROR("Answer tried to set send when offer did not set recv");
-      return NS_ERROR_INVALID_ARG;
-    }
-
-    bool sending;
-    bool receiving;
-
-    if (mIsOfferer) {
-      receiving = answerMsection.IsSending();
-      sending = answerMsection.IsReceiving();
-    } else {
-      sending = answerMsection.IsSending();
-      receiving = answerMsection.IsReceiving();
-    }
-
     // The transport details are not necessarily on the m-section we're
     // currently processing.
     size_t transportLevel = i;
     bool usingBundle = false;
-    if (answerMsection.GetAttributeList().HasAttribute(
-          SdpAttribute::kMidAttribute)) {
-      if (bundleMids.count(answerMsection.GetAttributeList().GetMid())) {
-        transportLevel = bundleMsection->GetLevel();
-        usingBundle = true;
+    {
+      const SdpMediaSection& answerMsection(answer.GetMediaSection(i));
+      if (answerMsection.GetAttributeList().HasAttribute(
+            SdpAttribute::kMidAttribute)) {
+        if (bundleMids.count(answerMsection.GetAttributeList().GetMid())) {
+          transportLevel = bundleMsection->GetLevel();
+          usingBundle = true;
+        }
       }
     }
 
-    RefPtr<JsepTransport> transport;
-
     // Transports are created in SetLocal.
-    MOZ_ASSERT(mTransports.size() > transportLevel);
     if (mTransports.size() < transportLevel) {
       JSEP_SET_ERROR("Fewer transports set up than m-lines");
+      MOZ_ASSERT(false);
       return NS_ERROR_FAILURE;
     }
-    transport = mTransports[transportLevel];
-
-    // If doing bundle, we need to grab all of the transport specifics from the
-    // bundle m-section, not the m-section we're currently processing.
-    auto& remoteTransportAttrs =
-      remote->GetMediaSection(transportLevel).GetAttributeList();
-
-    auto& answerTransportAttrs =
-      answer.GetMediaSection(transportLevel).GetAttributeList();
-
-    auto& offerTransportAttrs =
-      offer.GetMediaSection(transportLevel).GetAttributeList();
+
+    RefPtr<JsepTransport> transport = mTransports[transportLevel];
 
     rv = SetupTransport(
-        remoteTransportAttrs, answerTransportAttrs, transport);
+        remote->GetMediaSection(transportLevel).GetAttributeList(),
+        answer.GetMediaSection(transportLevel).GetAttributeList(),
+        transport);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (!sending && !receiving) {
+    if (!answer.GetMediaSection(i).IsSending() &&
+        !answer.GetMediaSection(i).IsReceiving()) {
       MOZ_MTLOG(ML_DEBUG, "Inactive m-section, skipping creation of negotiated "
                           "track pair.");
       continue;
     }
 
-    MOZ_MTLOG(ML_DEBUG, "Negotiated m= line"
-                            << " index=" << i << " type=" << lm.GetMediaType()
-                            << " sending=" << sending
-                            << " receiving=" << receiving);
-
-    JsepTrackPair jpair;
-
-    jpair.mLevel = i;
-
-    if (usingBundle) {
-      jpair.mBundleLevel = Some(transportLevel);
-    }
-
-    if (sending) {
-      auto sendTrack = FindTrackByLevel(mLocalTracks, i);
-      if (sendTrack == mLocalTracks.end()) {
-        JSEP_SET_ERROR("Failed to find local track for level " << i
-                       << " in local SDP. This should never happen.");
-        NS_ASSERTION(false, "Failed to find local track for level");
-        return NS_ERROR_FAILURE;
-      }
-
-      rv = NegotiateTrack(rm,
-                          lm,
-                          JsepTrack::kJsepTrackSending,
-                          &sendTrack->mTrack);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      jpair.mSending = sendTrack->mTrack;
-    }
-
-    if (receiving) {
-      auto recvTrack = FindTrackByLevel(mRemoteTracks, i);
-      if (recvTrack == mRemoteTracks.end()) {
-        JSEP_SET_ERROR("Failed to find remote track for level " << i
-                       << " in remote SDP. This should never happen.");
-        NS_ASSERTION(false, "Failed to find remote track for level");
-        return NS_ERROR_FAILURE;
-      }
-
-      rv = NegotiateTrack(rm,
-                          lm,
-                          JsepTrack::kJsepTrackReceiving,
-                          &recvTrack->mTrack);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      if (rm.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
-        auto& ssrcs = rm.GetAttributeList().GetSsrc().mSsrcs;
-        for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) {
-          recvTrack->mTrack->AddSsrc(i->ssrc);
-        }
-      }
-
-      if (jpair.mBundleLevel.isSome() &&
-          recvTrack->mTrack->GetSsrcs().empty() &&
-          recvTrack->mTrack->GetMediaType() != SdpMediaSection::kApplication) {
-        MOZ_MTLOG(ML_ERROR, "Bundled m-section has no ssrc attributes. "
-                            "This may cause media packets to be dropped.");
-      }
-
-      jpair.mReceiving = recvTrack->mTrack;
-    }
-
-    jpair.mRtpTransport = transport;
-
-    if (HasRtcp(lm.GetProtocol())) {
-      // RTCP MUX or not.
-      // TODO(bug 1095743): verify that the PTs are consistent with mux.
-      if (offerTransportAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute) &&
-          answerTransportAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
-        jpair.mRtcpTransport = nullptr; // We agree on mux.
-        MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is on");
-      } else {
-        MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is off");
-        jpair.mRtcpTransport = transport;
-      }
-    }
-
-    trackPairs.push_back(jpair);
+    JsepTrackPair trackPair;
+    rv = MakeNegotiatedTrackPair(remote->GetMediaSection(i),
+                                 local->GetMediaSection(i),
+                                 transport,
+                                 usingBundle,
+                                 transportLevel,
+                                 &trackPair);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    trackPairs.push_back(trackPair);
   }
 
   rv = SetUniquePayloadTypes();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Ouch, this probably needs some dirty bit instead of just clearing
   // stuff for renegotiation.
   mNegotiatedTrackPairs = trackPairs;
@@ -1652,16 +1544,122 @@ JsepSessionImpl::HandleNegotiatedSession
     if ((*i)->mState != JsepTransport::kJsepTransportAccepted) {
       (*i)->mState = JsepTransport::kJsepTransportClosed;
     }
   }
   return NS_OK;
 }
 
 nsresult
+JsepSessionImpl::MakeNegotiatedTrackPair(const SdpMediaSection& remote,
+                                         const SdpMediaSection& local,
+                                         const RefPtr<JsepTransport>& transport,
+                                         bool usingBundle,
+                                         size_t transportLevel,
+                                         JsepTrackPair* trackPairOut)
+{
+  const SdpMediaSection& answer = mIsOfferer ? remote : local;
+
+  bool sending;
+  bool receiving;
+
+  if (mIsOfferer) {
+    receiving = answer.IsSending();
+    sending = answer.IsReceiving();
+  } else {
+    sending = answer.IsSending();
+    receiving = answer.IsReceiving();
+  }
+
+  MOZ_MTLOG(ML_DEBUG, "Negotiated m= line"
+                          << " index=" << local.GetLevel()
+                          << " type=" << local.GetMediaType()
+                          << " sending=" << sending
+                          << " receiving=" << receiving);
+
+  trackPairOut->mLevel = local.GetLevel();
+
+  if (usingBundle) {
+    trackPairOut->mBundleLevel = Some(transportLevel);
+  }
+
+  if (sending) {
+    auto sendTrack = FindTrackByLevel(mLocalTracks, local.GetLevel());
+    if (sendTrack == mLocalTracks.end()) {
+      JSEP_SET_ERROR("Failed to find local track for level " <<
+                     local.GetLevel()
+                     << " in local SDP. This should never happen.");
+      NS_ASSERTION(false, "Failed to find local track for level");
+      return NS_ERROR_FAILURE;
+    }
+
+    nsresult rv = NegotiateTrack(remote,
+                                 local,
+                                 JsepTrack::kJsepTrackSending,
+                                 &sendTrack->mTrack);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    trackPairOut->mSending = sendTrack->mTrack;
+  }
+
+  if (receiving) {
+    auto recvTrack = FindTrackByLevel(mRemoteTracks, local.GetLevel());
+    if (recvTrack == mRemoteTracks.end()) {
+      JSEP_SET_ERROR("Failed to find remote track for level "
+                     << local.GetLevel()
+                     << " in remote SDP. This should never happen.");
+      NS_ASSERTION(false, "Failed to find remote track for level");
+      return NS_ERROR_FAILURE;
+    }
+
+    nsresult rv = NegotiateTrack(remote,
+                                 local,
+                                 JsepTrack::kJsepTrackReceiving,
+                                 &recvTrack->mTrack);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (remote.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
+      auto& ssrcs = remote.GetAttributeList().GetSsrc().mSsrcs;
+      for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) {
+        recvTrack->mTrack->AddSsrc(i->ssrc);
+      }
+    }
+
+    if (trackPairOut->mBundleLevel.isSome() &&
+        recvTrack->mTrack->GetSsrcs().empty() &&
+        recvTrack->mTrack->GetMediaType() != SdpMediaSection::kApplication) {
+      MOZ_MTLOG(ML_ERROR, "Bundled m-section has no ssrc attributes. "
+                          "This may cause media packets to be dropped.");
+    }
+
+    trackPairOut->mReceiving = recvTrack->mTrack;
+  }
+
+  trackPairOut->mRtpTransport = transport;
+
+  const SdpAttributeList& remoteAttrs(remote.GetAttributeList());
+  const SdpAttributeList& localAttrs(local.GetAttributeList());
+
+  if (HasRtcp(local.GetProtocol())) {
+    // RTCP MUX or not.
+    // TODO(bug 1095743): verify that the PTs are consistent with mux.
+    if (remoteAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute) &&
+        localAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
+      trackPairOut->mRtcpTransport = nullptr; // We agree on mux.
+      MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is on");
+    } else {
+      MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is off");
+      trackPairOut->mRtcpTransport = transport;
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
 JsepSessionImpl::NegotiateTrack(const SdpMediaSection& remoteMsection,
                                 const SdpMediaSection& localMsection,
                                 JsepTrack::Direction direction,
                                 RefPtr<JsepTrack>* track)
 {
   UniquePtr<JsepTrackNegotiatedDetailsImpl> negotiatedDetails =
       MakeUnique<JsepTrackNegotiatedDetailsImpl>();
   negotiatedDetails->mProtocol = remoteMsection.GetProtocol();
@@ -1822,16 +1820,72 @@ JsepSessionImpl::AddTransportAttributes(
       new SdpStringAttribute(SdpAttribute::kIcePwdAttribute, mIcePwd));
 
   msection->GetAttributeList().SetAttribute(new SdpSetupAttribute(dtlsRole));
 
   return NS_OK;
 }
 
 nsresult
+JsepSessionImpl::CopyTransportParams(const SdpMediaSection& source,
+                                     SdpMediaSection* dest)
+{
+  // Copy over m-section details
+  dest->SetPort(source.GetPort());
+  dest->GetConnection() = source.GetConnection();
+
+  auto& sourceAttrs = source.GetAttributeList();
+  auto& destAttrs = dest->GetAttributeList();
+
+  // Now we copy over attributes that won't be added by the usual logic
+  if (sourceAttrs.HasAttribute(SdpAttribute::kCandidateAttribute)) {
+    auto* candidateAttrs =
+      new SdpMultiStringAttribute(SdpAttribute::kCandidateAttribute);
+    candidateAttrs->mValues = sourceAttrs.GetCandidate();
+    destAttrs.SetAttribute(candidateAttrs);
+  }
+
+  if (sourceAttrs.HasAttribute(SdpAttribute::kEndOfCandidatesAttribute)) {
+    destAttrs.SetAttribute(
+        new SdpFlagAttribute(SdpAttribute::kEndOfCandidatesAttribute));
+  }
+
+  if (!destAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute) &&
+      sourceAttrs.HasAttribute(SdpAttribute::kRtcpAttribute)) {
+    // copy rtcp attribute
+    destAttrs.SetAttribute(new SdpRtcpAttribute(sourceAttrs.GetRtcp()));
+  }
+
+  return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::CopyStickyParams(const SdpMediaSection& source,
+                                  SdpMediaSection* dest)
+{
+  auto& sourceAttrs = source.GetAttributeList();
+  auto& destAttrs = dest->GetAttributeList();
+
+  // There's no reason to renegotiate rtcp-mux
+  if (sourceAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
+    destAttrs.SetAttribute(
+        new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
+  }
+
+  // mid must stay the same
+  if (sourceAttrs.HasAttribute(SdpAttribute::kMidAttribute)) {
+    destAttrs.SetAttribute(
+        new SdpStringAttribute(SdpAttribute::kMidAttribute,
+          sourceAttrs.GetMid()));
+  }
+
+  return NS_OK;
+}
+
+nsresult
 JsepSessionImpl::ParseSdp(const std::string& sdp, UniquePtr<Sdp>* parsedp)
 {
   UniquePtr<Sdp> parsed = mParser.Parse(sdp);
   if (!parsed) {
     std::string error = "Failed to parse SDP: ";
     appendSdpParseErrors(mParser.GetParseErrors(), &error);
     JSEP_SET_ERROR(error);
     return NS_ERROR_INVALID_ARG;
@@ -1932,19 +1986,23 @@ nsresult
 JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type,
                                             UniquePtr<Sdp> answer)
 {
   MOZ_ASSERT(mState == kJsepStateHaveLocalOffer ||
              mState == kJsepStateHaveRemotePranswer);
 
   mPendingRemoteDescription = Move(answer);
 
+  nsresult rv = ValidateAnswer(*mPendingLocalDescription,
+                               *mPendingRemoteDescription);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // TODO(bug 1095780): Note that this creates remote tracks even if
   // we offered sendonly and other side offered sendrecv or recvonly.
-  nsresult rv = SetRemoteTracksFromDescription(*mPendingRemoteDescription);
+  rv = SetRemoteTracksFromDescription(*mPendingRemoteDescription);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = HandleNegotiatedSession(mPendingLocalDescription,
                                mPendingRemoteDescription);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mCurrentRemoteDescription = Move(mPendingRemoteDescription);
   mCurrentLocalDescription = Move(mPendingLocalDescription);
@@ -2132,16 +2190,50 @@ JsepSessionImpl::ValidateRemoteDescripti
       return NS_ERROR_INVALID_ARG;
     }
   }
 
   return NS_OK;
 }
 
 nsresult
+JsepSessionImpl::ValidateAnswer(const Sdp& offer, const Sdp& answer)
+{
+  if (offer.GetMediaSectionCount() != answer.GetMediaSectionCount()) {
+    JSEP_SET_ERROR("Offer and answer have different number of m-lines "
+                   << "(" << offer.GetMediaSectionCount() << " vs "
+                   << answer.GetMediaSectionCount() << ")");
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) {
+    const SdpMediaSection& offerMsection = offer.GetMediaSection(i);
+    const SdpMediaSection& answerMsection = answer.GetMediaSection(i);
+
+    if (offerMsection.GetMediaType() != answerMsection.GetMediaType()) {
+      JSEP_SET_ERROR(
+          "Answer and offer have different media types at m-line " << i);
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    if (!offerMsection.IsSending() && answerMsection.IsReceiving()) {
+      JSEP_SET_ERROR("Answer tried to set recv when offer did not set send");
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    if (!offerMsection.IsReceiving() && answerMsection.IsSending()) {
+      JSEP_SET_ERROR("Answer tried to set send when offer did not set recv");
+      return NS_ERROR_INVALID_ARG;
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
 JsepSessionImpl::CreateReceivingTrack(size_t mline,
                                       const Sdp& sdp,
                                       const SdpMediaSection& msection,
                                       RefPtr<JsepTrack>* track)
 {
   std::string streamId;
   std::string trackId;
 
@@ -2205,16 +2297,42 @@ JsepSessionImpl::CreateGenericSDP(Unique
   std::vector<std::string> msids;
   msids.push_back("*");
   SetupMsidSemantic(msids, sdp.get());
 
   *sdpp = Move(sdp);
   return NS_OK;
 }
 
+nsresult
+JsepSessionImpl::SetupIds()
+{
+  SECStatus rv = PK11_GenerateRandom(
+      reinterpret_cast<unsigned char*>(&mSessionId), sizeof(mSessionId));
+  // RFC 3264 says that session-ids MUST be representable as a _signed_
+  // 64 bit number, meaning the MSB cannot be set.
+  mSessionId = mSessionId >> 1;
+  if (rv != SECSuccess) {
+    JSEP_SET_ERROR("Failed to generate session id: " << rv);
+    return NS_ERROR_FAILURE;
+  }
+
+  if (!mUuidGen->Generate(&mDefaultRemoteStreamId)) {
+    JSEP_SET_ERROR("Failed to generate default uuid for streams");
+    return NS_ERROR_FAILURE;
+  }
+
+  if (!mUuidGen->Generate(&mCNAME)) {
+    JSEP_SET_ERROR("Failed to generate CNAME");
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
 void
 JsepSessionImpl::SetupDefaultCodecs()
 {
   // Supported audio codecs.
   mCodecs.push_back(new JsepAudioCodecDescription(
       "109",
       "opus",
       48000,
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -182,71 +182,99 @@ private:
       const std::string& pt,
       const SdpMediaSection& msection) const;
   const std::vector<SdpExtmapAttributeList::Extmap>* GetRtpExtensions(
       SdpMediaSection::MediaType type) const;
   void AddCommonCodecs(const SdpMediaSection& remoteMsection,
                        SdpMediaSection* msection);
   void AddCommonExtmaps(const SdpMediaSection& remoteMsection,
                         SdpMediaSection* msection);
+  nsresult SetupIds();
   void SetupDefaultCodecs();
   void SetupDefaultRtpExtensions();
   void SetState(JsepSignalingState state);
   // Non-const so it can set mLastError
   nsresult ParseSdp(const std::string& sdp, UniquePtr<Sdp>* parsedp);
   nsresult SetLocalDescriptionOffer(UniquePtr<Sdp> offer);
   nsresult SetLocalDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer);
   nsresult SetRemoteDescriptionOffer(UniquePtr<Sdp> offer);
   nsresult SetRemoteDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer);
   nsresult ValidateLocalDescription(const Sdp& description);
   nsresult ValidateRemoteDescription(const Sdp& description);
+  nsresult ValidateAnswer(const Sdp& offer, const Sdp& answer);
   nsresult SetRemoteTracksFromDescription(const Sdp& remoteDescription);
   // Non-const because we use our Uuid generator
   nsresult CreateReceivingTrack(size_t mline,
                                 const Sdp& sdp,
                                 const SdpMediaSection& msection,
                                 RefPtr<JsepTrack>* track);
   nsresult HandleNegotiatedSession(const UniquePtr<Sdp>& local,
                                    const UniquePtr<Sdp>& remote);
   nsresult AddTransportAttributes(SdpMediaSection* msection,
                                   SdpSetupAttribute::Role dtlsRole);
+  nsresult CopyTransportParams(const SdpMediaSection& source,
+                               SdpMediaSection* dest);
+  nsresult CopyStickyParams(const SdpMediaSection& source,
+                            SdpMediaSection* dest);
+  nsresult AddOfferMSections(const JsepOfferOptions& options, Sdp* sdp);
   // Non-const so it can assign m-line index to tracks
   nsresult AddOfferMSectionsByType(SdpMediaSection::MediaType type,
                                    Maybe<size_t> offerToReceive,
                                    Sdp* sdp);
+  nsresult BindLocalTracks(SdpMediaSection::MediaType mediatype,
+                           Sdp* sdp);
   nsresult BindTrackToMsection(JsepSendingTrack* track,
                                SdpMediaSection* msection);
+  nsresult EnsureRecvForRemoteTracks(SdpMediaSection::MediaType mediatype,
+                                     Sdp* sdp,
+                                     size_t* offerToReceive);
+  nsresult SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype,
+                                    Sdp* sdp,
+                                    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;
   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,
                         std::string* trackId);
   nsresult GetMsids(const SdpMediaSection& msection,
                     std::vector<SdpMsidAttributeList::Msid>* msids);
+  nsresult ParseMsid(const std::string& msidAttribute,
+                     std::string* streamId,
+                     std::string* trackId);
   nsresult CreateOfferMSection(SdpMediaSection::MediaType type,
                                SdpDirectionAttribute::Direction direction,
                                Sdp* sdp);
   nsresult GetFreeMsectionForSend(SdpMediaSection::MediaType type,
                                   Sdp* sdp,
                                   SdpMediaSection** msection);
   nsresult CreateAnswerMSection(const JsepAnswerOptions& options,
                                 size_t mlineIndex,
                                 const SdpMediaSection& remoteMsection,
                                 SdpMediaSection* msection,
                                 Sdp* sdp);
+  nsresult BindMatchingLocalTrackForAnswer(SdpMediaSection* msection);
   nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection,
                                       SdpSetupAttribute::Role* rolep);
+  nsresult MakeNegotiatedTrackPair(const SdpMediaSection& remote,
+                                   const SdpMediaSection& local,
+                                   const RefPtr<JsepTransport>& transport,
+                                   bool usingBundle,
+                                   size_t transportLevel,
+                                   JsepTrackPair* trackPairOut);
   nsresult NegotiateTrack(const SdpMediaSection& remoteMsection,
                           const SdpMediaSection& localMsection,
                           JsepTrack::Direction,
                           RefPtr<JsepTrack>* track);
 
   nsresult CreateTransport(const SdpMediaSection& msection,
                            RefPtr<JsepTransport>* transport);