/* 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 "jsep/JsepTrack.h"

#include <algorithm>

#include "jsep/JsepCodecDescription.h"
#include "jsep/JsepTrackEncoding.h"
#include "transport/logging.h"

namespace mozilla {

MOZ_MTLOG_MODULE("jsep")

void JsepTrack::GetNegotiatedPayloadTypes(
    std::vector<uint16_t>* payloadTypes) const {
  if (!mNegotiatedDetails) {
    return;
  }

  for (const auto& encoding : mNegotiatedDetails->mEncodings) {
    GetPayloadTypes(encoding->GetCodecs(), payloadTypes);
  }

  // Prune out dupes
  std::sort(payloadTypes->begin(), payloadTypes->end());
  auto newEnd = std::unique(payloadTypes->begin(), payloadTypes->end());
  payloadTypes->erase(newEnd, payloadTypes->end());
}

/* static */
void JsepTrack::GetPayloadTypes(
    const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
    std::vector<uint16_t>* payloadTypes) {
  for (const auto& codec : codecs) {
    uint16_t pt;
    if (!codec->GetPtAsInt(&pt)) {
      MOZ_MTLOG(ML_ERROR, "Codec " << codec->mName
                                   << " does not have a valid payload type");
      MOZ_ASSERT(false);
      continue;
    }
    payloadTypes->push_back(pt);
  }
}

void JsepTrack::EnsureNoDuplicatePayloadTypes(
    std::vector<UniquePtr<JsepCodecDescription>>* codecs) {
  std::set<std::string> uniquePayloadTypes;
  for (auto& codec : *codecs) {
    codec->EnsureNoDuplicatePayloadTypes(uniquePayloadTypes);
  }
}

void JsepTrack::EnsureSsrcs(SsrcGenerator& ssrcGenerator, size_t aNumber) {
  while (mSsrcs.size() < aNumber) {
    uint32_t ssrc, rtxSsrc;
    if (!ssrcGenerator.GenerateSsrc(&ssrc) ||
        !ssrcGenerator.GenerateSsrc(&rtxSsrc)) {
      MOZ_MTLOG(ML_ERROR, "Unable to generate SSRC");
      return;
    }
    mSsrcs.push_back(ssrc);
    mSsrcToRtxSsrc[ssrc] = rtxSsrc;
    if (mSsrcs.size() != mSsrcToRtxSsrc.size()) {
      MOZ_MTLOG(ML_ERROR,
                "[" << mTrackId
                    << "]: mSsrcToRtxSsrc has different size than mSsrcs.");
      MOZ_ASSERT(false);
      mSsrcs.clear();
      mSsrcToRtxSsrc.clear();
    }
  }
}

std::vector<uint32_t> JsepTrack::GetRtxSsrcs() const {
  std::vector<uint32_t> result;
  if (mRtxIsAllowed &&
      Preferences::GetBool("media.peerconnection.video.use_rtx", false) &&
      !mSsrcToRtxSsrc.empty()) {
    if (mSsrcToRtxSsrc.size() != mSsrcs.size()) {
      MOZ_MTLOG(ML_ERROR,
                "[" << mTrackId
                    << "]: mSsrcToRtxSsrc has different size than mSsrcs.");
      return {};
    }
    for (const auto ssrc : mSsrcs) {
      auto it = mSsrcToRtxSsrc.find(ssrc);
      if (it != mSsrcToRtxSsrc.end()) {
        result.push_back(it->second);
      } else {
        MOZ_MTLOG(ML_ERROR,
                  "[" << mTrackId << "]: No RTX SSRC found for SSRC " << ssrc);
        return {};
      }
    }
  }
  return result;
}

void JsepTrack::PopulateCodecs(
    const std::vector<UniquePtr<JsepCodecDescription>>& prototype,
    bool aUsePreferredCodecsOrder) {
  mPrototypeCodecs.clear();
  mUsePreferredCodecsOrder = aUsePreferredCodecsOrder;
  for (const auto& prototypeCodec : prototype) {
    if (prototypeCodec->Type() == mType) {
      mPrototypeCodecs.emplace_back(prototypeCodec->Clone());
      mPrototypeCodecs.back()->mDirection = mDirection;
    }
  }

  EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs);
}

