media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
author Iris Hsiao <ihsiao@mozilla.com>
Thu, 21 Jul 2016 16:13:50 +0800
changeset 331111 37e87a7c3771ffa468c1f50f810d331dfd53877c
parent 331109 9fa64e0224e2c16a8097d1d3b3c7d17cc2d59a1a
child 331407 e461c427a87edfb768b315d8a0ac4e4036c9d4a9
permissions -rw-r--r--
Backed out changeset 9fa64e0224e2 (bug 1204099) for bustage JsepSessionImpl.cpp

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "logging.h"

#include "signaling/src/jsep/JsepSessionImpl.h"
#include <string>
#include <set>
#include <stdlib.h>

#include "nspr.h"
#include "nss.h"
#include "pk11pub.h"
#include "nsDebug.h"

#include <mozilla/Move.h>
#include <mozilla/UniquePtr.h>

#include "signaling/src/jsep/JsepTrack.h"
#include "signaling/src/jsep/JsepTrack.h"
#include "signaling/src/jsep/JsepTransport.h"
#include "signaling/src/sdp/Sdp.h"
#include "signaling/src/sdp/SipccSdp.h"
#include "signaling/src/sdp/SipccSdpParser.h"
#include "mozilla/net/DataChannelProtocol.h"

namespace mozilla {

MOZ_MTLOG_MODULE("jsep")

#define JSEP_SET_ERROR(error)                                                  \
  do {                                                                         \
    std::ostringstream os;                                                     \
    os << error;                                                               \
    mLastError = os.str();                                                     \
    MOZ_MTLOG(ML_ERROR, mLastError);                                           \
  } while (0);

nsresult
JsepSessionImpl::Init()
{
  mLastError.clear();

  MOZ_ASSERT(!mSessionId, "Init called more than once");

  nsresult rv = SetupIds();
  NS_ENSURE_SUCCESS(rv, rv);

  SetupDefaultCodecs();
  SetupDefaultRtpExtensions();

  return NS_OK;
}

// Helper function to find the track for a given m= section.
template <class T>
typename std::vector<T>::iterator
FindTrackByLevel(std::vector<T>& tracks, size_t level)
{
  for (auto t = tracks.begin(); t != tracks.end(); ++t) {
    if (t->mAssignedMLine.isSome() &&
        (*t->mAssignedMLine == level)) {
      return t;
    }
  }

  return tracks.end();
}

template <class T>
typename std::vector<T>::iterator
FindTrackByIds(std::vector<T>& tracks,
               const std::string& streamId,
               const std::string& trackId)
{
  for (auto t = tracks.begin(); t != tracks.end(); ++t) {
    if (t->mTrack->GetStreamId() == streamId &&
        (t->mTrack->GetTrackId() == trackId)) {
      return t;
    }
  }

  return tracks.end();
}

template <class T>
typename std::vector<T>::iterator
FindUnassignedTrackByType(std::vector<T>& tracks,
                          SdpMediaSection::MediaType type)
{
  for (auto t = tracks.begin(); t != tracks.end(); ++t) {
    if (!t->mAssignedMLine.isSome() &&
        (t->mTrack->GetMediaType() == type)) {
      return t;
    }
  }

  return tracks.end();
}

nsresult
JsepSessionImpl::AddTrack(const RefPtr<JsepTrack>& track)
{
  mLastError.clear();
  MOZ_ASSERT(track->GetDirection() == sdp::kSend);

  if (track->GetMediaType() != SdpMediaSection::kApplication) {
    track->SetCNAME(mCNAME);

    if (track->GetSsrcs().empty()) {
      uint32_t ssrc;
      nsresult rv = CreateSsrc(&ssrc);
      NS_ENSURE_SUCCESS(rv, rv);
      track->AddSsrc(ssrc);
    }
  }

  track->PopulateCodecs(mSupportedCodecs.values);

  JsepSendingTrack strack;
  strack.mTrack = track;

  mLocalTracks.push_back(strack);

  return NS_OK;
}

nsresult
JsepSessionImpl::RemoveTrack(const std::string& streamId,
                             const std::string& trackId)
{
  if (mState != kJsepStateStable) {
    JSEP_SET_ERROR("Removing tracks outside of stable is unsupported.");
    return NS_ERROR_UNEXPECTED;
  }

  auto track = FindTrackByIds(mLocalTracks, streamId, trackId);

  if (track == mLocalTracks.end()) {
    return NS_ERROR_INVALID_ARG;
  }

  mLocalTracks.erase(track);
  return NS_OK;
}

nsresult
JsepSessionImpl::SetIceCredentials(const std::string& ufrag,
                                   const std::string& pwd)
{
  mLastError.clear();
  mIceUfrag = ufrag;
  mIcePwd = pwd;

  return NS_OK;
}

nsresult
JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy)
{
  mLastError.clear();
  if (mCurrentLocalDescription) {
    JSEP_SET_ERROR("Changing the bundle policy is only supported before the "
                   "first SetLocalDescription.");
    return NS_ERROR_UNEXPECTED;
  }

  mBundlePolicy = policy;
  return NS_OK;
}

nsresult
JsepSessionImpl::AddDtlsFingerprint(const std::string& algorithm,
                                    const std::vector<uint8_t>& value)
{
  mLastError.clear();
  JsepDtlsFingerprint fp;

  fp.mAlgorithm = algorithm;
  fp.mValue = value;

  mDtlsFingerprints.push_back(fp);

  return NS_OK;
}

nsresult
JsepSessionImpl::AddAudioRtpExtension(const std::string& extensionName)
{
  mLastError.clear();

  if (mAudioRtpExtensions.size() + 1 > UINT16_MAX) {
    JSEP_SET_ERROR("Too many audio rtp extensions have been added");
    return NS_ERROR_FAILURE;
  }

  SdpExtmapAttributeList::Extmap extmap =
      { static_cast<uint16_t>(mAudioRtpExtensions.size() + 1),
        SdpDirectionAttribute::kSendrecv,
        false, // don't actually specify direction
        extensionName,
        "" };

  mAudioRtpExtensions.push_back(extmap);
  return NS_OK;
}

nsresult
JsepSessionImpl::AddVideoRtpExtension(const std::string& extensionName)
{
  mLastError.clear();

  if (mVideoRtpExtensions.size() + 1 > UINT16_MAX) {
    JSEP_SET_ERROR("Too many video rtp extensions have been added");
    return NS_ERROR_FAILURE;
  }

  SdpExtmapAttributeList::Extmap extmap =
      { static_cast<uint16_t>(mVideoRtpExtensions.size() + 1),
        SdpDirectionAttribute::kSendrecv,
        false, // don't actually specify direction
        extensionName, "" };

  mVideoRtpExtensions.push_back(extmap);
  return NS_OK;
}

template<class T>
std::vector<RefPtr<JsepTrack>>
GetTracks(const std::vector<T>& wrappedTracks)
{
  std::vector<RefPtr<JsepTrack>> result;
  for (auto i = wrappedTracks.begin(); i != wrappedTracks.end(); ++i) {
    result.push_back(i->mTrack);
  }
  return result;
}

nsresult
JsepSessionImpl::ReplaceTrack(const std::string& oldStreamId,
                              const std::string& oldTrackId,
                              const std::string& newStreamId,
                              const std::string& newTrackId)
{
  auto it = FindTrackByIds(mLocalTracks, oldStreamId, oldTrackId);

  if (it == mLocalTracks.end()) {
    JSEP_SET_ERROR("Track " << oldStreamId << "/" << oldTrackId
                   << " was never added.");
    return NS_ERROR_INVALID_ARG;
  }

  if (FindTrackByIds(mLocalTracks, newStreamId, newTrackId) !=
      mLocalTracks.end()) {
    JSEP_SET_ERROR("Track " << newStreamId << "/" << newTrackId
                   << " was already added.");
    return NS_ERROR_INVALID_ARG;
  }

  it->mTrack->SetStreamId(newStreamId);
  it->mTrack->SetTrackId(newTrackId);

  return NS_OK;
}

nsresult
JsepSessionImpl::SetParameters(const std::string& streamId,
                               const std::string& trackId,
                               const std::vector<JsepTrack::JsConstraints>& constraints)
{
  auto it = FindTrackByIds(mLocalTracks, streamId, trackId);

  if (it == mLocalTracks.end()) {
    JSEP_SET_ERROR("Track " << streamId << "/" << trackId << " was never added.");
    return NS_ERROR_INVALID_ARG;
  }
  it->mTrack->SetJsConstraints(constraints);
  return NS_OK;
}

nsresult
JsepSessionImpl::GetParameters(const std::string& streamId,
                               const std::string& trackId,
                               std::vector<JsepTrack::JsConstraints>* outConstraints)
{
  auto it = FindTrackByIds(mLocalTracks, streamId, trackId);

  if (it == mLocalTracks.end()) {
    JSEP_SET_ERROR("Track " << streamId << "/" << trackId << " was never added.");
    return NS_ERROR_INVALID_ARG;
  }

  it->mTrack->GetJsConstraints(outConstraints);
  return NS_OK;
}

