Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/media/webrtc/signaling/gtest/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 287 kB image not shown  

Quelle  jsep_session_unittest.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 <iostream>
#include <map>

#include "nss.h"
#include "ssl.h"

#include "mozilla/Preferences.h"
#include "mozilla/RefPtr.h"

#define GTEST_HAS_RTTI 0
#include "gtest/gtest.h"

#include "CodecConfig.h"
#include "PeerConnectionImpl.h"
#include "sdp/SdpMediaSection.h"
#include "sdp/SipccSdpParser.h"
#include "jsep/JsepCodecDescription.h"
#include "jsep/JsepTrack.h"
#include "jsep/JsepSession.h"
#include "jsep/JsepSessionImpl.h"

namespace mozilla {
MOZ_RUNINIT static std::string kAEqualsCandidate("a=candidate:");
const static size_t kNumCandidatesPerComponent = 3;

class JsepSessionTestBase : public ::testing::Test {
 public:
  static void SetUpTestCase() {
    NSS_NoDB_Init(nullptr);
    NSS_SetDomesticPolicy();
  }
};

class FakeUuidGenerator : public mozilla::JsepUuidGenerator {
 public:
  bool Generate(std::string* str) {
    std::ostringstream os;
    os << "FAKE_UUID_" << ++ctr;
    *str = os.str();

    return true;
  }

  mozilla::JsepUuidGenerator* Clone() const {
    return new FakeUuidGenerator(*this);
  }