void JsepTrack::AddToOffer(SsrcGenerator& ssrcGenerator,
                           SdpMediaSection* offer) {
  AddToMsection(mPrototypeCodecs, offer);

  for (const auto& codec : mPrototypeCodecs) {
    uint16_t pt;
    if (SdpHelper::GetPtAsInt(codec->mDefaultPt, &pt)) {
      mReceivePayloadTypes.push_back(pt);
    }
  }

  if (mDirection == sdp::kSend) {
    std::vector<std::string> rids;
    if (offer->IsSending()) {
      rids = mRids;
    }

    AddToMsection(rids, sdp::kSend, ssrcGenerator,
                  IsRtxEnabled(mPrototypeCodecs), offer);
  }
}

void JsepTrack::AddToAnswer(const SdpMediaSection& offer,
                            SsrcGenerator& ssrcGenerator,
                            SdpMediaSection* answer) {
  // We do not modify mPrototypeCodecs here, since we're only creating an
  // answer. Once offer/answer concludes, we will update mPrototypeCodecs.
  std::vector<UniquePtr<JsepCodecDescription>> codecs =
      NegotiateCodecs(offer, true, Nothing());
  if (codecs.empty()) {
    return;
  }

  AddToMsection(codecs, answer);

  if (mDirection == sdp::kSend) {
    AddToMsection(mRids, sdp::kSend, ssrcGenerator, IsRtxEnabled(codecs),
                  answer);
  }
}

void JsepTrack::SetRids(const std::vector<std::string>& aRids) {
  if (!aRids.size()) {
    MOZ_MTLOG(ML_ERROR, "cannot set empty rids");
    return;
  }
  if (!mRids.empty()) {
    return;
  }
  mRids = aRids;
}

void JsepTrack::SetMaxEncodings(size_t aMax) {
  mMaxEncodings = aMax;
  if (mRids.size() > mMaxEncodings) {
    mRids.resize(mMaxEncodings);
  }
}

void JsepTrack::RecvTrackSetRemote(const Sdp& aSdp,
                                   const SdpMediaSection& aMsection,
                                   const std::vector<uint32_t>& aOwnSendSsrcs) {
  mInHaveRemote = true;
  if (mDirection != sdp::kRecv) {
    MOZ_MTLOG(ML_ERROR, "RecvTrackSetRemote called on non-receive track");
    MOZ_ASSERT(false);
    return;
  }
  if (aMsection.GetMediaType() == SdpMediaSection::kApplication) {
    MOZ_MTLOG(ML_ERROR,
              "RecvTrackSetRemote called on application media section");
    MOZ_ASSERT(false);
    return;
  }
  std::string error;
  SdpHelper helper(&error);

  mRemoteSetSendBit = aMsection.IsSending();
  if (!mRemoteSetSendBit) {
    mReceptive = false;
  }

  if (aMsection.IsSending()) {
    (void)helper.GetIdsFromMsid(aSdp, aMsection, &mStreamIds);
  } else {
    mStreamIds.clear();
  }

  // We do this whether or not the track is active
  SetCNAME(helper.GetCNAME(aMsection));
  mSsrcs.clear();
  // Storage of mSsrcs and mSsrcToRtxSsrc could be improved, see Bug 1990364
  mSsrcToRtxSsrc.clear();

  if (aMsection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
    std::set<uint32_t> seen;
    for (const auto& s : aMsection.GetAttributeList().GetSsrc().mSsrcs) {
      // Each a=ssrc line carries one attribute (cname, msid, etc.) for the
      // same SSRC; only add each unique SSRC value once.
      if (seen.insert(s.ssrc).second) {
        mSsrcs.push_back(s.ssrc);
      }
    }
  }

  // Use FID ssrc-group to associate rtx ssrcs with "regular" ssrcs. Despite
  // not being part of RFC 4588, this is how rtx is negotiated by libwebrtc
  // and jitsi.
  if (aMsection.GetAttributeList().HasAttribute(
          SdpAttribute::kSsrcGroupAttribute)) {
    for (const auto& group :
         aMsection.GetAttributeList().GetSsrcGroup().mSsrcGroups) {
      if (group.semantics == SdpSsrcGroupAttributeList::kFid &&
          group.ssrcs.size() == 2) {
        // Ensure we have a "regular" ssrc for each rtx ssrc.
        if (std::find(mSsrcs.begin(), mSsrcs.end(), group.ssrcs[0]) !=
            mSsrcs.end()) {
          mSsrcToRtxSsrc[group.ssrcs[0]] = group.ssrcs[1];

          // Remove rtx ssrcs from mSsrcs
          auto res = std::remove_if(
              mSsrcs.begin(), mSsrcs.end(),
              [group](uint32_t ssrc) { return ssrc == group.ssrcs[1]; });
          mSsrcs.erase(res, mSsrcs.end());
        }
      }
    }
  }

  // Remove any extracted SSRCs that duplicate our own send SSRCs, to prevent
  // EnsureLocalSSRC() from regenerating our send SSRC to a value the peer
  // never negotiated.
  for (uint32_t sendSsrc : aOwnSendSsrcs) {
    auto it = std::find(mSsrcs.begin(), mSsrcs.end(), sendSsrc);
    if (it != mSsrcs.end()) {
      mSsrcToRtxSsrc.erase(sendSsrc);
      mSsrcs.erase(it);
    }
  }
}