std::vector<RefPtr<JsepTrack>>
JsepSessionImpl::GetLocalTracks() const
{
  return GetTracks(mLocalTracks);
}

std::vector<RefPtr<JsepTrack>>
JsepSessionImpl::GetRemoteTracks() const
{
  return GetTracks(mRemoteTracks);
}

std::vector<RefPtr<JsepTrack>>
JsepSessionImpl::GetRemoteTracksAdded() const
{
  return GetTracks(mRemoteTracksAdded);
}

std::vector<RefPtr<JsepTrack>>
JsepSessionImpl::GetRemoteTracksRemoved() const
{
  return GetTracks(mRemoteTracksRemoved);
}

nsresult
JsepSessionImpl::SetupOfferMSections(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 = SetupOfferMSectionsByType(
      SdpMediaSection::kAudio, options.mOfferToReceiveAudio, sdp);

  NS_ENSURE_SUCCESS(rv, rv);

  rv = SetupOfferMSectionsByType(
      SdpMediaSection::kVideo, options.mOfferToReceiveVideo, sdp);

  NS_ENSURE_SUCCESS(rv, rv);

  if (!(options.mDontOfferDataChannel.isSome() &&
        *options.mDontOfferDataChannel)) {
    rv = SetupOfferMSectionsByType(
        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::SetupOfferMSectionsByType(SdpMediaSection::MediaType mediatype,
                                           Maybe<size_t> offerToReceiveMaybe,
                                           Sdp* sdp)
{
  // 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 = BindRemoteTracks(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 (JsepSendingTrack& track : mLocalTracks) {
    if (mediatype != track.mTrack->GetMediaType()) {
      continue;
    }

    SdpMediaSection* msection;
    if (track.mAssignedMLine.isSome()) {
      msection = &sdp->GetMediaSection(*track.mAssignedMLine);
    } else {
      nsresult rv = GetFreeMsectionForSend(track.mTrack->GetMediaType(),
                                           sdp,
                                           &msection);
      NS_ENSURE_SUCCESS(rv, rv);
      track.mAssignedMLine = Some(msection->GetLevel());
    }

    track.mTrack->AddToOffer(msection);
  }
  return NS_OK;
}

nsresult
JsepSessionImpl::BindRemoteTracks(SdpMediaSection::MediaType mediatype,
                                  Sdp* sdp,
                                  size_t* offerToReceive)
{
  for (JsepReceivingTrack& track : mRemoteTracks) {
    if (mediatype != track.mTrack->GetMediaType()) {
      continue;
    }

    if (!track.mAssignedMLine.isSome()) {
      MOZ_ASSERT(false);
      continue;
    }

    auto& msection = sdp->GetMediaSection(*track.mAssignedMLine);

    if (mSdpHelper.MsectionIsDisabled(msection)) {
      // TODO(bug 1095226) Content probably disabled this? Should we allow
      // content to do this?
      continue;
    }

    track.mTrack->AddToOffer(&msection);

    if (offerToReceive && *offerToReceive) {
      --(*offerToReceive);
    }
  }

  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 (mSdpHelper.MsectionIsDisabled(msection) ||
        msection.GetMediaType() != mediatype ||
        msection.IsReceiving()) {
      continue;
    }

    if (offerToRecv) {
      if (*offerToRecv) {
        SetupOfferToReceiveMsection(&msection);
        --(*offerToRecv);
        continue;
      }
    } else if (msection.IsSending()) {
      SetupOfferToReceiveMsection(&msection);
      continue;
    }

    if (!msection.IsSending()) {
      // Unused m-section, and no reason to offer to recv on it
      mSdpHelper.DisableMsection(sdp, &msection);
    }
  }

  return NS_OK;
}

void
JsepSessionImpl::SetupOfferToReceiveMsection(SdpMediaSection* offer)
{
  // Create a dummy recv track, and have it add codecs, set direction, etc.
  RefPtr<JsepTrack> dummy = new JsepTrack(offer->GetMediaType(),
                                          "",
                                          "",
                                          sdp::kRecv);
  dummy->PopulateCodecs(mSupportedCodecs.values);
  dummy->AddToOffer(offer);
}

nsresult
JsepSessionImpl::AddRecvonlyMsections(SdpMediaSection::MediaType mediatype,
                                      size_t count,
                                      Sdp* sdp)
{
  while (count--) {
    nsresult rv = CreateOfferMSection(
        mediatype,
        mSdpHelper.GetProtocolForMediaType(mediatype),
        SdpDirectionAttribute::kRecvonly,
        sdp);

    NS_ENSURE_SUCCESS(rv, rv);
    SetupOfferToReceiveMsection(
        &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1));
  }
  return NS_OK;
}

// This function creates a skeleton SDP based on the old descriptions
// (ie; all m-sections are inactive).
nsresult
JsepSessionImpl::AddReofferMsections(const Sdp& oldLocalSdp,
                                     const Sdp& oldAnswer,
                                     Sdp* newSdp)
{
  nsresult rv;

  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 |SetupOfferMSectionsByType|
    rv = CreateOfferMSection(oldLocalSdp.GetMediaSection(i).GetMediaType(),
                             oldLocalSdp.GetMediaSection(i).GetProtocol(),
                             SdpDirectionAttribute::kInactive,
                             newSdp);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = mSdpHelper.CopyStickyParams(oldAnswer.GetMediaSection(i),
                                     &newSdp->GetMediaSection(i));
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

void
JsepSessionImpl::SetupBundle(Sdp* sdp) const
{
  std::vector<std::string> mids;
  std::set<SdpMediaSection::MediaType> observedTypes;

  // 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)) {
      bool useBundleOnly = false;
      switch (mBundlePolicy) {
        case kBundleMaxCompat:
          // We don't use bundle-only for max-compat
          break;
        case kBundleBalanced:
          // balanced means we use bundle-only on everything but the first
          // m-section of a given type
          if (observedTypes.count(sdp->GetMediaSection(i).GetMediaType())) {
            useBundleOnly = true;
          }
          observedTypes.insert(sdp->GetMediaSection(i).GetMediaType());
          break;
        case kBundleMaxBundle:
          // max-bundle means we use bundle-only on everything but the first
          // m-section
          useBundleOnly = !mids.empty();
          break;
      }

      if (useBundleOnly) {
        attrs.SetAttribute(
            new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
      }

      mids.push_back(attrs.GetMid());
    }
  }

  if (mids.size() > 1) {
    UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
    groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids);
    sdp->GetAttributeList().SetAttribute(groupAttr.release());
  }
}

nsresult
JsepSessionImpl::GetRemoteIds(const Sdp& sdp,
                              const SdpMediaSection& msection,
                              std::string* streamId,
                              std::string* trackId)
{
  nsresult rv = mSdpHelper.GetIdsFromMsid(sdp, msection, streamId, trackId);
  if (rv == NS_ERROR_NOT_AVAILABLE) {
    *streamId = mDefaultRemoteStreamId;

    if (!mDefaultRemoteTrackIdsByLevel.count(msection.GetLevel())) {
      // Generate random track ids.
      if (!mUuidGen->Generate(trackId)) {
        JSEP_SET_ERROR("Failed to generate UUID for JsepTrack");
        return NS_ERROR_FAILURE;
      }

      mDefaultRemoteTrackIdsByLevel[msection.GetLevel()] = *trackId;
    } else {
      *trackId = mDefaultRemoteTrackIdsByLevel[msection.GetLevel()];
    }
    return NS_OK;
  }

  if (NS_SUCCEEDED(rv)) {
    // If, for whatever reason, the other end renegotiates with an msid where
    // there wasn't one before, don't allow the old default to pop up again
    // later.
    mDefaultRemoteTrackIdsByLevel.erase(msection.GetLevel());
  }

  return rv;
}