 private:
  static uint64_t ctr;
};

uint64_t FakeUuidGenerator::ctr = 1000;

class JsepSessionTest : public JsepSessionTestBase,
                        public ::testing::WithParamInterface<std::string> {
 public:
  JsepSessionTest() : mSdpHelper(&mLastError) {
    Preferences::SetCString("media.peerconnection.sdp.parser""legacy");
    Preferences::SetCString("media.peerconnection.sdp.alternate_parse_mode",
                            "never");
    Preferences::SetBool("media.peerconnection.video.use_rtx"true);
    Preferences::SetBool("media.navigator.video.use_transport_cc"true);
    Preferences::SetBool("media.navigator.video.disable_h264_baseline"false);
    Preferences::SetBool("media.webrtc.codec.video.av1.enabled"true);

    mSessionOff =
        MakeUnique<JsepSessionImpl>("Offerer", MakeUnique<FakeUuidGenerator>());
    mSessionAns = MakeUnique<JsepSessionImpl>("Answerer",
                                              MakeUnique<FakeUuidGenerator>());

    EXPECT_EQ(NS_OK, mSessionOff->Init());
    EXPECT_EQ(NS_OK, mSessionAns->Init());

    std::vector<UniquePtr<JsepCodecDescription>> preferredCodecs;
    PeerConnectionImpl::SetupPreferredCodecs(preferredCodecs);
    mSessionOff->SetDefaultCodecs(preferredCodecs);
    mSessionAns->SetDefaultCodecs(preferredCodecs);

    std::vector<PeerConnectionImpl::RtpExtensionHeader> preferredHeaders;
    PeerConnectionImpl::SetupPreferredRtpExtensions(preferredHeaders);

    for (const auto& header : preferredHeaders) {
      mSessionOff->AddRtpExtension(header.mMediaType, header.extensionname,
                                   header.direction);
      mSessionAns->AddRtpExtension(header.mMediaType, header.extensionname,
                                   header.direction);
    }

    mOffererTransport = MakeUnique<TransportData>();
    mAnswererTransport = MakeUnique<TransportData>();

    AddTransportData(*mSessionOff, *mOffererTransport);
    AddTransportData(*mSessionAns, *mAnswererTransport);

    mOffCandidates = MakeUnique<CandidateSet>();
    mAnsCandidates = MakeUnique<CandidateSet>();
  }

  static std::vector<JsepTransceiver>& GetTransceivers(JsepSession& aSession) {
    return aSession.GetTransceivers();
  }

  static const std::vector<JsepTransceiver>& GetTransceivers(
      const JsepSession& aSession) {
    return aSession.GetTransceivers();
  }

 protected:
  struct TransportData {
    std::map<nsCString, std::vector<uint8_t>> mFingerprints;
  };

  void AddDtlsFingerprint(const nsCString& alg, JsepSessionImpl& session,
                          TransportData& tdata) {
    std::vector<uint8_t> fp;
    fp.assign((alg == "sha-1") ? 20 : 32,
              (session.GetName() == "Offerer") ? 0x4f : 0x41);
    session.AddDtlsFingerprint(alg, fp);
    tdata.mFingerprints[alg] = fp;
  }

  void AddTransportData(JsepSessionImpl& session, TransportData& tdata) {
    AddDtlsFingerprint("sha-1"_ns, session, tdata);
    AddDtlsFingerprint("sha-256"_ns, session, tdata);
  }

  void CheckTransceiverInvariants(
      const std::vector<JsepTransceiver>& oldTransceivers,
      const std::vector<JsepTransceiver>& newTransceivers) {
    ASSERT_LE(oldTransceivers.size(), newTransceivers.size());
    std::set<size_t> levels;

    for (const auto& newTransceiver : newTransceivers) {
      if (newTransceiver.HasLevel()) {
        ASSERT_FALSE(levels.count(newTransceiver.GetLevel()))
        << "Two new transceivers are mapped to level "
        << newTransceiver.GetLevel();
        levels.insert(newTransceiver.GetLevel());
      }
    }

    auto last = levels.rbegin();
    if (last != levels.rend()) {
      ASSERT_LE(*last, levels.size())
          << "Max level observed in transceivers was " << *last
          << ", but there are only " << levels.size()
          << " levels in the "
             "transceivers.";
    }

    for (const auto& oldTransceiver : oldTransceivers) {
      if (oldTransceiver.HasLevel()) {
        ASSERT_TRUE(levels.count(oldTransceiver.GetLevel()))
        << "Level " << oldTransceiver.GetLevel()
        << " had a transceiver in the old, but not the new (or, "
           "perhaps this level had more than one transceiver in the "
           "old)";
        levels.erase(oldTransceiver.GetLevel());
      }
    }
  }

  std::string CreateOffer(const Maybe<JsepOfferOptions>& options = Nothing()) {
    std::vector<JsepTransceiver> transceiversBefore =
        GetTransceivers(*mSessionOff);
    JsepOfferOptions defaultOptions;
    const JsepOfferOptions& optionsRef = options ? *options : defaultOptions;
    std::string offer;
    JsepSession::Result result = mSessionOff->CreateOffer(optionsRef, &offer);
    EXPECT_FALSE(result.mError.isSome()) << mSessionOff->GetLastError();

    std::cerr << "OFFER: " << offer << std::endl;

    ValidateTransport(*mOffererTransport, offer, sdp::kOffer);

    if (transceiversBefore.size() != GetTransceivers(*mSessionOff).size()) {
      EXPECT_TRUE(false) << "CreateOffer changed number of transceivers!";
      return offer;
    }

    CheckTransceiverInvariants(transceiversBefore,
                               GetTransceivers(*mSessionOff));

    for (size_t i = 0; i < transceiversBefore.size(); ++i) {
      JsepTransceiver oldTransceiver = transceiversBefore[i];
      JsepTransceiver newTransceiver = GetTransceivers(*mSessionOff)[i];
      EXPECT_EQ(oldTransceiver.IsStopped(), newTransceiver.IsStopped());

      if (oldTransceiver.IsStopped()) {
        if (!newTransceiver.HasLevel()) {
          // Tolerate unmapping of stopped transceivers by removing this
          // difference.
          oldTransceiver.ClearLevel();
        }
      } else if (!oldTransceiver.HasLevel()) {
        EXPECT_TRUE(newTransceiver.HasLevel());
        // Tolerate new mappings.
        oldTransceiver.SetLevel(newTransceiver.GetLevel());
      }

      EXPECT_TRUE(Equals(oldTransceiver, newTransceiver));
    }

    return offer;
  }

  typedef enum { NO_ADDTRACK_MAGIC, ADDTRACK_MAGIC } AddTrackMagic;

  void AddTracks(JsepSessionImpl& side, AddTrackMagic magic = ADDTRACK_MAGIC) {
    // Add tracks.
    if (types.empty()) {
      types = BuildTypes(GetParam());
    }
    AddTracks(side, types, magic);

    // Now, we move datachannel to the end
    auto it =
        std::find(types.begin(), types.end(), SdpMediaSection::kApplication);
    if (it != types.end()) {
      types.erase(it);
      types.push_back(SdpMediaSection::kApplication);
    }
  }

  void AddTracks(JsepSessionImpl& side, const std::string& mediatypes,
                 AddTrackMagic magic = ADDTRACK_MAGIC) {
    AddTracks(side, BuildTypes(mediatypes), magic);
  }

  JsepTrack RemoveTrack(JsepSession& side, size_t index) {
    if (GetTransceivers(side).size() <= index) {
      EXPECT_TRUE(false) << "Index " << index << " out of bounds!";
      return JsepTrack(SdpMediaSection::kAudio, sdp::kSend);
    }

    JsepTransceiver transceiver(GetTransceivers(side)[index]);
    JsepTrack& track = transceiver.mSendTrack;
    EXPECT_FALSE(track.GetStreamIds().empty()) << "No track at index " << index;

    JsepTrack original(track);
    track.ClearStreamIds();
    transceiver.mJsDirection &= SdpDirectionAttribute::Direction::kRecvonly;
    side.SetTransceiver(transceiver);
    return original;
  }

  void SetDirection(JsepSession& side, size_t index,
                    SdpDirectionAttribute::Direction direction) {
    ASSERT_LT(index, GetTransceivers(side).size())
        << "Index " << index << " out of bounds!";

    auto transceiver = GetTransceivers(side)[index];
    transceiver.mJsDirection = direction;
    side.SetTransceiver(transceiver);
  }

  std::vector<SdpMediaSection::MediaType> BuildTypes(
      const std::string& mediatypes) {
    std::vector<SdpMediaSection::MediaType> result;
    size_t ptr = 0;

    for (;;) {
      size_t comma = mediatypes.find(',', ptr);
      std::string chunk = mediatypes.substr(ptr, comma - ptr);

      if (chunk == "audio") {
        result.push_back(SdpMediaSection::kAudio);
      } else if (chunk == "video") {
        result.push_back(SdpMediaSection::kVideo);
      } else if (chunk == "datachannel") {
        result.push_back(SdpMediaSection::kApplication);
      } else {
        MOZ_CRASH();
      }

      if (comma == std::string::npos) break;
      ptr = comma + 1;
    }

    return result;
  }

  void AddTracks(JsepSessionImpl& side,
                 const std::vector<SdpMediaSection::MediaType>& mediatypes,
                 AddTrackMagic magic = ADDTRACK_MAGIC) {
    std::string stream_id;
    std::string track_id;

    ASSERT_TRUE(mUuidGen.Generate(&stream_id));

    AddTracksToStream(side, stream_id, mediatypes, magic);
  }

  void AddTracksToStream(JsepSessionImpl& side, const std::string stream_id,
                         const std::string& mediatypes,
                         AddTrackMagic magic = ADDTRACK_MAGIC) {
    AddTracksToStream(side, stream_id, BuildTypes(mediatypes), magic);
  }

  // A bit of a hack. JsepSessionImpl populates the track-id automatically, just
  // in case, because the w3c spec requires msid to be set even when there's no
  // send track.
  bool IsNull(const JsepTrack& track) const {
    return track.GetStreamIds().empty() &&
           (track.GetMediaType() != SdpMediaSection::MediaType::kApplication);
  }

  void AddTracksToStream(
      JsepSessionImpl& side, const std::string stream_id,
      const std::vector<SdpMediaSection::MediaType>& mediatypes,
      AddTrackMagic magic = ADDTRACK_MAGIC)

  {
    std::string track_id;

    for (auto type : mediatypes) {
      ASSERT_TRUE(mUuidGen.Generate(&track_id));

      Maybe<JsepTransceiver> suitableTransceiver;
      size_t i;
      if (magic == ADDTRACK_MAGIC) {
        // We're simulating addTrack.
        for (i = 0; i < GetTransceivers(side).size(); ++i) {
          auto transceiver = GetTransceivers(side)[i];
          if (transceiver.mSendTrack.GetMediaType() != type) {
            continue;
          }

          if (IsNull(transceiver.mSendTrack) ||
              transceiver.GetMediaType() == SdpMediaSection::kApplication) {
            suitableTransceiver = Some(transceiver);
            break;
          }
        }
      }

      if (!suitableTransceiver) {
        i = GetTransceivers(side).size();
        side.AddTransceiver(JsepTransceiver(type, mUuidGen));
        suitableTransceiver = Some(GetTransceivers(side).back());
        if (magic == ADDTRACK_MAGIC) {
          suitableTransceiver->SetAddTrackMagic();
        }
      }

      std::cerr << "Updating send track for transceiver " << i << std::endl;
      suitableTransceiver->SetOnlyExistsBecauseOfSetRemote(false);
      suitableTransceiver->mJsDirection |=
          SdpDirectionAttribute::Direction::kSendonly;
      suitableTransceiver->mSendTrack.UpdateStreamIds(
          std::vector<std::string>(1, stream_id));
      side.SetTransceiver(*suitableTransceiver);
    }
  }

  bool HasMediaStream(const std::vector<JsepTrack>& tracks) const {
    for (const auto& track : tracks) {
      if (track.GetMediaType() != SdpMediaSection::kApplication) {
        return true;
      }
    }
    return false;
  }

  const std::string GetFirstLocalStreamId(JsepSessionImpl& side) const {
    auto tracks = GetLocalTracks(side);
    return tracks.begin()->GetStreamIds()[0];
  }

  std::vector<JsepTrack> GetLocalTracks(const JsepSession& session) const {
    std::vector<JsepTrack> result;
    for (const auto& transceiver : GetTransceivers(session)) {
      if (!IsNull(transceiver.mSendTrack)) {
        result.push_back(transceiver.mSendTrack);
      }
    }
    return result;
  }

  std::vector<JsepTrack> GetRemoteTracks(const JsepSession& session) const {
    std::vector<JsepTrack> result;
    for (const auto& transceiver : GetTransceivers(session)) {
      if (!IsNull(transceiver.mRecvTrack)) {
        result.push_back(transceiver.mRecvTrack);
      }
    }
    return result;
  }

  JsepTransceiver* GetDatachannelTransceiver(JsepSession& side) {
    for (auto& transceiver : GetTransceivers(side)) {
      if (transceiver.mSendTrack.GetMediaType() ==
          SdpMediaSection::MediaType::kApplication) {
        return &transceiver;
      }
    }

    return nullptr;
  }

  JsepTransceiver* GetNegotiatedTransceiver(JsepSession& side, size_t index) {
    for (auto& transceiver : GetTransceivers(side)) {
      if (transceiver.mSendTrack.GetNegotiatedDetails() ||
          transceiver.mRecvTrack.GetNegotiatedDetails()) {
        if (index) {
          --index;
          continue;
        }

        return &transceiver;
      }
    }

    return nullptr;
  }

  Maybe<JsepTransceiver> GetTransceiverByLevel(
      const std::vector<JsepTransceiver>& transceivers, size_t level) {
    for (auto& transceiver : transceivers) {
      if (transceiver.HasLevel() && transceiver.GetLevel() == level) {
        return Some(transceiver);
      }
    }

    return Nothing();
  }

  Maybe<JsepTransceiver> GetTransceiverByLevel(JsepSession& side,
                                               size_t level) {
    return GetTransceiverByLevel(GetTransceivers(side), level);
  }

  std::vector<std::string> GetMediaStreamIds(
      const std::vector<JsepTrack>& tracks) const {
    std::vector<std::string> ids;
    for (const auto& track : tracks) {
      // data channels don't have msid's
      if (track.GetMediaType() == SdpMediaSection::kApplication) {
        continue;
      }
      ids.insert(ids.end(), track.GetStreamIds().begin(),
                 track.GetStreamIds().end());
    }
    return ids;
  }

  std::vector<std::string> GetLocalMediaStreamIds(JsepSessionImpl& side) const {
    return GetMediaStreamIds(GetLocalTracks(side));
  }

  std::vector<std::string> GetRemoteMediaStreamIds(
      JsepSessionImpl& side) const {
    return GetMediaStreamIds(GetRemoteTracks(side));
  }

  std::vector<std::string> sortUniqueStrVector(
      std::vector<std::string> in) const {
    std::sort(in.begin(), in.end());
    auto it = std::unique(in.begin(), in.end());
    in.resize(std::distance(in.begin(), it));
    return in;
  }

  std::vector<std::string> GetLocalUniqueStreamIds(
      JsepSessionImpl& side) const {
    return sortUniqueStrVector(GetLocalMediaStreamIds(side));
  }

  std::vector<std::string> GetRemoteUniqueStreamIds(
      JsepSessionImpl& side) const {
    return sortUniqueStrVector(GetRemoteMediaStreamIds(side));
  }

  JsepTrack GetTrack(JsepSessionImpl& side, SdpMediaSection::MediaType type,
                     size_t index) const {
    for (const auto& transceiver : GetTransceivers(side)) {
      if (IsNull(transceiver.mSendTrack) ||
          transceiver.mSendTrack.GetMediaType() != type) {
        continue;
      }

      if (index != 0) {
        --index;
        continue;
      }

      return transceiver.mSendTrack;
    }

    return JsepTrack(type, sdp::kSend);
  }

  JsepTrack GetTrackOff(size_t index, SdpMediaSection::MediaType type) {
    return GetTrack(*mSessionOff, type, index);
  }

  JsepTrack GetTrackAns(size_t index, SdpMediaSection::MediaType type) {
    return GetTrack(*mSessionAns, type, index);
  }

  bool Equals(const SdpFingerprintAttributeList::Fingerprint& f1,
              const SdpFingerprintAttributeList::Fingerprint& f2) const {
    if (f1.hashFunc != f2.hashFunc) {
      return false;
    }

    if (f1.fingerprint != f2.fingerprint) {
      return false;
    }

    return true;
  }

  bool Equals(const SdpFingerprintAttributeList& f1,
              const SdpFingerprintAttributeList& f2) const {
    if (f1.mFingerprints.size() != f2.mFingerprints.size()) {
      return false;
    }

    for (size_t i = 0; i < f1.mFingerprints.size(); ++i) {
      if (!Equals(f1.mFingerprints[i], f2.mFingerprints[i])) {
        return false;
      }
    }

    return true;
  }

  bool Equals(const UniquePtr<JsepDtlsTransport>& t1,
              const UniquePtr<JsepDtlsTransport>& t2) const {
    if (!t1 && !t2) {
      return true;
    }

    if (!t1 || !t2) {
      return false;
    }

    if (!Equals(t1->GetFingerprints(), t2->GetFingerprints())) {
      return false;
    }

    if (t1->GetRole() != t2->GetRole()) {
      return false;
    }

    return true;
  }

  bool Equals(const UniquePtr<JsepIceTransport>& t1,
              const UniquePtr<JsepIceTransport>& t2) const {
    if (!t1 && !t2) {
      return true;
    }

    if (!t1 || !t2) {
      return false;
    }

    if (t1->GetUfrag() != t2->GetUfrag()) {
      return false;
    }

    if (t1->GetPassword() != t2->GetPassword()) {
      return false;
    }

    return true;
  }

  bool Equals(const JsepTransport& t1, const JsepTransport& t2) const {
    if (t1.mTransportId != t2.mTransportId) {
      std::cerr << "Transport id differs: " << t1.mTransportId << " vs "
                << t2.mTransportId << std::endl;
      return false;
    }

    if (t1.mComponents != t2.mComponents) {
      std::cerr << "Component count differs" << std::endl;
      return false;
    }

    if (!Equals(t1.mIce, t2.mIce)) {
      std::cerr << "ICE differs" << std::endl;
      return false;
    }

    return true;
  }

  bool Equals(const JsepTrack& t1, const JsepTrack& t2) const {
    if (t1.GetMediaType() != t2.GetMediaType()) {
      return false;
    }

    if (t1.GetDirection() != t2.GetDirection()) {
      return false;
    }

    if (t1.GetStreamIds() != t2.GetStreamIds()) {
      return false;
    }

    if (t1.GetActive() != t2.GetActive()) {
      return false;
    }

    if (t1.GetCNAME() != t2.GetCNAME()) {
      return false;
    }

    if (t1.GetSsrcs() != t2.GetSsrcs()) {
      return false;
    }

    return true;
  }

  bool Equals(const JsepTransceiver& p1, const JsepTransceiver& p2) const {
    if (p1.HasLevel() != p2.HasLevel()) {
      std::cerr << "One transceiver has a level, the other doesn't"
                << std::endl;
      return false;
    }

    if (p1.HasLevel() && (p1.GetLevel() != p2.GetLevel())) {
      std::cerr << "Level differs: " << p1.GetLevel() << " vs " << p2.GetLevel()
                << std::endl;
      return false;
    }

    // We don't check things like BundleLevel(), since that can change without
    // any changes to the transport, which is what we're really interested in.

    if (p1.IsStopped() != p2.IsStopped()) {
      std::cerr << "One transceiver is stopped, the other is not" << std::endl;
      return false;
    }

    if (p1.IsAssociated() != p2.IsAssociated()) {
      std::cerr << "One transceiver has a mid, the other doesn't" << std::endl;
      return false;
    }

    if (p1.IsAssociated() && (p1.GetMid() != p2.GetMid())) {
      std::cerr << "mid differs: " << p1.GetMid() << " vs " << p2.GetMid()
                << std::endl;
      return false;
    }

    if (!Equals(p1.mSendTrack, p2.mSendTrack)) {
      std::cerr << "Send track differs" << std::endl;
      return false;
    }

    if (!Equals(p1.mRecvTrack, p2.mRecvTrack)) {
      std::cerr << "Receive track differs" << std::endl;
      return false;
    }

    if (!Equals(p1.mTransport, p2.mTransport)) {
      std::cerr << "Transport differs" << std::endl;
      return false;
    }

    return true;
  }

  bool Equals(const std::vector<JsepTransceiver>& t1,
              const std::vector<JsepTransceiver>& t2) const {
    if (t1.size() != t2.size()) {
      std::cerr << "Size differs: t1.size = " << t1.size()
                << ", t2.size = " << t2.size() << std::endl;
      return false;
    }

    for (size_t i = 0; i < t1.size(); ++i) {
      if (!Equals(t1[i], t2[i])) {
        return false;
      }
    }

    return true;
  }

  size_t GetTrackCount(JsepSessionImpl& side,
                       SdpMediaSection::MediaType type) const {
    size_t result = 0;
    for (const auto& track : GetLocalTracks(side)) {
      if (track.GetMediaType() == type) {
        ++result;
      }
    }
    return result;
  }

  UniquePtr<Sdp> GetParsedLocalDescription(const JsepSessionImpl& side) const {
    return Parse(side.GetLocalDescription(kJsepDescriptionCurrent));
  }

  SdpMediaSection* GetMsection(Sdp& sdp, SdpMediaSection::MediaType type,
                               size_t index) const {
    for (size_t i = 0; i < sdp.GetMediaSectionCount(); ++i) {
      auto& msection = sdp.GetMediaSection(i);
      if (msection.GetMediaType() != type) {
        continue;
      }

      if (index) {
        --index;
        continue;
      }

      return &msection;
    }

    return nullptr;
  }

  void SetPayloadTypeNumber(JsepSession& session, const std::string& codecName,
                            const std::string& payloadType) {
    for (auto& codec : session.Codecs()) {
      if (codec->mName == codecName) {
        codec->mDefaultPt = payloadType;
      }
    }
  }

  void SetCodecEnabled(JsepSession& session, const std::string& codecName,
                       bool enabled) {
    for (auto& codec : session.Codecs()) {
      if (codec->mName == codecName) {
        codec->mEnabled = enabled;
      }
    }
  }

  void EnsureNegotiationFailure(SdpMediaSection::MediaType type,
                                const std::string& codecName) {
    for (auto& codec : mSessionOff->Codecs()) {
      if (codec->Type() == type && codec->mName != codecName) {
        codec->mEnabled = false;
      }
    }

    for (auto& codec : mSessionAns->Codecs()) {
      if (codec->Type() == type && codec->mName == codecName) {
        codec->mEnabled = false;
      }
    }
  }

  std::string CreateAnswer() {
    std::vector<JsepTransceiver> transceiversBefore =
        GetTransceivers(*mSessionAns);

    JsepAnswerOptions options;
    std::string answer;

    JsepSession::Result result = mSessionAns->CreateAnswer(options, &answer);
    EXPECT_FALSE(result.mError.isSome());

    std::cerr << "ANSWER: " << answer << std::endl;

    ValidateTransport(*mAnswererTransport, answer, sdp::kAnswer);
    CheckTransceiverInvariants(transceiversBefore,
                               GetTransceivers(*mSessionAns));

    return answer;
  }

  static const uint32_t NO_CHECKS = 0;
  static const uint32_t CHECK_SUCCESS = 1;
  static const uint32_t CHECK_TRACKS = 1 << 2;
  static const uint32_t ALL_CHECKS = CHECK_SUCCESS | CHECK_TRACKS;

  void OfferAnswer(uint32_t checkFlags = ALL_CHECKS,
                   const Maybe<JsepOfferOptions>& options = Nothing()) {
    std::string offer = CreateOffer(options);
    SetLocalOffer(offer, checkFlags);
    SetRemoteOffer(offer, checkFlags);

    std::string answer = CreateAnswer();
    SetLocalAnswer(answer, checkFlags);
    SetRemoteAnswer(answer, checkFlags);
  }

  void SetLocalOffer(const std::string& offer,
                     uint32_t checkFlags = ALL_CHECKS) {
    std::vector<JsepTransceiver> transceiversBefore =
        GetTransceivers(*mSessionOff);

    JsepSession::Result result =
        mSessionOff->SetLocalDescription(kJsepSdpOffer, offer);

    CheckTransceiverInvariants(transceiversBefore,
                               GetTransceivers(*mSessionOff));

    if (checkFlags & CHECK_SUCCESS) {
      ASSERT_FALSE(result.mError.isSome());
    }

    if (checkFlags & CHECK_TRACKS) {
      // This assumes no recvonly or inactive transceivers.
      ASSERT_EQ(types.size(), GetTransceivers(*mSessionOff).size());
      for (const auto& transceiver : GetTransceivers(*mSessionOff)) {
        if (!transceiver.HasLevel()) {
          continue;
        }
        const auto& track(transceiver.mSendTrack);
        size_t level = transceiver.GetLevel();
        ASSERT_FALSE(IsNull(track));
        ASSERT_EQ(types[level], track.GetMediaType());
        if (track.GetMediaType() != SdpMediaSection::kApplication) {
          std::string msidAttr("a=msid:");
          msidAttr += track.GetStreamIds()[0];
          ASSERT_NE(std::string::npos, offer.find(msidAttr))
              << "Did not find " << msidAttr << " in offer";
        }
      }
      if (types.size() == 1 && types[0] == SdpMediaSection::kApplication) {
        ASSERT_EQ(std::string::npos, offer.find("a=ssrc"))
            << "Data channel should not contain SSRC";
      }
    }
  }

  void SetRemoteOffer(const std::string& offer,
                      uint32_t checkFlags = ALL_CHECKS) {
    std::vector<JsepTransceiver> transceiversBefore =
        GetTransceivers(*mSessionAns);

    JsepSession::Result result =
        mSessionAns->SetRemoteDescription(kJsepSdpOffer, offer);

    CheckTransceiverInvariants(transceiversBefore,
                               GetTransceivers(*mSessionAns));

    if (checkFlags & CHECK_SUCCESS) {
      ASSERT_FALSE(result.mError.isSome());
    }

    if (checkFlags & CHECK_TRACKS) {
      // This assumes no recvonly or inactive transceivers.
      ASSERT_EQ(types.size(), GetTransceivers(*mSessionAns).size());
      for (const auto& transceiver : GetTransceivers(*mSessionAns)) {
        if (!transceiver.HasLevel()) {
          continue;
        }
        const auto& track(transceiver.mRecvTrack);
        size_t level = transceiver.GetLevel();
        ASSERT_FALSE(IsNull(track));
        ASSERT_EQ(types[level], track.GetMediaType());
        if (track.GetMediaType() != SdpMediaSection::kApplication) {
          std::string msidAttr("a=msid:");
          msidAttr += track.GetStreamIds()[0];
          // Track id will not match, and will eventually not be present at all
          ASSERT_NE(std::string::npos, offer.find(msidAttr))
              << "Did not find " << msidAttr << " in offer";
        }
      }
    }
  }

  void SetLocalAnswer(const std::string& answer,
                      uint32_t checkFlags = ALL_CHECKS) {
    std::vector<JsepTransceiver> transceiversBefore =
        GetTransceivers(*mSessionAns);

    JsepSession::Result result =
        mSessionAns->SetLocalDescription(kJsepSdpAnswer, answer);
    if (checkFlags & CHECK_SUCCESS) {
      ASSERT_FALSE(result.mError.isSome());
    }

    CheckTransceiverInvariants(transceiversBefore,
                               GetTransceivers(*mSessionAns));

    if (checkFlags & CHECK_TRACKS) {
      // Verify that the right stuff is in the tracks.
      ASSERT_EQ(types.size(), GetTransceivers(*mSessionAns).size());
      for (const auto& transceiver : GetTransceivers(*mSessionAns)) {
        if (!transceiver.HasLevel()) {
          continue;
        }
        const auto& sendTrack(transceiver.mSendTrack);
        const auto& recvTrack(transceiver.mRecvTrack);
        size_t level = transceiver.GetLevel();
        ASSERT_FALSE(IsNull(sendTrack));
        ASSERT_EQ(types[level], sendTrack.GetMediaType());
        // These might have been in the SDP, or might have been randomly
        // chosen by JsepSessionImpl
        ASSERT_FALSE(IsNull(recvTrack));
        ASSERT_EQ(types[level], recvTrack.GetMediaType());

        if (recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
          std::string msidAttr("a=msid:");
          msidAttr += sendTrack.GetStreamIds()[0];
          ASSERT_NE(std::string::npos, answer.find(msidAttr))
              << "Did not find " << msidAttr << " in answer";
        }
      }
      if (types.size() == 1 && types[0] == SdpMediaSection::kApplication) {
        ASSERT_EQ(std::string::npos, answer.find("a=ssrc"))
            << "Data channel should not contain SSRC";
      }
    }
    std::cerr << "Answerer transceivers:" << std::endl;
    DumpTransceivers(*mSessionAns);
  }

  void SetRemoteAnswer(const std::string& answer,
                       uint32_t checkFlags = ALL_CHECKS) {
    std::vector<JsepTransceiver> transceiversBefore =
        GetTransceivers(*mSessionOff);

    JsepSession::Result result =
        mSessionOff->SetRemoteDescription(kJsepSdpAnswer, answer);
    if (checkFlags & CHECK_SUCCESS) {
      ASSERT_FALSE(result.mError.isSome());
    }

    CheckTransceiverInvariants(transceiversBefore,
                               GetTransceivers(*mSessionOff));

    if (checkFlags & CHECK_TRACKS) {
      // Verify that the right stuff is in the tracks.
      ASSERT_EQ(types.size(), GetTransceivers(*mSessionOff).size());
      for (const auto& transceiver : GetTransceivers(*mSessionOff)) {
        if (!transceiver.HasLevel()) {
          continue;
        }
        const auto& sendTrack(transceiver.mSendTrack);
        const auto& recvTrack(transceiver.mRecvTrack);
        size_t level = transceiver.GetLevel();
        ASSERT_FALSE(IsNull(sendTrack));
        ASSERT_EQ(types[level], sendTrack.GetMediaType());
        // These might have been in the SDP, or might have been randomly
        // chosen by JsepSessionImpl
        ASSERT_FALSE(IsNull(recvTrack));
        ASSERT_EQ(types[level], recvTrack.GetMediaType());

        if (recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
          std::string msidAttr("a=msid:");
          msidAttr += recvTrack.GetStreamIds()[0];
          // Track id will not match, and will eventually not be present at all
          ASSERT_NE(std::string::npos, answer.find(msidAttr))
              << "Did not find " << msidAttr << " in answer";
        }
      }
    }
    std::cerr << "Offerer transceivers:" << std::endl;
    DumpTransceivers(*mSessionOff);
  }

  std::string GetTransportId(const JsepSession& session, size_t level) {
    for (const auto& transceiver : GetTransceivers(session)) {
      if (transceiver.HasLevel() && transceiver.GetLevel() == level) {
        return transceiver.mTransport.mTransportId;
      }
    }
    return std::string();
  }

  typedef enum { RTP = 1, RTCP = 2 } ComponentType;

  class CandidateSet {
   public:
    CandidateSet() {}

    void Gather(JsepSession& session, ComponentType maxComponent = RTCP) {
      for (const auto& transceiver : GetTransceivers(session)) {
        if (transceiver.HasOwnTransport()) {
          Gather(session, transceiver.mTransport.mTransportId, RTP);
          if (transceiver.mTransport.mComponents > 1) {
            Gather(session, transceiver.mTransport.mTransportId, RTCP);
          }
        }
      }
      FinishGathering(session);
    }

    void Gather(JsepSession& session, const std::string& transportId,
                ComponentType component) {
      static uint16_t port = 1000;
      std::vector<std::string> candidates;

      for (size_t i = 0; i < kNumCandidatesPerComponent; ++i) {
        ++port;
        std::ostringstream candidate;
        candidate << "0 " << static_cast<uint16_t>(component)
                  << " UDP 9999 192.168.0.1 " << port << " typ host";
        std::string mid;
        uint16_t level = 0;
        bool skipped;
        session.AddLocalIceCandidate(kAEqualsCandidate + candidate.str(),
                                     transportId, "", &level, &mid, &skipped);
        if (!skipped) {
          mCandidatesToTrickle.push_back(std::tuple<Level, Mid, Candidate>(
              level, mid, kAEqualsCandidate + candidate.str()));
          candidates.push_back(candidate.str());
        }
      }

      // Stomp existing candidates
      mCandidates[transportId][component] = candidates;

      // Stomp existing defaults
      mDefaultCandidates[transportId][component] =
          std::make_pair("192.168.0.1", port);
      session.UpdateDefaultCandidate(
          mDefaultCandidates[transportId][RTP].first,
          mDefaultCandidates[transportId][RTP].second,
          // Will be empty string if not present, which is how we indicate
          // that there is no default for RTCP
          mDefaultCandidates[transportId][RTCP].first,
          mDefaultCandidates[transportId][RTCP].second, transportId);
    }

    void FinishGathering(JsepSession& session) const {
      // Copy so we can be terse and use []
      for (auto idAndCandidates : mDefaultCandidates) {
        ASSERT_EQ(1U, idAndCandidates.second.count(RTP));
        // do a final UpdateDefaultCandidate here in case candidates were
        // cleared during renegotiation.
        session.UpdateDefaultCandidate(
            idAndCandidates.second[RTP].first,
            idAndCandidates.second[RTP].second,
            // Will be empty string if not present, which is how we indicate
            // that there is no default for RTCP
            idAndCandidates.second[RTCP].first,
            idAndCandidates.second[RTCP].second, idAndCandidates.first);
        std::string mid;
        uint16_t level = 0;
        bool skipped;
        session.AddLocalIceCandidate("", idAndCandidates.first, "", &level,
                                     &mid, &skipped);
      }
    }

    void Trickle(JsepSession& session) {
      std::string transportId;
      for (const auto& levelMidAndCandidate : mCandidatesToTrickle) {
        auto [level, mid, candidate] = levelMidAndCandidate;
        std::cerr << "trickling candidate: " << candidate << " level: " << level
                  << " mid: " << mid << std::endl;
        Maybe<unsigned long> lev = Some(level);
        session.AddRemoteIceCandidate(candidate, mid, lev, "", &transportId);
      }
      session.AddRemoteIceCandidate("""", Maybe<uint16_t>(), "",
                                    &transportId);
      mCandidatesToTrickle.clear();
    }

    void CheckRtpCandidates(bool expectRtpCandidates,
                            const SdpMediaSection& msection,
                            const std::string& transportId,
                            const std::string& context) const {
      auto& attrs = msection.GetAttributeList();

      ASSERT_EQ(expectRtpCandidates,
                attrs.HasAttribute(SdpAttribute::kCandidateAttribute))
          << context << " (level " << msection.GetLevel() << ")";

      if (expectRtpCandidates) {
        // Copy so we can be terse and use []
        auto expectedCandidates = mCandidates;
        ASSERT_LE(kNumCandidatesPerComponent,
                  expectedCandidates[transportId][RTP].size());

        auto& candidates = attrs.GetCandidate();
        ASSERT_LE(kNumCandidatesPerComponent, candidates.size())
            << context << " (level " << msection.GetLevel() << ")";
        for (size_t i = 0; i < kNumCandidatesPerComponent; ++i) {
          ASSERT_EQ(expectedCandidates[transportId][RTP][i], candidates[i])
              << context << " (level " << msection.GetLevel() << ")";
        }
      }
    }

    void CheckRtcpCandidates(bool expectRtcpCandidates,
                             const SdpMediaSection& msection,
                             const std::string& transportId,
                             const std::string& context) const {
      auto& attrs = msection.GetAttributeList();

      if (expectRtcpCandidates) {
        // Copy so we can be terse and use []
        auto expectedCandidates = mCandidates;
        ASSERT_LE(kNumCandidatesPerComponent,
                  expectedCandidates[transportId][RTCP].size());

        ASSERT_TRUE(attrs.HasAttribute(SdpAttribute::kCandidateAttribute))
        << context << " (level " << msection.GetLevel() << ")";
        auto& candidates = attrs.GetCandidate();
        ASSERT_EQ(kNumCandidatesPerComponent * 2, candidates.size())
            << context << " (level " << msection.GetLevel() << ")";
        for (size_t i = 0; i < kNumCandidatesPerComponent; ++i) {
          ASSERT_EQ(expectedCandidates[transportId][RTCP][i],
                    candidates[i + kNumCandidatesPerComponent])
              << context << " (level " << msection.GetLevel() << ")";
        }
      }
    }

    void CheckDefaultRtpCandidate(bool expectDefault,
                                  const SdpMediaSection& msection,
                                  const std::string& transportId,
                                  const std::string& context) const {
      Address expectedAddress = "0.0.0.0";
      Port expectedPort = 9U;

      if (expectDefault) {
        // Copy so we can be terse and use []
        auto defaultCandidates = mDefaultCandidates;
        expectedAddress = defaultCandidates[transportId][RTP].first;
        expectedPort = defaultCandidates[transportId][RTP].second;
      }

      // if bundle-only attribute is present, expect port 0
      const SdpAttributeList& attrs = msection.GetAttributeList();
      if (attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute)) {
        expectedPort = 0U;
      }

      ASSERT_EQ(expectedAddress, msection.GetConnection().GetAddress())
          << context << " (level " << msection.GetLevel() << ")";
      ASSERT_EQ(expectedPort, msection.GetPort())
          << context << " (level " << msection.GetLevel() << ")";
    }

    void CheckDefaultRtcpCandidate(bool expectDefault,
                                   const SdpMediaSection& msection,
                                   const std::string& transportId,
                                   const std::string& context) const {
      if (expectDefault) {
        // Copy so we can be terse and use []
        auto defaultCandidates = mDefaultCandidates;
        ASSERT_TRUE(msection.GetAttributeList().HasAttribute(
            SdpAttribute::kRtcpAttribute))
        << context << " (level " << msection.GetLevel() << ")";
        auto& rtcpAttr = msection.GetAttributeList().GetRtcp();
        ASSERT_EQ(defaultCandidates[transportId][RTCP].second, rtcpAttr.mPort)
            << context << " (level " << msection.GetLevel() << ")";
        ASSERT_EQ(sdp::kInternet, rtcpAttr.mNetType)
            << context << " (level " << msection.GetLevel() << ")";
        ASSERT_EQ(sdp::kIPv4, rtcpAttr.mAddrType)
            << context << " (level " << msection.GetLevel() << ")";
        ASSERT_EQ(defaultCandidates[transportId][RTCP].first, rtcpAttr.mAddress)
            << context << " (level " << msection.GetLevel() << ")";
      } else {
        ASSERT_FALSE(msection.GetAttributeList().HasAttribute(
            SdpAttribute::kRtcpAttribute))
        << context << " (level " << msection.GetLevel() << ")";
      }
    }

   private:
    typedef size_t Level;
    typedef std::string TransportId;
    typedef std::string Mid;
    typedef std::string Candidate;
    typedef std::string Address;
    typedef uint16_t Port;
    // Default candidates are put into the m-line, c-line, and rtcp
    // attribute for endpoints that don't support ICE.
    std::map<TransportId, std::map<ComponentType, std::pair<Address, Port>>>
        mDefaultCandidates;
    std::map<TransportId, std::map<ComponentType, std::vector<Candidate>>>
        mCandidates;
    // Level/mid/candidate tuples that need to be trickled
    std::vector<std::tuple<Level, Mid, Candidate>> mCandidatesToTrickle;
  };

  // For streaming parse errors
  std::string GetParseErrors(
      const UniquePtr<SdpParser::Results>& results) const {
    std::stringstream output;
    auto errors = std::move(results->Errors());
    for (auto error : errors) {
      output << error.first << ": " << error.second << std::endl;
    }
    return output.str();
  }

  void CheckEndOfCandidates(bool expectEoc, const SdpMediaSection& msection,
                            const std::string& context) {
    if (expectEoc) {
      ASSERT_TRUE(msection.GetAttributeList().HasAttribute(
          SdpAttribute::kEndOfCandidatesAttribute))
      << context << " (level " << msection.GetLevel() << ")";
    } else {
      ASSERT_FALSE(msection.GetAttributeList().HasAttribute(
          SdpAttribute::kEndOfCandidatesAttribute))
      << context << " (level " << msection.GetLevel() << ")";
    }
  }

  void CheckTransceiversAreBundled(const JsepSession& session,
                                   const std::string& context) {
    for (const auto& transceiver : GetTransceivers(session)) {
      ASSERT_TRUE(transceiver.HasBundleLevel())
      << context;
      ASSERT_EQ(0U, transceiver.BundleLevel()) << context;
      ASSERT_NE("", transceiver.mTransport.mTransportId);
    }
  }

  void DisableMsid(std::string* sdp) const {
    while (true) {
      size_t pos = sdp->find("a=msid");
      if (pos == std::string::npos) {
        break;
      }
      (*sdp)[pos + 2] = 'X';  // garble, a=Xsid
    }
  }

  void DisableBundle(std::string* sdp) const {
    size_t pos = sdp->find("a=group:BUNDLE");
    ASSERT_NE(std::string::npos, pos);
    (*sdp)[pos + 11] = 'G';  // garble, a=group:BUNGLE
  }

  void DisableMsection(std::string* sdp, size_t level) const {
    UniquePtr<Sdp> parsed(Parse(*sdp));
    ASSERT_TRUE(parsed.get());
    ASSERT_LT(level, parsed->GetMediaSectionCount());
    SdpHelper::DisableMsection(parsed.get(), &parsed->GetMediaSection(level));
    (*sdp) = parsed->ToString();
  }

  void CopyTransportAttributes(std::string* sdp, size_t src_level,
                               size_t dst_level) {
    UniquePtr<Sdp> parsed(Parse(*sdp));
    ASSERT_TRUE(parsed.get());
    ASSERT_LT(src_level, parsed->GetMediaSectionCount());
    ASSERT_LT(dst_level, parsed->GetMediaSectionCount());
    nsresult rv =
        mSdpHelper.CopyTransportParams(2, parsed->GetMediaSection(src_level),
                                       &parsed->GetMediaSection(dst_level));
    ASSERT_EQ(NS_OK, rv);
    (*sdp) = parsed->ToString();
  }

  void ReplaceInSdp(std::string* sdp, const char* searchStr,
                    const char* replaceStr) const {
    if (searchStr[0] == '\0'return;
    size_t pos = 0;
    while ((pos = sdp->find(searchStr, pos)) != std::string::npos) {
      sdp->replace(pos, strlen(searchStr), replaceStr);
      pos += strlen(replaceStr);
    }
  }

  void ValidateDisabledMSection(const SdpMediaSection* msection) {
    ASSERT_EQ(1U, msection->GetFormats().size());

    auto& attrs = msection->GetAttributeList();
    ASSERT_TRUE(attrs.HasAttribute(SdpAttribute::kMidAttribute));
    ASSERT_TRUE(attrs.HasAttribute(SdpAttribute::kDirectionAttribute));
    ASSERT_FALSE(attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute));
    ASSERT_EQ(SdpDirectionAttribute::kInactive,
              msection->GetDirectionAttribute().mValue);
    ASSERT_EQ(3U, attrs.Count());
    if (msection->GetMediaType() == SdpMediaSection::kAudio) {
      ASSERT_EQ("0", msection->GetFormats()[0]);
      const SdpRtpmapAttributeList::Rtpmap* rtpmap(msection->FindRtpmap("0"));
      ASSERT_TRUE(rtpmap);
      ASSERT_EQ("0", rtpmap->pt);
      ASSERT_EQ("PCMU", rtpmap->name);
    } else if (msection->GetMediaType() == SdpMediaSection::kVideo) {
      ASSERT_EQ("120", msection->GetFormats()[0]);
      const SdpRtpmapAttributeList::Rtpmap* rtpmap(msection->FindRtpmap("120"));
      ASSERT_TRUE(rtpmap);
      ASSERT_EQ("120", rtpmap->pt);
      ASSERT_EQ("VP8", rtpmap->name);
    } else if (msection->GetMediaType() == SdpMediaSection::kApplication) {
      if (msection->GetProtocol() == SdpMediaSection::kUdpDtlsSctp ||
          msection->GetProtocol() == SdpMediaSection::kTcpDtlsSctp) {
        // draft 21 format
        ASSERT_EQ("webrtc-datachannel", msection->GetFormats()[0]);
        ASSERT_FALSE(msection->GetSctpmap());
        ASSERT_EQ(0U, msection->GetSctpPort());
      } else {
        // old draft 05 format
        ASSERT_EQ("0", msection->GetFormats()[0]);
        const SdpSctpmapAttributeList::Sctpmap* sctpmap(msection->GetSctpmap());
        ASSERT_TRUE(sctpmap);
        ASSERT_EQ("0", sctpmap->pt);
        ASSERT_EQ("rejected", sctpmap->name);
        ASSERT_EQ(0U, sctpmap->streams);
      }
    } else {
      // Not that we would have any test which tests this...
      ASSERT_EQ("19", msection->GetFormats()[0]);
      const SdpRtpmapAttributeList::Rtpmap* rtpmap(msection->FindRtpmap("19"));
      ASSERT_TRUE(rtpmap);
      ASSERT_EQ("19", rtpmap->pt);
      ASSERT_EQ("reserved", rtpmap->name);
    }

    ASSERT_FALSE(msection->GetAttributeList().HasAttribute(
        SdpAttribute::kMsidAttribute));
  }

  void ValidateSetupAttribute(const JsepSessionImpl& side,
                              const SdpSetupAttribute::Role expectedRole) {
    auto sdp = GetParsedLocalDescription(side);
    for (size_t i = 0; sdp && i < sdp->GetMediaSectionCount(); ++i) {
      if (sdp->GetMediaSection(i).GetAttributeList().HasAttribute(
              SdpAttribute::kSetupAttribute)) {
        auto role = sdp->GetMediaSection(i).GetAttributeList().GetSetup().mRole;
        ASSERT_EQ(expectedRole, role);
      }
    }
  }

  void DumpTrack(const JsepTrack& track) {
    const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
    std::cerr << " type=" << track.GetMediaType() << std::endl;
    if (!details) {
      std::cerr << " not negotiated" << std::endl;
      return;
    }
    std::cerr << " encodings=" << std::endl;
    for (size_t i = 0; i < details->GetEncodingCount(); ++i) {
      const JsepTrackEncoding& encoding = details->GetEncoding(i);
      std::cerr << " id=" << encoding.mRid << std::endl;
      for (const auto& codec : encoding.GetCodecs()) {
        std::cerr << " " << codec->mName << " enabled("
                  << (codec->mEnabled ? "yes" : "no") << ")";
        if (track.GetMediaType() == SdpMediaSection::kAudio) {
          const JsepAudioCodecDescription* audioCodec =
              static_cast<const JsepAudioCodecDescription*>(codec.get());
          std::cerr << " dtmf(" << (audioCodec->mDtmfEnabled ? "yes" : "no")
                    << ")";
        }
        if (track.GetMediaType() == SdpMediaSection::kVideo) {
          const JsepVideoCodecDescription* videoCodec =
              static_cast<const JsepVideoCodecDescription*>(codec.get());
          std::cerr << " rtx("
                    << (videoCodec->mRtxEnabled ? videoCodec->mRtxPayloadType
                                                : "no")
                    << ")";
        }
        std::cerr << std::endl;
      }
    }
  }

  void DumpTransport(const JsepTransport& transport) {
    std::cerr << " id=" << transport.mTransportId << std::endl;
    std::cerr << " components=" << transport.mComponents << std::endl;
  }

  void DumpTransceivers(const JsepSessionImpl& session) {
    for (const auto& transceiver : GetTransceivers(session)) {
      std::cerr << "Transceiver ";
      if (transceiver.HasLevel()) {
        std::cerr << transceiver.GetLevel() << std::endl;
      } else {
        std::cerr << "" << std::endl;
      }
      if (transceiver.HasBundleLevel()) {
        std::cerr << "(bundle level is " << transceiver.BundleLevel() << ")"
                  << std::endl;
      }
      if (!IsNull(transceiver.mSendTrack)) {
        std::cerr << "Sending-->" << std::endl;
        DumpTrack(transceiver.mSendTrack);
      }
      if (!IsNull(transceiver.mRecvTrack)) {
        std::cerr << "Receiving-->" << std::endl;
        DumpTrack(transceiver.mRecvTrack);
      }
      std::cerr << "Transport-->" << std::endl;
      DumpTransport(transceiver.mTransport);
    }
  }

  UniquePtr<Sdp> Parse(const std::string& sdp) const {
    SipccSdpParser parser;
    auto results = parser.Parse(sdp);
    UniquePtr<Sdp> parsed = std::move(results->Sdp());
    EXPECT_TRUE(parsed.get()) << "Should have valid SDP" << std::endl
                              << "Errors were: " << GetParseErrors(results);
    return parsed;
  }

  std::string SetExtmap(const std::string& aSdp, const std::string& aUri,
                        uint16_t aId, uint16_t* aOldId = nullptr) {
    UniquePtr<Sdp> munge(Parse(aSdp));
    for (size_t i = 0; i < munge->GetMediaSectionCount(); ++i) {
      auto& attrs = munge->GetMediaSection(i).GetAttributeList();
      if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
        auto extmap = attrs.GetExtmap();
        for (auto it = extmap.mExtmaps.begin(); it != extmap.mExtmaps.end();
             ++it) {
          if (it->extensionname == aUri) {
            if (aOldId) {
              *aOldId = it->entry;
            }

            if (aId) {
              it->entry = aId;
            } else {
              extmap.mExtmaps.erase(it);
            }
            break;
          }
        }
        attrs.SetAttribute(extmap.Clone());
      }
    }
    return munge->ToString();
  }

  uint16_t GetExtmap(const std::string& aSdp, const std::string& aUri) {
    UniquePtr<Sdp> parsed(Parse(aSdp));
    for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
      auto& attrs = parsed->GetMediaSection(i).GetAttributeList();
      if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
        auto extmap = attrs.GetExtmap();
        for (auto& ext : extmap.mExtmaps) {
          if (ext.extensionname == aUri) {
            return ext.entry;
          }
        }
      }
    }
    return 0;
  }

  void SwapOfferAnswerRoles() {
    mSessionOff.swap(mSessionAns);
    mOffCandidates.swap(mAnsCandidates);
    mOffererTransport.swap(mAnswererTransport);
  }

  UniquePtr<JsepSessionImpl> mSessionOff;
  UniquePtr<CandidateSet> mOffCandidates;
  UniquePtr<JsepSessionImpl> mSessionAns;
  UniquePtr<CandidateSet> mAnsCandidates;

  std::vector<SdpMediaSection::MediaType> types;
  std::vector<std::pair<std::string, uint16_t>> mGatheredCandidates;

  FakeUuidGenerator mUuidGen;

 private:
  void ValidateTransport(TransportData& source, const std::string& sdp_str,
                         sdp::SdpType type) {
    UniquePtr<Sdp> sdp(Parse(sdp_str));
    ASSERT_TRUE(!!sdp);
    size_t num_m_sections = sdp->GetMediaSectionCount();
    for (size_t i = 0; i < num_m_sections; ++i) {
      auto& msection = sdp->GetMediaSection(i);

      if (msection.GetMediaType() == SdpMediaSection::kApplication) {
        if (!(msection.GetProtocol() == SdpMediaSection::kUdpDtlsSctp ||
              msection.GetProtocol() == SdpMediaSection::kTcpDtlsSctp)) {
          // old draft 05 format
          ASSERT_EQ(SdpMediaSection::kDtlsSctp, msection.GetProtocol());
        }
      } else {
        ASSERT_EQ(SdpMediaSection::kUdpTlsRtpSavpf, msection.GetProtocol());
      }

      const SdpAttributeList& attrs = msection.GetAttributeList();
      bool bundle_only = attrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute);

      // port 0 only means disabled when the bundle-only attribute is missing
      if (!bundle_only && msection.GetPort() == 0) {
        ValidateDisabledMSection(&msection);
        continue;
      }
      if (mSdpHelper.OwnsTransport(*sdp, i, type)) {
        const SdpAttributeList& attrs = msection.GetAttributeList();

        ASSERT_FALSE(attrs.GetIceUfrag().empty());
        ASSERT_FALSE(attrs.GetIcePwd().empty());
        const SdpFingerprintAttributeList& fps = attrs.GetFingerprint();
        for (auto fp = fps.mFingerprints.begin(); fp != fps.mFingerprints.end();
             ++fp) {
          nsCString alg_str = "None"_ns;

          if (fp->hashFunc == SdpFingerprintAttributeList::kSha1) {
            alg_str = "sha-1"_ns;
          } else if (fp->hashFunc == SdpFingerprintAttributeList::kSha256) {
            alg_str = "sha-256"_ns;
          }
          ASSERT_EQ(source.mFingerprints[alg_str], fp->fingerprint);
        }

        ASSERT_EQ(source.mFingerprints.size(), fps.mFingerprints.size());
      }
    }
  }

  std::string mLastError;
  SdpHelper mSdpHelper;

  UniquePtr<TransportData> mOffererTransport;
  UniquePtr<TransportData> mAnswererTransport;
};

TEST_F(JsepSessionTestBase, CreateDestroy) {}

TEST_P(JsepSessionTest, CreateOffer) {
  AddTracks(*mSessionOff);
  CreateOffer();
}

TEST_P(JsepSessionTest, CreateOfferSetLocal) {
  AddTracks(*mSessionOff);
  std::string offer = CreateOffer();
  SetLocalOffer(offer);
}

TEST_P(JsepSessionTest, CreateOfferSetLocalSetRemote) {
  AddTracks(*mSessionOff);
  std::string offer = CreateOffer();
  SetLocalOffer(offer);
  SetRemoteOffer(offer);
}

TEST_P(JsepSessionTest, CreateOfferSetLocalSetRemoteCreateAnswer) {
  AddTracks(*mSessionOff);
  std::string offer = CreateOffer();
  SetLocalOffer(offer);
  SetRemoteOffer(offer);
  AddTracks(*mSessionAns);
  std::string answer = CreateAnswer();
}

TEST_P(JsepSessionTest, CreateOfferSetLocalSetRemoteCreateAnswerSetLocal) {
  AddTracks(*mSessionOff);
  std::string offer = CreateOffer();
  SetLocalOffer(offer);
  SetRemoteOffer(offer);
  AddTracks(*mSessionAns);
  std::string answer = CreateAnswer();
  SetLocalAnswer(answer);
}

TEST_P(JsepSessionTest, FullCall) {
  AddTracks(*mSessionOff);
  std::string offer = CreateOffer();
  SetLocalOffer(offer);
  SetRemoteOffer(offer);
  AddTracks(*mSessionAns);
  std::string answer = CreateAnswer();
  SetLocalAnswer(answer);
  SetRemoteAnswer(answer);
}

TEST_P(JsepSessionTest, GetDescriptions) {
  AddTracks(*mSessionOff);
  std::string offer = CreateOffer();
  SetLocalOffer(offer);
  std::string desc = mSessionOff->GetLocalDescription(kJsepDescriptionCurrent);
  ASSERT_EQ(0U, desc.size());
  desc = mSessionOff->GetLocalDescription(kJsepDescriptionPending);
  ASSERT_NE(0U, desc.size());
  desc = mSessionOff->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionOff->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_EQ(0U, desc.size());
  desc = mSessionAns->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_EQ(0U, desc.size());
  desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_EQ(0U, desc.size());

  SetRemoteOffer(offer);
  desc = mSessionAns->GetRemoteDescription(kJsepDescriptionCurrent);
  ASSERT_EQ(0U, desc.size());
  desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPending);
  ASSERT_NE(0U, desc.size());
  desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionAns->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_EQ(0U, desc.size());
  desc = mSessionOff->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionOff->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_EQ(0U, desc.size());

  AddTracks(*mSessionAns);
  std::string answer = CreateAnswer();
  SetLocalAnswer(answer);
  desc = mSessionAns->GetLocalDescription(kJsepDescriptionCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionAns->GetLocalDescription(kJsepDescriptionPending);
  ASSERT_EQ(0U, desc.size());
  desc = mSessionAns->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionAns->GetRemoteDescription(kJsepDescriptionCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPending);
  ASSERT_EQ(0U, desc.size());
  desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionOff->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionOff->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_EQ(0U, desc.size());

  SetRemoteAnswer(answer);
  desc = mSessionOff->GetLocalDescription(kJsepDescriptionCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionOff->GetLocalDescription(kJsepDescriptionPending);
  ASSERT_EQ(0U, desc.size());
  desc = mSessionOff->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionOff->GetRemoteDescription(kJsepDescriptionCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionOff->GetRemoteDescription(kJsepDescriptionPending);
  ASSERT_EQ(0U, desc.size());
  desc = mSessionOff->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionAns->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_NE(0U, desc.size());
  desc = mSessionAns->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
  ASSERT_NE(0U, desc.size());
}

TEST_P(JsepSessionTest, RenegotiationNoChange) {
  AddTracks(*mSessionOff);
  std::string offer = CreateOffer();
  SetLocalOffer(offer);
  SetRemoteOffer(offer);

  AddTracks(*mSessionAns);
  std::string answer = CreateAnswer();
  SetLocalAnswer(answer);
  SetRemoteAnswer(answer);

  ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
  ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);

  std::vector<JsepTransceiver> origOffererTransceivers =
      GetTransceivers(*mSessionOff);
  std::vector<JsepTransceiver> origAnswererTransceivers =
      GetTransceivers(*mSessionAns);

  std::string reoffer = CreateOffer();
  SetLocalOffer(reoffer);
  SetRemoteOffer(reoffer);

  std::string reanswer = CreateAnswer();
  SetLocalAnswer(reanswer);
  SetRemoteAnswer(reanswer);

  ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
  ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);

  auto newOffererTransceivers = GetTransceivers(*mSessionOff);
  auto newAnswererTransceivers = GetTransceivers(*mSessionAns);

  ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
  ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
}

// Disabled: See Bug 1329028
TEST_P(JsepSessionTest, DISABLED_RenegotiationSwappedRolesNoChange) {
  AddTracks(*mSessionOff);
  std::string offer = CreateOffer();
  SetLocalOffer(offer);
  SetRemoteOffer(offer);

  AddTracks(*mSessionAns);
  std::string answer = CreateAnswer();
  SetLocalAnswer(answer);
  SetRemoteAnswer(answer);

  ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
  ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);

  auto offererTransceivers = GetTransceivers(*mSessionOff);
  auto answererTransceivers = GetTransceivers(*mSessionAns);

  SwapOfferAnswerRoles();

  std::string reoffer = CreateOffer();
  SetLocalOffer(reoffer);
  SetRemoteOffer(reoffer);

  std::string reanswer = CreateAnswer();
  SetLocalAnswer(reanswer);
  SetRemoteAnswer(reanswer);

  ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
  ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kPassive);

  auto newOffererTransceivers = GetTransceivers(*mSessionOff);
  auto newAnswererTransceivers = GetTransceivers(*mSessionAns);

  ASSERT_TRUE(Equals(offererTransceivers, newAnswererTransceivers));
  ASSERT_TRUE(Equals(answererTransceivers, newOffererTransceivers));
}

static void RemoveLastN(std::vector<JsepTransceiver>& aTransceivers,
                        size_t aNum) {
  while (aNum--) {
    // erase doesn't take reverse_iterator :(
    aTransceivers.erase(--aTransceivers.end());
  }
}

TEST_P(JsepSessionTest, RenegotiationOffererAddsTrack) {
  AddTracks(*mSessionOff);
  AddTracks(*mSessionAns);

  OfferAnswer();

  ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
  ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);

  std::vector<JsepTransceiver> origOffererTransceivers =
      GetTransceivers(*mSessionOff);
  std::vector<JsepTransceiver> origAnswererTransceivers =
      GetTransceivers(*mSessionAns);

  std::vector<SdpMediaSection::MediaType> extraTypes;
  extraTypes.push_back(SdpMediaSection::kAudio);
  extraTypes.push_back(SdpMediaSection::kVideo);
  AddTracks(*mSessionOff, extraTypes);
  types.insert(types.end(), extraTypes.begin(), extraTypes.end());

  OfferAnswer(CHECK_SUCCESS);

  ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
  ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);

  auto newOffererTransceivers = GetTransceivers(*mSessionOff);
  auto newAnswererTransceivers = GetTransceivers(*mSessionAns);

  ASSERT_LE(2U, newOffererTransceivers.size());
  RemoveLastN(newOffererTransceivers, 2);
  ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));

  ASSERT_LE(2U, newAnswererTransceivers.size());
  RemoveLastN(newAnswererTransceivers, 2);
  ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
}

TEST_P(JsepSessionTest, RenegotiationAnswererAddsTrack) {
  AddTracks(*mSessionOff);
  AddTracks(*mSessionAns);

  OfferAnswer();

  ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
  ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);

  std::vector<JsepTransceiver> origOffererTransceivers =
      GetTransceivers(*mSessionOff);
  std::vector<JsepTransceiver> origAnswererTransceivers =
      GetTransceivers(*mSessionAns);

  std::vector<SdpMediaSection::MediaType> extraTypes;
  extraTypes.push_back(SdpMediaSection::kAudio);
  extraTypes.push_back(SdpMediaSection::kVideo);
  AddTracks(*mSessionAns, extraTypes);
  types.insert(types.end(), extraTypes.begin(), extraTypes.end());

  // We need to add a recvonly m-section to the offer for this to work
  mSessionOff->AddTransceiver(
      JsepTransceiver(SdpMediaSection::kAudio, mUuidGen,
                      SdpDirectionAttribute::Direction::kRecvonly));
  mSessionOff->AddTransceiver(
      JsepTransceiver(SdpMediaSection::kVideo, mUuidGen,
                      SdpDirectionAttribute::Direction::kRecvonly));

  std::string offer = CreateOffer();
  SetLocalOffer(offer, CHECK_SUCCESS);
  SetRemoteOffer(offer, CHECK_SUCCESS);

  std::string answer = CreateAnswer();
  SetLocalAnswer(answer, CHECK_SUCCESS);
  SetRemoteAnswer(answer, CHECK_SUCCESS);

  ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
  ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);

  auto newOffererTransceivers = GetTransceivers(*mSessionOff);
  auto newAnswererTransceivers = GetTransceivers(*mSessionAns);

  ASSERT_LE(2U, newOffererTransceivers.size());
  RemoveLastN(newOffererTransceivers, 2);
  ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));

  ASSERT_LE(2U, newAnswererTransceivers.size());
  RemoveLastN(newAnswererTransceivers, 2);
  ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
}

TEST_P(JsepSessionTest, RenegotiationBothAddTrack) {
  AddTracks(*mSessionOff);
  AddTracks(*mSessionAns);

  OfferAnswer();

  ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
  ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);

  std::vector<JsepTransceiver> origOffererTransceivers =
      GetTransceivers(*mSessionOff);
  std::vector<JsepTransceiver> origAnswererTransceivers =
      GetTransceivers(*mSessionAns);

  std::vector<SdpMediaSection::MediaType> extraTypes;
  extraTypes.push_back(SdpMediaSection::kAudio);
  extraTypes.push_back(SdpMediaSection::kVideo);
  AddTracks(*mSessionAns, extraTypes);
  AddTracks(*mSessionOff, extraTypes);
  types.insert(types.end(), extraTypes.begin(), extraTypes.end());

  OfferAnswer(CHECK_SUCCESS);

  ValidateSetupAttribute(*mSessionOff, SdpSetupAttribute::kActpass);
  ValidateSetupAttribute(*mSessionAns, SdpSetupAttribute::kActive);

  auto newOffererTransceivers = GetTransceivers(*mSessionOff);
  auto newAnswererTransceivers = GetTransceivers(*mSessionAns);

  ASSERT_LE(2U, newOffererTransceivers.size());
  RemoveLastN(newOffererTransceivers, 2);
  ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));

  ASSERT_LE(2U, newAnswererTransceivers.size());
  RemoveLastN(newAnswererTransceivers, 2);
  ASSERT_TRUE(Equals(origAnswererTransceivers, newAnswererTransceivers));
}

TEST_P(JsepSessionTest, RenegotiationBothAddTracksToExistingStream) {
  AddTracks(*mSessionOff);
  AddTracks(*mSessionAns);
  if (GetParam() == "datachannel") {
    return;
  }

  OfferAnswer();

  auto oHasStream = HasMediaStream(GetLocalTracks(*mSessionOff));
  auto aHasStream = HasMediaStream(GetLocalTracks(*mSessionAns));
  ASSERT_EQ(oHasStream, !GetLocalUniqueStreamIds(*mSessionOff).empty());
  ASSERT_EQ(aHasStream, !GetLocalUniqueStreamIds(*mSessionAns).empty());
  ASSERT_EQ(aHasStream, !GetRemoteUniqueStreamIds(*mSessionOff).empty());
  ASSERT_EQ(oHasStream, !GetRemoteUniqueStreamIds(*mSessionAns).empty());

  auto firstOffId = GetFirstLocalStreamId(*mSessionOff);
  auto firstAnsId = GetFirstLocalStreamId(*mSessionAns);

  auto offererTransceivers = GetTransceivers(*mSessionOff);
  auto answererTransceivers = GetTransceivers(*mSessionAns);

  std::vector<SdpMediaSection::MediaType> extraTypes;
  extraTypes.push_back(SdpMediaSection::kAudio);
  extraTypes.push_back(SdpMediaSection::kVideo);
  AddTracksToStream(*mSessionOff, firstOffId, extraTypes);
  AddTracksToStream(*mSessionAns, firstAnsId, extraTypes);
  types.insert(types.end(), extraTypes.begin(), extraTypes.end());

  OfferAnswer(CHECK_SUCCESS);

  oHasStream = HasMediaStream(GetLocalTracks(*mSessionOff));
  aHasStream = HasMediaStream(GetLocalTracks(*mSessionAns));

  ASSERT_EQ(oHasStream, !GetLocalUniqueStreamIds(*mSessionOff).empty());
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=86 G=91

¤ Dauer der Verarbeitung: 0.24 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.