void JsepTrack::RecvTrackSetLocal(const SdpMediaSection& aMsection) {
  if (mDirection != sdp::kRecv) {
    MOZ_MTLOG(ML_ERROR, "RecvTrackSetLocal called on non-receive track");
    MOZ_ASSERT(false);
    return;
  }

  // TODO: Should more stuff live in here? Anything that needs to happen when we
  // decide we're ready to receive packets should probably go in here.
  mReceptive = aMsection.IsReceiving();
}

void JsepTrack::SendTrackSetRemote(SsrcGenerator& aSsrcGenerator,
                                   const SdpMediaSection& aRemoteMsection) {
  mInHaveRemote = true;
  if (mType == SdpMediaSection::kApplication) {
    return;
  }

  std::vector<SdpRidAttributeList::Rid> rids;

  // TODO: Current language in webrtc-pc is completely broken, and so I will
  // not be quoting it here.
  if ((mType == SdpMediaSection::kVideo) &&
      aRemoteMsection.GetAttributeList().HasAttribute(
          SdpAttribute::kSimulcastAttribute)) {
    // Note: webrtc-pc does not appear to support the full IETF simulcast
    // spec. In particular, the IETF simulcast spec supports requesting
    // multiple different sets of encodings. For example, "a=simulcast:send
    // 1,2;3,4;5,6" means that there are three simulcast streams, the first of
    // which can use either rid 1 or 2 (but not both), the second of which can
    // use rid 3 or 4 (but not both), and the third of which can use rid 5 or
    // 6 (but not both). webrtc-pc does not support this either/or stuff for
    // rid; each simulcast stream gets exactly one rid.
    // Also, webrtc-pc does not support the '~' pause syntax at all
    // See https://github.com/w3c/webrtc-pc/issues/2769
    GetRids(aRemoteMsection, sdp::kRecv, &rids);
  }

  if (mRids.empty()) {
    // Initial configuration
    for (const auto& ridAttr : rids) {
      // TODO: Spec might change, making a length > 16 invalid SDP.
      std::string dummy;
      if (SdpRidAttributeList::CheckRidValidity(ridAttr.id, &dummy) &&
          ridAttr.id.size() <= SdpRidAttributeList::kMaxRidLength) {
        mRids.push_back(ridAttr.id);
      }
    }
    if (mRids.size() > mMaxEncodings) {
      mRids.resize(mMaxEncodings);
    }
  } else {
    // JSEP is allowed to remove or reorder rids. RTCRtpSender won't pay
    // attention to reordering.
    std::vector<std::string> newRids;
    for (const auto& ridAttr : rids) {
      for (const auto& oldRid : mRids) {
        if (oldRid == ridAttr.id) {
          newRids.push_back(oldRid);
          break;
        }
      }
    }
    mRids = std::move(newRids);
  }

  if (mRids.empty()) {
    mRids.push_back("");
  }

  UpdateSsrcs(aSsrcGenerator, mRids.size());
}

void JsepTrack::AddToMsection(
    const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
    SdpMediaSection* msection) const {
  if (msection->GetMediaType() != mType) {
    MOZ_MTLOG(ML_ERROR, "AddToMsection called on wrong media section type");
    MOZ_ASSERT(false);
    return;
  }
  if (codecs.empty()) {
    MOZ_MTLOG(ML_ERROR, "AddToMsection called with empty codecs");
    MOZ_ASSERT(false);
    return;
  }
  for (const auto& codec : codecs) {
    codec->AddToMediaSection(*msection);
  }

  if ((mDirection == sdp::kSend) && (mType != SdpMediaSection::kApplication) &&
      msection->IsSending()) {
    if (mStreamIds.empty()) {
      msection->AddMsid("-", mTrackId);
    } else {
      for (const std::string& streamId : mStreamIds) {
        msection->AddMsid(streamId, mTrackId);
      }
    }
  }
}