nsresult
JsepSessionImpl::CreateOffer(const JsepOfferOptions& options,
                             std::string* offer)
{
  mLastError.clear();

  if (mState != kJsepStateStable) {
    JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
    return NS_ERROR_UNEXPECTED;
  }

  // Undo track assignments from a previous call to CreateOffer
  // (ie; if the track has not been negotiated yet, it doesn't necessarily need
  // to stay in the same m-section that it was in)
  for (JsepSendingTrack& trackWrapper : mLocalTracks) {
    if (!trackWrapper.mTrack->GetNegotiatedDetails()) {
      trackWrapper.mAssignedMLine.reset();
    }
  }

  UniquePtr<Sdp> sdp;

  // Make the basic SDP that is common to offer/answer.
  nsresult rv = CreateGenericSDP(&sdp);
  NS_ENSURE_SUCCESS(rv, rv);

  if (mCurrentLocalDescription) {
    rv = AddReofferMsections(*mCurrentLocalDescription,
                             *GetAnswer(),
                             sdp.get());
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Ensure that we have all the m-sections we need, and disable extras
  rv = SetupOfferMSections(options, sdp.get());
  NS_ENSURE_SUCCESS(rv, rv);

  SetupBundle(sdp.get());

  if (mCurrentLocalDescription) {
    rv = CopyPreviousTransportParams(*GetAnswer(),
                                     *mCurrentLocalDescription,
                                     *sdp,
                                     sdp.get());
    NS_ENSURE_SUCCESS(rv,rv);
  }

  *offer = sdp->ToString();
  mGeneratedLocalDescription = Move(sdp);
  ++mSessionVersion;

  return NS_OK;
}

std::string
JsepSessionImpl::GetLocalDescription() const
{
  std::ostringstream os;
  mozilla::Sdp* sdp = GetParsedLocalDescription();
  if (sdp) {
    sdp->Serialize(os);
  }
  return os.str();
}

std::string
JsepSessionImpl::GetRemoteDescription() const
{
  std::ostringstream os;
  mozilla::Sdp* sdp =  GetParsedRemoteDescription();
  if (sdp) {
    sdp->Serialize(os);
  }
  return os.str();
}

void
JsepSessionImpl::AddExtmap(SdpMediaSection* msection) const
{
  const auto* extensions = GetRtpExtensions(msection->GetMediaType());

  if (extensions && !extensions->empty()) {
    SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList;
    extmap->mExtmaps = *extensions;
    msection->GetAttributeList().SetAttribute(extmap);
  }
}

void
JsepSessionImpl::AddMid(const std::string& mid,
                        SdpMediaSection* msection) const
{
  msection->GetAttributeList().SetAttribute(new SdpStringAttribute(
        SdpAttribute::kMidAttribute, mid));
}

const std::vector<SdpExtmapAttributeList::Extmap>*
JsepSessionImpl::GetRtpExtensions(SdpMediaSection::MediaType type) const
{
  switch (type) {
    case SdpMediaSection::kAudio:
      return &mAudioRtpExtensions;
    case SdpMediaSection::kVideo:
      return &mVideoRtpExtensions;
    default:
      return nullptr;
  }
}

void
JsepSessionImpl::AddCommonExtmaps(const SdpMediaSection& remoteMsection,
                                  SdpMediaSection* msection)
{
  auto* ourExtensions = GetRtpExtensions(remoteMsection.GetMediaType());

  if (ourExtensions) {
    mSdpHelper.AddCommonExtmaps(remoteMsection, *ourExtensions, msection);
  }
}

nsresult
JsepSessionImpl::CreateAnswer(const JsepAnswerOptions& options,
                              std::string* answer)
{
  mLastError.clear();

  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
  //    in sequence and see if any are unassigned. If so, assign
  //    them and mark it sendrecv, otherwise it's recvonly.
  // 3. Just replicate their media attributes.
  // 4. Profit.
  UniquePtr<Sdp> sdp;

  // Make the basic SDP that is common to offer/answer.
  nsresult rv = CreateGenericSDP(&sdp);
  NS_ENSURE_SUCCESS(rv, rv);

  const Sdp& offer = *mPendingRemoteDescription;

  // Copy the bundle groups into our answer
  UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
  mSdpHelper.GetBundleGroups(offer, &groupAttr->mGroups);
  sdp->GetAttributeList().SetAttribute(groupAttr.release());

  // Disable send for local tracks if the offer no longer allows it
  // (i.e., the m-section is recvonly, inactive or disabled)
  for (JsepSendingTrack& trackWrapper : mLocalTracks) {
    if (!trackWrapper.mAssignedMLine.isSome()) {
      continue;
    }

    // Get rid of all m-line assignments that have not been negotiated
    if (!trackWrapper.mTrack->GetNegotiatedDetails()) {
      trackWrapper.mAssignedMLine.reset();
      continue;
    }

    if (!offer.GetMediaSection(*trackWrapper.mAssignedMLine).IsReceiving()) {
      trackWrapper.mAssignedMLine.reset();
    }
  }

  size_t numMsections = offer.GetMediaSectionCount();

  for (size_t i = 0; i < numMsections; ++i) {
    const SdpMediaSection& remoteMsection = offer.GetMediaSection(i);
    rv = CreateAnswerMSection(options, i, remoteMsection, sdp.get());
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (mCurrentLocalDescription) {
    // per discussion with bwc, 3rd parm here should be offer, not *sdp. (mjf)
    rv = CopyPreviousTransportParams(*GetAnswer(),
                                     *mCurrentRemoteDescription,
                                     offer,
                                     sdp.get());
    NS_ENSURE_SUCCESS(rv,rv);
  }

  *answer = sdp->ToString();
  mGeneratedLocalDescription = Move(sdp);
  ++mSessionVersion;

  return NS_OK;
}

nsresult
JsepSessionImpl::CreateOfferMSection(SdpMediaSection::MediaType mediatype,
                                     SdpMediaSection::Protocol proto,
                                     SdpDirectionAttribute::Direction dir,
                                     Sdp* sdp)
{
  SdpMediaSection* msection =
      &sdp->AddMediaSection(mediatype, dir, 0, proto, sdp::kIPv4, "0.0.0.0");

  return EnableOfferMsection(msection);
}

nsresult
JsepSessionImpl::GetFreeMsectionForSend(
    SdpMediaSection::MediaType type,
    Sdp* sdp,
    SdpMediaSection** msectionOut)
{
  for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
    SdpMediaSection& msection = sdp->GetMediaSection(i);
    // draft-ietf-rtcweb-jsep-08 says we should reclaim disabled m-sections
    // regardless of media type. This breaks some pretty fundamental rules of
    // SDP offer/answer, so we probably should not do it.
    if (msection.GetMediaType() != type) {
      continue;
    }

    if (FindTrackByLevel(mLocalTracks, i) != mLocalTracks.end()) {
      // Not free
      continue;
    }

    if (mSdpHelper.MsectionIsDisabled(msection)) {
      // Was disabled; revive
      nsresult rv = EnableOfferMsection(&msection);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    *msectionOut = &msection;
    return NS_OK;
  }

  // Ok, no pre-existing m-section. Make a new one.
  nsresult rv = CreateOfferMSection(type,
                                    mSdpHelper.GetProtocolForMediaType(type),
                                    SdpDirectionAttribute::kInactive,
                                    sdp);
  NS_ENSURE_SUCCESS(rv, rv);

  *msectionOut = &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1);
  return NS_OK;
}

nsresult
JsepSessionImpl::CreateAnswerMSection(const JsepAnswerOptions& options,
                                      size_t mlineIndex,
                                      const SdpMediaSection& remoteMsection,
                                      Sdp* sdp)
{
  SdpMediaSection& msection =
      sdp->AddMediaSection(remoteMsection.GetMediaType(),
                           SdpDirectionAttribute::kInactive,
                           9,
                           remoteMsection.GetProtocol(),
                           sdp::kIPv4,
                           "0.0.0.0");

  nsresult rv = mSdpHelper.CopyStickyParams(remoteMsection, &msection);
  NS_ENSURE_SUCCESS(rv, rv);

  if (mSdpHelper.MsectionIsDisabled(remoteMsection)) {
    mSdpHelper.DisableMsection(sdp, &msection);
    return NS_OK;
  }

  SdpSetupAttribute::Role role;
  rv = DetermineAnswererSetupRole(remoteMsection, &role);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = AddTransportAttributes(&msection, role);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = SetRecvonlySsrc(&msection);
  NS_ENSURE_SUCCESS(rv, rv);

  // Only attempt to match up local tracks if the offerer has elected to
  // receive traffic.
  if (remoteMsection.IsReceiving()) {
    rv = BindMatchingLocalTrackToAnswer(&msection);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (remoteMsection.IsSending()) {
    BindMatchingRemoteTrackToAnswer(&msection);
  }

  if (!msection.IsReceiving() && !msection.IsSending()) {
    mSdpHelper.DisableMsection(sdp, &msection);
    return NS_OK;
  }

  // Add extmap attributes.
  AddCommonExtmaps(remoteMsection, &msection);

  if (msection.GetFormats().empty()) {
    // Could not negotiate anything. Disable m-section.
    mSdpHelper.DisableMsection(sdp, &msection);
  }

  return NS_OK;
}

nsresult
JsepSessionImpl::SetRecvonlySsrc(SdpMediaSection* msection)
{
  // If previous m-sections are disabled, we do not call this function for them
  while (mRecvonlySsrcs.size() <= msection->GetLevel()) {
    uint32_t ssrc;
    nsresult rv = CreateSsrc(&ssrc);
    NS_ENSURE_SUCCESS(rv, rv);
    mRecvonlySsrcs.push_back(ssrc);
  }

  std::vector<uint32_t> ssrcs;
  ssrcs.push_back(mRecvonlySsrcs[msection->GetLevel()]);
  msection->SetSsrcs(ssrcs, mCNAME);
  return NS_OK;
}

nsresult
JsepSessionImpl::BindMatchingLocalTrackToAnswer(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()) {
    track->mAssignedMLine = Some(msection->GetLevel());
    track->mTrack->AddToAnswer(
        mPendingRemoteDescription->GetMediaSection(msection->GetLevel()),
        msection);
  }

  return NS_OK;
}

nsresult
JsepSessionImpl::BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection)
{
  auto it = FindTrackByLevel(mRemoteTracks, msection->GetLevel());
  if (it == mRemoteTracks.end()) {
    MOZ_ASSERT(false);
    JSEP_SET_ERROR("Failed to find remote track for local answer m-section");
    return NS_ERROR_FAILURE;
  }

  it->mTrack->AddToAnswer(
      mPendingRemoteDescription->GetMediaSection(msection->GetLevel()),
      msection);
  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].
  //   The endpoint that is the offerer MUST use the setup attribute
  //   value of setup:actpass and be prepared to receive a client_hello
  //   before it receives the answer.  The answerer MUST use either a
  //   setup attribute value of setup:active or setup:passive.  Note that
  //   if the answerer uses setup:passive, then the DTLS handshake will
  //   not begin until the answerer is received, which adds additional
  //   latency. setup:active allows the answer and the DTLS handshake to
  //   occur in parallel.  Thus, setup:active is RECOMMENDED.  Whichever
  //   party is active MUST initiate a DTLS handshake by sending a
  //   ClientHello over each flow (host/port quartet).
  //
  //   We default to assuming that the offerer is passive and we are active.
  SdpSetupAttribute::Role role = SdpSetupAttribute::kActive;

  if (remoteMsection.GetAttributeList().HasAttribute(
          SdpAttribute::kSetupAttribute)) {
    switch (remoteMsection.GetAttributeList().GetSetup().mRole) {
      case SdpSetupAttribute::kActive:
        role = SdpSetupAttribute::kPassive;
        break;
      case SdpSetupAttribute::kPassive:
      case SdpSetupAttribute::kActpass:
        role = SdpSetupAttribute::kActive;
        break;
      case SdpSetupAttribute::kHoldconn:
        // This should have been caught by ParseSdp
        MOZ_ASSERT(false);
        JSEP_SET_ERROR("The other side used an illegal setup attribute"
                       " (\"holdconn\").");
        return NS_ERROR_INVALID_ARG;
    }
  }

  *rolep = role;
  return NS_OK;
}