void JsepTrack::UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings) {
  if (mDirection != sdp::kSend) {
    MOZ_MTLOG(ML_ERROR, "UpdateSsrcs called on non-send track");
    MOZ_ASSERT(false);
    return;
  }
  if (mType == SdpMediaSection::kApplication) {
    MOZ_MTLOG(ML_ERROR, "UpdateSsrcs called on application media section");
    MOZ_ASSERT(false);
    return;
  }
  size_t numSsrcs = std::max<size_t>(encodings, 1U);

  EnsureSsrcs(ssrcGenerator, numSsrcs);
  PruneSsrcs(numSsrcs);
  if (mNegotiatedDetails && mNegotiatedDetails->GetEncodingCount() > numSsrcs) {
    mNegotiatedDetails->TruncateEncodings(numSsrcs);
  }
  if (mSsrcs.empty()) {
    MOZ_MTLOG(ML_ERROR, "UpdateSsrcs resulted in empty mSsrcs");
    MOZ_ASSERT(false);
  }
}

void JsepTrack::PruneSsrcs(size_t aNumSsrcs) {
  mSsrcs.resize(aNumSsrcs);

  // We might have duplicate entries in mSsrcs, so we need to resize first and
  // then remove ummatched rtx ssrcs.
  auto itor = mSsrcToRtxSsrc.begin();
  while (itor != mSsrcToRtxSsrc.end()) {
    if (std::find(mSsrcs.begin(), mSsrcs.end(), itor->first) == mSsrcs.end()) {
      itor = mSsrcToRtxSsrc.erase(itor);
    } else {
      ++itor;
    }
  }
}

bool JsepTrack::IsRtxEnabled(
    const std::vector<UniquePtr<JsepCodecDescription>>& codecs) const {
  for (const auto& codec : codecs) {
    if (codec->Type() == SdpMediaSection::kVideo &&
        static_cast<const JsepVideoCodecDescription*>(codec.get())
            ->mRtxEnabled) {
      return true;
    }
  }

  return false;
}

void JsepTrack::AddToMsection(const std::vector<std::string>& aRids,
                              sdp::Direction direction,
                              SsrcGenerator& ssrcGenerator, bool rtxEnabled,
                              SdpMediaSection* msection) {
  if (aRids.size() > 1) {
    auto simulcast = MakeUnique<SdpSimulcastAttribute>();
    auto ridAttrs = MakeUnique<SdpRidAttributeList>();
    for (const std::string& rid : aRids) {
      SdpRidAttributeList::Rid ridAttr;
      ridAttr.id = rid;
      ridAttr.direction = direction;
      ridAttrs->mRids.push_back(std::move(ridAttr));

      SdpSimulcastAttribute::Version version;
      version.choices.push_back(SdpSimulcastAttribute::Encoding(rid, false));
      if (direction == sdp::kSend) {
        simulcast->sendVersions.push_back(std::move(version));
      } else {
        simulcast->recvVersions.push_back(std::move(version));
      }
    }

    msection->GetAttributeList().SetAttribute(std::move(simulcast));
    msection->GetAttributeList().SetAttribute(std::move(ridAttrs));
  }

  bool requireRtxSsrcs = rtxEnabled && msection->IsSending();

  if (mType != SdpMediaSection::kApplication && mDirection == sdp::kSend) {
    UpdateSsrcs(ssrcGenerator, aRids.size());

    if (requireRtxSsrcs) {
      if (mSsrcs.size() != mSsrcToRtxSsrc.size()) {
        MOZ_MTLOG(ML_ERROR,
                  "[" << mTrackId
                      << "]: mSsrcToRtxSsrc has different size than mSsrcs.");
        return;
      }
      std::vector<uint32_t> allSsrcs;
      auto group = MakeUnique<SdpSsrcGroupAttributeList>();
      for (const auto& ssrc : mSsrcs) {
        const auto rtxSsrc = mSsrcToRtxSsrc[ssrc];
        allSsrcs.push_back(ssrc);
        allSsrcs.push_back(rtxSsrc);
        group->PushEntry(SdpSsrcGroupAttributeList::kFid, {ssrc, rtxSsrc});
      }
      msection->SetSsrcs(allSsrcs, mCNAME);
      msection->GetAttributeList().SetAttribute(std::move(group));
    } else {
      msection->SetSsrcs(mSsrcs, mCNAME);
    }
  }
}

void JsepTrack::GetRids(const SdpMediaSection& msection,
                        sdp::Direction direction,
                        std::vector<SdpRidAttributeList::Rid>* rids) const {
  rids->clear();
  if (!msection.GetAttributeList().HasAttribute(
          SdpAttribute::kSimulcastAttribute)) {
    return;
  }

  const SdpSimulcastAttribute& simulcast(
      msection.GetAttributeList().GetSimulcast());

  const SdpSimulcastAttribute::Versions* versions = nullptr;
  switch (direction) {
    case sdp::kSend:
      versions = &simulcast.sendVersions;
      break;
    case sdp::kRecv:
      versions = &simulcast.recvVersions;
      break;
  }

  if (!versions->IsSet()) {
    return;
  }

  // RFC 8853 does not seem to forbid duplicate rids in a simulcast attribute.
  // So, while this is obviously silly, we should be prepared for it and
  // ignore those duplicate rids.
  std::set<std::string> uniqueRids;
  for (const SdpSimulcastAttribute::Version& version : *versions) {
    if (!version.choices.empty() && !uniqueRids.count(version.choices[0].rid)) {
      // We validate that rids are present (and sane) elsewhere.
      rids->push_back(*msection.FindRid(version.choices[0].rid));
      uniqueRids.insert(version.choices[0].rid);
    }
  }
}

void JsepTrack::CreateEncodings(
    const SdpMediaSection& remote,
    const std::vector<UniquePtr<JsepCodecDescription>>& negotiatedCodecs,
    JsepTrackNegotiatedDetails* negotiatedDetails) {
  negotiatedDetails->mTias = remote.GetBandwidth("TIAS");

  webrtc::RtcpMode rtcpMode = webrtc::RtcpMode::kCompound;
  // rtcp-rsize (video only)
  if (remote.GetMediaType() == SdpMediaSection::kVideo &&
      remote.GetAttributeList().HasAttribute(
          SdpAttribute::kRtcpRsizeAttribute)) {
    rtcpMode = webrtc::RtcpMode::kReducedSize;
  }
  // extmap-allow-mixed which can be at the media level or the session level
  constexpr bool SESSION_FALLBACK = true;
  bool extmapAllowMixed = remote.GetAttributeList().HasAttribute(
      SdpAttribute::kExtmapAllowMixedAttribute, SESSION_FALLBACK);
  negotiatedDetails->mRtpRtcpConf = RtpRtcpConfig(rtcpMode, extmapAllowMixed);

  // TODO add support for b=AS if TIAS is not set (bug 976521)

  if (mRids.empty()) {
    mRids.push_back("");
  }

  size_t numEncodings = mRids.size();

  // Drop SSRCs if fewer RIDs were offered than we have encodings
  if (mSsrcs.size() > numEncodings) {
    PruneSsrcs(numEncodings);
  }

  // For each stream make sure we have an encoding, and configure
  // that encoding appropriately.
  for (size_t i = 0; i < numEncodings; ++i) {
    auto encoding = MakeUnique<JsepTrackEncoding>();
    if (mRids.size() > i) {
      encoding->mRid = mRids[i];
    }
    for (const auto& codec : negotiatedCodecs) {
      encoding->AddCodec(*codec);
    }
    negotiatedDetails->mEncodings.push_back(std::move(encoding));
  }
}

std::vector<UniquePtr<JsepCodecDescription>> JsepTrack::GetCodecClones() const {
  std::vector<UniquePtr<JsepCodecDescription>> clones;
  clones.reserve(mPrototypeCodecs.size());
  for (const auto& codec : mPrototypeCodecs) {
    clones.emplace_back(codec->Clone());
  }
  return clones;
}

static bool CompareCodec(const UniquePtr<JsepCodecDescription>& lhs,
                         const UniquePtr<JsepCodecDescription>& rhs) {
  return lhs->mStronglyPreferred && !rhs->mStronglyPreferred;
}