nsresult
JsepSessionImpl::SetLocalDescription(JsepSdpType type, const std::string& sdp)
{
  mLastError.clear();

  MOZ_MTLOG(ML_DEBUG, "SetLocalDescription type=" << type << "\nSDP=\n"
                                                  << sdp);

  if (type == kJsepSdpRollback) {
    if (mState != kJsepStateHaveLocalOffer) {
      JSEP_SET_ERROR("Cannot rollback local description in "
                     << GetStateStr(mState));
      return NS_ERROR_UNEXPECTED;
    }

    mPendingLocalDescription.reset();
    SetState(kJsepStateStable);
    mTransports = mOldTransports;
    mOldTransports.clear();
    return NS_OK;
  }

  switch (mState) {
    case kJsepStateStable:
      if (type != kJsepSdpOffer) {
        JSEP_SET_ERROR("Cannot set local answer in state "
                       << GetStateStr(mState));
        return NS_ERROR_UNEXPECTED;
      }
      mIsOfferer = true;
      break;
    case kJsepStateHaveRemoteOffer:
      if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
        JSEP_SET_ERROR("Cannot set local offer in state "
                       << GetStateStr(mState));
        return NS_ERROR_UNEXPECTED;
      }
      break;
    default:
      JSEP_SET_ERROR("Cannot set local offer or answer in state "
                     << GetStateStr(mState));
      return NS_ERROR_UNEXPECTED;
  }

  UniquePtr<Sdp> parsed;
  nsresult rv = ParseSdp(sdp, &parsed);
  NS_ENSURE_SUCCESS(rv, rv);

  // Check that content hasn't done anything unsupported with the SDP
  rv = ValidateLocalDescription(*parsed);
  NS_ENSURE_SUCCESS(rv, rv);

  // Create transport objects.
  mOldTransports = mTransports; // Save in case we need to rollback
  for (size_t t = 0; t < parsed->GetMediaSectionCount(); ++t) {
    if (t >= mTransports.size()) {
      mTransports.push_back(RefPtr<JsepTransport>(new JsepTransport));
    }

    UpdateTransport(parsed->GetMediaSection(t), mTransports[t].get());
  }

  switch (type) {
    case kJsepSdpOffer:
      rv = SetLocalDescriptionOffer(Move(parsed));
      break;
    case kJsepSdpAnswer:
    case kJsepSdpPranswer:
      rv = SetLocalDescriptionAnswer(type, Move(parsed));
      break;
    case kJsepSdpRollback:
      MOZ_CRASH(); // Handled above
  }

  return rv;
}

nsresult
JsepSessionImpl::SetLocalDescriptionOffer(UniquePtr<Sdp> offer)
{
  MOZ_ASSERT(mState == kJsepStateStable);
  mPendingLocalDescription = Move(offer);
  SetState(kJsepStateHaveLocalOffer);
  return NS_OK;
}

nsresult
JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type,
                                           UniquePtr<Sdp> answer)
{
  MOZ_ASSERT(mState == kJsepStateHaveRemoteOffer);
  mPendingLocalDescription = Move(answer);

  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);
  mWasOffererLastTime = mIsOfferer;

  SetState(kJsepStateStable);
  return NS_OK;
}

nsresult
JsepSessionImpl::SetRemoteDescription(JsepSdpType type, const std::string& sdp)
{
  mLastError.clear();
  mRemoteTracksAdded.clear();
  mRemoteTracksRemoved.clear();

  MOZ_MTLOG(ML_DEBUG, "SetRemoteDescription type=" << type << "\nSDP=\n"
                                                   << sdp);

  if (type == kJsepSdpRollback) {
    if (mState != kJsepStateHaveRemoteOffer) {
      JSEP_SET_ERROR("Cannot rollback remote description in "
                     << GetStateStr(mState));
      return NS_ERROR_UNEXPECTED;
    }

    mPendingRemoteDescription.reset();
    SetState(kJsepStateStable);

    // Update the remote tracks to what they were before the SetRemote
    return SetRemoteTracksFromDescription(mCurrentRemoteDescription.get());
  }

  switch (mState) {
    case kJsepStateStable:
      if (type != kJsepSdpOffer) {
        JSEP_SET_ERROR("Cannot set remote answer in state "
                       << GetStateStr(mState));
        return NS_ERROR_UNEXPECTED;
      }
      mIsOfferer = false;
      break;
    case kJsepStateHaveLocalOffer:
    case kJsepStateHaveRemotePranswer:
      if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
        JSEP_SET_ERROR("Cannot set remote offer in state "
                       << GetStateStr(mState));
        return NS_ERROR_UNEXPECTED;
      }
      break;
    default:
      JSEP_SET_ERROR("Cannot set remote offer or answer in current state "
                     << GetStateStr(mState));
      return NS_ERROR_UNEXPECTED;
  }

  // Parse.
  UniquePtr<Sdp> parsed;
  nsresult rv = ParseSdp(sdp, &parsed);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = ValidateRemoteDescription(*parsed);
  NS_ENSURE_SUCCESS(rv, rv);

  bool iceLite =
      parsed->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);

  // check for mismatch ufrag/pwd indicating ice restart
  // can't just check the first one because it might be disabled
  bool iceRestarting = false;
  if (mCurrentRemoteDescription.get()) {
    for (size_t i = 0;
         !iceRestarting &&
           i < mCurrentRemoteDescription->GetMediaSectionCount();
         ++i) {

      const SdpMediaSection& newMsection = parsed->GetMediaSection(i);
      const SdpMediaSection& oldMsection =
        mCurrentRemoteDescription->GetMediaSection(i);

      if (mSdpHelper.MsectionIsDisabled(newMsection) ||
          mSdpHelper.MsectionIsDisabled(oldMsection)) {
        continue;
      }

      iceRestarting = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
    }
  }

  std::vector<std::string> iceOptions;
  if (parsed->GetAttributeList().HasAttribute(
          SdpAttribute::kIceOptionsAttribute)) {
    iceOptions = parsed->GetAttributeList().GetIceOptions().mValues;
  }

  switch (type) {
    case kJsepSdpOffer:
      rv = SetRemoteDescriptionOffer(Move(parsed));
      break;
    case kJsepSdpAnswer:
    case kJsepSdpPranswer:
      rv = SetRemoteDescriptionAnswer(type, Move(parsed));
      break;
    case kJsepSdpRollback:
      MOZ_CRASH(); // Handled above
  }

  if (NS_SUCCEEDED(rv)) {
    mRemoteIsIceLite = iceLite;
    mIceOptions = iceOptions;
    mRemoteIceIsRestarting = iceRestarting;
  }

  return rv;
}