void JsepTrack::MaybeStoreCodecToLog(const std::string& codec,
                                     SdpMediaSection::MediaType type) {
  // We are logging ulpfec and red elsewhere and will not log rtx.
  if (!nsCRT::strcasecmp(codec.c_str(), "ulpfec") ||
      !nsCRT::strcasecmp(codec.c_str(), "red") ||
      !nsCRT::strcasecmp(codec.c_str(), "rtx")) {
    return;
  }

  if (type == SdpMediaSection::kVideo) {
    if (nsCRT::strcasestr(codec.c_str(), "fec") && mFecCodec.empty()) {
      mFecCodec = codec;
    } else if (!nsCRT::strcasestr(codec.c_str(), "fec") &&
               mVideoPreferredCodec.empty()) {
      mVideoPreferredCodec = codec;
    }
  } else if (type == SdpMediaSection::kAudio && mAudioPreferredCodec.empty()) {
    mAudioPreferredCodec = codec;
  }
}

std::vector<UniquePtr<JsepCodecDescription>> JsepTrack::NegotiateCodecs(
    const SdpMediaSection& remote, bool remoteIsOffer,
    Maybe<const SdpMediaSection&> local) {
  // See Bug 1927371 - :ng
  // This also effects the ordering of codecs in RTCRTPSender::GetParameters,
  // but occurs after the initial local description codec ordering is
  // established in the SDP. We should protect against this in the future by
  // applying the same ordering logic to the initial local description.

  std::vector<UniquePtr<JsepCodecDescription>> negotiatedCodecs;
  std::vector<UniquePtr<JsepCodecDescription>> newPrototypeCodecs;
  // "Pseudo codecs" include things that aren't actually stand-alone codecs (ie;
  // ulpfec, red, rtx).
  std::vector<UniquePtr<JsepCodecDescription>> negotiatedPseudoCodecs;
  std::vector<UniquePtr<JsepCodecDescription>> newPrototypePseudoCodecs;

  std::vector<std::string> remoteFormats;

  // If setCodecPreferences has been used we need to ensure the order of codecs
  // matches what was set.
  if (mUsePreferredCodecsOrder) {
    for (auto& codec : mPrototypeCodecs) {
      if (!codec || !codec->mEnabled) {
        continue;
      }
      for (const std::string& fmt : remote.GetFormats()) {
        if (!codec->Matches(fmt, remote)) {
          continue;
        }
        remoteFormats.push_back(fmt);
        break;
      }
    }
  } else {
    remoteFormats = remote.GetFormats();
  }

  // Outer loop establishes the remote side's preference
  for (const std::string& fmt : remoteFormats) {
    // Decide if we want to store this codec for logging.
    const auto* entry = remote.FindRtpmap(fmt);
    if (entry) {
      MaybeStoreCodecToLog(entry->name, remote.GetMediaType());
    }

    for (auto& codec : mPrototypeCodecs) {
      if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) {
        continue;
      }

      // First codec of ours that matches. See if we can negotiate it.
      UniquePtr<JsepCodecDescription> clone(codec->Clone());
      if (clone->Negotiate(fmt, remote, remoteIsOffer, local)) {
        // If negotiation succeeded, remember the payload type the other side
        // used for reoffers.
        codec->mDefaultPt = fmt;

        // Remember whether we negotiated rtx and the associated pt for later.
        if (codec->Type() == SdpMediaSection::kVideo) {
          JsepVideoCodecDescription* videoCodec =
              static_cast<JsepVideoCodecDescription*>(codec.get());
          JsepVideoCodecDescription* cloneVideoCodec =
              static_cast<JsepVideoCodecDescription*>(clone.get());
          bool useRtx =
              mRtxIsAllowed &&
              Preferences::GetBool("media.peerconnection.video.use_rtx", false);
          videoCodec->mRtxEnabled = useRtx && cloneVideoCodec->mRtxEnabled;
          videoCodec->mRtxPayloadType = cloneVideoCodec->mRtxPayloadType;
        }

        // Moves the codec out of mPrototypeCodecs, leaving an empty
        // UniquePtr, so we don't use it again. Also causes successfully
        // negotiated codecs to be placed up front in the future.
        if (codec->mName == "red" || codec->mName == "ulpfec" ||
            codec->mName == "rtx") {
          newPrototypePseudoCodecs.emplace_back(std::move(codec));
          negotiatedPseudoCodecs.emplace_back(std::move(clone));
        } else {
          newPrototypeCodecs.emplace_back(std::move(codec));
          negotiatedCodecs.emplace_back(std::move(clone));
        }
        break;
      }
    }
  }

  // If we are the offerer we need to be prepared to receive all codecs we
  // offered even codecs missing from the answer. To achieve this we add the
  // remaining codecs from mPrototypeCodecs to the back of the negotiated
  // Codecs/PseudoCodecs.
  for (auto& codec : mPrototypeCodecs) {
    bool codecEnabled = codec && codec->mEnabled;
    bool addAllCodecs =
        !remoteIsOffer && mDirection != sdp::kSend && remote.IsSending();
    // 0 is a valid PT but not a dynamic PT so we validate if we see 0 that it
    // is for PCMU
    bool validPT = codecEnabled && (codec->mDefaultPt.compare("0") != 0 ||
                                    (codec->mName.compare("PCMU") == 0));
    if (!codecEnabled || (!addAllCodecs || !validPT)) {
      continue;
    }

    UniquePtr<JsepCodecDescription> clone(codec->Clone());
    // Moves the codec out of mPrototypeCodecs, leaving an empty
    // UniquePtr, so we don't use it again.
    if (codec->mName == "red" || codec->mName == "ulpfec" ||
        codec->mName == "rtx") {
      newPrototypePseudoCodecs.emplace_back(std::move(codec));
      negotiatedPseudoCodecs.emplace_back(std::move(clone));
    } else {
      newPrototypeCodecs.emplace_back(std::move(codec));
      negotiatedCodecs.emplace_back(std::move(clone));
    }
  }

  if (negotiatedCodecs.empty()) {
    // We don't have any real codecs. Clearing so we don't attempt to create a
    // connection signaling only RED and/or ULPFEC.
    negotiatedPseudoCodecs.clear();
  }

  // Put the pseudo codecs at the back
  for (auto& pseudoCodec : negotiatedPseudoCodecs) {
    negotiatedCodecs.emplace_back(std::move(pseudoCodec));
  }

  for (auto& pseudoCodec : newPrototypePseudoCodecs) {
    newPrototypeCodecs.emplace_back(std::move(pseudoCodec));
  }

  // newPrototypeCodecs contains just the negotiated stuff so far. Add the rest.
  for (auto& codec : mPrototypeCodecs) {
    if (codec) {
      newPrototypeCodecs.emplace_back(std::move(codec));
    }
  }

  // Negotiated stuff is up front, so it will take precedence when ensuring
  // there are no duplicate payload types.
  EnsureNoDuplicatePayloadTypes(&newPrototypeCodecs);
  std::swap(newPrototypeCodecs, mPrototypeCodecs);

  // Find the (potential) red codec and ulpfec codec or telephone-event
  JsepVideoCodecDescription* red = nullptr;
  JsepVideoCodecDescription* ulpfec = nullptr;
  JsepAudioCodecDescription* dtmf = nullptr;
  // We can safely cast here since JsepTrack has a MediaType and only codecs
  // that match that MediaType (kAudio or kVideo) are added.
  for (auto& codec : negotiatedCodecs) {
    if (codec->mName == "red") {
      red = static_cast<JsepVideoCodecDescription*>(codec.get());
    } else if (codec->mName == "ulpfec") {
      ulpfec = static_cast<JsepVideoCodecDescription*>(codec.get());
    } else if (codec->mName == "telephone-event") {
      dtmf = static_cast<JsepAudioCodecDescription*>(codec.get());
    }
  }
  // Video FEC is indicated by the existence of the red and ulpfec
  // codecs and not an attribute on the particular video codec (like in
  // a rtcpfb attr). If we see both red and ulpfec codecs, we enable FEC
  // on all the other codecs.
  if (red && ulpfec) {
    for (auto& codec : negotiatedCodecs) {
      if (codec->mName != "red" && codec->mName != "ulpfec") {
        JsepVideoCodecDescription* videoCodec =
            static_cast<JsepVideoCodecDescription*>(codec.get());
        videoCodec->EnableFec(red->mDefaultPt, ulpfec->mDefaultPt,
                              red->mRtxPayloadType);
      }
    }
  }

  // Dtmf support is indicated by the existence of the telephone-event
  // codec, and not an attribute on the particular audio codec (like in a
  // rtcpfb attr). If we see the telephone-event codec, we enabled dtmf
  // support on all the other audio codecs.
  if (dtmf) {
    for (auto& codec : negotiatedCodecs) {
      JsepAudioCodecDescription* audioCodec =
          static_cast<JsepAudioCodecDescription*>(codec.get());
      audioCodec->mDtmfEnabled = true;
    }
  }

  // Make sure strongly preferred codecs are up front, overriding the remote
  // side's preference.
  std::stable_sort(negotiatedCodecs.begin(), negotiatedCodecs.end(),
                   CompareCodec);

  if (!red) {
    // No red, remove ulpfec
    negotiatedCodecs.erase(
        std::remove_if(negotiatedCodecs.begin(), negotiatedCodecs.end(),
                       [ulpfec](const UniquePtr<JsepCodecDescription>& codec) {
                         return codec.get() == ulpfec;
                       }),
        negotiatedCodecs.end());
    // Make sure there's no dangling ptr here
    ulpfec = nullptr;
  }

  return negotiatedCodecs;
}

nsresult JsepTrack::Negotiate(const SdpMediaSection& answer,
                              const SdpMediaSection& remote,
                              const SdpMediaSection& local) {
  std::vector<UniquePtr<JsepCodecDescription>> negotiatedCodecs =
      NegotiateCodecs(remote, &answer != &remote, SomeRef(local));

  if (negotiatedCodecs.empty()) {
    return NS_ERROR_FAILURE;
  }

  UniquePtr<JsepTrackNegotiatedDetails> negotiatedDetails =
      MakeUnique<JsepTrackNegotiatedDetails>();

  CreateEncodings(remote, negotiatedCodecs, negotiatedDetails.get());

  if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) {
    for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) {
      SdpDirectionAttribute::Direction direction = extmapAttr.direction;
      if (&remote == &answer) {
        // Answer is remote, we need to flip this.
        direction = reverse(direction);
      }

      if (direction & mDirection) {
        negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr;
      }
    }
  }

  mInHaveRemote = false;
  mNegotiatedDetails = std::move(negotiatedDetails);
  return NS_OK;
}

// When doing bundle, if all else fails we can try to figure out which m-line a
// given RTP packet belongs to by looking at the payload type field. This only
// works, however, if that payload type appeared in only one m-section.
// We figure that out here.
/* static */
void JsepTrack::SetReceivePayloadTypes(std::vector<JsepTrack*>& tracks,
                                       bool localOffer) {
  // Maps payload types to:
  // - Nothing() temporarily when just initialized
  // - Some(nullptr) when the payload type is registered to multiple tracks
  // - Some(track) when the payload type is unique on track
  std::map<uint16_t, Maybe<JsepTrack*>> payloadTypeToTrackCount;

  for (JsepTrack* track : tracks) {
    track->mUniqueReceivePayloadTypes.clear();
    track->mOtherReceivePayloadTypes.clear();

    if (track->GetMediaType() == SdpMediaSection::kApplication) {
      continue;
    }

    std::vector<uint16_t> payloadTypesForTrack;
    if (!localOffer) {
      auto* details = track->GetNegotiatedDetails();
      if (!details) {
        // Can happen if negotiation fails on a track
        continue;
      }
      track->GetNegotiatedPayloadTypes(&payloadTypesForTrack);
    } else {
      payloadTypesForTrack = track->mReceivePayloadTypes;
    }

    for (uint16_t pt : payloadTypesForTrack) {
      // Note std::map::operator[] inserts a default-initialized value, i.e.
      // Nothing(), if one doesn't exist.
      auto& entry = payloadTypeToTrackCount[pt];
      entry = entry
                  // If unique, i.e. Some(track), set it to Some(nullptr)
                  .andThen([](JsepTrack*) { return Some<JsepTrack*>(nullptr); })
                  // If not unique, i.e. Nothing(), set it to Some(track)
                  .orElse([track] { return Some(track); });
    }
  }

  for (const auto& [key, track] : payloadTypeToTrackCount) {
    const auto pt = AssertedCast<uint8_t>(key);
    JsepTrack* uniqueTrack = *track;
    if (uniqueTrack) {
      uniqueTrack->mUniqueReceivePayloadTypes.push_back(pt);
    }
    for (JsepTrack* track : tracks) {
      if (track != uniqueTrack) {
        track->mOtherReceivePayloadTypes.push_back(pt);
      }
    }
  }
}

}  // namespace mozilla