nsresult
JsepSessionImpl::HandleNegotiatedSession(const UniquePtr<Sdp>& local,
                                         const UniquePtr<Sdp>& remote)
{
  bool remoteIceLite =
      remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);

  mIceControlling = remoteIceLite || mIsOfferer;

  const Sdp& answer = mIsOfferer ? *remote : *local;

  SdpHelper::BundledMids bundledMids;
  nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids);
  NS_ENSURE_SUCCESS(rv, rv);

  if (mTransports.size() < local->GetMediaSectionCount()) {
    JSEP_SET_ERROR("Fewer transports set up than m-lines");
    MOZ_ASSERT(false);
    return NS_ERROR_FAILURE;
  }

  for (JsepSendingTrack& trackWrapper : mLocalTracks) {
    trackWrapper.mTrack->ClearNegotiatedDetails();
  }

  for (JsepReceivingTrack& trackWrapper : mRemoteTracks) {
    trackWrapper.mTrack->ClearNegotiatedDetails();
  }

  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) {
    // Skip disabled m-sections.
    if (answer.GetMediaSection(i).GetPort() == 0) {
      mTransports[i]->Close();
      continue;
    }

    // The transport details are not necessarily on the m-section we're
    // currently processing.
    size_t transportLevel = i;
    bool usingBundle = false;
    {
      const SdpMediaSection& answerMsection(answer.GetMediaSection(i));
      if (answerMsection.GetAttributeList().HasAttribute(
            SdpAttribute::kMidAttribute)) {
        if (bundledMids.count(answerMsection.GetAttributeList().GetMid())) {
          const SdpMediaSection* masterBundleMsection =
            bundledMids[answerMsection.GetAttributeList().GetMid()];
          transportLevel = masterBundleMsection->GetLevel();
          usingBundle = true;
          if (i != transportLevel) {
            mTransports[i]->Close();
          }
        }
      }
    }

    RefPtr<JsepTransport> transport = mTransports[transportLevel];

    rv = FinalizeTransport(
        remote->GetMediaSection(transportLevel).GetAttributeList(),
        answer.GetMediaSection(transportLevel).GetAttributeList(),
        transport);
    NS_ENSURE_SUCCESS(rv, rv);

    JsepTrackPair trackPair;
    rv = MakeNegotiatedTrackPair(remote->GetMediaSection(i),
                                 local->GetMediaSection(i),
                                 transport,
                                 usingBundle,
                                 transportLevel,
                                 &trackPair);
    NS_ENSURE_SUCCESS(rv, rv);

    trackPairs.push_back(trackPair);
  }

  JsepTrack::SetUniquePayloadTypes(GetTracks(mRemoteTracks));

  // Ouch, this probably needs some dirty bit instead of just clearing
  // stuff for renegotiation.
  mNegotiatedTrackPairs = trackPairs;

  mGeneratedLocalDescription.reset();

  mNegotiations++;
  return NS_OK;
}

nsresult
JsepSessionImpl::MakeNegotiatedTrackPair(const SdpMediaSection& remote,
                                         const SdpMediaSection& local,
                                         const RefPtr<JsepTransport>& transport,
                                         bool usingBundle,
                                         size_t transportLevel,
                                         JsepTrackPair* trackPairOut)
{
  MOZ_ASSERT(transport->mComponents);
  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();

  MOZ_ASSERT(mRecvonlySsrcs.size() > local.GetLevel(),
             "Failed to set the default ssrc for an active m-section");
  trackPairOut->mRecvonlySsrc = mRecvonlySsrcs[local.GetLevel()];

  if (usingBundle) {
    trackPairOut->mBundleLevel = Some(transportLevel);
  }

  auto sendTrack = FindTrackByLevel(mLocalTracks, local.GetLevel());
  if (sendTrack != mLocalTracks.end()) {
    sendTrack->mTrack->Negotiate(answer, remote);
    sendTrack->mTrack->SetActive(sending);
    trackPairOut->mSending = sendTrack->mTrack;
  } else if (sending) {
    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;
  }

  auto recvTrack = FindTrackByLevel(mRemoteTracks, local.GetLevel());
  if (recvTrack != mRemoteTracks.end()) {
    recvTrack->mTrack->Negotiate(answer, remote);
    recvTrack->mTrack->SetActive(receiving);
    trackPairOut->mReceiving = recvTrack->mTrack;

    if (receiving &&
        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.");
    }
  } else if (receiving) {
    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;
  }

  trackPairOut->mRtpTransport = transport;

  if (transport->mComponents == 2) {
    // RTCP MUX or not.
    // TODO(bug 1095743): verify that the PTs are consistent with mux.
    MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is off");
    trackPairOut->mRtcpTransport = transport;
  }

  return NS_OK;
}

void
JsepSessionImpl::UpdateTransport(const SdpMediaSection& msection,
                                 JsepTransport* transport)
{
  if (mSdpHelper.MsectionIsDisabled(msection)) {
    transport->Close();
    return;
  }

  if (mSdpHelper.HasRtcp(msection.GetProtocol())) {
    transport->mComponents = 2;
  } else {
    transport->mComponents = 1;
  }

  if (msection.GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) {
    transport->mTransportId = msection.GetAttributeList().GetMid();
  } else {
    std::ostringstream os;
    os << "level_" << msection.GetLevel() << "(no mid)";
    transport->mTransportId = os.str();
  }
}

nsresult
JsepSessionImpl::FinalizeTransport(const SdpAttributeList& remote,
                                   const SdpAttributeList& answer,
                                   const RefPtr<JsepTransport>& transport)
{
  UniquePtr<JsepIceTransport> ice = MakeUnique<JsepIceTransport>();

  // We do sanity-checking for these in ParseSdp
  ice->mUfrag = remote.GetIceUfrag();
  ice->mPwd = remote.GetIcePwd();
  if (remote.HasAttribute(SdpAttribute::kCandidateAttribute)) {
    ice->mCandidates = remote.GetCandidate();
  }

  // RFC 5763 says:
  //
  //   The endpoint MUST use the setup attribute defined in [RFC4145].
  //   The endpoint that is the offerer MUST use the setup attribute
  //   value of setup:actpass and be prepared to receive a client_hello
  //   before it receives the answer.  The answerer MUST use either a
  //   setup attribute value of setup:active or setup:passive.  Note that
  //   if the answerer uses setup:passive, then the DTLS handshake will
  //   not begin until the answerer is received, which adds additional
  //   latency. setup:active allows the answer and the DTLS handshake to
  //   occur in parallel.  Thus, setup:active is RECOMMENDED.  Whichever
  //   party is active MUST initiate a DTLS handshake by sending a
  //   ClientHello over each flow (host/port quartet).
  UniquePtr<JsepDtlsTransport> dtls = MakeUnique<JsepDtlsTransport>();
  dtls->mFingerprints = remote.GetFingerprint();
  if (!answer.HasAttribute(mozilla::SdpAttribute::kSetupAttribute)) {
    dtls->mRole = mIsOfferer ? JsepDtlsTransport::kJsepDtlsServer
                             : JsepDtlsTransport::kJsepDtlsClient;
  } else {
    if (mIsOfferer) {
      dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
                        ? JsepDtlsTransport::kJsepDtlsServer
                        : JsepDtlsTransport::kJsepDtlsClient;
    } else {
      dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
                        ? JsepDtlsTransport::kJsepDtlsClient
                        : JsepDtlsTransport::kJsepDtlsServer;
    }
  }

  transport->mIce = Move(ice);
  transport->mDtls = Move(dtls);

  if (answer.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
    transport->mComponents = 1;
  }

  return NS_OK;
}

nsresult
JsepSessionImpl::AddTransportAttributes(SdpMediaSection* msection,
                                        SdpSetupAttribute::Role dtlsRole)
{
  if (mIceUfrag.empty() || mIcePwd.empty()) {
    JSEP_SET_ERROR("Missing ICE ufrag or password");
    return NS_ERROR_FAILURE;
  }

  SdpAttributeList& attrList = msection->GetAttributeList();
  attrList.SetAttribute(
      new SdpStringAttribute(SdpAttribute::kIceUfragAttribute, mIceUfrag));
  attrList.SetAttribute(
      new SdpStringAttribute(SdpAttribute::kIcePwdAttribute, mIcePwd));

  msection->GetAttributeList().SetAttribute(new SdpSetupAttribute(dtlsRole));

  return NS_OK;
}

nsresult
JsepSessionImpl::CopyPreviousTransportParams(const Sdp& oldAnswer,
                                             const Sdp& offerersPreviousSdp,
                                             const Sdp& newOffer,
                                             Sdp* newLocal)
{
  for (size_t i = 0; i < oldAnswer.GetMediaSectionCount(); ++i) {
    if (!mSdpHelper.MsectionIsDisabled(newLocal->GetMediaSection(i)) &&
        mSdpHelper.AreOldTransportParamsValid(oldAnswer,
                                              offerersPreviousSdp,
                                              newOffer,
                                              i) &&
        !mRemoteIceIsRestarting
       ) {
      // If newLocal is an offer, this will be the number of components we used
      // last time, and if it is an answer, this will be the number of
      // components we've decided we're using now.
      size_t numComponents = mTransports[i]->mComponents;
      nsresult rv = mSdpHelper.CopyTransportParams(
          numComponents,
          mCurrentLocalDescription->GetMediaSection(i),
          &newLocal->GetMediaSection(i));
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }

  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: ";
    mSdpHelper.appendSdpParseErrors(mParser.GetParseErrors(), &error);
    JSEP_SET_ERROR(error);
    return NS_ERROR_INVALID_ARG;
  }

  // Verify that the JSEP rules for all SDP are followed
  if (!parsed->GetMediaSectionCount()) {
    JSEP_SET_ERROR("Description has no media sections");
    return NS_ERROR_INVALID_ARG;
  }

  std::set<std::string> trackIds;

  for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
    if (mSdpHelper.MsectionIsDisabled(parsed->GetMediaSection(i))) {
      // Disabled, let this stuff slide.
      continue;
    }

    const SdpMediaSection& msection(parsed->GetMediaSection(i));
    auto& mediaAttrs = msection.GetAttributeList();

    if (mediaAttrs.GetIceUfrag().empty()) {
      JSEP_SET_ERROR("Invalid description, no ice-ufrag attribute");
      return NS_ERROR_INVALID_ARG;
    }

    if (mediaAttrs.GetIcePwd().empty()) {
      JSEP_SET_ERROR("Invalid description, no ice-pwd attribute");
      return NS_ERROR_INVALID_ARG;
    }

    if (!mediaAttrs.HasAttribute(SdpAttribute::kFingerprintAttribute)) {
      JSEP_SET_ERROR("Invalid description, no fingerprint attribute");
      return NS_ERROR_INVALID_ARG;
    }

    const SdpFingerprintAttributeList& fingerprints(
        mediaAttrs.GetFingerprint());
    if (fingerprints.mFingerprints.empty()) {
      JSEP_SET_ERROR("Invalid description, no supported fingerprint algorithms "
                     "present");
      return NS_ERROR_INVALID_ARG;
    }

    if (mediaAttrs.HasAttribute(SdpAttribute::kSetupAttribute) &&
        mediaAttrs.GetSetup().mRole == SdpSetupAttribute::kHoldconn) {
      JSEP_SET_ERROR("Description has illegal setup attribute "
                     "\"holdconn\" at level "
                     << i);
      return NS_ERROR_INVALID_ARG;
    }

    auto& formats = parsed->GetMediaSection(i).GetFormats();
    for (auto f = formats.begin(); f != formats.end(); ++f) {
      uint16_t pt;
      if (!SdpHelper::GetPtAsInt(*f, &pt)) {
        JSEP_SET_ERROR("Payload type \""
                       << *f << "\" is not a 16-bit unsigned int at level "
                       << i);
        return NS_ERROR_INVALID_ARG;
      }
    }

    std::string streamId;
    std::string trackId;
    nsresult rv = mSdpHelper.GetIdsFromMsid(*parsed,
                                            parsed->GetMediaSection(i),
                                            &streamId,
                                            &trackId);

    if (NS_SUCCEEDED(rv)) {
      if (trackIds.count(trackId)) {
        JSEP_SET_ERROR("track id:" << trackId
                       << " appears in more than one m-section at level " << i);
        return NS_ERROR_INVALID_ARG;
      }

      trackIds.insert(trackId);
    } else if (rv != NS_ERROR_NOT_AVAILABLE) {
      // Error has already been set
      return rv;
    }

    if (msection.GetMediaType() == SdpMediaSection::kAudio ||
        msection.GetMediaType() == SdpMediaSection::kVideo) {
      // Sanity-check that payload type can work with RTP
      for (const std::string& fmt : msection.GetFormats()) {
        uint16_t payloadType;
        // TODO (bug 1204099): Make this check for reserved ranges.
        if (!SdpHelper::GetPtAsInt(fmt, &payloadType) || payloadType > 127) {
          JSEP_SET_ERROR("audio/video payload type is too large: " << fmt);
          return NS_ERROR_INVALID_ARG;
        }
      }
    }
  }

  *parsedp = Move(parsed);
  return NS_OK;
}

nsresult
JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr<Sdp> offer)
{
  MOZ_ASSERT(mState == kJsepStateStable);

  // TODO(bug 1095780): Note that we create remote tracks even when
  // They contain only codecs we can't negotiate or other craziness.
  nsresult rv = SetRemoteTracksFromDescription(offer.get());
  NS_ENSURE_SUCCESS(rv, rv);

  mPendingRemoteDescription = Move(offer);

  SetState(kJsepStateHaveRemoteOffer);
  return NS_OK;
}

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.
  rv = SetRemoteTracksFromDescription(mPendingRemoteDescription.get());
  NS_ENSURE_SUCCESS(rv, rv);

  rv = HandleNegotiatedSession(mPendingLocalDescription,
                               mPendingRemoteDescription);
  NS_ENSURE_SUCCESS(rv, rv);

  mCurrentRemoteDescription = Move(mPendingRemoteDescription);
  mCurrentLocalDescription = Move(mPendingLocalDescription);
  mWasOffererLastTime = mIsOfferer;

  SetState(kJsepStateStable);
  return NS_OK;
}

nsresult
JsepSessionImpl::SetRemoteTracksFromDescription(const Sdp* remoteDescription)
{
  // Unassign all remote tracks
  for (auto i = mRemoteTracks.begin(); i != mRemoteTracks.end(); ++i) {
    i->mAssignedMLine.reset();
  }

  // This will not exist if we're rolling back the first remote description
  if (remoteDescription) {
    size_t numMlines = remoteDescription->GetMediaSectionCount();
    nsresult rv;

    // Iterate over the sdp, re-assigning or creating remote tracks as we go
    for (size_t i = 0; i < numMlines; ++i) {
      const SdpMediaSection& msection = remoteDescription->GetMediaSection(i);

      if (mSdpHelper.MsectionIsDisabled(msection) || !msection.IsSending()) {
        continue;
      }

      std::vector<JsepReceivingTrack>::iterator track;

      if (msection.GetMediaType() == SdpMediaSection::kApplication) {
        // Datachannel doesn't have msid, just search by type
        track = FindUnassignedTrackByType(mRemoteTracks,
                                          msection.GetMediaType());
      } else {
        std::string streamId;
        std::string trackId;
        rv = GetRemoteIds(*remoteDescription, msection, &streamId, &trackId);
        NS_ENSURE_SUCCESS(rv, rv);

        track = FindTrackByIds(mRemoteTracks, streamId, trackId);
      }

      if (track == mRemoteTracks.end()) {
        RefPtr<JsepTrack> track;
        rv = CreateReceivingTrack(i, *remoteDescription, msection, &track);
        NS_ENSURE_SUCCESS(rv, rv);

        JsepReceivingTrack rtrack;
        rtrack.mTrack = track;
        rtrack.mAssignedMLine = Some(i);
        mRemoteTracks.push_back(rtrack);
        mRemoteTracksAdded.push_back(rtrack);
      } else {
        track->mAssignedMLine = Some(i);
      }
    }
  }

  // Remove any unassigned remote track ids
  for (size_t i = 0; i < mRemoteTracks.size();) {
    if (!mRemoteTracks[i].mAssignedMLine.isSome()) {
      mRemoteTracksRemoved.push_back(mRemoteTracks[i]);
      mRemoteTracks.erase(mRemoteTracks.begin() + i);
    } else {
      ++i;
    }
  }

  return NS_OK;
}

nsresult
JsepSessionImpl::ValidateLocalDescription(const Sdp& description)
{
  // TODO(bug 1095226): Better checking.
  if (!mGeneratedLocalDescription) {
    JSEP_SET_ERROR("Calling SetLocal without first calling CreateOffer/Answer"
                   " is not supported.");
    return NS_ERROR_UNEXPECTED;
  }

  if (description.GetMediaSectionCount() !=
      mGeneratedLocalDescription->GetMediaSectionCount()) {
    JSEP_SET_ERROR("Changing the number of m-sections is not allowed");
    return NS_ERROR_INVALID_ARG;
  }

  for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) {
    auto& origMsection = mGeneratedLocalDescription->GetMediaSection(i);
    auto& finalMsection = description.GetMediaSection(i);
    if (origMsection.GetMediaType() != finalMsection.GetMediaType()) {
      JSEP_SET_ERROR("Changing the media-type of m-sections is not allowed");
      return NS_ERROR_INVALID_ARG;
    }

    // These will be present in reoffer
    if (!mCurrentLocalDescription) {
      if (finalMsection.GetAttributeList().HasAttribute(
              SdpAttribute::kCandidateAttribute)) {
        JSEP_SET_ERROR("Adding your own candidate attributes is not supported");
        return NS_ERROR_INVALID_ARG;
      }

      if (finalMsection.GetAttributeList().HasAttribute(
              SdpAttribute::kEndOfCandidatesAttribute)) {
        JSEP_SET_ERROR("Why are you trying to set a=end-of-candidates?");
        return NS_ERROR_INVALID_ARG;
      }
    }

    // TODO(bug 1095218): Check msid
    // TODO(bug 1095226): Check ice-ufrag and ice-pwd
    // TODO(bug 1095226): Check fingerprints
    // TODO(bug 1095226): Check payload types (at least ensure that payload
    // types we don't actually support weren't added)
    // TODO(bug 1095226): Check ice-options?
  }

  if (description.GetAttributeList().HasAttribute(
          SdpAttribute::kIceLiteAttribute)) {
    JSEP_SET_ERROR("Running ICE in lite mode is unsupported");
    return NS_ERROR_INVALID_ARG;
  }

  return NS_OK;
}

nsresult
JsepSessionImpl::ValidateRemoteDescription(const Sdp& description)
{
  if (!mCurrentRemoteDescription || !mCurrentLocalDescription) {
    // Not renegotiation; checks for whether a remote answer are consistent
    // with our offer are handled in ValidateAnswer()
    return NS_OK;
  }

  if (mCurrentRemoteDescription->GetMediaSectionCount() >
      description.GetMediaSectionCount()) {
    JSEP_SET_ERROR("New remote description has fewer m-sections than the "
                   "previous remote description.");
    return NS_ERROR_INVALID_ARG;
  }

  // These are solely to check that bundle is valid
  SdpHelper::BundledMids bundledMids;
  nsresult rv = GetNegotiatedBundledMids(&bundledMids);
  NS_ENSURE_SUCCESS(rv, rv);

  SdpHelper::BundledMids newBundledMids;
  rv = mSdpHelper.GetBundledMids(description, &newBundledMids);
  NS_ENSURE_SUCCESS(rv, rv);

  // check for partial ice restart, which is not supported
  Maybe<bool> iceCredsDiffer;
  for (size_t i = 0;
       i < mCurrentRemoteDescription->GetMediaSectionCount();
       ++i) {

    const SdpMediaSection& newMsection = description.GetMediaSection(i);
    const SdpMediaSection& oldMsection =
      mCurrentRemoteDescription->GetMediaSection(i);

    if (mSdpHelper.MsectionIsDisabled(newMsection) ||
        mSdpHelper.MsectionIsDisabled(oldMsection)) {
      continue;
    }

    if (oldMsection.GetMediaType() != newMsection.GetMediaType()) {
      JSEP_SET_ERROR("Remote description changes the media type of m-line "
                     << i);
      return NS_ERROR_INVALID_ARG;
    }

    bool differ = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
    // Detect whether all the creds are the same or all are different
    if (!iceCredsDiffer.isSome()) {
      // for the first msection capture whether creds are different or same
      iceCredsDiffer = mozilla::Some(differ);
    } else if (iceCredsDiffer.isSome() && *iceCredsDiffer != differ) {
      // subsequent msections must match the first sections
      JSEP_SET_ERROR("Partial ICE restart is unsupported at this time "
                     "(new remote description changes either the ice-ufrag "
                     "or ice-pwd on fewer than all msections)");
      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;
    }

    const SdpAttributeList& answerAttrs(answerMsection.GetAttributeList());
    const SdpAttributeList& offerAttrs(offerMsection.GetAttributeList());
    if (answerAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
        offerAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
        offerAttrs.GetMid() != answerAttrs.GetMid()) {
      JSEP_SET_ERROR("Answer changes mid for level, was \'"
                     << offerMsection.GetAttributeList().GetMid()
                     << "\', now \'"
                     << answerMsection.GetAttributeList().GetMid() << "\'");
      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;

  nsresult rv = GetRemoteIds(sdp, msection, &streamId, &trackId);
  NS_ENSURE_SUCCESS(rv, rv);

  *track = new JsepTrack(msection.GetMediaType(),
                         streamId,
                         trackId,
                         sdp::kRecv);

  (*track)->SetCNAME(mSdpHelper.GetCNAME(msection));
  (*track)->PopulateCodecs(mSupportedCodecs.values);

  return NS_OK;
}

nsresult
JsepSessionImpl::CreateGenericSDP(UniquePtr<Sdp>* sdpp)
{
  // draft-ietf-rtcweb-jsep-08 Section 5.2.1:
  //  o  The second SDP line MUST be an "o=" line, as specified in
  //     [RFC4566], Section 5.2.  The value of the <username> field SHOULD
  //     be "-".  The value of the <sess-id> field SHOULD be a
  //     cryptographically random number.  To ensure uniqueness, this
  //     number SHOULD be at least 64 bits long.  The value of the <sess-
  //     version> field SHOULD be zero.  The value of the <nettype>
  //     <addrtype> <unicast-address> tuple SHOULD be set to a non-
  //     meaningful address, such as IN IP4 0.0.0.0, to prevent leaking the
  //     local address in this field.  As mentioned in [RFC4566], the
  //     entire o= line needs to be unique, but selecting a random number
  //     for <sess-id> is sufficient to accomplish this.

  auto origin =
      SdpOrigin("mozilla...THIS_IS_SDPARTA-" MOZ_APP_UA_VERSION,
                mSessionId,
                mSessionVersion,
                sdp::kIPv4,
                "0.0.0.0");

  UniquePtr<Sdp> sdp = MakeUnique<SipccSdp>(origin);

  if (mDtlsFingerprints.empty()) {
    JSEP_SET_ERROR("Missing DTLS fingerprint");
    return NS_ERROR_FAILURE;
  }

  UniquePtr<SdpFingerprintAttributeList> fpl =
      MakeUnique<SdpFingerprintAttributeList>();
  for (auto fp = mDtlsFingerprints.begin(); fp != mDtlsFingerprints.end();
       ++fp) {
    fpl->PushEntry(fp->mAlgorithm, fp->mValue);
  }
  sdp->GetAttributeList().SetAttribute(fpl.release());

  auto* iceOpts = new SdpOptionsAttribute(SdpAttribute::kIceOptionsAttribute);
  iceOpts->PushEntry("trickle");
  sdp->GetAttributeList().SetAttribute(iceOpts);

  // This assumes content doesn't add a bunch of msid attributes with a
  // different semantic in mind.
  std::vector<std::string> msids;
  msids.push_back("*");
  mSdpHelper.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;
}

nsresult
JsepSessionImpl::CreateSsrc(uint32_t* ssrc)
{
  do {
    SECStatus rv = PK11_GenerateRandom(
        reinterpret_cast<unsigned char*>(ssrc), sizeof(uint32_t));
    if (rv != SECSuccess) {
      JSEP_SET_ERROR("Failed to generate SSRC, error=" << rv);
      return NS_ERROR_FAILURE;
    }
  } while (mSsrcs.count(*ssrc));
  mSsrcs.insert(*ssrc);

  return NS_OK;
}

void
JsepSessionImpl::SetupDefaultCodecs()
{
  // Supported audio codecs.
  // Per jmspeex on IRC:
  // For 32KHz sampling, 28 is ok, 32 is good, 40 should be really good
  // quality.  Note that 1-2Kbps will be wasted on a stereo Opus channel
  // with mono input compared to configuring it for mono.
  // If we reduce bitrate enough Opus will low-pass us; 16000 will kill a
  // 9KHz tone.  This should be adaptive when we're at the low-end of video
  // bandwidth (say <100Kbps), and if we're audio-only, down to 8 or
  // 12Kbps.
  mSupportedCodecs.values.push_back(new JsepAudioCodecDescription(
      "109",
      "opus",
      48000,
      2,
      960,
#ifdef WEBRTC_GONK
      // TODO Move this elsewhere to be adaptive to rate - Bug 1207925
      16000 // B2G uses lower capture sampling rate
#else
      40000
#endif
      ));

  mSupportedCodecs.values.push_back(new JsepAudioCodecDescription(
      "9",
      "G722",
      8000,
      1,
      320,
      64000));

  // packet size and bitrate values below copied from sipcc.
  // May need reevaluation from a media expert.
  mSupportedCodecs.values.push_back(
      new JsepAudioCodecDescription("0",
                                    "PCMU",
                                    8000,
                                    1,
                                    8000 / 50,   // frequency / 50
                                    8 * 8000 * 1 // 8 * frequency * channels
                                    ));

  mSupportedCodecs.values.push_back(
      new JsepAudioCodecDescription("8",
                                    "PCMA",
                                    8000,
                                    1,
                                    8000 / 50,   // frequency / 50
                                    8 * 8000 * 1 // 8 * frequency * channels
                                    ));

  // Supported video codecs.
  // Note: order here implies priority for building offers!
  JsepVideoCodecDescription* vp9 = new JsepVideoCodecDescription(
      "121",
      "VP9",
      90000
      );
  // Defaults for mandatory params
  vp9->mConstraints.maxFs = 12288; // Enough for 2048x1536
  vp9->mConstraints.maxFps = 60;
  mSupportedCodecs.values.push_back(vp9);

  JsepVideoCodecDescription* vp8 = new JsepVideoCodecDescription(
      "120",
      "VP8",
      90000
      );
  // Defaults for mandatory params
  vp8->mConstraints.maxFs = 12288; // Enough for 2048x1536
  vp8->mConstraints.maxFps = 60;
  mSupportedCodecs.values.push_back(vp8);

  JsepVideoCodecDescription* h264_1 = new JsepVideoCodecDescription(
      "126",
      "H264",
      90000
      );
  h264_1->mPacketizationMode = 1;
  // Defaults for mandatory params
  h264_1->mProfileLevelId = 0x42E00D;
  mSupportedCodecs.values.push_back(h264_1);

  JsepVideoCodecDescription* h264_0 = new JsepVideoCodecDescription(
      "97",
      "H264",
      90000
      );
  h264_0->mPacketizationMode = 0;
  // Defaults for mandatory params
  h264_0->mProfileLevelId = 0x42E00D;
  mSupportedCodecs.values.push_back(h264_0);

  mSupportedCodecs.values.push_back(new JsepApplicationCodecDescription(
      "5000",
      "webrtc-datachannel",
      WEBRTC_DATACHANNEL_STREAMS_DEFAULT
      ));
}

void
JsepSessionImpl::SetupDefaultRtpExtensions()
{
  AddAudioRtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level");
}

void
JsepSessionImpl::SetState(JsepSignalingState state)
{
  if (state == mState)
    return;

  MOZ_MTLOG(ML_NOTICE, "[" << mName << "]: " <<
            GetStateStr(mState) << " -> " << GetStateStr(state));
  mState = state;
}

nsresult
JsepSessionImpl::AddRemoteIceCandidate(const std::string& candidate,
                                       const std::string& mid,
                                       uint16_t level)
{
  mLastError.clear();

  mozilla::Sdp* sdp = GetParsedRemoteDescription();

  if (!sdp) {
    JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
    return NS_ERROR_UNEXPECTED;
  }

  return mSdpHelper.AddCandidateToSdp(sdp, candidate, mid, level);
}

nsresult
JsepSessionImpl::AddLocalIceCandidate(const std::string& candidate,
                                      uint16_t level,
                                      std::string* mid,
                                      bool* skipped)
{
  mLastError.clear();

  mozilla::Sdp* sdp = GetParsedLocalDescription();

  if (!sdp) {
    JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
    return NS_ERROR_UNEXPECTED;
  }

  if (sdp->GetMediaSectionCount() <= level) {
    // mainly here to make some testing less complicated, but also just in case
    *skipped = true;
    return NS_OK;
  }

  if (mState == kJsepStateStable) {
    const Sdp* answer(GetAnswer());
    if (mSdpHelper.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;
    }
  }

  nsresult rv = mSdpHelper.GetMidFromLevel(*sdp, level, mid);
  if (NS_FAILED(rv)) {
    return rv;
  }

  *skipped = false;

  return mSdpHelper.AddCandidateToSdp(sdp, candidate, *mid, level);
}

nsresult
JsepSessionImpl::UpdateDefaultCandidate(
    const std::string& defaultCandidateAddr,
    uint16_t defaultCandidatePort,
    const std::string& defaultRtcpCandidateAddr,
    uint16_t defaultRtcpCandidatePort,
    uint16_t level)
{
  mLastError.clear();

  mozilla::Sdp* sdp = GetParsedLocalDescription();

  if (!sdp) {
    JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
    return NS_ERROR_UNEXPECTED;
  }

  if (level >= sdp->GetMediaSectionCount()) {
    return NS_OK;
  }

  std::string defaultRtcpCandidateAddrCopy(defaultRtcpCandidateAddr);
  if (mState == kJsepStateStable && mTransports[level]->mComponents == 1) {
    // We know we're doing rtcp-mux by now. Don't create an rtcp attr.
    defaultRtcpCandidateAddrCopy = "";
    defaultRtcpCandidatePort = 0;
  }

  // If offer/answer isn't done, it is too early to tell whether these defaults
  // need to be applied to other m-sections.
  SdpHelper::BundledMids bundledMids;
  if (mState == kJsepStateStable) {
    nsresult rv = GetNegotiatedBundledMids(&bundledMids);
    if (NS_FAILED(rv)) {
      MOZ_ASSERT(false);
      mLastError += " (This should have been caught sooner!)";
      return NS_ERROR_FAILURE;
    }
  }

  mSdpHelper.SetDefaultAddresses(
      defaultCandidateAddr,
      defaultCandidatePort,
      defaultRtcpCandidateAddrCopy,
      defaultRtcpCandidatePort,
      sdp,
      level,
      bundledMids);

  return NS_OK;
}

nsresult
JsepSessionImpl::EndOfLocalCandidates(uint16_t level)
{
  mLastError.clear();

  mozilla::Sdp* sdp = GetParsedLocalDescription();

  if (!sdp) {
    JSEP_SET_ERROR("Cannot mark end of local ICE candidates in state "
                   << GetStateStr(mState));
    return NS_ERROR_UNEXPECTED;
  }

  if (level >= sdp->GetMediaSectionCount()) {
    return NS_OK;
  }

  // If offer/answer isn't done, it is too early to tell whether this update
  // needs to be applied to other m-sections.
  SdpHelper::BundledMids bundledMids;
  if (mState == kJsepStateStable) {
    nsresult rv = GetNegotiatedBundledMids(&bundledMids);
    if (NS_FAILED(rv)) {
      MOZ_ASSERT(false);
      mLastError += " (This should have been caught sooner!)";
      return NS_ERROR_FAILURE;
    }
  }

  mSdpHelper.SetIceGatheringComplete(sdp,
                                     level,
                                     bundledMids);

  return NS_OK;
}

nsresult
JsepSessionImpl::GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids)
{
  const Sdp* answerSdp = GetAnswer();

  if (!answerSdp) {
    return NS_OK;
  }

  return mSdpHelper.GetBundledMids(*answerSdp, bundledMids);
}

nsresult
JsepSessionImpl::EnableOfferMsection(SdpMediaSection* msection)
{
  // We assert here because adding rtcp-mux to a non-disabled m-section that
  // did not already have rtcp-mux can cause problems.
  MOZ_ASSERT(mSdpHelper.MsectionIsDisabled(*msection));

  msection->SetPort(9);

  // We don't do this in AddTransportAttributes because that is also used for
  // making answers, and we don't want to unconditionally set rtcp-mux there.
  if (mSdpHelper.HasRtcp(msection->GetProtocol())) {
    // Set RTCP-MUX.
    msection->GetAttributeList().SetAttribute(
        new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
  }

  nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = SetRecvonlySsrc(msection);
  NS_ENSURE_SUCCESS(rv, rv);

  AddExtmap(msection);

  std::ostringstream osMid;
  osMid << "sdparta_" << msection->GetLevel();
  AddMid(osMid.str(), msection);

  return NS_OK;
}

mozilla::Sdp*
JsepSessionImpl::GetParsedLocalDescription() const
{
  if (mPendingLocalDescription) {
    return mPendingLocalDescription.get();
  } else if (mCurrentLocalDescription) {
    return mCurrentLocalDescription.get();
  }

  return nullptr;
}

mozilla::Sdp*
JsepSessionImpl::GetParsedRemoteDescription() const
{
  if (mPendingRemoteDescription) {
    return mPendingRemoteDescription.get();
  } else if (mCurrentRemoteDescription) {
    return mCurrentRemoteDescription.get();
  }

  return nullptr;
}

const Sdp*
JsepSessionImpl::GetAnswer() const
{
  return mWasOffererLastTime ? mCurrentRemoteDescription.get()
                             : mCurrentLocalDescription.get();
}

nsresult
JsepSessionImpl::Close()
{
  mLastError.clear();
  SetState(kJsepStateClosed);
  return NS_OK;
}

const std::string
JsepSessionImpl::GetLastError() const
{
  return mLastError;
}

bool
JsepSessionImpl::AllLocalTracksAreAssigned() const
{
  for (auto i = mLocalTracks.begin(); i != mLocalTracks.end(); ++i) {
    if (!i->mAssignedMLine.isSome()) {
      return false;
    }
  }

  return true;
}

} // namespace mozilla