/*
 *  Copyright 2004 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "pc/media_session.h"

#include <array>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <map>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "absl/algorithm/container.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "api/audio_codecs/audio_format.h"
#include "api/candidate.h"
#include "api/environment/environment.h"
#include "api/field_trials_view.h"
#include "api/media_types.h"
#include "api/payload_type.h"
#include "api/rtp_parameters.h"
#include "api/rtp_transceiver_direction.h"
#include "api/sctp_transport_interface.h"
#include "api/video_codecs/sdp_video_format.h"
#include "call/fake_payload_type_suggester.h"
#include "call/payload_type.h"
#include "media/base/codec.h"
#include "media/base/codec_list.h"
#include "media/base/fake_media_engine.h"
#include "media/base/media_constants.h"
#include "media/base/rid_description.h"
#include "media/base/stream_params.h"
#include "media/base/test_utils.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_description_factory.h"
#include "p2p/base/transport_info.h"
#include "pc/codec_vendor.h"
#include "pc/media_options.h"
#include "pc/media_protocol_names.h"
#include "pc/rtp_media_utils.h"
#include "pc/rtp_parameters_conversion.h"
#include "pc/session_description.h"
#include "pc/simulcast_description.h"
#include "rtc_base/checks.h"
#include "rtc_base/fake_ssl_identity.h"
#include "rtc_base/logging.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_identity.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/system/plan_b_only.h"
#include "rtc_base/unique_id_generator.h"
#include "test/create_test_environment.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/pc/sctp/fake_sctp_transport.h"

namespace webrtc {
namespace {

using ::testing::Bool;
using ::testing::Combine;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Gt;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::NotNull;
using ::testing::Pointwise;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAreArray;
using ::testing::Values;
using ::testing::ValuesIn;

using Candidates = std::vector<Candidate>;

constexpr char kAudioMid[] = "0";
constexpr char kVideoMid[] = "1";
constexpr char kDataMid[] = "2";

class CodecLookupHelperForTesting : public CodecLookupHelper {
 public:
  explicit CodecLookupHelperForTesting(const FieldTrialsView& field_trials)
      : field_trials_(field_trials) {}

  webrtc::PayloadTypeSuggester* PayloadTypeSuggester() override {
    return &payload_type_suggester_;
  }

  CodecVendor* GetCodecVendor() override {
    if (!codec_vendor_) {
      codec_vendor_.reset(
          new CodecVendor(&media_engine_, rtx_enabled_, field_trials_));
    }
    return codec_vendor_.get();
  }

  void RegisterExpectations(absl::string_view mid,
                            std::span<const Codec> codecs) {
    for (const Codec& c : codecs) {
      if (c.id.IsSet()) {
        RTC_CHECK(payload_type_suggester_.AddLocalMapping(mid, c.id, c).ok());
      }
    }
  }

  void SetVideoCodecs(std::span<const Codec> send,
                      std::span<const Codec> receive) {
    media_engine_.SetVideoRecvCodecs(
        std::vector<Codec>(receive.begin(), receive.end()));
    media_engine_.SetVideoSendCodecs(
        std::vector<Codec>(send.begin(), send.end()));
    if (field_trials_.IsEnabled("WebRTC-PayloadTypesInTransport")) {
      RegisterExpectations(kVideoMid, send);
      RegisterExpectations(kVideoMid, receive);
    }
    MaybeEnableRtx(send);
    codec_vendor_.reset();
  }

  void SetAudioCodecs(std::span<const Codec> send,
                      std::span<const Codec> receive) {
    media_engine_.SetAudioSendCodecs(
        std::vector<Codec>(send.begin(), send.end()));
    media_engine_.SetAudioRecvCodecs(
        std::vector<Codec>(receive.begin(), receive.end()));
    if (field_trials_.IsEnabled("WebRTC-PayloadTypesInTransport")) {
      RegisterExpectations(kAudioMid, send);
      RegisterExpectations(kAudioMid, receive);
    }
    codec_vendor_.reset();
  }

  void SetAudioCodecs(std::span<const Codec> codecs) {
    SetAudioCodecs(codecs, codecs);
  }

  void SetVideoCodecs(std::span<const Codec> codecs) {
    SetVideoCodecs(codecs, codecs);
  }

  void SetAudioCodecs(std::initializer_list<Codec> codecs) {
    SetAudioCodecs(std::span<const Codec>(codecs.begin(), codecs.end()));
  }

  void SetVideoCodecs(std::initializer_list<Codec> codecs) {
    SetVideoCodecs(std::span<const Codec>(codecs.begin(), codecs.end()));
  }

  void ClearAudioCodecs() { SetAudioCodecs(std::vector<Codec>()); }
  void ClearVideoCodecs() { SetVideoCodecs(std::vector<Codec>()); }

 private:
  void MaybeEnableRtx(std::span<const Codec> codecs) {
    for (const Codec& c : codecs) {
      if (c.name == kRtxCodecName) {
        rtx_enabled_ = true;
        break;
      }
    }
  }
  const FieldTrialsView& field_trials_;
  FakePayloadTypeSuggester payload_type_suggester_;
  bool rtx_enabled_ = false;
  FakeMediaEngine media_engine_;
  std::unique_ptr<CodecVendor> codec_vendor_;
};

Codec CreateRedAudioCodec(absl::string_view encoding_id) {
  Codec red = CreateAudioCodec(63, "red", 48000, 2);
  red.SetParam(kCodecParamNotInNameValueFormat,
               std::string(encoding_id) + '/' + std::string(encoding_id));
  return red;
}

const std::array kAudioCodecs1 = {CreateAudioCodec(111, "opus", 48000, 2),
                                  CreateRedAudioCodec("111"),
                                  CreateAudioCodec(103, "G722", 16000, 1),
                                  CreateAudioCodec(0, "PCMU", 8000, 1),
                                  CreateAudioCodec(8, "PCMA", 8000, 1),
                                  CreateAudioCodec(107, "CN", 48000, 1)};

const std::array kAudioCodecs2 = {
    CreateAudioCodec(126, "foo", 16000, 1),
    CreateAudioCodec(0, "PCMU", 8000, 1),
    CreateAudioCodec(127, "G722", 16000, 1),
};

const std::array kAudioCodecsAnswer = {
    CreateAudioCodec(103, "G722", 16000, 1),
    CreateAudioCodec(0, "PCMU", 8000, 1),
};

const std::array kVideoCodecs1 = {CreateVideoCodec(96, "H264-SVC"),
                                  CreateVideoCodec(97, "H264")};

const std::array kVideoCodecs1Reverse = {CreateVideoCodec(97, "H264"),
                                         CreateVideoCodec(96, "H264-SVC")};

const std::array kVideoCodecs2 = {CreateVideoCodec(126, "H264"),
                                  CreateVideoCodec(127, "H263")};

const std::array kVideoCodecsAnswer = {CreateVideoCodec(97, "H264")};

// H.265 level-id, according to H.265 spec, is calculated this way:
// For any given H.265 level a.b, level-id = (a * 10 + b) * 3. For level 6.0,
// level-id = (6 * 10 + 0) * 3 = 180. Similar for all other H.265 levels.
constexpr char kVideoCodecsH265Level6LevelId[] = "180";
constexpr char kVideoCodecsH265Level52LevelId[] = "156";
constexpr char kVideoCodecsH265Level5LevelId[] = "150";
constexpr char kVideoCodecsH265Level4LevelId[] = "120";
constexpr char kVideoCodecsH265Level31LevelId[] = "93";

const SdpVideoFormat kH265MainProfileLevel31Sdp(
    "H265",
    {{"profile-id", "1"},
     {"tier-flag", "0"},
     {"level-id", kVideoCodecsH265Level31LevelId},
     {"tx-mode", "SRST"}});
const SdpVideoFormat kH265MainProfileLevel4Sdp("H265",
                                               {{"profile-id", "1"},
                                                {"tier-flag", "0"},
                                                {"level-id",
                                                 kVideoCodecsH265Level4LevelId},
                                                {"tx-mode", "SRST"}});
const SdpVideoFormat kH265MainProfileLevel5Sdp("H265",
                                               {{"profile-id", "1"},
                                                {"tier-flag", "0"},
                                                {"level-id",
                                                 kVideoCodecsH265Level5LevelId},
                                                {"tx-mode", "SRST"}});
const SdpVideoFormat kH265MainProfileLevel52Sdp(
    "H265",
    {{"profile-id", "1"},
     {"tier-flag", "0"},
     {"level-id", kVideoCodecsH265Level52LevelId},
     {"tx-mode", "SRST"}});
const SdpVideoFormat kH265MainProfileLevel6Sdp("H265",
                                               {{"profile-id", "1"},
                                                {"tier-flag", "0"},
                                                {"level-id",
                                                 kVideoCodecsH265Level6LevelId},
                                                {"tx-mode", "SRST"}});

const std::array kVideoCodecsH265Level31 = {
    CreateVideoCodec(96, kH265MainProfileLevel31Sdp)};
const std::array kVideoCodecsH265Level4 = {
    CreateVideoCodec(96, kH265MainProfileLevel4Sdp)};
const std::array kVideoCodecsH265Level5 = {
    CreateVideoCodec(96, kH265MainProfileLevel5Sdp)};
const std::array kVideoCodecsH265Level52 = {
    CreateVideoCodec(96, kH265MainProfileLevel52Sdp)};
const std::array kVideoCodecsH265Level6 = {
    CreateVideoCodec(96, kH265MainProfileLevel6Sdp)};

// Match two codec lists for content, but ignore the ID and feedback params.
MATCHER_P2(CodecListsMatch, expected, field_trials, "") {
  if (expected.size() != arg.size()) {
    return false;
  }
  for (size_t i = 0; i < expected.size(); ++i) {
    if (!expected[i].Matches(arg[i])) {
      return false;
    }
  }
  return true;
}

MATCHER_P(NamesMatchImpl, expected_names, "") {
  std::vector<std::string> actual_names;
  for (const auto& item : arg) {
    actual_names.push_back(item.name);
  }
  return ::testing::ExplainMatchResult(
      ::testing::ElementsAreArray(expected_names), actual_names,
      result_listener);
}

inline auto NamesMatch(std::vector<std::string> expected_names) {
  return NamesMatchImpl(std::move(expected_names));
}

const std::array kAudioRtpExtension1 = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
    RtpExtension("http://google.com/testing/audio_something", 10),
};

const std::array kAudioRtpExtensionEncrypted1 = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
    RtpExtension("http://google.com/testing/audio_something", 11, true),
};

const std::array kAudioRtpExtension2 = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 2),
    RtpExtension("http://google.com/testing/audio_something_else", 8),
    RtpExtension("http://google.com/testing/both_audio_and_video", 7),
};

const std::array kAudioRtpExtensionEncrypted2 = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 2),
    RtpExtension("http://google.com/testing/audio_something", 13, true),
    RtpExtension("http://google.com/testing/audio_something_else", 5, true),
};

const std::array kAudioRtpExtension3 = {
    RtpExtension("http://google.com/testing/audio_something", 2),
    RtpExtension("http://google.com/testing/both_audio_and_video", 3),
};

const std::array kAudioRtpExtensionMixedEncryption1 = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
    RtpExtension("http://google.com/testing/audio_something", 9),
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 10, true),
    RtpExtension("http://google.com/testing/audio_something", 11, true),
    RtpExtension("http://google.com/testing/audio_something_else", 12, true),
};

const std::array kAudioRtpExtensionMixedEncryption2 = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 5),
    RtpExtension("http://google.com/testing/audio_something", 6),
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 7, true),
    RtpExtension("http://google.com/testing/audio_something", 8, true),
    RtpExtension("http://google.com/testing/audio_something_else", 9),
};

const std::array kAudioRtpExtensionAnswer = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
};

const std::array kAudioRtpExtensionEncryptedAnswer = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
    RtpExtension("http://google.com/testing/audio_something", 11, true),
};

const std::array kAudioRtpExtensionMixedEncryptionAnswerEncryptionEnabled = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 10, true),
    RtpExtension("http://google.com/testing/audio_something", 11, true),
};

const std::array kAudioRtpExtensionMixedEncryptionAnswerEncryptionDisabled = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
    RtpExtension("http://google.com/testing/audio_something", 9),
};

const std::array kVideoRtpExtension1 = {
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14),
    RtpExtension("http://google.com/testing/video_something", 13),
};

const std::array kVideoRtpExtensionEncrypted1 = {
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14),
    RtpExtension("http://google.com/testing/video_something", 7, true),
};

const std::array kVideoRtpExtension2 = {
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 2),
    RtpExtension("http://google.com/testing/video_something_else", 14),
    RtpExtension("http://google.com/testing/both_audio_and_video", 7),
};

const std::array kVideoRtpExtensionEncrypted2 = {
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 8),
    RtpExtension("http://google.com/testing/video_something", 10, true),
    RtpExtension("http://google.com/testing/video_something_else", 4, true),
};

const std::array kVideoRtpExtension3 = {
    RtpExtension("http://google.com/testing/video_something", 4),
    RtpExtension("http://google.com/testing/both_audio_and_video", 5),
};

const std::array kVideoRtpExtensionMixedEncryption = {
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14),
    RtpExtension("http://google.com/testing/video_something", 13),
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 15, true),
    RtpExtension("http://google.com/testing/video_something", 16, true),
};

const std::array kVideoRtpExtensionAnswer = {
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14),
};

const std::array kVideoRtpExtensionEncryptedAnswer = {
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14),
    RtpExtension("http://google.com/testing/video_something", 7, true),
};

const std::array kVideoRtpExtensionMixedEncryptionAnswerEncryptionEnabled = {
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 15, true),
    RtpExtension("http://google.com/testing/video_something", 16, true),
};

const std::array kVideoRtpExtensionMixedEncryptionAnswerEncryptionDisabled = {
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14),
    RtpExtension("http://google.com/testing/video_something", 13),
};

const std::array kRtpExtensionTransportSequenceNumber01 = {
    RtpExtension("http://www.ietf.org/id/"
                 "draft-holmer-rmcat-transport-wide-cc-extensions-01",
                 1),
};

const std::array kRtpExtensionTransportSequenceNumber01And02 = {
    RtpExtension("http://www.ietf.org/id/"
                 "draft-holmer-rmcat-transport-wide-cc-extensions-01",
                 1),
    RtpExtension(
        "http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02",
        2),
};

const std::array kRtpExtensionTransportSequenceNumber02 = {
    RtpExtension(
        "http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02",
        2),
};

const std::array kRtpExtensionGenericFrameDescriptorUri00 = {
    RtpExtension("http://www.webrtc.org/experiments/rtp-hdrext/"
                 "generic-frame-descriptor-00",
                 3),
};

constexpr uint32_t kSimulcastParamsSsrc[] = {10, 11, 20, 21, 30, 31};
constexpr uint32_t kSimSsrc[] = {10, 20, 30};
constexpr uint32_t kFec1Ssrc[] = {10, 11};
constexpr uint32_t kFec2Ssrc[] = {20, 21};
constexpr uint32_t kFec3Ssrc[] = {30, 31};

constexpr char kMediaStream1[] = "stream_1";
constexpr char kMediaStream2[] = "stream_2";
constexpr char kVideoTrack1[] = "video_1";
constexpr char kVideoTrack2[] = "video_2";
constexpr char kAudioTrack1[] = "audio_1";
constexpr char kAudioTrack2[] = "audio_2";
constexpr char kAudioTrack3[] = "audio_3";

const char* kMediaProtocols[] = {"RTP/AVP", "RTP/SAVP", "RTP/AVPF",
                                 "RTP/SAVPF"};
const char* kMediaProtocolsDtls[] = {"TCP/TLS/RTP/SAVPF", "TCP/TLS/RTP/SAVP",
                                     "UDP/TLS/RTP/SAVPF", "UDP/TLS/RTP/SAVP"};

// These constants are used to make the code using "AddMediaDescriptionOptions"
// more readable.
constexpr bool kStopped = true;
constexpr bool kActive = false;

// Helper used for debugging. It reports the media type and the parameters.
std::string FullMimeType(Codec codec) {
  StringBuilder sb;
  switch (codec.type) {
    case Codec::Type::kAudio:
      sb << "audio/";
      break;
    case Codec::Type::kVideo:
      sb << "video/";
      break;
  }
  sb << codec.name;
  for (auto& param : codec.params) {
    sb << ";" << param.first << "=" << param.second;
  }
  return sb.Release();
}

bool IsMediaContentOfType(const ContentInfo* content, MediaType media_type) {
  RTC_DCHECK(content);
  return content->media_description()->type() == media_type;
}

RtpTransceiverDirection GetMediaDirection(const ContentInfo* content) {
  RTC_DCHECK(content);
  return content->media_description()->direction();
}

void AddRtxCodec(const Codec& rtx_codec, std::vector<Codec>* codecs) {
  RTC_LOG(LS_VERBOSE) << "Adding RTX codec " << FullMimeType(rtx_codec);
  ASSERT_FALSE(FindCodecById(*codecs, rtx_codec.id));
  codecs->push_back(rtx_codec);
}

std::vector<std::string> GetCodecNames(std::span<const Codec> codecs) {
  std::vector<std::string> codec_names;
  codec_names.reserve(codecs.size());
  for (const Codec& codec : codecs) {
    codec_names.push_back(codec.name);
  }
  return codec_names;
}

// This is used for test only. MIDs are not the identification of the
// MediaDescriptionOptions since some end points may not support MID and the SDP
// may not contain 'mid'.
std::vector<MediaDescriptionOptions>::iterator FindFirstMediaDescriptionByMid(
    const std::string& mid,
    MediaSessionOptions* opts) {
  return absl::c_find_if(
      opts->media_description_options,
      [&mid](const MediaDescriptionOptions& t) { return t.mid == mid; });
}

std::vector<MediaDescriptionOptions>::const_iterator
FindFirstMediaDescriptionByMid(const std::string& mid,
                               const MediaSessionOptions& opts) {
  return absl::c_find_if(
      opts.media_description_options,
      [&mid](const MediaDescriptionOptions& t) { return t.mid == mid; });
}

// Add a media section to the `session_options`.
void AddMediaDescriptionOptions(MediaType type,
                                const std::string& mid,
                                RtpTransceiverDirection direction,
                                bool stopped,
                                MediaSessionOptions* opts) {
  opts->media_description_options.push_back(
      MediaDescriptionOptions(type, mid, direction, stopped));
}

void AddAudioVideoSections(RtpTransceiverDirection direction,
                           MediaSessionOptions* opts) {
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid, direction, kActive,
                             opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid, direction, kActive,
                             opts);
}

void AddDataSection(RtpTransceiverDirection direction,
                    MediaSessionOptions* opts) {
  AddMediaDescriptionOptions(MediaType::DATA, kDataMid, direction, kActive,
                             opts);
}

void AttachSenderToMediaDescriptionOptions(
    const std::string& mid,
    MediaType type,
    const std::string& track_id,
    std::span<const std::string> stream_ids,
    std::span<const RidDescription> rids,
    const SimulcastLayerList& simulcast_layers,
    int num_sim_layer,
    MediaSessionOptions* session_options) {
  auto it = FindFirstMediaDescriptionByMid(mid, session_options);
  RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
  switch (type) {
    case MediaType::AUDIO:
      it->AddAudioSender(track_id, std::vector<std::string>(stream_ids.begin(),
                                                            stream_ids.end()));
      break;
    case MediaType::VIDEO:
      it->AddVideoSender(
          track_id,
          std::vector<std::string>(stream_ids.begin(), stream_ids.end()),
          std::vector<RidDescription>(rids.begin(), rids.end()),
          simulcast_layers, num_sim_layer);
      break;
    default:
      RTC_DCHECK_NOTREACHED();
  }
  RTC_ALLOW_PLAN_B_DEPRECATION_END();
}

void AttachSenderToMediaDescriptionOptions(
    const std::string& mid,
    MediaType type,
    const std::string& track_id,
    std::initializer_list<std::string> stream_ids,
    std::span<const RidDescription> rids,
    const SimulcastLayerList& simulcast_layers,
    int num_sim_layer,
    MediaSessionOptions* session_options) {
  AttachSenderToMediaDescriptionOptions(
      mid, type, track_id, std::span<const std::string>(stream_ids), rids,
      simulcast_layers, num_sim_layer, session_options);
}

void AttachSenderToMediaDescriptionOptions(
    const std::string& mid,
    MediaType type,
    const std::string& track_id,
    std::span<const std::string> stream_ids,
    int num_sim_layer,
    MediaSessionOptions* session_options) {
  AttachSenderToMediaDescriptionOptions(mid, type, track_id, stream_ids, {},
                                        SimulcastLayerList(), num_sim_layer,
                                        session_options);
}

void AttachSenderToMediaDescriptionOptions(
    const std::string& mid,
    MediaType type,
    const std::string& track_id,
    std::initializer_list<std::string> stream_ids,
    int num_sim_layer,
    MediaSessionOptions* session_options) {
  AttachSenderToMediaDescriptionOptions(
      mid, type, track_id, std::span<const std::string>(stream_ids),
      num_sim_layer, session_options);
}

void AttachSenderToMediaDescriptionOptions(
    const std::string& mid,
    MediaType type,
    const std::string& track_id,
    std::initializer_list<std::string> stream_ids,
    std::initializer_list<RidDescription> rids,
    const SimulcastLayerList& simulcast_layers,
    int num_sim_layer,
    MediaSessionOptions* session_options) {
  AttachSenderToMediaDescriptionOptions(
      mid, type, track_id, std::span<const std::string>(stream_ids),
      std::span<const RidDescription>(rids), simulcast_layers, num_sim_layer,
      session_options);
}

void DetachSenderFromMediaSection(const std::string& mid,
                                  const std::string& track_id,
                                  MediaSessionOptions* session_options) {
  std::vector<SenderOptions>& sender_options_list =
      FindFirstMediaDescriptionByMid(mid, session_options)->sender_options;
  auto sender_it = absl::c_find_if(
      sender_options_list, [track_id](const SenderOptions& sender_options) {
        return sender_options.track_id == track_id;
      });
  RTC_DCHECK(sender_it != sender_options_list.end());
  sender_options_list.erase(sender_it);
}

// Helper function used to create recv-only audio MediaSessionOptions.
MediaSessionOptions CreateAudioMediaSession() {
  MediaSessionOptions session_options;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &session_options);
  return session_options;
}

// TODO(zhihuang): Most of these tests were written while MediaSessionOptions
// was designed for Plan B SDP, where only one audio "m=" section and one video
// "m=" section could be generated, and ordering couldn't be controlled. Many of
// these tests may be obsolete as a result, and should be refactored or removed.
class MediaSessionDescriptionFactoryTest : public testing::Test {
 public:
  MediaSessionDescriptionFactoryTest(absl::string_view field_trials_string = "")
      : env_(CreateTestEnvironment({.field_trials = field_trials_string})),
        tdf1_(env_.field_trials()),
        tdf2_(env_.field_trials()),
        codec_lookup_helper_1_(env_.field_trials()),
        codec_lookup_helper_2_(env_.field_trials()),
        f1_(env_,
            nullptr,
            false,
            &ssrc_generator1,
            &tdf1_,
            &sctp_factory_1_,
            &codec_lookup_helper_1_),
        f2_(env_,
            nullptr,
            false,
            &ssrc_generator2,
            &tdf2_,
            &sctp_factory_2_,
            &codec_lookup_helper_2_) {
    codec_lookup_helper_1_.SetAudioCodecs(kAudioCodecs1);
    codec_lookup_helper_1_.SetVideoCodecs(kVideoCodecs1);
    codec_lookup_helper_2_.SetAudioCodecs(kAudioCodecs2);
    codec_lookup_helper_2_.SetVideoCodecs(kVideoCodecs2);
    tdf1_.set_certificate(RTCCertificate::Create(
        std::unique_ptr<SSLIdentity>(new FakeSSLIdentity("id1"))));
    tdf2_.set_certificate(RTCCertificate::Create(
        std::unique_ptr<SSLIdentity>(new FakeSSLIdentity("id2"))));
  }

  // Create a video StreamParamsVec object with:
  // - one video stream with 3 simulcast streams and FEC,
  StreamParamsVec CreateComplexVideoStreamParamsVec() {
    SsrcGroup sim_group("SIM", MAKE_VECTOR(kSimSsrc));
    SsrcGroup fec_group1("FEC", MAKE_VECTOR(kFec1Ssrc));
    SsrcGroup fec_group2("FEC", MAKE_VECTOR(kFec2Ssrc));
    SsrcGroup fec_group3("FEC", MAKE_VECTOR(kFec3Ssrc));

    std::vector<SsrcGroup> ssrc_groups;
    ssrc_groups.push_back(sim_group);
    ssrc_groups.push_back(fec_group1);
    ssrc_groups.push_back(fec_group2);
    ssrc_groups.push_back(fec_group3);

    StreamParams simulcast_params;
    simulcast_params.id = kVideoTrack1;
    simulcast_params.ssrcs = MAKE_VECTOR(kSimulcastParamsSsrc);
    simulcast_params.ssrc_groups = ssrc_groups;
    simulcast_params.cname = "Video_SIM_FEC";
    simulcast_params.set_stream_ids({kMediaStream1});

    StreamParamsVec video_streams;
    video_streams.push_back(simulcast_params);

    return video_streams;
  }

  // Returns true if the transport info contains "renomination" as an
  // ICE option.
  bool GetIceRenomination(const TransportInfo* transport_info) {
    return absl::c_linear_search(transport_info->description.transport_options,
                                 "renomination");
  }

  void TestTransportInfo(bool offer,
                         const MediaSessionOptions& options,
                         bool has_current_desc) {
    const std::string current_audio_ufrag = "current_audio_ufrag";
    const std::string current_audio_pwd = "current_audio_pwd";
    const std::string current_video_ufrag = "current_video_ufrag";
    const std::string current_video_pwd = "current_video_pwd";
    const std::string current_data_ufrag = "current_data_ufrag";
    const std::string current_data_pwd = "current_data_pwd";
    std::unique_ptr<SessionDescription> current_desc;
    std::unique_ptr<SessionDescription> desc;
    if (has_current_desc) {
      current_desc = std::make_unique<SessionDescription>();
      current_desc->AddTransportInfo(TransportInfo(
          kAudioMid,
          TransportDescription(current_audio_ufrag, current_audio_pwd)));
      current_desc->AddTransportInfo(TransportInfo(
          kVideoMid,
          TransportDescription(current_video_ufrag, current_video_pwd)));
      current_desc->AddTransportInfo(TransportInfo(
          kDataMid,
          TransportDescription(current_data_ufrag, current_data_pwd)));
    }
    if (offer) {
      desc = f1_.CreateOfferOrError(options, current_desc.get()).MoveValue();
    } else {
      std::unique_ptr<SessionDescription> offer_desc;
      offer_desc = f1_.CreateOfferOrError(options, nullptr).MoveValue();
      desc =
          f1_.CreateAnswerOrError(offer_desc.get(), options, current_desc.get())
              .MoveValue();
    }
    ASSERT_TRUE(desc);
    const TransportInfo* ti_audio = desc->GetTransportInfoByName(kAudioMid);
    if (options.has_audio()) {
      if (has_current_desc) {
        EXPECT_EQ(current_audio_ufrag, ti_audio->description.ice_ufrag);
        EXPECT_EQ(current_audio_pwd, ti_audio->description.ice_pwd);
      } else {
        EXPECT_THAT(ti_audio->description.ice_ufrag, SizeIs(ICE_UFRAG_LENGTH));
        EXPECT_THAT(ti_audio->description.ice_pwd, SizeIs(ICE_PWD_LENGTH));
      }
      auto media_desc_options_it =
          FindFirstMediaDescriptionByMid(kAudioMid, options);
      EXPECT_EQ(
          media_desc_options_it->transport_options.enable_ice_renomination,
          GetIceRenomination(ti_audio));
    }
    const TransportInfo* ti_video = desc->GetTransportInfoByName(kVideoMid);
    if (options.has_video()) {
      auto media_desc_options_it =
          FindFirstMediaDescriptionByMid(kVideoMid, options);
      if (options.bundle_enabled) {
        EXPECT_EQ(ti_audio->description.ice_ufrag,
                  ti_video->description.ice_ufrag);
        EXPECT_EQ(ti_audio->description.ice_pwd, ti_video->description.ice_pwd);
      } else {
        if (has_current_desc) {
          EXPECT_EQ(current_video_ufrag, ti_video->description.ice_ufrag);
          EXPECT_EQ(current_video_pwd, ti_video->description.ice_pwd);
        } else {
          EXPECT_THAT(ti_video->description.ice_ufrag,
                      SizeIs(ICE_UFRAG_LENGTH));
          EXPECT_THAT(ti_video->description.ice_pwd, SizeIs(ICE_PWD_LENGTH));
        }
      }
      EXPECT_EQ(
          media_desc_options_it->transport_options.enable_ice_renomination,
          GetIceRenomination(ti_video));
    }
    const TransportInfo* ti_data = desc->GetTransportInfoByName(kDataMid);
    if (options.has_data()) {
      if (options.bundle_enabled) {
        EXPECT_EQ(ti_audio->description.ice_ufrag,
                  ti_data->description.ice_ufrag);
        EXPECT_EQ(ti_audio->description.ice_pwd, ti_data->description.ice_pwd);
      } else {
        if (has_current_desc) {
          EXPECT_EQ(current_data_ufrag, ti_data->description.ice_ufrag);
          EXPECT_EQ(current_data_pwd, ti_data->description.ice_pwd);
        } else {
          EXPECT_THAT(ti_data->description.ice_ufrag, SizeIs(ICE_UFRAG_LENGTH));
          EXPECT_THAT(ti_data->description.ice_pwd, SizeIs(ICE_PWD_LENGTH));
        }
      }
      auto media_desc_options_it =
          FindFirstMediaDescriptionByMid(kDataMid, options);
      EXPECT_EQ(
          media_desc_options_it->transport_options.enable_ice_renomination,
          GetIceRenomination(ti_data));
    }
  }

  // This test that the audio and video media direction is set to
  // `expected_direction_in_answer` in an answer if the offer direction is set
  // to `direction_in_offer` and the answer is willing to both send and receive.
  void TestMediaDirectionInAnswer(
      RtpTransceiverDirection direction_in_offer,
      RtpTransceiverDirection expected_direction_in_answer) {
    MediaSessionOptions offer_opts;
    AddAudioVideoSections(direction_in_offer, &offer_opts);

    std::unique_ptr<SessionDescription> offer =
        f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
    ASSERT_TRUE(offer.get());
    ContentInfo* ac_offer = offer->GetContentByName(kAudioMid);
    ASSERT_TRUE(ac_offer);
    ContentInfo* vc_offer = offer->GetContentByName(kVideoMid);
    ASSERT_TRUE(vc_offer);

    MediaSessionOptions answer_opts;
    AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &answer_opts);
    std::unique_ptr<SessionDescription> answer =
        f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
    const AudioContentDescription* acd_answer =
        GetFirstAudioContentDescription(answer.get());
    EXPECT_EQ(expected_direction_in_answer, acd_answer->direction());
    const VideoContentDescription* vcd_answer =
        GetFirstVideoContentDescription(answer.get());
    EXPECT_EQ(expected_direction_in_answer, vcd_answer->direction());
  }

  bool VerifyNoCNCodecs(const ContentInfo* content) {
    RTC_DCHECK(content);
    RTC_CHECK(content->media_description());
    for (const Codec& codec : content->media_description()->codecs()) {
      if (codec.name == "CN") {
        return false;
      }
    }
    return true;
  }

  void TestTransportSequenceNumberNegotiation(
      std::span<const RtpExtension> local,
      std::span<const RtpExtension> offered,
      std::span<const RtpExtension> expectedAnswer) {
    MediaSessionOptions opts;
    AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
    SetAudioVideoRtpHeaderExtensions(offered, offered, &opts);
    std::unique_ptr<SessionDescription> offer =
        f1_.CreateOfferOrError(opts, nullptr).MoveValue();
    ASSERT_TRUE(offer.get());
    SetAudioVideoRtpHeaderExtensions(local, local, &opts);
    std::unique_ptr<SessionDescription> answer =
        f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

    EXPECT_THAT(
        expectedAnswer,
        UnorderedElementsAreArray(GetFirstAudioContentDescription(answer.get())
                                      ->rtp_header_extensions()));
    EXPECT_THAT(
        expectedAnswer,
        UnorderedElementsAreArray(GetFirstVideoContentDescription(answer.get())
                                      ->rtp_header_extensions()));
  }

  std::vector<RtpHeaderExtensionCapability>
  HeaderExtensionCapabilitiesFromRtpExtensions(
      std::span<const RtpExtension> extensions) {
    std::vector<RtpHeaderExtensionCapability> capabilities;
    for (const RtpExtension& extension : extensions) {
      RtpHeaderExtensionCapability capability(
          extension.uri, extension.id, extension.encrypt,
          RtpTransceiverDirection::kSendRecv);
      capabilities.push_back(capability);
    }
    return capabilities;
  }

  void SetAudioVideoRtpHeaderExtensions(
      std::span<const RtpExtension> audio_exts,
      std::span<const RtpExtension> video_exts,
      MediaSessionOptions* opts) {
    std::vector<RtpHeaderExtensionCapability> audio_caps =
        HeaderExtensionCapabilitiesFromRtpExtensions(audio_exts);
    std::vector<RtpHeaderExtensionCapability> video_caps =
        HeaderExtensionCapabilitiesFromRtpExtensions(video_exts);
    for (MediaDescriptionOptions& entry : opts->media_description_options) {
      switch (entry.type) {
        case MediaType::AUDIO:
          entry.header_extensions = audio_caps;
          break;
        case MediaType::VIDEO:
          entry.header_extensions = video_caps;
          break;
        default:
          break;
      }
    }
  }

 protected:
  Environment env_;
  UniqueRandomIdGenerator ssrc_generator1;
  UniqueRandomIdGenerator ssrc_generator2;
  TransportDescriptionFactory tdf1_;
  TransportDescriptionFactory tdf2_;
  CodecLookupHelperForTesting codec_lookup_helper_1_;
  CodecLookupHelperForTesting codec_lookup_helper_2_;
  FakeSctpTransportFactory sctp_factory_1_;
  FakeSctpTransportFactory sctp_factory_2_;
  MediaSessionDescriptionFactory f1_;
  MediaSessionDescriptionFactory f2_;
};

// Create a typical audio offer, and ensure it matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOffer) {
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(CreateAudioMediaSession(), nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* ac = offer->GetContentByName(kAudioMid);
  const ContentInfo* vc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  EXPECT_FALSE(vc);
  EXPECT_EQ(ac->type, MediaProtocolType::kRtp);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(codec_lookup_helper_1_.GetCodecVendor()
                                  ->audio_sendrecv_codecs()
                                  .codecs(),
                              &env_.field_trials()));
  EXPECT_EQ(acd->first_ssrc(), 0U);             // no sender is attached.
  EXPECT_EQ(acd->bandwidth(), kAutoBandwidth);  // default bandwidth (auto)
  EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
  EXPECT_EQ(acd->protocol(), kMediaProtocolDtlsSavpf);
}

// Create an offer with just Opus and RED.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateAudioOfferWithJustOpusAndRed) {
  // First, prefer to only use opus and red.
  std::vector<RtpCodecCapability> preferences;
  preferences.push_back(ToRtpCodecCapability(
      codec_lookup_helper_1_.GetCodecVendor()->audio_sendrecv_codecs()[0]));
  preferences.push_back(ToRtpCodecCapability(
      codec_lookup_helper_1_.GetCodecVendor()->audio_sendrecv_codecs()[1]));
  EXPECT_THAT(preferences, NamesMatch({"opus", "red"}));

  MediaSessionOptions opts = CreateAudioMediaSession();
  opts.media_description_options.at(0).codec_preferences = preferences;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* ac = offer->GetContentByName(kAudioMid);
  const ContentInfo* vc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac != nullptr);
  ASSERT_TRUE(vc == nullptr);
  EXPECT_EQ(ac->type, MediaProtocolType::kRtp);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(), NamesMatch({"opus", "red"}));
}

// Create an offer with RED before Opus, which enables RED with Opus encoding.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOfferWithRedForOpus) {
  // First, prefer to only use opus and red.
  std::vector<RtpCodecCapability> preferences;
  preferences.push_back(ToRtpCodecCapability(
      codec_lookup_helper_1_.GetCodecVendor()->audio_sendrecv_codecs()[1]));
  preferences.push_back(ToRtpCodecCapability(
      codec_lookup_helper_1_.GetCodecVendor()->audio_sendrecv_codecs()[0]));
  EXPECT_THAT(preferences, NamesMatch({"red", "opus"}));

  MediaSessionOptions opts = CreateAudioMediaSession();
  opts.media_description_options.at(0).codec_preferences = preferences;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* ac = offer->GetContentByName(kAudioMid);
  const ContentInfo* vc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac != nullptr);
  ASSERT_TRUE(vc == nullptr);
  EXPECT_EQ(ac->type, MediaProtocolType::kRtp);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(), NamesMatch({"red", "opus"}));
}

// Create a typical video offer, and ensure it matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* ac = offer->GetContentByName(kAudioMid);
  const ContentInfo* vc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  EXPECT_EQ(ac->type, MediaProtocolType::kRtp);
  EXPECT_EQ(vc->type, MediaProtocolType::kRtp);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();
  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(codec_lookup_helper_1_.GetCodecVendor()
                                  ->audio_sendrecv_codecs()
                                  .codecs(),
                              &env_.field_trials()));
  EXPECT_EQ(acd->first_ssrc(), 0U);             // no sender is attached
  EXPECT_EQ(acd->bandwidth(), kAutoBandwidth);  // default bandwidth (auto)
  EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
  EXPECT_EQ(acd->protocol(), kMediaProtocolDtlsSavpf);
  EXPECT_EQ(vcd->type(), MediaType::VIDEO);
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(codec_lookup_helper_1_.GetCodecVendor()
                                  ->video_sendrecv_codecs()
                                  .codecs(),
                              &env_.field_trials()));
  EXPECT_EQ(vcd->first_ssrc(), 0U);             // no sender is attached
  EXPECT_EQ(vcd->bandwidth(), kAutoBandwidth);  // default bandwidth (auto)
  EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on
  EXPECT_EQ(vcd->protocol(), kMediaProtocolDtlsSavpf);
}

TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferWithCustomCodecs) {
  MediaSessionOptions opts;

  SdpAudioFormat audio_format("custom-audio", 8000, 2);
  Codec custom_audio_codec = CreateAudioCodec(audio_format);
  custom_audio_codec.id = 123;  // picked at random, but valid
  auto audio_options = MediaDescriptionOptions(
      MediaType::AUDIO, "0", RtpTransceiverDirection::kSendRecv, kActive);
  audio_options.codecs_to_include.push_back(custom_audio_codec);
  opts.media_description_options.push_back(audio_options);

  Codec custom_video_codec = CreateVideoCodec("custom-video");
  custom_video_codec.id = 124;  // picked at random, but valid
  auto video_options = MediaDescriptionOptions(
      MediaType::VIDEO, "1", RtpTransceiverDirection::kSendRecv, kActive);
  video_options.codecs_to_include.push_back(custom_video_codec);
  opts.media_description_options.push_back(video_options);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* ac = offer->GetContentByName("0");
  const ContentInfo* vc = offer->GetContentByName("1");
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  EXPECT_EQ(ac->type, MediaProtocolType::kRtp);
  EXPECT_EQ(vc->type, MediaProtocolType::kRtp);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();
  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(), NamesMatch({custom_audio_codec.name}));

  EXPECT_EQ(vcd->type(), MediaType::VIDEO);
  EXPECT_THAT(vcd->codecs(), NamesMatch({custom_video_codec.name}));
}

TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAnswerWithCustomCodecs) {
  MediaSessionOptions offer_opts;
  MediaSessionOptions answer_opts;

  AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &offer_opts);
  // Create custom codecs and add to answer. These will override
  // the normally generated codec list in the answer.
  // This breaks O/A rules - the responsibility for obeying those is
  // on the caller, not on this function.
  SdpAudioFormat audio_format("custom-audio", 8000, 2);
  Codec custom_audio_codec = CreateAudioCodec(audio_format);
  custom_audio_codec.id = 123;  // picked at random, but valid
  auto audio_options = MediaDescriptionOptions(
      MediaType::AUDIO, kAudioMid, RtpTransceiverDirection::kSendRecv, kActive);
  audio_options.codecs_to_include.push_back(custom_audio_codec);
  answer_opts.media_description_options.push_back(audio_options);

  Codec custom_video_codec = CreateVideoCodec("custom-video");
  custom_video_codec.id = 124;
  auto video_options = MediaDescriptionOptions(
      MediaType::VIDEO, kVideoMid, RtpTransceiverDirection::kSendRecv, kActive);
  video_options.codecs_to_include.push_back(custom_video_codec);
  answer_opts.media_description_options.push_back(video_options);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  std::unique_ptr<SessionDescription> answer =
      f1_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
  const ContentInfo* ac = answer->GetContentByName(kAudioMid);
  const ContentInfo* vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  EXPECT_EQ(ac->type, MediaProtocolType::kRtp);
  EXPECT_EQ(vc->type, MediaProtocolType::kRtp);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();
  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(), NamesMatch({custom_audio_codec.name}));

  EXPECT_EQ(vcd->type(), MediaType::VIDEO);
  EXPECT_THAT(vcd->codecs(), NamesMatch({custom_video_codec.name}));
}

// Test creating an offer with bundle where the Codecs have the same dynamic
// RTP paylod type. The test verifies that the offer don't contain the
// duplicate RTP payload types.
TEST_F(MediaSessionDescriptionFactoryTest, TestBundleOfferWithSameCodecPlType) {
  Codec offered_video_codec =
      codec_lookup_helper_2_.GetCodecVendor()->video_sendrecv_codecs()[0];
  Codec offered_audio_codec =
      codec_lookup_helper_2_.GetCodecVendor()->audio_sendrecv_codecs()[0];
  ASSERT_EQ(offered_video_codec.id, offered_audio_codec.id);

  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  opts.bundle_enabled = true;
  std::unique_ptr<SessionDescription> offer =
      f2_.CreateOfferOrError(opts, nullptr).MoveValue();
  const VideoContentDescription* vcd =
      GetFirstVideoContentDescription(offer.get());
  const AudioContentDescription* acd =
      GetFirstAudioContentDescription(offer.get());
  ASSERT_TRUE(vcd);
  ASSERT_TRUE(acd);
  EXPECT_NE(vcd->codecs()[0].id, acd->codecs()[0].id);
  EXPECT_EQ(vcd->codecs()[0].name, offered_video_codec.name);
  EXPECT_EQ(acd->codecs()[0].name, offered_audio_codec.name);
}

// Test creating an updated offer with bundle, audio, video and data
// after an audio only session has been negotiated.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateUpdatedVideoOfferWithBundle) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kInactive, kStopped,
                             &opts);
  opts.bundle_enabled = true;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  MediaSessionOptions updated_opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &updated_opts);
  updated_opts.bundle_enabled = true;
  std::unique_ptr<SessionDescription> updated_offer(
      f1_.CreateOfferOrError(updated_opts, answer.get()).MoveValue());

  const AudioContentDescription* acd =
      GetFirstAudioContentDescription(updated_offer.get());
  const VideoContentDescription* vcd =
      GetFirstVideoContentDescription(updated_offer.get());
  EXPECT_TRUE(vcd);
  EXPECT_TRUE(acd);

  EXPECT_EQ(acd->protocol(), kMediaProtocolDtlsSavpf);
  EXPECT_EQ(vcd->protocol(), kMediaProtocolDtlsSavpf);
}

// Create an SCTP data offer with bundle without error.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSctpDataOffer) {
  MediaSessionOptions opts;
  opts.bundle_enabled = true;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  EXPECT_TRUE(offer.get());
  EXPECT_TRUE(offer->GetContentByName(kDataMid));
  auto dcd = GetFirstSctpDataContentDescription(offer.get());
  ASSERT_TRUE(dcd);
  // Since this transport is insecure, the protocol should be "SCTP".
  EXPECT_EQ(dcd->protocol(), kMediaProtocolUdpDtlsSctp);
}

// Create an SCTP data offer with bundle without error.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSecureSctpDataOffer) {
  MediaSessionOptions opts;
  opts.bundle_enabled = true;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  EXPECT_TRUE(offer.get());
  EXPECT_TRUE(offer->GetContentByName(kDataMid));
  auto dcd = GetFirstSctpDataContentDescription(offer.get());
  ASSERT_TRUE(dcd);
  // The protocol should now be "UDP/DTLS/SCTP"
  EXPECT_EQ(dcd->protocol(), kMediaProtocolUdpDtlsSctp);
}

// Test creating an sctp data channel from an already generated offer.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateImplicitSctpDataOffer) {
  MediaSessionOptions opts;
  opts.bundle_enabled = true;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer1(
      f1_.CreateOfferOrError(opts, nullptr).MoveValue());
  ASSERT_TRUE(offer1.get());
  const ContentInfo* data = offer1->GetContentByName(kDataMid);
  ASSERT_TRUE(data);
  ASSERT_EQ(data->media_description()->protocol(), kMediaProtocolUdpDtlsSctp);

  std::unique_ptr<SessionDescription> offer2(
      f1_.CreateOfferOrError(opts, offer1.get()).MoveValue());
  data = offer2->GetContentByName(kDataMid);
  ASSERT_TRUE(data);
  EXPECT_EQ(data->media_description()->protocol(), kMediaProtocolUdpDtlsSctp);
}

// Test that if BUNDLE is enabled and all media sections are rejected then the
// BUNDLE group is not present in the re-offer.
TEST_F(MediaSessionDescriptionFactoryTest, ReOfferNoBundleGroupIfAllRejected) {
  MediaSessionOptions opts;
  opts.bundle_enabled = true;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();

  opts.media_description_options[0].stopped = true;
  std::unique_ptr<SessionDescription> reoffer =
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue();

  EXPECT_FALSE(reoffer->GetGroupByName(GROUP_TYPE_BUNDLE));
}

// Test that if BUNDLE is enabled and the remote re-offer does not include a
// BUNDLE group since all media sections are rejected, then the re-answer also
// does not include a BUNDLE group.
TEST_F(MediaSessionDescriptionFactoryTest, ReAnswerNoBundleGroupIfAllRejected) {
  MediaSessionOptions opts;
  opts.bundle_enabled = true;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  opts.media_description_options[0].stopped = true;
  std::unique_ptr<SessionDescription> reoffer =
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
  std::unique_ptr<SessionDescription> reanswer =
      f2_.CreateAnswerOrError(reoffer.get(), opts, answer.get()).MoveValue();

  EXPECT_FALSE(reanswer->GetGroupByName(GROUP_TYPE_BUNDLE));
}

// Test that if BUNDLE is enabled and the previous offerer-tagged media section
// was rejected then the new offerer-tagged media section is the non-rejected
// media section.
TEST_F(MediaSessionDescriptionFactoryTest, ReOfferChangeBundleOffererTagged) {
  MediaSessionOptions opts;
  opts.bundle_enabled = true;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();

  // Reject the audio m= section and add a video m= section.
  opts.media_description_options[0].stopped = true;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> reoffer =
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue();

  const ContentGroup* bundle_group = reoffer->GetGroupByName(GROUP_TYPE_BUNDLE);
  ASSERT_TRUE(bundle_group);
  EXPECT_FALSE(bundle_group->HasContentName(kAudioMid));
  EXPECT_TRUE(bundle_group->HasContentName(kVideoMid));
}

// Test that if BUNDLE is enabled and the previous offerer-tagged media section
// was rejected and a new media section is added, then the re-answer BUNDLE
// group will contain only the non-rejected media section.
TEST_F(MediaSessionDescriptionFactoryTest, ReAnswerChangedBundleOffererTagged) {
  MediaSessionOptions opts;
  opts.bundle_enabled = true;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  // Reject the audio m= section and add a video m= section.
  opts.media_description_options[0].stopped = true;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> reoffer =
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
  std::unique_ptr<SessionDescription> reanswer =
      f2_.CreateAnswerOrError(reoffer.get(), opts, answer.get()).MoveValue();

  const ContentGroup* bundle_group =
      reanswer->GetGroupByName(GROUP_TYPE_BUNDLE);
  ASSERT_TRUE(bundle_group);
  EXPECT_FALSE(bundle_group->HasContentName(kAudioMid));
  EXPECT_TRUE(bundle_group->HasContentName(kVideoMid));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       CreateAnswerForOfferWithMultipleBundleGroups) {
  // Create an offer with 4 m= sections, initially without BUNDLE groups.
  MediaSessionOptions opts;
  opts.bundle_enabled = false;
  AddMediaDescriptionOptions(MediaType::AUDIO, "1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::AUDIO, "2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::AUDIO, "3",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::AUDIO, "4",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer->groups().empty());

  // Munge the offer to have two groups. Offers like these cannot be generated
  // without munging, but it is valid to receive such offers from remote
  // endpoints.
  ContentGroup bundle_group1(GROUP_TYPE_BUNDLE);
  bundle_group1.AddContentName("1");
  bundle_group1.AddContentName("2");
  ContentGroup bundle_group2(GROUP_TYPE_BUNDLE);
  bundle_group2.AddContentName("3");
  bundle_group2.AddContentName("4");
  offer->AddGroup(bundle_group1);
  offer->AddGroup(bundle_group2);

  // If BUNDLE is enabled, the answer to this offer should accept both BUNDLE
  // groups.
  opts.bundle_enabled = true;
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  std::vector<const ContentGroup*> answer_groups =
      answer->GetGroupsByName(GROUP_TYPE_BUNDLE);
  ASSERT_EQ(answer_groups.size(), 2u);
  EXPECT_EQ(answer_groups[0]->content_names().size(), 2u);
  EXPECT_TRUE(answer_groups[0]->HasContentName("1"));
  EXPECT_TRUE(answer_groups[0]->HasContentName("2"));
  EXPECT_EQ(answer_groups[1]->content_names().size(), 2u);
  EXPECT_TRUE(answer_groups[1]->HasContentName("3"));
  EXPECT_TRUE(answer_groups[1]->HasContentName("4"));

  // If BUNDLE is disabled, the answer to this offer should reject both BUNDLE
  // groups.
  opts.bundle_enabled = false;
  answer = f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  answer_groups = answer->GetGroupsByName(GROUP_TYPE_BUNDLE);
  // Rejected groups are still listed, but they are empty.
  ASSERT_EQ(answer_groups.size(), 2u);
  EXPECT_TRUE(answer_groups[0]->content_names().empty());
  EXPECT_TRUE(answer_groups[1]->content_names().empty());
}

// Test that if the BUNDLE offerer-tagged media section is changed in a reoffer
// and there is still a non-rejected media section that was in the initial
// offer, then the ICE credentials do not change in the reoffer offerer-tagged
// media section.
TEST_F(MediaSessionDescriptionFactoryTest,
       ReOfferChangeBundleOffererTaggedKeepsIceCredentials) {
  MediaSessionOptions opts;
  opts.bundle_enabled = true;
  AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  // Reject the audio m= section.
  opts.media_description_options[0].stopped = true;
  std::unique_ptr<SessionDescription> reoffer =
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue();

  const TransportDescription* offer_tagged =
      offer->GetTransportDescriptionByName(kAudioMid);
  ASSERT_TRUE(offer_tagged);
  const TransportDescription* reoffer_tagged =
      reoffer->GetTransportDescriptionByName(kVideoMid);
  ASSERT_TRUE(reoffer_tagged);
  EXPECT_EQ(offer_tagged->ice_ufrag, reoffer_tagged->ice_ufrag);
  EXPECT_EQ(offer_tagged->ice_pwd, reoffer_tagged->ice_pwd);
}

// Test that if the BUNDLE offerer-tagged media section is changed in a reoffer
// and there is still a non-rejected media section that was in the initial
// offer, then the ICE credentials do not change in the reanswer answerer-tagged
// media section.
TEST_F(MediaSessionDescriptionFactoryTest,
       ReAnswerChangeBundleOffererTaggedKeepsIceCredentials) {
  MediaSessionOptions opts;
  opts.bundle_enabled = true;
  AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  // Reject the audio m= section.
  opts.media_description_options[0].stopped = true;
  std::unique_ptr<SessionDescription> reoffer =
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
  std::unique_ptr<SessionDescription> reanswer =
      f2_.CreateAnswerOrError(reoffer.get(), opts, answer.get()).MoveValue();

  const TransportDescription* answer_tagged =
      answer->GetTransportDescriptionByName(kAudioMid);
  ASSERT_TRUE(answer_tagged);
  const TransportDescription* reanswer_tagged =
      reanswer->GetTransportDescriptionByName(kVideoMid);
  ASSERT_TRUE(reanswer_tagged);
  EXPECT_EQ(answer_tagged->ice_ufrag, reanswer_tagged->ice_ufrag);
  EXPECT_EQ(answer_tagged->ice_pwd, reanswer_tagged->ice_pwd);
}

// Create an audio, video offer without legacy StreamParams.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateOfferWithoutLegacyStreams) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* ac = offer->GetContentByName(kAudioMid);
  const ContentInfo* vc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();

  EXPECT_FALSE(vcd->has_ssrcs());  // No StreamParams.
  EXPECT_FALSE(acd->has_ssrcs());  // No StreamParams.
}

// Creates an audio+video sendonly offer.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSendOnlyOffer) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kSendOnly, &opts);
  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);
  AttachSenderToMediaDescriptionOptions(
      kAudioMid, MediaType::AUDIO, kAudioTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  EXPECT_EQ(offer->contents().size(), 2u);
  EXPECT_TRUE(IsMediaContentOfType(&offer->contents()[0], MediaType::AUDIO));
  EXPECT_TRUE(IsMediaContentOfType(&offer->contents()[1], MediaType::VIDEO));

  EXPECT_EQ(GetMediaDirection(&offer->contents()[0]),
            RtpTransceiverDirection::kSendOnly);
  EXPECT_EQ(GetMediaDirection(&offer->contents()[1]),
            RtpTransceiverDirection::kSendOnly);
}

// Verifies that the order of the media contents in the current
// SessionDescription is preserved in the new SessionDescription.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferContentOrder) {
  MediaSessionOptions opts;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);

  std::unique_ptr<SessionDescription> offer1(
      f1_.CreateOfferOrError(opts, nullptr).MoveValue());
  ASSERT_TRUE(offer1.get());
  EXPECT_EQ(offer1->contents().size(), 1u);
  EXPECT_TRUE(IsMediaContentOfType(&offer1->contents()[0], MediaType::DATA));

  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer2(
      f1_.CreateOfferOrError(opts, offer1.get()).MoveValue());
  ASSERT_TRUE(offer2.get());
  EXPECT_EQ(offer2->contents().size(), 2u);
  EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[0], MediaType::DATA));
  EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[1], MediaType::VIDEO));

  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer3(
      f1_.CreateOfferOrError(opts, offer2.get()).MoveValue());
  ASSERT_TRUE(offer3.get());
  EXPECT_EQ(offer3->contents().size(), 3u);
  EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[0], MediaType::DATA));
  EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[1], MediaType::VIDEO));
  EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[2], MediaType::AUDIO));
}

// Create a typical audio answer, and ensure it matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswer) {
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(CreateAudioMediaSession(), nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), CreateAudioMediaSession(), nullptr)
          .MoveValue();
  const ContentInfo* ac = answer->GetContentByName(kAudioMid);
  const ContentInfo* vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  EXPECT_FALSE(vc);
  EXPECT_EQ(ac->type, MediaProtocolType::kRtp);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kAudioCodecsAnswer, &env_.field_trials()));
  EXPECT_EQ(acd->first_ssrc(), 0U);             // no sender is attached
  EXPECT_EQ(acd->bandwidth(), kAutoBandwidth);  // negotiated auto bw
  EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
  EXPECT_EQ(acd->protocol(), kMediaProtocolDtlsSavpf);
}

// Create a typical audio answer with GCM ciphers enabled, and ensure it
// matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerGcm) {
  MediaSessionOptions opts = CreateAudioMediaSession();
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* ac = answer->GetContentByName(kAudioMid);
  const ContentInfo* vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  EXPECT_FALSE(vc);
  EXPECT_EQ(ac->type, MediaProtocolType::kRtp);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kAudioCodecsAnswer, &env_.field_trials()));
  EXPECT_EQ(acd->first_ssrc(), 0U);             // no sender is attached
  EXPECT_EQ(acd->bandwidth(), kAutoBandwidth);  // negotiated auto bw
  EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
  EXPECT_EQ(acd->protocol(), kMediaProtocolDtlsSavpf);
}

// Create an audio answer with no common codecs, and ensure it is rejected.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateAudioAnswerWithNoCommonCodecs) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  codec_lookup_helper_1_.SetAudioCodecs(
      {CreateAudioCodec(96, "opus", 48000, 1)});
  codec_lookup_helper_2_.SetAudioCodecs({CreateAudioCodec(0, "PCMU", 8000, 1)});

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* ac = answer->GetContentByName(kAudioMid);
  ASSERT_TRUE(ac);
  EXPECT_TRUE(ac->rejected);
}

// Create a typical video answer, and ensure it matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* ac = answer->GetContentByName(kAudioMid);
  const ContentInfo* vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  EXPECT_EQ(ac->type, MediaProtocolType::kRtp);
  EXPECT_EQ(vc->type, MediaProtocolType::kRtp);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();
  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kAudioCodecsAnswer, &env_.field_trials()));
  EXPECT_EQ(acd->bandwidth(), kAutoBandwidth);  // negotiated auto bw
  EXPECT_EQ(acd->first_ssrc(), 0U);             // no sender is attached
  EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
  EXPECT_EQ(vcd->type(), MediaType::VIDEO);
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(kVideoCodecsAnswer, &env_.field_trials()));
  EXPECT_EQ(vcd->first_ssrc(), 0U);  // no sender is attached
  EXPECT_TRUE(vcd->rtcp_mux());      // negotiated rtcp-mux
  EXPECT_EQ(vcd->protocol(), kMediaProtocolDtlsSavpf);
}

// Create a video answer with no common codecs, and ensure it is rejected.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateVideoAnswerWithNoCommonCodecs) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  codec_lookup_helper_1_.SetVideoCodecs({CreateVideoCodec(96, "H264")});
  codec_lookup_helper_2_.SetVideoCodecs({CreateVideoCodec(97, "VP8")});

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(vc);
  EXPECT_TRUE(vc->rejected);
}

// Create a video answer with no common codecs (but a common FEC codec), and
// ensure it is rejected.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateVideoAnswerWithOnlyFecCodecsCommon) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  codec_lookup_helper_1_.SetVideoCodecs(
      {CreateVideoCodec(96, "H264"), CreateVideoCodec(118, "flexfec-03")});
  codec_lookup_helper_2_.SetVideoCodecs(
      {CreateVideoCodec(97, "VP8"), CreateVideoCodec(118, "flexfec-03")});

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(vc);
  EXPECT_TRUE(vc->rejected);
}

// The use_sctpmap flag should be set in an Sctp DataContentDescription by
// default. The answer's use_sctpmap flag should match the offer's.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerUsesSctpmap) {
  MediaSessionOptions opts;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  ContentInfo* dc_offer = offer->GetContentByName(kDataMid);
  ASSERT_TRUE(dc_offer);
  SctpDataContentDescription* dcd_offer =
      dc_offer->media_description()->as_sctp();
  EXPECT_TRUE(dcd_offer->use_sctpmap());

  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* dc_answer = answer->GetContentByName(kDataMid);
  ASSERT_TRUE(dc_answer);
  const SctpDataContentDescription* dcd_answer =
      dc_answer->media_description()->as_sctp();
  EXPECT_TRUE(dcd_answer->use_sctpmap());
}

// The answer's use_sctpmap flag should match the offer's.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerWithoutSctpmap) {
  MediaSessionOptions opts;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  ContentInfo* dc_offer = offer->GetContentByName(kDataMid);
  ASSERT_TRUE(dc_offer);
  SctpDataContentDescription* dcd_offer =
      dc_offer->media_description()->as_sctp();
  dcd_offer->set_use_sctpmap(false);

  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* dc_answer = answer->GetContentByName(kDataMid);
  ASSERT_TRUE(dc_answer);
  const SctpDataContentDescription* dcd_answer =
      dc_answer->media_description()->as_sctp();
  EXPECT_FALSE(dcd_answer->use_sctpmap());
}

// Test that a valid answer will be created for "DTLS/SCTP", "UDP/DTLS/SCTP"
// and "TCP/DTLS/SCTP" offers.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateDataAnswerToDifferentOfferedProtos) {
  MediaSessionOptions opts;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  ContentInfo* dc_offer = offer->GetContentByName(kDataMid);
  ASSERT_TRUE(dc_offer);
  SctpDataContentDescription* dcd_offer =
      dc_offer->media_description()->as_sctp();
  ASSERT_TRUE(dcd_offer);

  std::vector<std::string> protos = {"DTLS/SCTP", "UDP/DTLS/SCTP",
                                     "TCP/DTLS/SCTP"};
  for (const std::string& proto : protos) {
    dcd_offer->set_protocol(proto);
    std::unique_ptr<SessionDescription> answer =
        f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
    const ContentInfo* dc_answer = answer->GetContentByName(kDataMid);
    ASSERT_TRUE(dc_answer);
    const SctpDataContentDescription* dcd_answer =
        dc_answer->media_description()->as_sctp();
    EXPECT_FALSE(dc_answer->rejected);
    EXPECT_EQ(proto, dcd_answer->protocol());
  }
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateDataAnswerToOfferWithDefinedMessageSize) {
  MediaSessionOptions opts;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  ContentInfo* dc_offer = offer->GetContentByName(kDataMid);
  ASSERT_TRUE(dc_offer);
  SctpDataContentDescription* dcd_offer =
      dc_offer->media_description()->as_sctp();
  ASSERT_TRUE(dcd_offer);
  dcd_offer->set_max_message_size(1234);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* dc_answer = answer->GetContentByName(kDataMid);
  ASSERT_TRUE(dc_answer);
  const SctpDataContentDescription* dcd_answer =
      dc_answer->media_description()->as_sctp();
  EXPECT_FALSE(dc_answer->rejected);
  EXPECT_EQ(dcd_answer->max_message_size(), 1234);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateDataAnswerToOfferWithZeroMessageSize) {
  MediaSessionOptions opts;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  ContentInfo* dc_offer = offer->GetContentByName(kDataMid);
  ASSERT_TRUE(dc_offer);
  SctpDataContentDescription* dcd_offer =
      dc_offer->media_description()->as_sctp();
  ASSERT_TRUE(dcd_offer);
  dcd_offer->set_max_message_size(0);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* dc_answer = answer->GetContentByName(kDataMid);
  ASSERT_TRUE(dc_answer);
  const SctpDataContentDescription* dcd_answer =
      dc_answer->media_description()->as_sctp();
  EXPECT_FALSE(dc_answer->rejected);
  EXPECT_EQ(dcd_answer->max_message_size(), kSctpSendBufferSize);
}

class MediaSessionDescriptionFactorySnapTest
    : public MediaSessionDescriptionFactoryTest {
 public:
  MediaSessionDescriptionFactorySnapTest()
      : MediaSessionDescriptionFactoryTest("WebRTC-Sctp-Snap/Enabled/") {}
};

TEST_F(MediaSessionDescriptionFactorySnapTest,
       TestCreateDataAnswerToOfferWithSctpInit) {
  MediaSessionOptions opts;
  opts.use_sctp_snap = true;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ContentInfo* dc_offer = offer->GetContentByName("2");
  ASSERT_THAT(dc_offer, NotNull());
  SctpDataContentDescription* dcd_offer =
      dc_offer->media_description()->as_sctp();
  ASSERT_THAT(dcd_offer, NotNull());
  EXPECT_NE(dcd_offer->sctp_init(), std::nullopt);
  dcd_offer->set_sctp_init(std::nullopt);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* dc_answer = answer->GetContentByName("2");
  ASSERT_THAT(dc_answer, NotNull());
  const SctpDataContentDescription* dcd_answer =
      dc_answer->media_description()->as_sctp();
  ASSERT_THAT(dcd_answer, NotNull());
  EXPECT_FALSE(dc_answer->rejected);
  EXPECT_EQ(dcd_answer->sctp_init(), std::nullopt);
}

TEST_F(MediaSessionDescriptionFactorySnapTest,
       TestCreateOfferAfterAnswerWithoutSctpInit) {
  MediaSessionOptions opts;
  opts.use_sctp_snap = true;
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ContentInfo* dc_offer = offer->GetContentByName("2");
  ASSERT_THAT(dc_offer, NotNull());
  SctpDataContentDescription* dcd_offer =
      dc_offer->media_description()->as_sctp();
  ASSERT_THAT(dcd_offer, NotNull());
  EXPECT_NE(dcd_offer->sctp_init(), std::nullopt);
  dcd_offer->set_sctp_init(std::nullopt);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* dc_answer = answer->GetContentByName("2");
  ASSERT_THAT(dc_answer, NotNull());
  const SctpDataContentDescription* dcd_answer =
      dc_answer->media_description()->as_sctp();
  ASSERT_THAT(dcd_answer, NotNull());
  EXPECT_FALSE(dc_answer->rejected);
  EXPECT_EQ(dcd_answer->sctp_init(), std::nullopt);

  std::unique_ptr<SessionDescription> reoffer =
      f2_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(reoffer, NotNull());
  ContentInfo* dc_reoffer = reoffer->GetContentByName("2");
  ASSERT_THAT(dc_reoffer, NotNull());
  SctpDataContentDescription* dcd_reoffer =
      dc_reoffer->media_description()->as_sctp();
  ASSERT_THAT(dcd_reoffer, NotNull());
  EXPECT_FALSE(dc_reoffer->rejected);
  EXPECT_NE(dcd_reoffer->sctp_init(), std::nullopt);
}

// TODO: bugs.webrtc.org/426480601 - add more SNAP tests here:
// - two calls to createOffer without SLD get different cookie
// - subsequent createOffer after SLD retains old cookie
// - subsequent createOffer after offer without cookie has new cookie
//   (or stale cookie?)

// Verifies that the order of the media contents in the offer is preserved in
// the answer.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAnswerContentOrder) {
  MediaSessionOptions opts;

  // Creates a data only offer.
  AddDataSection(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer1(
      f1_.CreateOfferOrError(opts, nullptr).MoveValue());
  ASSERT_TRUE(offer1.get());

  // Appends audio to the offer.
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer2(
      f1_.CreateOfferOrError(opts, offer1.get()).MoveValue());
  ASSERT_TRUE(offer2.get());

  // Appends video to the offer.
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer3(
      f1_.CreateOfferOrError(opts, offer2.get()).MoveValue());
  ASSERT_TRUE(offer3.get());

  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer3.get(), opts, nullptr).MoveValue();
  ASSERT_TRUE(answer.get());
  EXPECT_EQ(answer->contents().size(), 3u);
  EXPECT_TRUE(IsMediaContentOfType(&answer->contents()[0], MediaType::DATA));
  EXPECT_TRUE(IsMediaContentOfType(&answer->contents()[1], MediaType::AUDIO));
  EXPECT_TRUE(IsMediaContentOfType(&answer->contents()[2], MediaType::VIDEO));
}

// TODO(deadbeef): Extend these tests to ensure the correct direction with other
// answerer settings.

// This test that the media direction is set to send/receive in an answer if
// the offer is send receive.
TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerToSendReceiveOffer) {
  TestMediaDirectionInAnswer(RtpTransceiverDirection::kSendRecv,
                             RtpTransceiverDirection::kSendRecv);
}

// This test that the media direction is set to receive only in an answer if
// the offer is send only.
TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerToSendOnlyOffer) {
  TestMediaDirectionInAnswer(RtpTransceiverDirection::kSendOnly,
                             RtpTransceiverDirection::kRecvOnly);
}

// This test that the media direction is set to send only in an answer if
// the offer is recv only.
TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerToRecvOnlyOffer) {
  TestMediaDirectionInAnswer(RtpTransceiverDirection::kRecvOnly,
                             RtpTransceiverDirection::kSendOnly);
}

// This test that the media direction is set to inactive in an answer if
// the offer is inactive.
TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerToInactiveOffer) {
  TestMediaDirectionInAnswer(RtpTransceiverDirection::kInactive,
                             RtpTransceiverDirection::kInactive);
}

// Test that the media protocol is RTP/AVPF if DTLS is disabled.
TEST_F(MediaSessionDescriptionFactoryTest, AudioOfferAnswerWithCryptoDisabled) {
  MediaSessionOptions opts = CreateAudioMediaSession();
  tdf1_.SetInsecureForTesting();
  tdf1_.set_certificate(nullptr);
  tdf2_.SetInsecureForTesting();
  tdf2_.set_certificate(nullptr);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  const AudioContentDescription* offer_acd =
      GetFirstAudioContentDescription(offer.get());
  ASSERT_TRUE(offer_acd);
  EXPECT_EQ(offer_acd->protocol(), kMediaProtocolAvpf);

  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  const ContentInfo* ac_answer = answer->GetContentByName(kAudioMid);
  ASSERT_TRUE(ac_answer);
  EXPECT_FALSE(ac_answer->rejected);

  const AudioContentDescription* answer_acd =
      GetFirstAudioContentDescription(answer.get());
  ASSERT_TRUE(answer_acd);
  EXPECT_EQ(answer_acd->protocol(), kMediaProtocolAvpf);
}

// Create a audio/video offer and answer and ensure that the
// TransportSequenceNumber RTP v1 and v2 header extensions are handled
// correctly.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithTransportSequenceNumberV1LocalAndV1InOffer) {
  TestTransportSequenceNumberNegotiation(
      kRtpExtensionTransportSequenceNumber01,   // Local.
      kRtpExtensionTransportSequenceNumber01,   // Offer.
      kRtpExtensionTransportSequenceNumber01);  // Expected answer.
}
TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithTransportSequenceNumberV1LocalAndV1V2InOffer) {
  TestTransportSequenceNumberNegotiation(
      kRtpExtensionTransportSequenceNumber01,       // Local.
      kRtpExtensionTransportSequenceNumber01And02,  // Offer.
      kRtpExtensionTransportSequenceNumber01);      // Expected answer.
}
TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithTransportSequenceNumberV1LocalAndV2InOffer) {
  TestTransportSequenceNumberNegotiation(
      kRtpExtensionTransportSequenceNumber01,  // Local.
      kRtpExtensionTransportSequenceNumber02,  // Offer.
      {});                                     // Expected answer.
}
TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithTransportSequenceNumberV2LocalAndV1InOffer) {
  TestTransportSequenceNumberNegotiation(
      kRtpExtensionTransportSequenceNumber02,  // Local.
      kRtpExtensionTransportSequenceNumber01,  // Offer.
      {});                                     // Expected answer.
}
TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithTransportSequenceNumberV2LocalAndV1V2InOffer) {
  TestTransportSequenceNumberNegotiation(
      kRtpExtensionTransportSequenceNumber02,       // Local.
      kRtpExtensionTransportSequenceNumber01And02,  // Offer.
      kRtpExtensionTransportSequenceNumber02);      // Expected answer.
}
TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithTransportSequenceNumberV2LocalAndV2InOffer) {
  TestTransportSequenceNumberNegotiation(
      kRtpExtensionTransportSequenceNumber02,   // Local.
      kRtpExtensionTransportSequenceNumber02,   // Offer.
      kRtpExtensionTransportSequenceNumber02);  // Expected answer.
}
TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithTransportSequenceNumberV1V2LocalAndV1InOffer) {
  TestTransportSequenceNumberNegotiation(
      kRtpExtensionTransportSequenceNumber01And02,  // Local.
      kRtpExtensionTransportSequenceNumber01,       // Offer.
      kRtpExtensionTransportSequenceNumber01);      // Expected answer.
}
TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithTransportSequenceNumberV1V2LocalAndV2InOffer) {
  TestTransportSequenceNumberNegotiation(
      kRtpExtensionTransportSequenceNumber01And02,  // Local.
      kRtpExtensionTransportSequenceNumber02,       // Offer.
      kRtpExtensionTransportSequenceNumber02);      // Expected answer.
}
TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithTransportSequenceNumberV1V2LocalAndV1V2InOffer) {
  TestTransportSequenceNumberNegotiation(
      kRtpExtensionTransportSequenceNumber01And02,   // Local.
      kRtpExtensionTransportSequenceNumber01And02,   // Offer.
      kRtpExtensionTransportSequenceNumber01And02);  // Expected answer.
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestNegotiateFrameDescriptorWhenUnexposedLocally) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  SetAudioVideoRtpHeaderExtensions(kRtpExtensionGenericFrameDescriptorUri00,
                                   kRtpExtensionGenericFrameDescriptorUri00,
                                   &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  SetAudioVideoRtpHeaderExtensions(kRtpExtensionTransportSequenceNumber01,
                                   kRtpExtensionTransportSequenceNumber01,
                                   &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      ElementsAreArray(kRtpExtensionGenericFrameDescriptorUri00));
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      ElementsAreArray(kRtpExtensionGenericFrameDescriptorUri00));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestNegotiateFrameDescriptorWhenExposedLocally) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  SetAudioVideoRtpHeaderExtensions(kRtpExtensionGenericFrameDescriptorUri00,
                                   kRtpExtensionGenericFrameDescriptorUri00,
                                   &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      ElementsAreArray(kRtpExtensionGenericFrameDescriptorUri00));
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      ElementsAreArray(kRtpExtensionGenericFrameDescriptorUri00));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       NegotiateDependencyDescriptorWhenUnexposedLocally) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  RtpExtension offer_dd(RtpExtension::kDependencyDescriptorUri, 7);
  SetAudioVideoRtpHeaderExtensions({}, RtpHeaderExtensions{offer_dd}, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  RtpExtension local_tsn(RtpExtension::kTransportSequenceNumberUri, 5);
  SetAudioVideoRtpHeaderExtensions({}, RtpHeaderExtensions{local_tsn}, &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      ElementsAre(offer_dd));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       NegotiateDependencyDescriptorWhenExposedLocally) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  RtpExtension offer_dd(RtpExtension::kDependencyDescriptorUri, 7);
  RtpExtension local_dd(RtpExtension::kDependencyDescriptorUri, 5);
  SetAudioVideoRtpHeaderExtensions({}, RtpHeaderExtensions{offer_dd}, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  SetAudioVideoRtpHeaderExtensions({}, RtpHeaderExtensions{local_dd}, &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      ElementsAre(offer_dd));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       NegotiateAbsoluteCaptureTimeWhenUnexposedLocally) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  const RtpHeaderExtensions offered_extensions = {
      RtpExtension(RtpExtension::kAbsoluteCaptureTimeUri, 7)};
  const RtpHeaderExtensions local_extensions = {
      RtpExtension(RtpExtension::kTransportSequenceNumberUri, 5)};
  SetAudioVideoRtpHeaderExtensions(offered_extensions, offered_extensions,
                                   &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  SetAudioVideoRtpHeaderExtensions(local_extensions, local_extensions, &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      ElementsAreArray(offered_extensions));
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      ElementsAreArray(offered_extensions));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       NegotiateAbsoluteCaptureTimeWhenExposedLocally) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  const RtpHeaderExtensions offered_extensions = {
      RtpExtension(RtpExtension::kAbsoluteCaptureTimeUri, 7)};
  const RtpHeaderExtensions local_extensions = {
      RtpExtension(RtpExtension::kAbsoluteCaptureTimeUri, 5)};
  SetAudioVideoRtpHeaderExtensions(offered_extensions, offered_extensions,
                                   &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  SetAudioVideoRtpHeaderExtensions(local_extensions, local_extensions, &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      ElementsAreArray(offered_extensions));
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      ElementsAreArray(offered_extensions));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       DoNotNegotiateAbsoluteCaptureTimeWhenNotOffered) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  const RtpHeaderExtensions offered_extensions = {
      RtpExtension(RtpExtension::kTransportSequenceNumberUri, 7)};
  const RtpHeaderExtensions local_extensions = {
      RtpExtension(RtpExtension::kAbsoluteCaptureTimeUri, 5)};
  SetAudioVideoRtpHeaderExtensions(offered_extensions, offered_extensions,
                                   &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  SetAudioVideoRtpHeaderExtensions(local_extensions, local_extensions, &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      IsEmpty());
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      IsEmpty());
}

TEST_F(MediaSessionDescriptionFactoryTest,
       OffersUnstoppedExtensionsWithAudioVideoExtensionStopped) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri1", 1,
                                   RtpTransceiverDirection::kStopped),
      RtpHeaderExtensionCapability("uri2", 3,
                                   RtpTransceiverDirection::kSendOnly)};
  AddMediaDescriptionOptions(MediaType::VIDEO, "video1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri1", 1,
                                   RtpTransceiverDirection::kStopped),
      RtpHeaderExtensionCapability("uri3", 7,
                                   RtpTransceiverDirection::kSendOnly)};
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  EXPECT_THAT(
      offer->contents(),
      ElementsAre(
          Property(&ContentInfo::media_description,
                   Pointee(Property(
                       &MediaContentDescription::rtp_header_extensions,
                       ElementsAre(Field(&RtpExtension::uri, "uri2"))))),
          Property(&ContentInfo::media_description,
                   Pointee(Property(
                       &MediaContentDescription::rtp_header_extensions,
                       ElementsAre(Field(&RtpExtension::uri, "uri3")))))));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       OffersUnstoppedExtensionsWithAudioExtensionStopped) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri1", 1,
                                   RtpTransceiverDirection::kSendOnly),
      RtpHeaderExtensionCapability("uri2", 3,
                                   RtpTransceiverDirection::kStopped)};
  AddMediaDescriptionOptions(MediaType::VIDEO, "video1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri42", 42,
                                   RtpTransceiverDirection::kSendRecv),
      RtpHeaderExtensionCapability("uri3", 7,
                                   RtpTransceiverDirection::kSendOnly)};
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  EXPECT_THAT(
      offer->contents(),
      ElementsAre(
          Property(&ContentInfo::media_description,
                   Pointee(Property(
                       &MediaContentDescription::rtp_header_extensions,
                       ElementsAre(Field(&RtpExtension::uri, "uri1"))))),
          Property(
              &ContentInfo::media_description,
              Pointee(Property(
                  &MediaContentDescription::rtp_header_extensions,
                  UnorderedElementsAre(Field(&RtpExtension::uri, "uri3"),
                                       Field(&RtpExtension::uri, "uri42")))))));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       OffersUnstoppedExtensionsWithVideoExtensionStopped) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri1", 5,
                                   RtpTransceiverDirection::kSendOnly),
      RtpHeaderExtensionCapability("uri2", 7,
                                   RtpTransceiverDirection::kSendRecv)};
  AddMediaDescriptionOptions(MediaType::VIDEO, "video1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri42", 42,
                                   RtpTransceiverDirection::kSendRecv),
      RtpHeaderExtensionCapability("uri3", 7,
                                   RtpTransceiverDirection::kStopped)};
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  EXPECT_THAT(
      offer->contents(),
      ElementsAre(
          Property(
              &ContentInfo::media_description,
              Pointee(Property(
                  &MediaContentDescription::rtp_header_extensions,
                  UnorderedElementsAre(Field(&RtpExtension::uri, "uri1"),
                                       Field(&RtpExtension::uri, "uri2"))))),
          Property(&ContentInfo::media_description,
                   Pointee(Property(
                       &MediaContentDescription::rtp_header_extensions,
                       ElementsAre(Field(&RtpExtension::uri, "uri42")))))));
}

TEST_F(MediaSessionDescriptionFactoryTest, AnswersUnstoppedExtensions) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri1", 4,
                                   RtpTransceiverDirection::kStopped),
      RtpHeaderExtensionCapability("uri2", 3,
                                   RtpTransceiverDirection::kSendOnly),
      RtpHeaderExtensionCapability("uri3", 2,
                                   RtpTransceiverDirection::kRecvOnly),
      RtpHeaderExtensionCapability("uri4", 1,
                                   RtpTransceiverDirection::kSendRecv)};
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri1", 4,
                                   RtpTransceiverDirection::kSendOnly),
      RtpHeaderExtensionCapability("uri2", 3,
                                   RtpTransceiverDirection::kRecvOnly),
      RtpHeaderExtensionCapability("uri3", 2,
                                   RtpTransceiverDirection::kStopped),
      RtpHeaderExtensionCapability("uri4", 1,
                                   RtpTransceiverDirection::kSendRecv)};
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  EXPECT_THAT(
      answer->contents(),
      ElementsAre(Property(
          &ContentInfo::media_description,
          Pointee(Property(&MediaContentDescription::rtp_header_extensions,
                           ElementsAre(Field(&RtpExtension::uri, "uri2"),
                                       Field(&RtpExtension::uri, "uri4")))))));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       AppendsUnstoppedExtensionsToCurrentDescription) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri1", 1,
                                   RtpTransceiverDirection::kSendRecv)};
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri1", 2,
                                   RtpTransceiverDirection::kSendRecv),
      RtpHeaderExtensionCapability("uri2", 3,
                                   RtpTransceiverDirection::kRecvOnly),
      RtpHeaderExtensionCapability("uri3", 5,
                                   RtpTransceiverDirection::kStopped),
      RtpHeaderExtensionCapability("uri4", 6,
                                   RtpTransceiverDirection::kSendRecv)};
  auto offer2 = f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
  EXPECT_THAT(
      offer2->contents(),
      ElementsAre(Property(
          &ContentInfo::media_description,
          Pointee(Property(&MediaContentDescription::rtp_header_extensions,
                           ElementsAre(Field(&RtpExtension::uri, "uri1"),
                                       Field(&RtpExtension::uri, "uri2"),
                                       Field(&RtpExtension::uri, "uri4")))))));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       AllowsStoppedExtensionsToBeRemovedFromSubsequentOffer) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri1", 1,
                                   RtpTransceiverDirection::kSendRecv),
      RtpHeaderExtensionCapability("uri2", 2,
                                   RtpTransceiverDirection::kSendRecv)};
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();

  // Check that a subsequent offer after setting "uri2" to stopped no longer
  // contains the extension.
  opts.media_description_options.back().header_extensions = {
      RtpHeaderExtensionCapability("uri1", 1,
                                   RtpTransceiverDirection::kSendRecv),
      RtpHeaderExtensionCapability("uri2", 2,
                                   RtpTransceiverDirection::kStopped)};
  auto offer2 = f1_.CreateOfferOrError(opts, offer.get()).MoveValue();
  EXPECT_THAT(
      offer2->contents(),
      ElementsAre(Property(
          &ContentInfo::media_description,
          Pointee(Property(&MediaContentDescription::rtp_header_extensions,
                           ElementsAre(Field(&RtpExtension::uri, "uri1")))))));
}

// Create a video offer and answer and ensure the RTP header extensions
// matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithRtpExtensionHeadersWithNoEncryption) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtension1, kVideoRtpExtension1,
                                   &opts);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtension2, kVideoRtpExtension2,
                                   &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  EXPECT_THAT(
      GetFirstAudioContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtension1));
  EXPECT_THAT(
      GetFirstVideoContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kVideoRtpExtension1));
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtensionAnswer));
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kVideoRtpExtensionAnswer));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestOfferAnswerWithRtpExtensionHeadersWithEncryption) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  f1_.set_enable_encrypted_rtp_header_extensions(true);
  f2_.set_enable_encrypted_rtp_header_extensions(true);

  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtensionEncrypted1,
                                   kVideoRtpExtensionEncrypted1, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtensionEncrypted2,
                                   kVideoRtpExtensionEncrypted2, &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  EXPECT_THAT(
      GetFirstAudioContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtensionEncrypted1));
  EXPECT_THAT(
      GetFirstVideoContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kVideoRtpExtensionEncrypted1));
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtensionEncryptedAnswer));
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kVideoRtpExtensionEncryptedAnswer));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       NegotiationWithEncryptedRtpExtensionHeadersDisabledInReceiver) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  f2_.set_enable_encrypted_rtp_header_extensions(false);

  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtensionEncrypted1,
                                   kVideoRtpExtensionEncrypted1, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtensionEncrypted2,
                                   kVideoRtpExtensionEncrypted2, &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  EXPECT_THAT(
      GetFirstAudioContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtensionEncrypted1));
  EXPECT_THAT(
      GetFirstVideoContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kVideoRtpExtensionEncrypted1));
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtensionAnswer));
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kVideoRtpExtensionAnswer));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       NegotiationWithEncryptedRtpExtensionHeadersDisabledInSender) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  f1_.set_enable_encrypted_rtp_header_extensions(false);

  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtensionEncrypted1,
                                   kVideoRtpExtensionEncrypted1, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtensionEncrypted2,
                                   kVideoRtpExtensionEncrypted2, &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  EXPECT_THAT(
      GetFirstAudioContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtensionAnswer));
  EXPECT_THAT(
      GetFirstVideoContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kVideoRtpExtensionAnswer));
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtensionAnswer));
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kVideoRtpExtensionAnswer));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       PreferEncryptedRtpHeaderExtensionsWhenEncryptionEnabled) {
  MediaSessionOptions opts;
  opts.offer_extmap_allow_mixed = true;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtensionMixedEncryption1,
                                   kVideoRtpExtensionMixedEncryption, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtensionMixedEncryption2,
                                   kVideoRtpExtensionMixedEncryption, &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ASSERT_TRUE(answer.get());

  EXPECT_THAT(
      GetFirstAudioContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtensionMixedEncryption1));
  EXPECT_THAT(
      GetFirstVideoContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kVideoRtpExtensionMixedEncryption));
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(
          kAudioRtpExtensionMixedEncryptionAnswerEncryptionEnabled));
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(
          kVideoRtpExtensionMixedEncryptionAnswerEncryptionEnabled));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       UseUnencryptedRtpHeaderExtensionsWhenEncryptionDisabled) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  f1_.set_enable_encrypted_rtp_header_extensions(false);
  f2_.set_enable_encrypted_rtp_header_extensions(false);

  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtensionMixedEncryption1,
                                   kVideoRtpExtensionMixedEncryption, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtensionMixedEncryption2,
                                   kVideoRtpExtensionMixedEncryption, &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ASSERT_TRUE(answer.get());

  EXPECT_THAT(
      GetFirstAudioContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(
          kAudioRtpExtensionMixedEncryptionAnswerEncryptionDisabled));
  EXPECT_THAT(
      GetFirstVideoContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(
          kVideoRtpExtensionMixedEncryptionAnswerEncryptionDisabled));
  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(
          kAudioRtpExtensionMixedEncryptionAnswerEncryptionDisabled));
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(
          kVideoRtpExtensionMixedEncryptionAnswerEncryptionDisabled));
}

// Create an audio, video, data answer without legacy StreamParams.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateAnswerWithoutLegacyStreams) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* ac = answer->GetContentByName(kAudioMid);
  const ContentInfo* vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();

  EXPECT_FALSE(acd->has_ssrcs());  // No StreamParams.
  EXPECT_FALSE(vcd->has_ssrcs());  // No StreamParams.
}

// Create a typical video answer, and ensure it matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerRtcpMux) {
  MediaSessionOptions offer_opts;
  AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &offer_opts);

  MediaSessionOptions answer_opts;
  AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &answer_opts);

  std::unique_ptr<SessionDescription> offer;
  std::unique_ptr<SessionDescription> answer;

  offer_opts.rtcp_mux_enabled = true;
  answer_opts.rtcp_mux_enabled = true;
  offer = f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
  ASSERT_TRUE(GetFirstAudioContentDescription(offer.get()));
  ASSERT_TRUE(GetFirstVideoContentDescription(offer.get()));
  ASSERT_TRUE(GetFirstAudioContentDescription(answer.get()));
  ASSERT_TRUE(GetFirstVideoContentDescription(answer.get()));
  EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
  EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
  EXPECT_TRUE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
  EXPECT_TRUE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());

  offer_opts.rtcp_mux_enabled = true;
  answer_opts.rtcp_mux_enabled = false;
  offer = f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
  ASSERT_TRUE(GetFirstAudioContentDescription(offer.get()));
  ASSERT_TRUE(GetFirstVideoContentDescription(offer.get()));
  ASSERT_TRUE(GetFirstAudioContentDescription(answer.get()));
  ASSERT_TRUE(GetFirstVideoContentDescription(answer.get()));
  EXPECT_TRUE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
  EXPECT_TRUE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());

  offer_opts.rtcp_mux_enabled = false;
  answer_opts.rtcp_mux_enabled = true;
  offer = f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
  ASSERT_TRUE(GetFirstAudioContentDescription(offer.get()));
  ASSERT_TRUE(GetFirstVideoContentDescription(offer.get()));
  ASSERT_TRUE(GetFirstAudioContentDescription(answer.get()));
  ASSERT_TRUE(GetFirstVideoContentDescription(answer.get()));
  EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
  EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());

  offer_opts.rtcp_mux_enabled = false;
  answer_opts.rtcp_mux_enabled = false;
  offer = f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
  ASSERT_TRUE(GetFirstAudioContentDescription(offer.get()));
  ASSERT_TRUE(GetFirstVideoContentDescription(offer.get()));
  ASSERT_TRUE(GetFirstAudioContentDescription(answer.get()));
  ASSERT_TRUE(GetFirstVideoContentDescription(answer.get()));
  EXPECT_FALSE(GetFirstAudioContentDescription(offer.get())->rtcp_mux());
  EXPECT_FALSE(GetFirstVideoContentDescription(offer.get())->rtcp_mux());
  EXPECT_FALSE(GetFirstAudioContentDescription(answer.get())->rtcp_mux());
  EXPECT_FALSE(GetFirstVideoContentDescription(answer.get())->rtcp_mux());
}

// Create an audio-only answer to a video offer.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerToVideo) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());

  opts.media_description_options[1].stopped = true;
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* ac = answer->GetContentByName(kAudioMid);
  const ContentInfo* vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  ASSERT_TRUE(vc->media_description());
  EXPECT_TRUE(vc->rejected);
}

// Create an answer that rejects the contents which are rejected in the offer.
TEST_F(MediaSessionDescriptionFactoryTest,
       CreateAnswerToOfferWithRejectedMedia) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  ContentInfo* ac = offer->GetContentByName(kAudioMid);
  ContentInfo* vc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  ac->rejected = true;
  vc->rejected = true;
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ac = answer->GetContentByName(kAudioMid);
  vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  EXPECT_TRUE(ac->rejected);
  EXPECT_TRUE(vc->rejected);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       OfferAndAnswerDoesNotHaveMixedByteSessionAttribute) {
  MediaSessionOptions opts;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, /*current_description=*/nullptr).MoveValue();
  offer->set_extmap_allow_mixed(false);

  std::unique_ptr<SessionDescription> answer(
      f2_.CreateAnswerOrError(offer.get(), opts,
                              /*current_description=*/nullptr)
          .MoveValue());

  EXPECT_FALSE(answer->extmap_allow_mixed());
}

TEST_F(MediaSessionDescriptionFactoryTest,
       OfferAndAnswerHaveMixedByteSessionAttribute) {
  MediaSessionOptions opts;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, /*current_description=*/nullptr).MoveValue();
  offer->set_extmap_allow_mixed(true);

  std::unique_ptr<SessionDescription> answer_support(
      f2_.CreateAnswerOrError(offer.get(), opts,
                              /*current_description=*/nullptr)
          .MoveValue());

  EXPECT_TRUE(answer_support->extmap_allow_mixed());
}

TEST_F(MediaSessionDescriptionFactoryTest,
       OfferAndAnswerDoesNotHaveMixedByteMediaAttributes) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, /*current_description=*/nullptr).MoveValue();
  offer->set_extmap_allow_mixed(false);
  MediaContentDescription* audio_offer =
      offer->GetContentDescriptionByName(kAudioMid);
  MediaContentDescription* video_offer =
      offer->GetContentDescriptionByName(kVideoMid);
  ASSERT_EQ(audio_offer->extmap_allow_mixed_level(),
            MediaContentDescription::AttributeLevel::kNone);
  ASSERT_EQ(video_offer->extmap_allow_mixed_level(),
            MediaContentDescription::AttributeLevel::kNone);

  std::unique_ptr<SessionDescription> answer(
      f2_.CreateAnswerOrError(offer.get(), opts,
                              /*current_description=*/nullptr)
          .MoveValue());

  MediaContentDescription* audio_answer =
      answer->GetContentDescriptionByName(kAudioMid);
  MediaContentDescription* video_answer =
      answer->GetContentDescriptionByName(kVideoMid);
  EXPECT_EQ(audio_answer->extmap_allow_mixed_level(),
            MediaContentDescription::AttributeLevel::kNone);
  EXPECT_EQ(video_answer->extmap_allow_mixed_level(),
            MediaContentDescription::AttributeLevel::kNone);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       OfferAndAnswerHaveSameMixedByteMediaAttributes) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, /*current_description=*/nullptr).MoveValue();
  offer->set_extmap_allow_mixed(false);
  MediaContentDescription* audio_offer =
      offer->GetContentDescriptionByName(kAudioMid);
  audio_offer->set_extmap_allow_mixed_level(
      MediaContentDescription::AttributeLevel::kMedia);
  MediaContentDescription* video_offer =
      offer->GetContentDescriptionByName(kVideoMid);
  video_offer->set_extmap_allow_mixed_level(
      MediaContentDescription::AttributeLevel::kMedia);

  std::unique_ptr<SessionDescription> answer(
      f2_.CreateAnswerOrError(offer.get(), opts,
                              /*current_description=*/nullptr)
          .MoveValue());

  MediaContentDescription* audio_answer =
      answer->GetContentDescriptionByName(kAudioMid);
  MediaContentDescription* video_answer =
      answer->GetContentDescriptionByName(kVideoMid);
  EXPECT_EQ(audio_answer->extmap_allow_mixed_level(),
            MediaContentDescription::AttributeLevel::kMedia);
  EXPECT_EQ(video_answer->extmap_allow_mixed_level(),
            MediaContentDescription::AttributeLevel::kMedia);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       OfferAndAnswerHaveDifferentMixedByteMediaAttributes) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, /*current_description=*/nullptr).MoveValue();
  offer->set_extmap_allow_mixed(false);
  MediaContentDescription* audio_offer =
      offer->GetContentDescriptionByName(kAudioMid);
  audio_offer->set_extmap_allow_mixed_level(
      MediaContentDescription::AttributeLevel::kNone);
  MediaContentDescription* video_offer =
      offer->GetContentDescriptionByName(kVideoMid);
  video_offer->set_extmap_allow_mixed_level(
      MediaContentDescription::AttributeLevel::kMedia);

  std::unique_ptr<SessionDescription> answer(
      f2_.CreateAnswerOrError(offer.get(), opts,
                              /*current_description=*/nullptr)
          .MoveValue());

  MediaContentDescription* audio_answer =
      answer->GetContentDescriptionByName(kAudioMid);
  MediaContentDescription* video_answer =
      answer->GetContentDescriptionByName(kVideoMid);
  EXPECT_EQ(audio_answer->extmap_allow_mixed_level(),
            MediaContentDescription::AttributeLevel::kNone);
  EXPECT_EQ(video_answer->extmap_allow_mixed_level(),
            MediaContentDescription::AttributeLevel::kMedia);
}

// Create an audio and video offer with:
// - one video track
// - two audio tracks
// and ensure it matches what we expect. Also updates the initial offer by
// adding a new video track and replaces one of the audio tracks.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoOffer) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &opts);
  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);
  AttachSenderToMediaDescriptionOptions(
      kAudioMid, MediaType::AUDIO, kAudioTrack1, {kMediaStream1}, 1, &opts);
  AttachSenderToMediaDescriptionOptions(
      kAudioMid, MediaType::AUDIO, kAudioTrack2, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();

  ASSERT_TRUE(offer.get());
  const ContentInfo* ac = offer->GetContentByName(kAudioMid);
  const ContentInfo* vc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();
  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(codec_lookup_helper_1_.GetCodecVendor()
                                  ->audio_sendrecv_codecs()
                                  .codecs(),
                              &env_.field_trials()));

  const StreamParamsVec& audio_streams = acd->streams();
  ASSERT_EQ(audio_streams.size(), 2U);
  EXPECT_EQ(audio_streams[0].cname, audio_streams[1].cname);
  EXPECT_EQ(audio_streams[0].id, kAudioTrack1);
  ASSERT_EQ(audio_streams[0].ssrcs.size(), 1U);
  EXPECT_NE(audio_streams[0].ssrcs[0], 0U);
  EXPECT_EQ(audio_streams[1].id, kAudioTrack2);
  ASSERT_EQ(audio_streams[1].ssrcs.size(), 1U);
  EXPECT_NE(audio_streams[1].ssrcs[0], 0U);

  EXPECT_EQ(acd->bandwidth(), kAutoBandwidth);  // default bandwidth (auto)
  EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on

  EXPECT_EQ(vcd->type(), MediaType::VIDEO);
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(codec_lookup_helper_1_.GetCodecVendor()
                                  ->video_sendrecv_codecs()
                                  .codecs(),
                              &env_.field_trials()));

  const StreamParamsVec& video_streams = vcd->streams();
  ASSERT_EQ(video_streams.size(), 1U);
  EXPECT_EQ(video_streams[0].cname, audio_streams[0].cname);
  EXPECT_EQ(video_streams[0].id, kVideoTrack1);
  EXPECT_EQ(vcd->bandwidth(), kAutoBandwidth);  // default bandwidth (auto)
  EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on

  // Update the offer. Add a new video track that is not synched to the
  // other tracks and replace audio track 2 with audio track 3.
  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack2, {kMediaStream2}, 1, &opts);
  DetachSenderFromMediaSection(kAudioMid, kAudioTrack2, &opts);
  AttachSenderToMediaDescriptionOptions(
      kAudioMid, MediaType::AUDIO, kAudioTrack3, {kMediaStream1}, 1, &opts);
  std::unique_ptr<SessionDescription> updated_offer(
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue());

  ASSERT_TRUE(updated_offer.get());
  ac = updated_offer->GetContentByName(kAudioMid);
  vc = updated_offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  const MediaContentDescription* updated_acd = ac->media_description();
  const MediaContentDescription* updated_vcd = vc->media_description();

  EXPECT_EQ(acd->type(), updated_acd->type());
  EXPECT_THAT(updated_acd->codecs(),
              CodecListsMatch(acd->codecs(), &env_.field_trials()));
  EXPECT_EQ(vcd->type(), updated_vcd->type());
  EXPECT_THAT(updated_vcd->codecs(),
              CodecListsMatch(vcd->codecs(), &env_.field_trials()));

  const StreamParamsVec& updated_audio_streams = updated_acd->streams();
  ASSERT_EQ(updated_audio_streams.size(), 2U);
  EXPECT_EQ(audio_streams[0], updated_audio_streams[0]);
  EXPECT_EQ(updated_audio_streams[1].id, kAudioTrack3);  // New audio track.
  ASSERT_EQ(updated_audio_streams[1].ssrcs.size(), 1U);
  EXPECT_NE(updated_audio_streams[1].ssrcs[0], 0U);
  EXPECT_EQ(updated_audio_streams[0].cname, updated_audio_streams[1].cname);

  const StreamParamsVec& updated_video_streams = updated_vcd->streams();
  ASSERT_EQ(updated_video_streams.size(), 2U);
  EXPECT_EQ(video_streams[0], updated_video_streams[0]);
  EXPECT_EQ(updated_video_streams[1].id, kVideoTrack2);
  // All the media streams in one PeerConnection share one RTCP CNAME.
  EXPECT_EQ(updated_video_streams[1].cname, updated_video_streams[0].cname);
}

// Create an offer with simulcast video stream.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSimulcastVideoOffer) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  const int num_sim_layers = 3;
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1},
                                        num_sim_layers, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();

  ASSERT_TRUE(offer.get());
  const ContentInfo* vc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(vc);
  const MediaContentDescription* vcd = vc->media_description();

  const StreamParamsVec& video_streams = vcd->streams();
  ASSERT_EQ(video_streams.size(), 1U);
  EXPECT_EQ(video_streams[0].id, kVideoTrack1);
  const SsrcGroup* sim_ssrc_group =
      video_streams[0].get_ssrc_group(kSimSsrcGroupSemantics);
  ASSERT_TRUE(sim_ssrc_group);
  EXPECT_EQ(static_cast<size_t>(num_sim_layers), sim_ssrc_group->ssrcs.size());
}

MATCHER(RidDescriptionEquals, "Verifies that two RidDescriptions are equal.") {
  const RidDescription& rid1 = std::get<0>(arg);
  const RidDescription& rid2 = std::get<1>(arg);
  return rid1.rid == rid2.rid && rid1.direction == rid2.direction;
}

void CheckSimulcastInSessionDescription(
    const SessionDescription* description,
    const std::string& content_name,
    std::span<const RidDescription> send_rids,
    const SimulcastLayerList& send_layers) {
  ASSERT_NE(description, nullptr);
  const ContentInfo* content = description->GetContentByName(content_name);
  ASSERT_NE(content, nullptr);
  const MediaContentDescription* cd = content->media_description();
  ASSERT_NE(cd, nullptr);
  const StreamParamsVec& streams = cd->streams();
  ASSERT_THAT(streams, SizeIs(1));
  const StreamParams& stream = streams[0];
  ASSERT_THAT(stream.ssrcs, IsEmpty());
  EXPECT_TRUE(stream.has_rids());
  const std::vector<RidDescription> rids = stream.rids();

  EXPECT_THAT(rids, Pointwise(RidDescriptionEquals(), send_rids));

  EXPECT_TRUE(cd->HasSimulcast());
  const SimulcastDescription& simulcast = cd->simulcast_description();
  EXPECT_THAT(simulcast.send_layers(), SizeIs(send_layers.size()));
  EXPECT_THAT(simulcast.send_layers(), Pointwise(Eq(), send_layers));

  ASSERT_THAT(simulcast.receive_layers().GetAllLayers(), SizeIs(0));
}

// Create an offer with spec-compliant simulcast video stream.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateCompliantSimulcastOffer) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::vector<RidDescription> send_rids;
  send_rids.push_back(RidDescription("f", RidDirection::kSend));
  send_rids.push_back(RidDescription("h", RidDirection::kSend));
  send_rids.push_back(RidDescription("q", RidDirection::kSend));
  SimulcastLayerList simulcast_layers;
  simulcast_layers.AddLayer(SimulcastLayer(send_rids[0].rid, false));
  simulcast_layers.AddLayer(SimulcastLayer(send_rids[1].rid, true));
  simulcast_layers.AddLayer(SimulcastLayer(send_rids[2].rid, false));
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1},
                                        send_rids, simulcast_layers, 0, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();

  CheckSimulcastInSessionDescription(offer.get(), kVideoMid, send_rids,
                                     simulcast_layers);
}

// Create an offer that signals RIDs (not SSRCs) without Simulcast.
// In this scenario, RIDs do not need to be negotiated (there is only one).
TEST_F(MediaSessionDescriptionFactoryTest, TestOfferWithRidsNoSimulcast) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  RidDescription rid("f", RidDirection::kSend);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, {rid},
                                        SimulcastLayerList(), 0, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();

  ASSERT_NE(offer.get(), nullptr);
  const ContentInfo* content = offer->GetContentByName(kVideoMid);
  ASSERT_NE(content, nullptr);
  const MediaContentDescription* cd = content->media_description();
  ASSERT_NE(cd, nullptr);
  const StreamParamsVec& streams = cd->streams();
  ASSERT_THAT(streams, SizeIs(1));
  const StreamParams& stream = streams[0];
  ASSERT_THAT(stream.ssrcs, IsEmpty());
  EXPECT_FALSE(stream.has_rids());
  EXPECT_FALSE(cd->HasSimulcast());
}

// Create an answer with spec-compliant simulcast video stream.
// In this scenario, the SFU is the caller requesting that we send Simulcast.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateCompliantSimulcastAnswer) {
  MediaSessionOptions offer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &offer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &offer_opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);

  std::vector<RidDescription> rid_descriptions{
      RidDescription("f", RidDirection::kSend),
      RidDescription("h", RidDirection::kSend),
      RidDescription("q", RidDirection::kSend),
  };
  SimulcastLayerList simulcast_layers;
  simulcast_layers.AddLayer(SimulcastLayer(rid_descriptions[0].rid, false));
  simulcast_layers.AddLayer(SimulcastLayer(rid_descriptions[1].rid, true));
  simulcast_layers.AddLayer(SimulcastLayer(rid_descriptions[2].rid, false));
  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1},
      rid_descriptions, simulcast_layers, 0, &answer_opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();

  CheckSimulcastInSessionDescription(answer.get(), kVideoMid, rid_descriptions,
                                     simulcast_layers);
}

// Create an answer that signals RIDs (not SSRCs) without Simulcast.
// In this scenario, RIDs do not need to be negotiated (there is only one).
// Note that RID Direction is not the same as the transceiver direction.
TEST_F(MediaSessionDescriptionFactoryTest, TestAnswerWithRidsNoSimulcast) {
  MediaSessionOptions offer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &offer_opts);
  RidDescription rid_offer("f", RidDirection::kSend);
  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, {rid_offer},
      SimulcastLayerList(), 0, &offer_opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);

  RidDescription rid_answer("f", RidDirection::kReceive);
  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, {rid_answer},
      SimulcastLayerList(), 0, &answer_opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();

  ASSERT_NE(answer.get(), nullptr);
  const ContentInfo* content = offer->GetContentByName(kVideoMid);
  ASSERT_NE(content, nullptr);
  const MediaContentDescription* cd = content->media_description();
  ASSERT_NE(cd, nullptr);
  const StreamParamsVec& streams = cd->streams();
  ASSERT_THAT(streams, SizeIs(1));
  const StreamParams& stream = streams[0];
  ASSERT_THAT(stream.ssrcs, IsEmpty());
  EXPECT_FALSE(stream.has_rids());
  EXPECT_FALSE(cd->HasSimulcast());
}

// Create an audio and video answer to a standard video offer with:
// - one video track
// - two audio tracks
// - two data tracks
// and ensure it matches what we expect. Also updates the initial answer by
// adding a new video track and removes one of the audio tracks.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoAnswer) {
  MediaSessionOptions offer_opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &offer_opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &offer_opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);
  AttachSenderToMediaDescriptionOptions(kAudioMid, MediaType::AUDIO,
                                        kAudioTrack1, {kMediaStream1}, 1,
                                        &answer_opts);
  AttachSenderToMediaDescriptionOptions(kAudioMid, MediaType::AUDIO,
                                        kAudioTrack2, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();

  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kAudioMid);
  const ContentInfo* vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();

  EXPECT_EQ(acd->type(), MediaType::AUDIO);
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kAudioCodecsAnswer, &env_.field_trials()));

  const StreamParamsVec& audio_streams = acd->streams();
  ASSERT_EQ(audio_streams.size(), 2U);
  EXPECT_TRUE(audio_streams[0].cname == audio_streams[1].cname);
  EXPECT_EQ(audio_streams[0].id, kAudioTrack1);
  ASSERT_EQ(audio_streams[0].ssrcs.size(), 1U);
  EXPECT_NE(audio_streams[0].ssrcs[0], 0U);
  EXPECT_EQ(audio_streams[1].id, kAudioTrack2);
  ASSERT_EQ(audio_streams[1].ssrcs.size(), 1U);
  EXPECT_NE(audio_streams[1].ssrcs[0], 0U);

  EXPECT_EQ(acd->bandwidth(), kAutoBandwidth);  // default bandwidth (auto)
  EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on

  EXPECT_EQ(vcd->type(), MediaType::VIDEO);
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(kVideoCodecsAnswer, &env_.field_trials()));

  const StreamParamsVec& video_streams = vcd->streams();
  ASSERT_EQ(video_streams.size(), 1U);
  EXPECT_EQ(video_streams[0].cname, audio_streams[0].cname);
  EXPECT_EQ(video_streams[0].id, kVideoTrack1);
  EXPECT_EQ(vcd->bandwidth(), kAutoBandwidth);  // default bandwidth (auto)
  EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on

  // Update the answer. Add a new video track that is not synched to the
  // other tracks and remove 1 audio track.
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack2, {kMediaStream2}, 1,
                                        &answer_opts);
  DetachSenderFromMediaSection(kAudioMid, kAudioTrack2, &answer_opts);
  std::unique_ptr<SessionDescription> updated_answer(
      f2_.CreateAnswerOrError(offer.get(), answer_opts, answer.get())
          .MoveValue());

  ASSERT_TRUE(updated_answer.get());
  ac = updated_answer->GetContentByName(kAudioMid);
  vc = updated_answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  const MediaContentDescription* updated_acd = ac->media_description();
  const MediaContentDescription* updated_vcd = vc->media_description();

  EXPECT_EQ(acd->type(), updated_acd->type());
  EXPECT_THAT(updated_acd->codecs(),
              CodecListsMatch(acd->codecs(), &env_.field_trials()));
  EXPECT_EQ(vcd->type(), updated_vcd->type());
  EXPECT_THAT(updated_vcd->codecs(),
              CodecListsMatch(vcd->codecs(), &env_.field_trials()));

  const StreamParamsVec& updated_audio_streams = updated_acd->streams();
  ASSERT_EQ(updated_audio_streams.size(), 1U);
  EXPECT_TRUE(audio_streams[0] == updated_audio_streams[0]);

  const StreamParamsVec& updated_video_streams = updated_vcd->streams();
  ASSERT_EQ(updated_video_streams.size(), 2U);
  EXPECT_EQ(video_streams[0], updated_video_streams[0]);
  EXPECT_EQ(updated_video_streams[1].id, kVideoTrack2);
  // All media streams in one PeerConnection share one CNAME.
  EXPECT_EQ(updated_video_streams[1].cname, updated_video_streams[0].cname);
}

// Create an updated offer after creating an answer to the original offer and
// verify that the codecs that were part of the original answer are not changed
// in the updated offer.
TEST_F(MediaSessionDescriptionFactoryTest,
       RespondentCreatesOfferAfterCreatingAnswer) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  const AudioContentDescription* acd =
      GetFirstAudioContentDescription(answer.get());
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kAudioCodecsAnswer, &env_.field_trials()));

  const VideoContentDescription* vcd =
      GetFirstVideoContentDescription(answer.get());
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(kVideoCodecsAnswer, &env_.field_trials()));

  std::unique_ptr<SessionDescription> updated_offer(
      f2_.CreateOfferOrError(opts, answer.get()).MoveValue());

  // The expected audio codecs are the common audio codecs from the first
  // offer/answer exchange plus the audio codecs only `f2_` offer, sorted in
  // preference order.
  // TODO(wu): `updated_offer` should not include the codec
  // (i.e. `kAudioCodecs2[0]`) the other side doesn't support.
  const std::vector<Codec> kUpdatedAudioCodecOffer = {
      kAudioCodecsAnswer[0],
      kAudioCodecsAnswer[1],
      kAudioCodecs2[0],
  };

  // The expected video codecs are the common video codecs from the first
  // offer/answer exchange plus the video codecs only `f2_` offer, sorted in
  // preference order.
  const std::vector<Codec> kUpdatedVideoCodecOffer = {
      kVideoCodecsAnswer[0],
      kVideoCodecs2[1],
  };

  const AudioContentDescription* updated_acd =
      GetFirstAudioContentDescription(updated_offer.get());
  EXPECT_THAT(updated_acd->codecs(),
              CodecListsMatch(kUpdatedAudioCodecOffer, &env_.field_trials()));

  const VideoContentDescription* updated_vcd =
      GetFirstVideoContentDescription(updated_offer.get());
  EXPECT_THAT(updated_vcd->codecs(),
              CodecListsMatch(kUpdatedVideoCodecOffer, &env_.field_trials()));
}

// Test that a reoffer does not reuse audio codecs from a previous media section
// that is being recycled.
TEST_F(MediaSessionDescriptionFactoryTest,
       ReOfferDoesNotReUseRecycledAudioCodecs) {
  codec_lookup_helper_1_.ClearVideoCodecs();
  codec_lookup_helper_2_.ClearVideoCodecs();

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, "a0",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  // Recycle the media section by changing its mid.
  opts.media_description_options[0].mid = "a1";
  std::unique_ptr<SessionDescription> reoffer =
      f2_.CreateOfferOrError(opts, answer.get()).MoveValue();

  // Expect that the results of the first negotiation are ignored. If the m=
  // section was not recycled the payload types would match the initial offerer.
  const AudioContentDescription* acd =
      GetFirstAudioContentDescription(reoffer.get());
  EXPECT_THAT(acd->codecs(), NamesMatch(GetCodecNames(kAudioCodecs2)));
}

// Test that a reoffer does not reuse video codecs from a previous media section
// that is being recycled.
TEST_F(MediaSessionDescriptionFactoryTest,
       ReOfferDoesNotReUseRecycledVideoCodecs) {
  codec_lookup_helper_1_.ClearAudioCodecs();
  codec_lookup_helper_2_.ClearAudioCodecs();

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, "v0",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  // Recycle the media section by changing its mid.
  opts.media_description_options[0].mid = "v1";
  std::unique_ptr<SessionDescription> reoffer =
      f2_.CreateOfferOrError(opts, answer.get()).MoveValue();

  // Expect that the results of the first negotiation are ignored. If the m=
  // section was not recycled the payload types would match the initial offerer.
  const VideoContentDescription* vcd =
      GetFirstVideoContentDescription(reoffer.get());
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(kVideoCodecs2, &env_.field_trials()));
}

// Test that a reanswer does not reuse audio codecs from a previous media
// section that is being recycled.
TEST_F(MediaSessionDescriptionFactoryTest,
       ReAnswerDoesNotReUseRecycledAudioCodecs) {
  codec_lookup_helper_1_.ClearVideoCodecs();
  codec_lookup_helper_2_.ClearVideoCodecs();

  // Perform initial offer/answer in reverse (`f2_` as offerer) so that the
  // second offer/answer is forward (`f1_` as offerer).
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, "a0",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f2_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f1_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  // Recycle the media section by changing its mid.
  opts.media_description_options[0].mid = "a1";
  std::unique_ptr<SessionDescription> reoffer =
      f1_.CreateOfferOrError(opts, answer.get()).MoveValue();
  std::unique_ptr<SessionDescription> reanswer =
      f2_.CreateAnswerOrError(reoffer.get(), opts, offer.get()).MoveValue();

  // Expect that the results of the first negotiation are ignored. If the m=
  // section was not recycled the payload types would match the initial offerer.
  const AudioContentDescription* acd =
      GetFirstAudioContentDescription(reanswer.get());
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kAudioCodecsAnswer, &env_.field_trials()));
}

// Test that a reanswer does not reuse video codecs from a previous media
// section that is being recycled.
TEST_F(MediaSessionDescriptionFactoryTest,
       ReAnswerDoesNotReUseRecycledVideoCodecs) {
  codec_lookup_helper_1_.ClearAudioCodecs();
  codec_lookup_helper_2_.ClearAudioCodecs();

  // Perform initial offer/answer in reverse (`f2_` as offerer) so that the
  // second offer/answer is forward (`f1_` as offerer).
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, "v0",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f2_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f1_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  // Recycle the media section by changing its mid.
  opts.media_description_options[0].mid = "v1";
  std::unique_ptr<SessionDescription> reoffer =
      f1_.CreateOfferOrError(opts, answer.get()).MoveValue();
  std::unique_ptr<SessionDescription> reanswer =
      f2_.CreateAnswerOrError(reoffer.get(), opts, offer.get()).MoveValue();

  // Expect that the results of the first negotiation are ignored. If the m=
  // section was not recycled the payload types would match the initial offerer.
  const VideoContentDescription* vcd =
      GetFirstVideoContentDescription(reanswer.get());
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(kVideoCodecsAnswer, &env_.field_trials()));
}

// Create an updated offer after creating an answer to the original offer and
// verify that the codecs that were part of the original answer are not changed
// in the updated offer. In this test Rtx is enabled.
TEST_F(MediaSessionDescriptionFactoryTest,
       RespondentCreatesOfferAfterCreatingAnswerWithRtx) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::vector<Codec> f1_codecs(kVideoCodecs1.begin(), kVideoCodecs1.end());
  // This creates rtx for H264 with the payload type `f1_` uses.
  AddRtxCodec(CreateVideoRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  std::vector<Codec> f2_codecs(kVideoCodecs2.begin(), kVideoCodecs2.end());
  // This creates rtx for H264 with the payload type `f2_` uses.
  AddRtxCodec(CreateVideoRtxCodec(125, kVideoCodecs2[0].id), &f2_codecs);
  codec_lookup_helper_2_.SetVideoCodecs(f2_codecs);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  const VideoContentDescription* vcd =
      GetFirstVideoContentDescription(answer.get());

  std::vector<Codec> expected_codecs(kVideoCodecsAnswer.begin(),
                                     kVideoCodecsAnswer.end());
  AddRtxCodec(CreateVideoRtxCodec(126, kVideoCodecs1[1].id), &expected_codecs);

  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(expected_codecs, &env_.field_trials()));

  // Now, make sure we get same result (except for the order) if `f2_` creates
  // an updated offer even though the default payload types between `f1_` and
  // `f2_` are different.
  std::unique_ptr<SessionDescription> updated_offer(
      f2_.CreateOfferOrError(opts, answer.get()).MoveValue());
  ASSERT_TRUE(updated_offer);
  std::unique_ptr<SessionDescription> updated_answer(
      f1_.CreateAnswerOrError(updated_offer.get(), opts, answer.get())
          .MoveValue());

  const VideoContentDescription* updated_vcd =
      GetFirstVideoContentDescription(updated_answer.get());

  EXPECT_THAT(updated_vcd->codecs(),
              CodecListsMatch(expected_codecs, &env_.field_trials()));
}

// Regression test for:
// https://bugs.chromium.org/p/webrtc/issues/detail?id=8332
// Existing codecs should always appear before new codecs in re-offers. But
// under a specific set of circumstances, the existing RTX codec was ending up
// added to the end of the list.
TEST_F(MediaSessionDescriptionFactoryTest,
       RespondentCreatesOfferAfterCreatingAnswerWithRemappedRtxPayloadType) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  // We specifically choose different preferred payload types for VP8 to
  // trigger the issue.
  Codec vp8_offerer = CreateVideoCodec(100, "VP8");
  Codec vp8_offerer_rtx = CreateVideoRtxCodec(101, vp8_offerer.id);
  Codec vp8_answerer = CreateVideoCodec(110, "VP8");
  Codec vp8_answerer_rtx = CreateVideoRtxCodec(111, vp8_answerer.id);
  Codec vp9 = CreateVideoCodec(120, "VP9");
  Codec vp9_rtx = CreateVideoRtxCodec(121, vp9.id);

  codec_lookup_helper_1_.SetVideoCodecs({vp8_offerer, vp8_offerer_rtx});
  // We also specifically cause the answerer to prefer VP9, such that if it
  // *doesn't* honor the existing preferred codec (VP8) we'll notice.
  codec_lookup_helper_2_.SetVideoCodecs(
      {vp9, vp9_rtx, vp8_answerer, vp8_answerer_rtx});
  std::vector<Codec> audio_codecs;
  codec_lookup_helper_1_.SetAudioCodecs(audio_codecs);
  codec_lookup_helper_2_.SetAudioCodecs(audio_codecs);

  // Offer will be {VP8, RTX for VP8}. Answer will be the same.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  // Updated offer *should* be {VP8, RTX for VP8, VP9, RTX for VP9}.
  // But if the bug is triggered, RTX for VP8 ends up last.
  std::unique_ptr<SessionDescription> updated_offer(
      f2_.CreateOfferOrError(opts, answer.get()).MoveValue());

  const VideoContentDescription* vcd =
      GetFirstVideoContentDescription(updated_offer.get());
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(std::vector<Codec>{vp8_offerer, vp8_offerer_rtx,
                                                 vp9, vp9_rtx},
                              &env_.field_trials()));
}

// Create an updated offer that adds video after creating an audio only answer
// to the original offer. This test verifies that if a video codec and the RTX
// codec have the same default payload type as an audio codec that is already in
// use, the added codecs payload types are changed.
// TODO(bugs.webrtc.org/400630582): Investigate why this fails in the legacy
// path with the new test-only PT registration.
TEST_F(MediaSessionDescriptionFactoryTest,
       RespondentCreatesOfferWithVideoAndRtxAfterCreatingAudioAnswer) {
  std::vector<Codec> f1_codecs(kVideoCodecs1.begin(), kVideoCodecs1.end());
  // This creates rtx for H264 with the payload type `f1_` uses.
  AddRtxCodec(CreateVideoRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  const AudioContentDescription* acd =
      GetFirstAudioContentDescription(answer.get());
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kAudioCodecsAnswer, &env_.field_trials()));

  // Now - let `f2_` add video with RTX and let the payload type the RTX codec
  // reference  be the same as an audio codec that was negotiated in the
  // first offer/answer exchange.
  opts.media_description_options.clear();
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  std::vector<Codec> f2_codecs(kVideoCodecs2.begin(), kVideoCodecs2.end());
  ASSERT_THAT(acd->codecs().size(), Gt(0));
  int used_pl_type = acd->codecs()[0].id;
  f2_codecs[0].id = used_pl_type;  // Set the payload type for H264.
  AddRtxCodec(CreateVideoRtxCodec(125, used_pl_type), &f2_codecs);
  codec_lookup_helper_2_.SetVideoCodecs(f2_codecs);

  std::unique_ptr<SessionDescription> updated_offer(
      f2_.CreateOfferOrError(opts, answer.get()).MoveValue());
  ASSERT_TRUE(updated_offer);
  std::unique_ptr<SessionDescription> updated_answer(
      f1_.CreateAnswerOrError(updated_offer.get(), opts, answer.get())
          .MoveValue());

  const AudioContentDescription* updated_acd =
      GetFirstAudioContentDescription(updated_answer.get());
  EXPECT_THAT(updated_acd->codecs(),
              CodecListsMatch(kAudioCodecsAnswer, &env_.field_trials()));

  const VideoContentDescription* updated_vcd =
      GetFirstVideoContentDescription(updated_answer.get());

  ASSERT_EQ(updated_vcd->codecs()[0].name, "H264");
  ASSERT_EQ(updated_vcd->codecs()[1].name, kRtxCodecName);
  int new_h264_pl_type = updated_vcd->codecs()[0].id;
  EXPECT_NE(used_pl_type, new_h264_pl_type);
  Codec rtx = updated_vcd->codecs()[1];
  int pt_referenced_by_rtx =
      FromString<int>(rtx.params[kCodecParamAssociatedPayloadType]);
  EXPECT_EQ(new_h264_pl_type, pt_referenced_by_rtx);
}

// Create an updated offer with RTX after creating an answer to an offer
// without RTX, and with different default payload types.
// Verify that the added RTX codec references the correct payload type.
TEST_F(MediaSessionDescriptionFactoryTest,
       RespondentCreatesOfferWithRtxAfterCreatingAnswerWithoutRtx) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  std::vector<Codec> f2_codecs(kVideoCodecs2.begin(), kVideoCodecs2.end());
  // This creates rtx for H264 with the payload type `f2_` uses.
  AddRtxCodec(CreateVideoRtxCodec(125, kVideoCodecs2[0].id), &f2_codecs);
  codec_lookup_helper_2_.SetVideoCodecs(f2_codecs);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  const VideoContentDescription* vcd =
      GetFirstVideoContentDescription(answer.get());

  std::vector<Codec> expected_codecs(kVideoCodecsAnswer.begin(),
                                     kVideoCodecsAnswer.end());
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(expected_codecs, &env_.field_trials()));

  // Now, ensure that the RTX codec is created correctly when `f2_` creates an
  // updated offer, even though the default payload types are different from
  // those of `f1_`.
  std::unique_ptr<SessionDescription> updated_offer(
      f2_.CreateOfferOrError(opts, answer.get()).MoveValue());
  ASSERT_TRUE(updated_offer);

  const VideoContentDescription* updated_vcd =
      GetFirstVideoContentDescription(updated_offer.get());

  // New offer should attempt to add H263, and RTX for H264.
  expected_codecs.push_back(kVideoCodecs2[1]);
  AddRtxCodec(CreateVideoRtxCodec(125, kVideoCodecs1[1].id), &expected_codecs);
  EXPECT_THAT(updated_vcd->codecs(),
              CodecListsMatch(expected_codecs, &env_.field_trials()));
}

// Test that RTX is ignored when there is no associated payload type parameter.
TEST_F(MediaSessionDescriptionFactoryTest, RtxWithoutApt) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::vector<Codec> f1_codecs(kVideoCodecs1.begin(), kVideoCodecs1.end());
  // This creates RTX without associated payload type parameter.
  AddRtxCodec(CreateVideoCodec(126, kRtxCodecName), &f1_codecs);
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  std::vector<Codec> f2_codecs(kVideoCodecs2.begin(), kVideoCodecs2.end());
  // This creates RTX for H264 with the payload type `f2_` uses.
  AddRtxCodec(CreateVideoRtxCodec(125, kVideoCodecs2[0].id), &f2_codecs);
  codec_lookup_helper_2_.SetVideoCodecs(f2_codecs);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  // kCodecParamAssociatedPayloadType will always be added to the offer when RTX
  // is selected. Manually remove kCodecParamAssociatedPayloadType so that it
  // is possible to test that that RTX is dropped when
  // kCodecParamAssociatedPayloadType is missing in the offer.
  MediaContentDescription* media_desc =
      offer->GetContentDescriptionByName(kVideoMid);
  ASSERT_TRUE(media_desc);
  std::vector<Codec> codecs = media_desc->codecs();
  for (Codec& codec : codecs) {
    if (absl::StartsWith(codec.name, kRtxCodecName)) {
      codec.params.clear();
    }
  }
  media_desc->set_codecs(codecs);

  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  EXPECT_THAT(
      GetCodecNames(GetFirstVideoContentDescription(answer.get())->codecs()),
      Not(Contains(kRtxCodecName)));
}

// Test that RTX will be filtered out in the answer if its associated payload
// type doesn't match the local value.
TEST_F(MediaSessionDescriptionFactoryTest, FilterOutRtxIfAptDoesntMatch) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::vector<Codec> f1_codecs(kVideoCodecs1.begin(), kVideoCodecs1.end());
  // This creates RTX for H264 in sender.
  AddRtxCodec(CreateVideoRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  std::vector<Codec> f2_codecs(kVideoCodecs2.begin(), kVideoCodecs2.end());
  // This creates RTX for H263 in receiver.
  AddRtxCodec(CreateVideoRtxCodec(125, kVideoCodecs2[1].id), &f2_codecs);
  codec_lookup_helper_2_.SetVideoCodecs(f2_codecs);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  // Associated payload type doesn't match, therefore, RTX codec is removed in
  // the answer.
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  EXPECT_THAT(
      GetCodecNames(GetFirstVideoContentDescription(answer.get())->codecs()),
      Not(Contains(kRtxCodecName)));
}

// Test that when multiple RTX codecs are offered, only the matched RTX codec
// is added in the answer, and the unsupported RTX codec is filtered out.
TEST_F(MediaSessionDescriptionFactoryTest,
       FilterOutUnsupportedRtxWhenCreatingAnswer) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::vector<Codec> f1_codecs(kVideoCodecs1.begin(), kVideoCodecs1.end());
  // This creates RTX for H264-SVC in sender.
  AddRtxCodec(CreateVideoRtxCodec(125, kVideoCodecs1[0].id), &f1_codecs);
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  // This creates RTX for H264 in sender.
  AddRtxCodec(CreateVideoRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  std::vector<Codec> f2_codecs(kVideoCodecs2.begin(), kVideoCodecs2.end());
  // This creates RTX for H264 in receiver.
  AddRtxCodec(CreateVideoRtxCodec(124, kVideoCodecs2[0].id), &f2_codecs);
  codec_lookup_helper_2_.SetVideoCodecs(f2_codecs, f1_codecs);

  // H264-SVC codec is removed in the answer, therefore, associated RTX codec
  // for H264-SVC should also be removed.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const VideoContentDescription* vcd =
      GetFirstVideoContentDescription(answer.get());
  std::vector<Codec> expected_codecs(kVideoCodecsAnswer.begin(),
                                     kVideoCodecsAnswer.end());
  AddRtxCodec(CreateVideoRtxCodec(126, kVideoCodecs1[1].id), &expected_codecs);

  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(expected_codecs, &env_.field_trials()));
}

// Test that after one RTX codec has been negotiated, a new offer can attempt
// to add another.
TEST_F(MediaSessionDescriptionFactoryTest, AddSecondRtxInNewOffer) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::vector<Codec> f1_codecs(kVideoCodecs1.begin(), kVideoCodecs1.end());
  // This creates RTX for H264 for the offerer.
  AddRtxCodec(CreateVideoRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  const VideoContentDescription* vcd =
      GetFirstVideoContentDescription(offer.get());

  std::vector<Codec> expected_codecs(kVideoCodecs1.begin(),
                                     kVideoCodecs1.end());
  AddRtxCodec(CreateVideoRtxCodec(126, kVideoCodecs1[1].id), &expected_codecs);
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(expected_codecs, &env_.field_trials()));

  // Now, attempt to add RTX for H264-SVC.
  AddRtxCodec(CreateVideoRtxCodec(125, kVideoCodecs1[0].id), &f1_codecs);
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  std::unique_ptr<SessionDescription> updated_offer(
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue());
  ASSERT_TRUE(updated_offer);
  vcd = GetFirstVideoContentDescription(updated_offer.get());

  AddRtxCodec(CreateVideoRtxCodec(125, kVideoCodecs1[0].id), &expected_codecs);
  EXPECT_THAT(vcd->codecs(),
              CodecListsMatch(expected_codecs, &env_.field_trials()));
}

// Test that when RTX is used in conjunction with simulcast, an RTX ssrc is
// generated for each simulcast ssrc and correctly grouped.
TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateMultipleRtxSsrcs) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  // Add simulcast streams.
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO, "stream1",
                                        {"stream1label"}, 3, &opts);

  // Use a single real codec, and then add RTX for it.
  std::vector<Codec> f1_codecs;
  f1_codecs.push_back(CreateVideoCodec(97, "H264"));
  AddRtxCodec(CreateVideoRtxCodec(125, 97), &f1_codecs);
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  // Ensure that the offer has an RTX ssrc for each regular ssrc, and that there
  // is a FID ssrc + grouping for each.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  MediaContentDescription* media_desc =
      offer->GetContentDescriptionByName(kVideoMid);
  ASSERT_TRUE(media_desc);
  const StreamParamsVec& streams = media_desc->streams();
  // Single stream.
  ASSERT_EQ(streams.size(), 1u);
  // Stream should have 6 ssrcs: 3 for video, 3 for RTX.
  EXPECT_EQ(streams[0].ssrcs.size(), 6u);
  // And should have a SIM group for the simulcast.
  EXPECT_TRUE(streams[0].has_ssrc_group("SIM"));
  // And a FID group for RTX.
  EXPECT_TRUE(streams[0].has_ssrc_group("FID"));
  std::vector<uint32_t> primary_ssrcs;
  streams[0].GetPrimarySsrcs(&primary_ssrcs);
  EXPECT_EQ(primary_ssrcs.size(), 3u);
  std::vector<uint32_t> fid_ssrcs;
  streams[0].GetFidSsrcs(primary_ssrcs, &fid_ssrcs);
  EXPECT_EQ(fid_ssrcs.size(), 3u);
}

class MediaSessionDescriptionFactoryFecTest
    : public MediaSessionDescriptionFactoryTest {
 public:
  MediaSessionDescriptionFactoryFecTest()
      : MediaSessionDescriptionFactoryTest("WebRTC-FlexFEC-03/Enabled/") {}
};

// Test that, when the FlexFEC codec is added, a FlexFEC ssrc is created
// together with a FEC-FR grouping. Guarded by WebRTC-FlexFEC-03 trial.
TEST_F(MediaSessionDescriptionFactoryFecTest, GenerateFlexfecSsrc) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  // Add single stream.
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO, "stream1",
                                        {"stream1label"}, 1, &opts);

  // Use a single real codec, and then add FlexFEC for it.
  std::vector<Codec> f1_codecs;
  f1_codecs.push_back(CreateVideoCodec(97, "H264"));
  f1_codecs.push_back(CreateVideoCodec(118, "flexfec-03"));
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  // Ensure that the offer has a single FlexFEC ssrc and that
  // there is no FEC-FR ssrc + grouping for each.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  MediaContentDescription* media_desc =
      offer->GetContentDescriptionByName(kVideoMid);
  ASSERT_TRUE(media_desc);
  const StreamParamsVec& streams = media_desc->streams();
  // Single stream.
  ASSERT_EQ(streams.size(), 1u);
  // Stream should have 2 ssrcs: 1 for video, 1 for FlexFEC.
  EXPECT_EQ(streams[0].ssrcs.size(), 2u);
  // And should have a FEC-FR group for FlexFEC.
  EXPECT_TRUE(streams[0].has_ssrc_group("FEC-FR"));
  std::vector<uint32_t> primary_ssrcs;
  streams[0].GetPrimarySsrcs(&primary_ssrcs);
  ASSERT_EQ(primary_ssrcs.size(), 1u);
  uint32_t flexfec_ssrc;
  EXPECT_TRUE(streams[0].GetFecFrSsrc(primary_ssrcs[0], &flexfec_ssrc));
  EXPECT_NE(flexfec_ssrc, 0u);
}

// Test that FlexFEC is disabled for simulcast.
// TODO(brandtr): Remove this test when we support simulcast, either through
// multiple FlexfecSenders, or through multistream protection.
TEST_F(MediaSessionDescriptionFactoryFecTest, SimSsrcsGenerateNoFlexfecSsrcs) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  // Add simulcast streams.
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO, "stream1",
                                        {"stream1label"}, 3, &opts);

  // Use a single real codec, and then add FlexFEC for it.
  std::vector<Codec> f1_codecs;
  f1_codecs.push_back(CreateVideoCodec(97, "H264"));
  f1_codecs.push_back(CreateVideoCodec(118, "flexfec-03"));
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  // Ensure that the offer has no FlexFEC ssrcs for each regular ssrc, and that
  // there is no FEC-FR ssrc + grouping for each.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  MediaContentDescription* media_desc =
      offer->GetContentDescriptionByName(kVideoMid);
  ASSERT_TRUE(media_desc);
  const StreamParamsVec& streams = media_desc->streams();
  // Single stream.
  ASSERT_EQ(streams.size(), 1u);
  // Stream should have 3 ssrcs: 3 for video, 0 for FlexFEC.
  EXPECT_EQ(streams[0].ssrcs.size(), 3u);
  // And should have a SIM group for the simulcast.
  EXPECT_TRUE(streams[0].has_ssrc_group("SIM"));
  // And not a FEC-FR group for FlexFEC.
  EXPECT_FALSE(streams[0].has_ssrc_group("FEC-FR"));
  std::vector<uint32_t> primary_ssrcs;
  streams[0].GetPrimarySsrcs(&primary_ssrcs);
  EXPECT_EQ(primary_ssrcs.size(), 3u);
  for (uint32_t primary_ssrc : primary_ssrcs) {
    uint32_t flexfec_ssrc;
    EXPECT_FALSE(streams[0].GetFecFrSsrc(primary_ssrc, &flexfec_ssrc));
  }
}

// Create an updated offer after creating an answer to the original offer and
// verify that the RTP header extensions that were part of the original answer
// are not changed in the updated offer.
TEST_F(MediaSessionDescriptionFactoryTest,
       RespondentCreatesOfferAfterCreatingAnswerWithRtpExtensions) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtension1, kVideoRtpExtension1,
                                   &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtension2, kVideoRtpExtension2,
                                   &opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  EXPECT_THAT(
      GetFirstAudioContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtensionAnswer));
  EXPECT_THAT(
      GetFirstVideoContentDescription(answer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kVideoRtpExtensionAnswer));

  std::unique_ptr<SessionDescription> updated_offer(
      f2_.CreateOfferOrError(opts, answer.get()).MoveValue());

  // The expected RTP header extensions in the new offer are the resulting
  // extensions from the first offer/answer exchange plus the extensions only
  // `f2_` offer.
  // Since the default local extension id `f2_` uses has already been used by
  // `f1_` for another extensions, it is changed to 13.
  const std::vector<RtpExtension> kUpdatedAudioRtpExtensions = {
      kAudioRtpExtensionAnswer[0],
      RtpExtension(kAudioRtpExtension2[1].uri, 13),
      kAudioRtpExtension2[2],
  };

  // Since the default local extension id `f2_` uses has already been used by
  // `f1_` for another extensions, is is changed to 12.
  const std::vector<RtpExtension> kUpdatedVideoRtpExtensions = {
      kVideoRtpExtensionAnswer[0],
      RtpExtension(kVideoRtpExtension2[1].uri, 12),
      kVideoRtpExtension2[2],
  };

  const AudioContentDescription* updated_acd =
      GetFirstAudioContentDescription(updated_offer.get());
  EXPECT_THAT(updated_acd->rtp_header_extensions(),
              UnorderedElementsAreArray(kUpdatedAudioRtpExtensions));

  const VideoContentDescription* updated_vcd =
      GetFirstVideoContentDescription(updated_offer.get());
  EXPECT_THAT(updated_vcd->rtp_header_extensions(),
              UnorderedElementsAreArray(kUpdatedVideoRtpExtensions));
}

// Verify that if the same RTP extension URI is used for audio and video, the
// same ID is used. Also verify that the ID isn't changed when creating an
// updated offer (this was previously a bug).
TEST_F(MediaSessionDescriptionFactoryTest, RtpExtensionIdReused) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);

  SetAudioVideoRtpHeaderExtensions(kAudioRtpExtension3, kVideoRtpExtension3,
                                   &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();

  // Since the audio extensions used ID 3 for "both_audio_and_video", so should
  // the video extensions.
  const std::vector<RtpExtension> kExpectedVideoRtpExtension = {
      kVideoRtpExtension3[0],
      kAudioRtpExtension3[1],
  };

  EXPECT_THAT(
      GetFirstAudioContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kAudioRtpExtension3));
  EXPECT_THAT(
      GetFirstVideoContentDescription(offer.get())->rtp_header_extensions(),
      UnorderedElementsAreArray(kExpectedVideoRtpExtension));

  // Nothing should change when creating a new offer
  std::unique_ptr<SessionDescription> updated_offer(
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue());

  EXPECT_THAT(GetFirstAudioContentDescription(updated_offer.get())
                  ->rtp_header_extensions(),
              UnorderedElementsAreArray(kAudioRtpExtension3));
  EXPECT_THAT(GetFirstVideoContentDescription(updated_offer.get())
                  ->rtp_header_extensions(),
              UnorderedElementsAreArray(kExpectedVideoRtpExtension));
}

TEST_F(MediaSessionDescriptionFactoryTest, CopySessionDescription) {
  SessionDescription source;
  ContentGroup group(kAudioMid);
  source.AddGroup(group);
  std::unique_ptr<AudioContentDescription> acd =
      std::make_unique<AudioContentDescription>();
  acd->set_codecs(
      std::vector<Codec>(kAudioCodecs1.begin(), kAudioCodecs1.end()));
  acd->AddLegacyStream(1);
  source.AddContent(kAudioMid, MediaProtocolType::kRtp, acd->Clone());
  std::unique_ptr<VideoContentDescription> vcd =
      std::make_unique<VideoContentDescription>();
  vcd->set_codecs(
      std::vector<Codec>(kVideoCodecs1.begin(), kVideoCodecs1.end()));
  vcd->AddLegacyStream(2);
  source.AddContent(kVideoMid, MediaProtocolType::kRtp, vcd->Clone());

  std::unique_ptr<SessionDescription> copy = source.Clone();
  ASSERT_TRUE(copy.get());
  EXPECT_TRUE(copy->HasGroup(kAudioMid));
  const ContentInfo* ac = copy->GetContentByName(kAudioMid);
  const ContentInfo* vc = copy->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  EXPECT_EQ(ac->type, MediaProtocolType::kRtp);
  const MediaContentDescription* acd_copy = ac->media_description();
  EXPECT_THAT(acd_copy->codecs(),
              CodecListsMatch(acd->codecs(), &env_.field_trials()));
  EXPECT_EQ(acd->first_ssrc(), 1u);

  EXPECT_EQ(vc->type, MediaProtocolType::kRtp);
  const MediaContentDescription* vcd_copy = vc->media_description();
  EXPECT_THAT(vcd_copy->codecs(),
              CodecListsMatch(vcd->codecs(), &env_.field_trials()));
  EXPECT_EQ(vcd->first_ssrc(), 2u);
}

// The below TestTransportInfoXXX tests create different offers/answers, and
// ensure the TransportInfo in the SessionDescription matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudio) {
  MediaSessionOptions options;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &options);
  TestTransportInfo(true, options, false);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestTransportInfoOfferIceRenomination) {
  MediaSessionOptions options;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &options);
  options.media_description_options[0]
      .transport_options.enable_ice_renomination = true;
  TestTransportInfo(true, options, false);
}

TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudioCurrent) {
  MediaSessionOptions options;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &options);
  TestTransportInfo(true, options, true);
}

TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferMultimedia) {
  MediaSessionOptions options;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
  TestTransportInfo(true, options, false);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestTransportInfoOfferMultimediaCurrent) {
  MediaSessionOptions options;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
  TestTransportInfo(true, options, true);
}

TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferBundle) {
  MediaSessionOptions options;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
  options.bundle_enabled = true;
  TestTransportInfo(true, options, false);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestTransportInfoOfferBundleCurrent) {
  MediaSessionOptions options;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
  options.bundle_enabled = true;
  TestTransportInfo(true, options, true);
}

TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerAudio) {
  MediaSessionOptions options;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &options);
  TestTransportInfo(false, options, false);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestTransportInfoAnswerIceRenomination) {
  MediaSessionOptions options;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &options);
  options.media_description_options[0]
      .transport_options.enable_ice_renomination = true;
  TestTransportInfo(false, options, false);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestTransportInfoAnswerAudioCurrent) {
  MediaSessionOptions options;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &options);
  TestTransportInfo(false, options, true);
}

TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerMultimedia) {
  MediaSessionOptions options;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
  TestTransportInfo(false, options, false);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestTransportInfoAnswerMultimediaCurrent) {
  MediaSessionOptions options;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
  TestTransportInfo(false, options, true);
}

TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerBundle) {
  MediaSessionOptions options;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
  options.bundle_enabled = true;
  TestTransportInfo(false, options, false);
}

TEST_F(MediaSessionDescriptionFactoryTest,
       TestTransportInfoAnswerBundleCurrent) {
  MediaSessionOptions options;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
  options.bundle_enabled = true;
  TestTransportInfo(false, options, true);
}

// Offers UDP/TLS/RTP/SAVPF and verifies the answer can be created and contains
// UDP/TLS/RTP/SAVPF.
TEST_F(MediaSessionDescriptionFactoryTest, TestOfferDtlsSavpfCreateAnswer) {
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(CreateAudioMediaSession(), nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  ContentInfo* offer_content = offer->GetContentByName(kAudioMid);
  ASSERT_TRUE(offer_content);
  MediaContentDescription* offer_audio_desc =
      offer_content->media_description();
  offer_audio_desc->set_protocol(kMediaProtocolDtlsSavpf);

  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), CreateAudioMediaSession(), nullptr)
          .MoveValue();
  ASSERT_THAT(answer, NotNull());

  const ContentInfo* answer_content = answer->GetContentByName(kAudioMid);
  ASSERT_TRUE(answer_content);
  ASSERT_FALSE(answer_content->rejected);

  const MediaContentDescription* answer_audio_desc =
      answer_content->media_description();
  EXPECT_EQ(answer_audio_desc->protocol(), kMediaProtocolDtlsSavpf);
}

// Test that we accept a DTLS offer without SDES and create an appropriate
// answer.
TEST_F(MediaSessionDescriptionFactoryTest, TestCryptoOfferDtlsButNotSdes) {
  /* TODO(hta): Figure this one out.
  f1_.set_secure(SEC_DISABLED);
  f2_.set_secure(SEC_ENABLED);
  tdf1_.set_secure(SEC_ENABLED);
  tdf2_.set_secure(SEC_ENABLED);
  */
  MediaSessionOptions options;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);

  // Generate an offer with DTLS
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(options, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());

  const TransportDescription* audio_offer_trans_desc =
      offer->GetTransportDescriptionByName(kAudioMid);
  ASSERT_TRUE(audio_offer_trans_desc->identity_fingerprint.get());
  const TransportDescription* video_offer_trans_desc =
      offer->GetTransportDescriptionByName(kVideoMid);
  ASSERT_TRUE(video_offer_trans_desc->identity_fingerprint.get());

  // Generate an answer with DTLS.
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), options, nullptr).MoveValue();
  ASSERT_TRUE(answer.get());

  const TransportDescription* audio_answer_trans_desc =
      answer->GetTransportDescriptionByName(kAudioMid);
  EXPECT_TRUE(audio_answer_trans_desc->identity_fingerprint.get());
  const TransportDescription* video_answer_trans_desc =
      answer->GetTransportDescriptionByName(kVideoMid);
  EXPECT_TRUE(video_answer_trans_desc->identity_fingerprint.get());
}

// Verifies if vad_enabled option is set to false, CN codecs are not present in
// offer or answer.
TEST_F(MediaSessionDescriptionFactoryTest, TestVADEnableOption) {
  MediaSessionOptions options;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &options);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(options, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* audio_content = offer->GetContentByName(kAudioMid);
  EXPECT_FALSE(VerifyNoCNCodecs(audio_content));

  options.vad_enabled = false;
  offer = f1_.CreateOfferOrError(options, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  audio_content = offer->GetContentByName(kAudioMid);
  EXPECT_TRUE(VerifyNoCNCodecs(audio_content));
  std::unique_ptr<SessionDescription> answer =
      f1_.CreateAnswerOrError(offer.get(), options, nullptr).MoveValue();
  ASSERT_TRUE(answer.get());
  audio_content = answer->GetContentByName(kAudioMid);
  EXPECT_TRUE(VerifyNoCNCodecs(audio_content));
}

// Test that the generated MIDs match the existing offer.
TEST_F(MediaSessionDescriptionFactoryTest, TestMIDsMatchesExistingOffer) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio_modified",
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, "video_modified",
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::DATA, "data_modified",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  // Create offer.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  std::unique_ptr<SessionDescription> updated_offer(
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue());

  const ContentInfo* audio_content = GetFirstAudioContent(updated_offer.get());
  const ContentInfo* video_content = GetFirstVideoContent(updated_offer.get());
  const ContentInfo* data_content = GetFirstDataContent(updated_offer.get());
  ASSERT_TRUE(audio_content);
  ASSERT_TRUE(video_content);
  ASSERT_TRUE(data_content);
  EXPECT_EQ(audio_content->mid(), "audio_modified");
  EXPECT_EQ(video_content->mid(), "video_modified");
  EXPECT_EQ(data_content->mid(), "data_modified");
}

// The following tests verify that the unified plan SDP is supported.
// Test that we can create an offer with multiple media sections of same media
// type.
TEST_F(MediaSessionDescriptionFactoryTest,
       CreateOfferWithMultipleAVMediaSections) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio_1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AttachSenderToMediaDescriptionOptions(
      "audio_1", MediaType::AUDIO, kAudioTrack1, {kMediaStream1}, 1, &opts);

  AddMediaDescriptionOptions(MediaType::VIDEO, "video_1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AttachSenderToMediaDescriptionOptions(
      "video_1", MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  AddMediaDescriptionOptions(MediaType::AUDIO, "audio_2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AttachSenderToMediaDescriptionOptions(
      "audio_2", MediaType::AUDIO, kAudioTrack2, {kMediaStream2}, 1, &opts);

  AddMediaDescriptionOptions(MediaType::VIDEO, "video_2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AttachSenderToMediaDescriptionOptions(
      "video_2", MediaType::VIDEO, kVideoTrack2, {kMediaStream2}, 1, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());

  ASSERT_EQ(offer->contents().size(), 4u);
  EXPECT_FALSE(offer->contents()[0].rejected);
  const MediaContentDescription* acd = offer->contents()[0].media_description();
  ASSERT_EQ(acd->streams().size(), 1u);
  EXPECT_EQ(acd->streams()[0].id, kAudioTrack1);
  EXPECT_EQ(acd->direction(), RtpTransceiverDirection::kSendRecv);

  EXPECT_FALSE(offer->contents()[1].rejected);
  const MediaContentDescription* vcd = offer->contents()[1].media_description();
  ASSERT_EQ(vcd->streams().size(), 1u);
  EXPECT_EQ(vcd->streams()[0].id, kVideoTrack1);
  EXPECT_EQ(vcd->direction(), RtpTransceiverDirection::kSendRecv);

  EXPECT_FALSE(offer->contents()[2].rejected);
  acd = offer->contents()[2].media_description();
  ASSERT_EQ(acd->streams().size(), 1u);
  EXPECT_EQ(acd->streams()[0].id, kAudioTrack2);
  EXPECT_EQ(acd->direction(), RtpTransceiverDirection::kSendRecv);

  EXPECT_FALSE(offer->contents()[3].rejected);
  vcd = offer->contents()[3].media_description();
  ASSERT_EQ(vcd->streams().size(), 1u);
  EXPECT_EQ(vcd->streams()[0].id, kVideoTrack2);
  EXPECT_EQ(vcd->direction(), RtpTransceiverDirection::kSendRecv);
}

// Test that we can create an answer with multiple media sections of same media
// type.
TEST_F(MediaSessionDescriptionFactoryTest,
       CreateAnswerWithMultipleAVMediaSections) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio_1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AttachSenderToMediaDescriptionOptions(
      "audio_1", MediaType::AUDIO, kAudioTrack1, {kMediaStream1}, 1, &opts);

  AddMediaDescriptionOptions(MediaType::VIDEO, "video_1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AttachSenderToMediaDescriptionOptions(
      "video_1", MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  AddMediaDescriptionOptions(MediaType::AUDIO, "audio_2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AttachSenderToMediaDescriptionOptions(
      "audio_2", MediaType::AUDIO, kAudioTrack2, {kMediaStream2}, 1, &opts);

  AddMediaDescriptionOptions(MediaType::VIDEO, "video_2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AttachSenderToMediaDescriptionOptions(
      "video_2", MediaType::VIDEO, kVideoTrack2, {kMediaStream2}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();

  ASSERT_EQ(answer->contents().size(), 4u);
  EXPECT_FALSE(answer->contents()[0].rejected);
  const MediaContentDescription* acd =
      answer->contents()[0].media_description();
  ASSERT_EQ(acd->streams().size(), 1u);
  EXPECT_EQ(acd->streams()[0].id, kAudioTrack1);
  EXPECT_EQ(acd->direction(), RtpTransceiverDirection::kSendRecv);

  EXPECT_FALSE(answer->contents()[1].rejected);
  const MediaContentDescription* vcd =
      answer->contents()[1].media_description();
  ASSERT_EQ(vcd->streams().size(), 1u);
  EXPECT_EQ(vcd->streams()[0].id, kVideoTrack1);
  EXPECT_EQ(vcd->direction(), RtpTransceiverDirection::kSendRecv);

  EXPECT_FALSE(answer->contents()[2].rejected);
  acd = answer->contents()[2].media_description();
  ASSERT_EQ(acd->streams().size(), 1u);
  EXPECT_EQ(acd->streams()[0].id, kAudioTrack2);
  EXPECT_EQ(acd->direction(), RtpTransceiverDirection::kSendRecv);

  EXPECT_FALSE(answer->contents()[3].rejected);
  vcd = answer->contents()[3].media_description();
  ASSERT_EQ(vcd->streams().size(), 1u);
  EXPECT_EQ(vcd->streams()[0].id, kVideoTrack2);
  EXPECT_EQ(vcd->direction(), RtpTransceiverDirection::kSendRecv);
}

// Test that the media section will be rejected in offer if the corresponding
// MediaDescriptionOptions is stopped by the offerer.
TEST_F(MediaSessionDescriptionFactoryTest,
       CreateOfferWithMediaSectionStoppedByOfferer) {
  // Create an offer with two audio sections and one of them is stopped.
  MediaSessionOptions offer_opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &offer_opts);
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio2",
                             RtpTransceiverDirection::kInactive, kStopped,
                             &offer_opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 2u);
  EXPECT_FALSE(offer->contents()[0].rejected);
  EXPECT_TRUE(offer->contents()[1].rejected);
}

// Test that the media section will be rejected in answer if the corresponding
// MediaDescriptionOptions is stopped by the offerer.
TEST_F(MediaSessionDescriptionFactoryTest,
       CreateAnswerWithMediaSectionStoppedByOfferer) {
  // Create an offer with two audio sections and one of them is stopped.
  MediaSessionOptions offer_opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &offer_opts);
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio2",
                             RtpTransceiverDirection::kInactive, kStopped,
                             &offer_opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 2u);
  EXPECT_FALSE(offer->contents()[0].rejected);
  EXPECT_TRUE(offer->contents()[1].rejected);

  // Create an answer based on the offer.
  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
  ASSERT_EQ(answer->contents().size(), 2u);
  EXPECT_FALSE(answer->contents()[0].rejected);
  EXPECT_TRUE(answer->contents()[1].rejected);
}

// Test that the media section will be rejected in answer if the corresponding
// MediaDescriptionOptions is stopped by the answerer.
TEST_F(MediaSessionDescriptionFactoryTest,
       CreateAnswerWithMediaSectionRejectedByAnswerer) {
  // Create an offer with two audio sections.
  MediaSessionOptions offer_opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &offer_opts);
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &offer_opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 2u);
  ASSERT_FALSE(offer->contents()[0].rejected);
  ASSERT_FALSE(offer->contents()[1].rejected);

  // The answerer rejects one of the audio sections.
  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  AddMediaDescriptionOptions(MediaType::AUDIO, "audio2",
                             RtpTransceiverDirection::kInactive, kStopped,
                             &answer_opts);
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
  ASSERT_EQ(answer->contents().size(), 2u);
  EXPECT_FALSE(answer->contents()[0].rejected);
  EXPECT_TRUE(answer->contents()[1].rejected);

  // The TransportInfo of the rejected m= section is expected to be added in the
  // answer.
  EXPECT_EQ(offer->transport_infos().size(), answer->transport_infos().size());
}

// Test the generated media sections has the same order of the
// corresponding MediaDescriptionOptions.
TEST_F(MediaSessionDescriptionFactoryTest,
       CreateOfferRespectsMediaDescriptionOptionsOrder) {
  MediaSessionOptions opts;
  // This tests put video section first because normally audio comes first by
  // default.
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();

  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 2u);
  EXPECT_EQ(offer->contents()[0].mid(), kVideoMid);
  EXPECT_EQ(offer->contents()[1].mid(), kAudioMid);
}

// Test that different media sections using the same codec have same payload
// type.
TEST_F(MediaSessionDescriptionFactoryTest,
       PayloadTypesSharedByMediaSectionsOfSameType) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, "video1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, "video2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  // Create an offer with two video sections using same codecs.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 2u);
  const MediaContentDescription* vcd1 =
      offer->contents()[0].media_description();
  const MediaContentDescription* vcd2 =
      offer->contents()[1].media_description();
  EXPECT_EQ(vcd1->codecs().size(), vcd2->codecs().size());
  ASSERT_EQ(vcd1->codecs().size(), 2u);
  EXPECT_EQ(vcd1->codecs()[0].name, vcd2->codecs()[0].name);
  EXPECT_EQ(vcd1->codecs()[0].id, vcd2->codecs()[0].id);
  EXPECT_EQ(vcd1->codecs()[1].name, vcd2->codecs()[1].name);
  EXPECT_EQ(vcd1->codecs()[1].id, vcd2->codecs()[1].id);

  // Create answer and negotiate the codecs.
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ASSERT_THAT(answer, NotNull());
  ASSERT_EQ(answer->contents().size(), 2u);
  vcd1 = answer->contents()[0].media_description();
  vcd2 = answer->contents()[1].media_description();
  EXPECT_EQ(vcd1->codecs().size(), vcd2->codecs().size());
  ASSERT_EQ(vcd1->codecs().size(), 1u);
  EXPECT_EQ(vcd1->codecs()[0].name, vcd2->codecs()[0].name);
  EXPECT_EQ(vcd1->codecs()[0].id, vcd2->codecs()[0].id);
}

// Parameterized Sframe tests over MediaType::AUDIO and MediaType::VIDEO.
// The offer/answer Sframe logic is identical for both media types.
class SframeMediaTypeTest : public MediaSessionDescriptionFactoryTest,
                            public ::testing::WithParamInterface<MediaType> {
 protected:
  static constexpr auto kTestMid = "sframe_test";

  // Creates MediaSessionOptions with a single recv-only media section
  // for the parameterized media type.
  MediaSessionOptions CreateMediaSession() {
    MediaSessionOptions opts;
    AddMediaDescriptionOptions(GetParam(), kTestMid,
                               RtpTransceiverDirection::kRecvOnly, kActive,
                               &opts);
    return opts;
  }
};

TEST_P(SframeMediaTypeTest, OfferWithoutSframeHasSframeEnabledFalse) {
  MediaSessionOptions opts = CreateMediaSession();
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 1u);
  EXPECT_FALSE(offer->contents()[0].media_description()->sframe_enabled());
}

TEST_P(SframeMediaTypeTest, OfferWithSframeHasSframeEnabledTrue) {
  MediaSessionOptions opts = CreateMediaSession();
  opts.media_description_options[0].sframe_enabled = true;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 1u);
  EXPECT_TRUE(offer->contents()[0].media_description()->sframe_enabled());
}

TEST_P(SframeMediaTypeTest, AnswerWithoutSframeHasSframeEnabledFalse) {
  MediaSessionOptions opts = CreateMediaSession();
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ASSERT_THAT(answer, NotNull());
  ASSERT_EQ(answer->contents().size(), 1u);
  EXPECT_FALSE(answer->contents()[0].media_description()->sframe_enabled());
}

TEST_P(SframeMediaTypeTest, AnswerWithSframeHasSframeEnabledTrue) {
  MediaSessionOptions offer_opts = CreateMediaSession();
  offer_opts.media_description_options[0].sframe_enabled = true;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  MediaSessionOptions answer_opts = CreateMediaSession();
  answer_opts.media_description_options[0].sframe_enabled = true;
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
  ASSERT_THAT(answer, NotNull());
  ASSERT_EQ(answer->contents().size(), 1u);
  EXPECT_TRUE(answer->contents()[0].media_description()->sframe_enabled());
}

INSTANTIATE_TEST_SUITE_P(Sframe,
                         SframeMediaTypeTest,
                         ::testing::Values(MediaType::AUDIO, MediaType::VIDEO),
                         [](const ::testing::TestParamInfo<MediaType>& info) {
                           return MediaTypeToString(info.param);
                         });

TEST_F(MediaSessionDescriptionFactoryTest,
       AudioVideoOfferWithoutSframeHasSframeEnabledFalse) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 2u);
  EXPECT_FALSE(offer->contents()[0].media_description()->sframe_enabled());
  EXPECT_FALSE(offer->contents()[1].media_description()->sframe_enabled());
}

// Verify that in an audio+video offer, Sframe on video does not affect audio.
TEST_F(MediaSessionDescriptionFactoryTest,
       AudioVideoOfferWithSframeHasSframeEnabledTrue) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  opts.media_description_options[1].sframe_enabled = true;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 2u);
  // Audio should not have Sframe since we only set it on the video section.
  EXPECT_FALSE(offer->contents()[0].media_description()->sframe_enabled());
  EXPECT_TRUE(offer->contents()[1].media_description()->sframe_enabled());
}

// Verify that when the offer has Sframe but the answerer does not,
// answer creation succeeds and the answer has sframe_enabled=false.
// The offerer is responsible for handling the mismatch (per
// draft-ietf-avtcore-rtp-sframe-02 §6).
TEST_F(MediaSessionDescriptionFactoryTest,
       AnswerWithoutSframeSucceedsWhenOfferHasSframe) {
  MediaSessionOptions offer_opts = CreateAudioMediaSession();
  offer_opts.media_description_options[0].sframe_enabled = true;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 1u);
  EXPECT_TRUE(offer->contents()[0].media_description()->sframe_enabled());
  // Answerer does NOT set sframe_enabled — answer creation should succeed.
  MediaSessionOptions answer_opts = CreateAudioMediaSession();
  auto result = f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr);
  ASSERT_TRUE(result.ok());
  ASSERT_EQ(result.value()->contents().size(), 1u);
  EXPECT_FALSE(
      result.value()->contents()[0].media_description()->sframe_enabled());
}

// Verify that when the answerer wants Sframe but the offer does not have it,
// the answer must NOT include a=sframe (RFC 3264: answer is constrained by
// the offer).
TEST_F(MediaSessionDescriptionFactoryTest,
       AnswerDoesNotInjectSframeWhenOfferLacksIt) {
  MediaSessionOptions offer_opts = CreateAudioMediaSession();
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 1u);
  EXPECT_FALSE(offer->contents()[0].media_description()->sframe_enabled());
  MediaSessionOptions answer_opts = CreateAudioMediaSession();
  answer_opts.media_description_options[0].sframe_enabled = true;
  auto result = f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr);
  ASSERT_TRUE(result.ok());
  ASSERT_EQ(result.value()->contents().size(), 1u);
  EXPECT_FALSE(
      result.value()->contents()[0].media_description()->sframe_enabled());
}

TEST_F(MediaSessionDescriptionFactoryTest, OfferSframeIsPerMediaSection) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  // Enable Sframe on audio only.
  opts.media_description_options[0].sframe_enabled = true;
  opts.media_description_options[1].sframe_enabled = false;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 2u);
  EXPECT_TRUE(offer->contents()[0].media_description()->sframe_enabled());
  EXPECT_FALSE(offer->contents()[1].media_description()->sframe_enabled());
}

TEST_F(MediaSessionDescriptionFactoryTest, AnswerSframeIsPerMediaSection) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  // Only audio has Sframe in the offer.
  opts.media_description_options[0].sframe_enabled = true;
  opts.media_description_options[1].sframe_enabled = false;
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  // The answerer's options must agree with the offer per section.
  MediaSessionOptions answer_opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &answer_opts);
  answer_opts.media_description_options[0].sframe_enabled = true;
  answer_opts.media_description_options[1].sframe_enabled = false;
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
  ASSERT_THAT(answer, NotNull());
  ASSERT_EQ(answer->contents().size(), 2u);
  EXPECT_TRUE(answer->contents()[0].media_description()->sframe_enabled());
  EXPECT_FALSE(answer->contents()[1].media_description()->sframe_enabled());
}

#ifdef RTC_ENABLE_H265
// Test verifying that negotiating codecs with the same tx-mode retains the
// tx-mode value.
TEST_F(MediaSessionDescriptionFactoryTest, H265TxModeIsEqualRetainIt) {
  std::vector f1_codecs = {CreateVideoCodec(96, "H265")};
  f1_codecs.back().tx_mode = "mrst";
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  std::vector f2_codecs = {CreateVideoCodec(96, "H265")};
  f2_codecs.back().tx_mode = "mrst";
  codec_lookup_helper_2_.SetVideoCodecs(f2_codecs);

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, "video1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  // Create an offer with two video sections using same codecs.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 1u);
  const MediaContentDescription* vcd1 =
      offer->contents()[0].media_description();
  ASSERT_EQ(vcd1->codecs().size(), 1u);
  EXPECT_EQ(vcd1->codecs()[0].tx_mode, "mrst");

  // Create answer and negotiate the codecs.
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ASSERT_THAT(answer, NotNull());
  ASSERT_EQ(answer->contents().size(), 1u);
  vcd1 = answer->contents()[0].media_description();
  ASSERT_EQ(vcd1->codecs().size(), 1u);
  EXPECT_EQ(vcd1->codecs()[0].tx_mode, "mrst");
}

// Test verifying that negotiating codecs with different tx_mode removes
// the tx_mode value.
TEST_F(MediaSessionDescriptionFactoryTest, H265TxModeIsDifferentDropCodecs) {
  std::vector f1_codecs = {CreateVideoCodec(96, "H265")};
  f1_codecs.back().tx_mode = "mrst";
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  std::vector f2_codecs = {CreateVideoCodec(96, "H265")};
  f2_codecs.back().tx_mode = "mrmt";
  codec_lookup_helper_2_.SetVideoCodecs(f2_codecs);

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, "video1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  // Create an offer with two video sections using same codecs.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 1u);
  const VideoContentDescription* vcd1 =
      offer->contents()[0].media_description()->as_video();
  ASSERT_EQ(vcd1->codecs().size(), 1u);
  EXPECT_EQ(vcd1->codecs()[0].tx_mode, "mrst");

  // Create answer and negotiate the codecs.
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ASSERT_THAT(answer, NotNull());
  ASSERT_EQ(answer->contents().size(), 1u);
  vcd1 = answer->contents()[0].media_description()->as_video();
  ASSERT_EQ(vcd1->codecs().size(), 1u);
  EXPECT_EQ(vcd1->codecs()[0].tx_mode, std::nullopt);
}
#endif

// Test verifying that negotiating codecs with the same packetization retains
// the packetization value.
TEST_F(MediaSessionDescriptionFactoryTest, PacketizationIsEqual) {
  std::vector f1_codecs = {CreateVideoCodec(96, "H264")};
  f1_codecs.back().packetization = "raw";
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  std::vector f2_codecs = {CreateVideoCodec(96, "H264")};
  f2_codecs.back().packetization = "raw";
  codec_lookup_helper_2_.SetVideoCodecs(f2_codecs);

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, "video1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  // Create an offer with two video sections using same codecs.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 1u);
  const MediaContentDescription* vcd1 =
      offer->contents()[0].media_description();
  ASSERT_EQ(vcd1->codecs().size(), 1u);
  EXPECT_EQ(vcd1->codecs()[0].packetization, "raw");

  // Create answer and negotiate the codecs.
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ASSERT_THAT(answer, NotNull());
  ASSERT_EQ(answer->contents().size(), 1u);
  vcd1 = answer->contents()[0].media_description();
  ASSERT_EQ(vcd1->codecs().size(), 1u);
  EXPECT_EQ(vcd1->codecs()[0].packetization, "raw");
}

// Test verifying that negotiating codecs with different packetization removes
// the packetization value.
TEST_F(MediaSessionDescriptionFactoryTest, PacketizationIsDifferent) {
  std::vector f1_codecs = {CreateVideoCodec(96, "H264")};
  f1_codecs.back().packetization = "raw";
  codec_lookup_helper_1_.SetVideoCodecs(f1_codecs);

  std::vector f2_codecs = {CreateVideoCodec(96, "H264")};
  f2_codecs.back().packetization = "notraw";
  codec_lookup_helper_2_.SetVideoCodecs(f2_codecs);

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, "video1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  // Create an offer with two video sections using same codecs.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 1u);
  const VideoContentDescription* vcd1 =
      offer->contents()[0].media_description()->as_video();
  ASSERT_EQ(vcd1->codecs().size(), 1u);
  EXPECT_EQ(vcd1->codecs()[0].packetization, "raw");

  // Create answer and negotiate the codecs.
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ASSERT_THAT(answer, NotNull());
  ASSERT_EQ(answer->contents().size(), 1u);
  vcd1 = answer->contents()[0].media_description()->as_video();
  ASSERT_EQ(vcd1->codecs().size(), 1u);
  EXPECT_EQ(vcd1->codecs()[0].packetization, std::nullopt);
}

// Test that the codec preference order per media section is respected in
// subsequent offer.
TEST_F(MediaSessionDescriptionFactoryTest,
       CreateOfferRespectsCodecPreferenceOrder) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, "video1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, "video2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  // Create an offer with two video sections using same codecs.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 2u);
  MediaContentDescription* vcd1 = offer->contents()[0].media_description();
  const MediaContentDescription* vcd2 =
      offer->contents()[1].media_description();
  auto video_codecs = kVideoCodecs1;
  EXPECT_THAT(vcd1->codecs(),
              CodecListsMatch(video_codecs, &env_.field_trials()));
  EXPECT_THAT(vcd2->codecs(),
              CodecListsMatch(video_codecs, &env_.field_trials()));

  // Change the codec preference of the first video section and create a
  // follow-up offer.
  auto video_codecs_reverse = kVideoCodecs1Reverse;
  vcd1->set_codecs(std::vector<Codec>(video_codecs_reverse.begin(),
                                      video_codecs_reverse.end()));
  std::unique_ptr<SessionDescription> updated_offer(
      f1_.CreateOfferOrError(opts, offer.get()).MoveValue());
  vcd1 = updated_offer->contents()[0].media_description();
  vcd2 = updated_offer->contents()[1].media_description();
  // The video codec preference order should be respected.
  EXPECT_THAT(vcd1->codecs(),
              CodecListsMatch(video_codecs_reverse, &env_.field_trials()));
  EXPECT_THAT(vcd2->codecs(),
              CodecListsMatch(video_codecs, &env_.field_trials()));
}

// Test that the codec preference order per media section is respected in
// the answer.
TEST_F(MediaSessionDescriptionFactoryTest,
       CreateAnswerRespectsCodecPreferenceOrder) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, "video1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, "video2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  // Create an offer with two video sections using same codecs.
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  ASSERT_EQ(offer->contents().size(), 2u);
  MediaContentDescription* vcd1 = offer->contents()[0].media_description();
  const MediaContentDescription* vcd2 =
      offer->contents()[1].media_description();
  auto video_codecs = kVideoCodecs1;
  EXPECT_THAT(vcd1->codecs(),
              CodecListsMatch(video_codecs, &env_.field_trials()));
  EXPECT_THAT(vcd2->codecs(),
              CodecListsMatch(video_codecs, &env_.field_trials()));

  // Change the codec preference of the first video section and create an
  // answer.
  auto video_codecs_reverse = kVideoCodecs1Reverse;
  vcd1->set_codecs(std::vector<Codec>(video_codecs_reverse.begin(),
                                      video_codecs_reverse.end()));
  std::unique_ptr<SessionDescription> answer =
      f1_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  vcd1 = answer->contents()[0].media_description();
  vcd2 = answer->contents()[1].media_description();
  // The video codec preference order should be respected.
  EXPECT_THAT(vcd1->codecs(),
              CodecListsMatch(video_codecs_reverse, &env_.field_trials()));
  EXPECT_THAT(vcd2->codecs(),
              CodecListsMatch(video_codecs, &env_.field_trials()));
}

// Test that when creating an answer, the codecs use local parameters instead of
// the remote ones.
TEST_F(MediaSessionDescriptionFactoryTest, CreateAnswerWithLocalCodecParams) {
  const std::string audio_param_name = "audio_param";
  const std::string audio_value1 = "audio_v1";
  const std::string audio_value2 = "audio_v2";
  const std::string video_param_name = "video_param";
  const std::string video_value1 = "video_v1";
  const std::string video_value2 = "video_v2";

  auto audio_codecs1 = kAudioCodecs1;
  auto audio_codecs2 = kAudioCodecs1;
  auto video_codecs1 = kVideoCodecs1;
  auto video_codecs2 = kVideoCodecs1;

  // Set the parameters for codecs.
  audio_codecs1[0].SetParam(audio_param_name, audio_value1);
  video_codecs1[0].SetParam(video_param_name, video_value1);
  audio_codecs2[0].SetParam(audio_param_name, audio_value2);
  video_codecs2[0].SetParam(video_param_name, video_value2);

  codec_lookup_helper_1_.SetAudioCodecs(audio_codecs1);
  codec_lookup_helper_1_.SetVideoCodecs(video_codecs1);
  codec_lookup_helper_2_.SetAudioCodecs(audio_codecs2);
  codec_lookup_helper_2_.SetVideoCodecs(video_codecs2);

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());
  auto offer_acd = offer->contents()[0].media_description();
  auto offer_vcd = offer->contents()[1].media_description();
  std::string value;
  EXPECT_TRUE(offer_acd->codecs()[0].GetParam(audio_param_name, &value));
  EXPECT_EQ(audio_value1, value);
  EXPECT_TRUE(offer_vcd->codecs()[0].GetParam(video_param_name, &value));
  EXPECT_EQ(video_value1, value);

  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ASSERT_THAT(answer, NotNull());
  auto answer_acd = answer->contents()[0].media_description();
  auto answer_vcd = answer->contents()[1].media_description();
  // Use the parameters from the local codecs.
  ASSERT_TRUE(answer_acd);
  ASSERT_THAT(answer_acd->codecs().size(), Gt(0));
  EXPECT_TRUE(answer_acd->codecs()[0].GetParam(audio_param_name, &value));
  EXPECT_EQ(audio_value2, value);
  EXPECT_TRUE(answer_vcd->codecs()[0].GetParam(video_param_name, &value));
  EXPECT_EQ(video_value2, value);
}

// Test that matching packetization-mode is part of the criteria for matching
// H264 codecs (in addition to profile-level-id). Previously, this was not the
// case, so the first H264 codec with the same profile-level-id would match and
// the payload type in the answer would be incorrect.
// This is a regression test for bugs.webrtc.org/8808
TEST_F(MediaSessionDescriptionFactoryTest,
       H264MatchCriteriaIncludesPacketizationMode) {
  // Create two H264 codecs with the same profile level ID and different
  // packetization modes.
  Codec h264_pm0 = CreateVideoCodec(96, "H264");
  h264_pm0.params[kH264FmtpProfileLevelId] = "42c01f";
  h264_pm0.params[kH264FmtpPacketizationMode] = "0";
  Codec h264_pm1 = CreateVideoCodec(97, "H264");
  h264_pm1.params[kH264FmtpProfileLevelId] = "42c01f";
  h264_pm1.params[kH264FmtpPacketizationMode] = "1";

  // Offerer will send both codecs, answerer should choose the one with matching
  // packetization mode (and not the first one it sees).
  codec_lookup_helper_1_.SetVideoCodecs({h264_pm0, h264_pm1});
  codec_lookup_helper_2_.SetVideoCodecs({h264_pm1});

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_THAT(offer, NotNull());

  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  ASSERT_THAT(answer, NotNull());

  // Answer should have one negotiated codec with packetization-mode=1 using the
  // offered payload type.
  ASSERT_EQ(answer->contents().size(), 1u);
  auto answer_vcd = answer->contents()[0].media_description();
  ASSERT_EQ(answer_vcd->codecs().size(), 1u);
  auto answer_codec = answer_vcd->codecs()[0];
  EXPECT_EQ(h264_pm1.id, answer_codec.id);
}

class MediaProtocolTest : public testing::TestWithParam<const char*> {
 public:
  MediaProtocolTest()
      : env_(CreateTestEnvironment()),
        tdf1_(env_.field_trials()),
        tdf2_(env_.field_trials()),
        codec_lookup_helper_1_(env_.field_trials()),
        codec_lookup_helper_2_(env_.field_trials()),
        f1_(env_,
            nullptr,
            false,
            &ssrc_generator1,
            &tdf1_,
            &sctp_factory_1_,
            &codec_lookup_helper_1_),
        f2_(env_,
            nullptr,
            false,
            &ssrc_generator2,
            &tdf2_,
            &sctp_factory_2_,
            &codec_lookup_helper_2_) {
    codec_lookup_helper_1_.SetAudioCodecs(kAudioCodecs1);
    codec_lookup_helper_1_.SetVideoCodecs(kVideoCodecs1);
    codec_lookup_helper_2_.SetAudioCodecs(kAudioCodecs2);
    codec_lookup_helper_2_.SetVideoCodecs(kVideoCodecs2);
    tdf1_.set_certificate(RTCCertificate::Create(
        std::unique_ptr<SSLIdentity>(new FakeSSLIdentity("id1"))));
    tdf2_.set_certificate(RTCCertificate::Create(
        std::unique_ptr<SSLIdentity>(new FakeSSLIdentity("id2"))));
  }

 protected:
  Environment env_;
  TransportDescriptionFactory tdf1_;
  TransportDescriptionFactory tdf2_;
  CodecLookupHelperForTesting codec_lookup_helper_1_;
  CodecLookupHelperForTesting codec_lookup_helper_2_;
  FakeSctpTransportFactory sctp_factory_1_;
  FakeSctpTransportFactory sctp_factory_2_;
  MediaSessionDescriptionFactory f1_;
  MediaSessionDescriptionFactory f2_;
  UniqueRandomIdGenerator ssrc_generator1;
  UniqueRandomIdGenerator ssrc_generator2;
};

TEST_P(MediaProtocolTest, TestAudioVideoAcceptance) {
  MediaSessionOptions opts;
  AddAudioVideoSections(RtpTransceiverDirection::kRecvOnly, &opts);
  std::unique_ptr<SessionDescription> offer =
      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  // Set the protocol for all the contents.
  for (ContentInfo& content : offer->contents()) {
    content.media_description()->set_protocol(GetParam());
  }
  std::unique_ptr<SessionDescription> answer =
      f2_.CreateAnswerOrError(offer.get(), opts, nullptr).MoveValue();
  const ContentInfo* ac = answer->GetContentByName(kAudioMid);
  const ContentInfo* vc = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  EXPECT_FALSE(ac->rejected);  // the offer is accepted
  EXPECT_FALSE(vc->rejected);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();
  EXPECT_EQ(GetParam(), acd->protocol());
  EXPECT_EQ(GetParam(), vcd->protocol());
}

INSTANTIATE_TEST_SUITE_P(MediaProtocolPatternTest,
                         MediaProtocolTest,
                         ValuesIn(kMediaProtocols));
INSTANTIATE_TEST_SUITE_P(MediaProtocolDtlsPatternTest,
                         MediaProtocolTest,
                         ValuesIn(kMediaProtocolsDtls));

void TestAudioCodecsOffer(RtpTransceiverDirection direction) {
  Environment env(CreateTestEnvironment());
  TransportDescriptionFactory tdf(env.field_trials());
  tdf.set_certificate(RTCCertificate::Create(
      std::unique_ptr<SSLIdentity>(new FakeSSLIdentity("id"))));

  UniqueRandomIdGenerator ssrc_generator;
  CodecLookupHelperForTesting codec_lookup_helper(env.field_trials());
  FakeSctpTransportFactory sctpf;
  MediaSessionDescriptionFactory sf(env, nullptr, false, &ssrc_generator, &tdf,
                                    &sctpf, &codec_lookup_helper);
  const std::vector<Codec> send_codecs(kAudioCodecs1.begin(),
                                       kAudioCodecs1.end());
  const std::vector<Codec> recv_codecs(kAudioCodecs2.begin(),
                                       kAudioCodecs2.end());
  const std::vector<Codec> sendrecv_codecs(kAudioCodecsAnswer.begin(),
                                           kAudioCodecsAnswer.end());
  codec_lookup_helper.SetAudioCodecs(send_codecs, recv_codecs);

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid, direction, kActive,
                             &opts);

  if (direction == RtpTransceiverDirection::kSendRecv ||
      direction == RtpTransceiverDirection::kSendOnly) {
    AttachSenderToMediaDescriptionOptions(
        kAudioMid, MediaType::AUDIO, kAudioTrack1, {kMediaStream1}, 1, &opts);
  }

  std::unique_ptr<SessionDescription> offer =
      sf.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  ContentInfo* ac = offer->GetContentByName(kAudioMid);

  // If the factory didn't add any audio content to the offer, we cannot check
  // that the codecs put in are right. This happens when we neither want to
  // send nor receive audio. The checks are still in place if at some point
  // we'd instead create an inactive stream.
  if (ac) {
    MediaContentDescription* acd = ac->media_description();
    // sendrecv and inactive should both present lists as if the channel was
    // to be used for sending and receiving. Inactive essentially means it
    // might eventually be used anything, but we don't know more at this
    // moment.
    if (acd->direction() == RtpTransceiverDirection::kSendOnly) {
      EXPECT_THAT(acd->codecs(),
                  CodecListsMatch(send_codecs, &env.field_trials()));
    } else if (acd->direction() == RtpTransceiverDirection::kRecvOnly) {
      EXPECT_THAT(acd->codecs(),
                  CodecListsMatch(recv_codecs, &env.field_trials()));
    } else {
      EXPECT_THAT(acd->codecs(),
                  CodecListsMatch(sendrecv_codecs, &env.field_trials()));
    }
  }
}

// Since the PT suggester reserves the static range for specific codecs,
// PT numbers from the 36-63 range are used.
const Codec kOfferAnswerCodecs[] = {CreateAudioCodec(40, "codec0", 16000, 1),
                                    CreateAudioCodec(41, "codec1", 8000, 1),
                                    CreateAudioCodec(42, "codec2", 8000, 1),
                                    CreateAudioCodec(43, "codec3", 8000, 1),
                                    CreateAudioCodec(44, "codec4", 8000, 2),
                                    CreateAudioCodec(45, "codec5", 32000, 1),
                                    CreateAudioCodec(46, "codec6", 48000, 1)};

/* The codecs groups below are chosen as per the matrix below. The objective
 * is to have different sets of codecs in the inputs, to get unique sets of
 * codecs after negotiation, depending on offer and answer communication
 * directions. One-way directions in the offer should either result in the
 * opposite direction in the answer, or an inactive answer. Regardless, the
 * choice of codecs should be as if the answer contained the opposite
 * direction. Inactive offers should be treated as sendrecv/sendrecv.
 *
 *         |     Offer   |      Answer  |         Result
 *    codec|send recv sr | send recv sr | s/r  r/s sr/s sr/r sr/sr
 *     0   | x    -    - |  -    x    - |  x    -    -    -    -
 *     1   | x    x    x |  -    x    - |  x    -    -    x    -
 *     2   | -    x    - |  x    -    - |  -    x    -    -    -
 *     3   | x    x    x |  x    -    - |  -    x    x    -    -
 *     4   | -    x    - |  x    x    x |  -    x    -    -    -
 *     5   | x    -    - |  x    x    x |  x    -    -    -    -
 *     6   | x    x    x |  x    x    x |  x    x    x    x    x
 */
// Codecs used by offerer in the AudioCodecsAnswerTest
constexpr int kOfferSendCodecs[] = {0, 1, 3, 5, 6};
constexpr int kOfferRecvCodecs[] = {1, 2, 3, 4, 6};
// Codecs used in the answerer in the AudioCodecsAnswerTest.  The order is
// jumbled to catch the answer not following the order in the offer.
constexpr int kAnswerSendCodecs[] = {6, 5, 2, 3, 4};
constexpr int kAnswerRecvCodecs[] = {6, 5, 4, 1, 0};
// The resulting sets of codecs in the answer in the AudioCodecsAnswerTest
constexpr int kResultSend_RecvCodecs[] = {0, 1, 5, 6};
constexpr int kResultRecv_SendCodecs[] = {2, 3, 4, 6};
constexpr int kResultSendrecv_SendCodecs[] = {3, 6};
constexpr int kResultSendrecv_RecvCodecs[] = {1, 6};
constexpr int kResultSendrecv_SendrecvCodecs[] = {6};

template <typename T, int IDXS>
std::vector<T> VectorFromIndices(const T* array, const int (&indices)[IDXS]) {
  std::vector<T> out;
  out.reserve(IDXS);
  for (int idx : indices)
    out.push_back(array[idx]);

  return out;
}

void TestAudioCodecsAnswer(RtpTransceiverDirection offer_direction,
                           RtpTransceiverDirection answer_direction,
                           bool add_legacy_stream) {
  Environment env(CreateTestEnvironment());
  TransportDescriptionFactory offer_tdf(env.field_trials());
  TransportDescriptionFactory answer_tdf(env.field_trials());
  FakeSctpTransportFactory offer_sctpf;
  FakeSctpTransportFactory answer_sctpf;
  offer_tdf.set_certificate(RTCCertificate::Create(
      std::unique_ptr<SSLIdentity>(new FakeSSLIdentity("offer_id"))));
  answer_tdf.set_certificate(RTCCertificate::Create(
      std::unique_ptr<SSLIdentity>(new FakeSSLIdentity("answer_id"))));
  UniqueRandomIdGenerator ssrc_generator1, ssrc_generator2;
  CodecLookupHelperForTesting offer_codec_lookup_helper(env.field_trials());
  MediaSessionDescriptionFactory offer_factory(
      env, nullptr, false, &ssrc_generator1, &offer_tdf, &offer_sctpf,
      &offer_codec_lookup_helper);
  CodecLookupHelperForTesting answer_codec_lookup_helper(env.field_trials());
  MediaSessionDescriptionFactory answer_factory(
      env, nullptr, false, &ssrc_generator2, &answer_tdf, &answer_sctpf,
      &answer_codec_lookup_helper);

  offer_codec_lookup_helper.SetAudioCodecs(
      VectorFromIndices(kOfferAnswerCodecs, kOfferSendCodecs),
      VectorFromIndices(kOfferAnswerCodecs, kOfferRecvCodecs));
  answer_codec_lookup_helper.SetAudioCodecs(
      VectorFromIndices(kOfferAnswerCodecs, kAnswerSendCodecs),
      VectorFromIndices(kOfferAnswerCodecs, kAnswerRecvCodecs));

  MediaSessionOptions offer_opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid, offer_direction,
                             kActive, &offer_opts);

  if (RtpTransceiverDirectionHasSend(offer_direction)) {
    AttachSenderToMediaDescriptionOptions(kAudioMid, MediaType::AUDIO,
                                          kAudioTrack1, {kMediaStream1}, 1,
                                          &offer_opts);
  }

  std::unique_ptr<SessionDescription> offer =
      offer_factory.CreateOfferOrError(offer_opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::AUDIO, kAudioMid, answer_direction,
                             kActive, &answer_opts);

  if (RtpTransceiverDirectionHasSend(answer_direction)) {
    AttachSenderToMediaDescriptionOptions(kAudioMid, MediaType::AUDIO,
                                          kAudioTrack1, {kMediaStream1}, 1,
                                          &answer_opts);
  }
  std::unique_ptr<SessionDescription> answer =
      answer_factory.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  const ContentInfo* ac = answer->GetContentByName(kAudioMid);

  // If the factory didn't add any audio content to the answer, we cannot
  // check that the codecs put in are right. This happens when we neither want
  // to send nor receive audio. The checks are still in place if at some point
  // we'd instead create an inactive stream.
  if (ac) {
    ASSERT_EQ(ac->media_description()->type(), MediaType::AUDIO);
    const MediaContentDescription* acd = ac->media_description();

    std::vector<Codec> target_codecs;
    // For offers with sendrecv or inactive, we should never reply with more
    // codecs than offered, with these codec sets.
    switch (offer_direction) {
      case RtpTransceiverDirection::kInactive:
        target_codecs = VectorFromIndices(kOfferAnswerCodecs,
                                          kResultSendrecv_SendrecvCodecs);
        break;
      case RtpTransceiverDirection::kSendOnly:
        target_codecs =
            VectorFromIndices(kOfferAnswerCodecs, kResultSend_RecvCodecs);
        break;
      case RtpTransceiverDirection::kRecvOnly:
        target_codecs =
            VectorFromIndices(kOfferAnswerCodecs, kResultRecv_SendCodecs);
        break;
      case RtpTransceiverDirection::kSendRecv:
        if (acd->direction() == RtpTransceiverDirection::kSendOnly) {
          target_codecs =
              VectorFromIndices(kOfferAnswerCodecs, kResultSendrecv_SendCodecs);
        } else if (acd->direction() == RtpTransceiverDirection::kRecvOnly) {
          target_codecs =
              VectorFromIndices(kOfferAnswerCodecs, kResultSendrecv_RecvCodecs);
        } else {
          target_codecs = VectorFromIndices(kOfferAnswerCodecs,
                                            kResultSendrecv_SendrecvCodecs);
        }
        break;
      case RtpTransceiverDirection::kStopped:
        // This does not happen in any current test.
        RTC_DCHECK_NOTREACHED();
    }

    auto format_codecs = [](std::span<const Codec> codecs) {
      StringBuilder os;
      bool first = true;
      os << "{";
      for (const Codec& c : codecs) {
        os << (first ? " " : ", ") << c.id << ":" << c.name;
        first = false;
      }
      os << " }";
      return os.Release();
    };

    EXPECT_THAT(acd->codecs(),
                CodecListsMatch(target_codecs, &env.field_trials()))
        << "Expected: " << format_codecs(target_codecs)
        << ", got: " << format_codecs(acd->codecs())
        << "; Offered: " << RtpTransceiverDirectionToString(offer_direction)
        << ", answerer wants: "
        << RtpTransceiverDirectionToString(answer_direction)
        << "; got: " << RtpTransceiverDirectionToString(acd->direction());
  } else {
    EXPECT_EQ(offer_direction, RtpTransceiverDirection::kInactive)
        << "Only inactive offers are allowed to not generate any audio "
           "content";
  }
}

using AudioCodecsOfferTest = testing::TestWithParam<RtpTransceiverDirection>;

TEST_P(AudioCodecsOfferTest, TestCodecsInOffer) {
  TestAudioCodecsOffer(GetParam());
}

INSTANTIATE_TEST_SUITE_P(MediaSessionDescriptionFactoryTest,
                         AudioCodecsOfferTest,
                         Values(RtpTransceiverDirection::kSendOnly,
                                RtpTransceiverDirection::kRecvOnly,
                                RtpTransceiverDirection::kSendRecv,
                                RtpTransceiverDirection::kInactive));

using AudioCodecsAnswerTest = testing::TestWithParam<
    std::tuple<RtpTransceiverDirection, RtpTransceiverDirection, bool>>;

TEST_P(AudioCodecsAnswerTest, TestCodecsInAnswer) {
  TestAudioCodecsAnswer(std::get<0>(GetParam()), std::get<1>(GetParam()),
                        std::get<2>(GetParam()));
}

INSTANTIATE_TEST_SUITE_P(MediaSessionDescriptionFactoryTest,
                         AudioCodecsAnswerTest,
                         Combine(Values(RtpTransceiverDirection::kSendOnly,
                                        RtpTransceiverDirection::kRecvOnly,
                                        RtpTransceiverDirection::kSendRecv,
                                        RtpTransceiverDirection::kInactive),
                                 Values(RtpTransceiverDirection::kSendOnly,
                                        RtpTransceiverDirection::kRecvOnly,
                                        RtpTransceiverDirection::kSendRecv,
                                        RtpTransceiverDirection::kInactive),
                                 Bool()));

#ifdef RTC_ENABLE_H265
class VideoCodecsOfferH265LevelIdTest : public testing::Test {
 public:
  VideoCodecsOfferH265LevelIdTest()
      : env_(CreateTestEnvironment()),
        tdf_offerer_(env_.field_trials()),
        tdf_answerer_(env_.field_trials()),
        sf_offerer_(env_,
                    nullptr,
                    false,
                    &ssrc_generator_offerer_,
                    &tdf_offerer_,
                    &sctpf_offerer_,
                    &codec_lookup_helper_offerer_),
        sf_answerer_(env_,
                     nullptr,
                     false,
                     &ssrc_generator_answerer_,
                     &tdf_answerer_,
                     &sctpf_answerer_,
                     &codec_lookup_helper_answerer_),
        codec_lookup_helper_offerer_(env_.field_trials()),
        codec_lookup_helper_answerer_(env_.field_trials()) {
    tdf_offerer_.set_certificate(RTCCertificate::Create(
        std::unique_ptr<SSLIdentity>(new FakeSSLIdentity("offer_id"))));
    tdf_answerer_.set_certificate(RTCCertificate::Create(
        std::unique_ptr<SSLIdentity>(new FakeSSLIdentity("answer_id"))));
  }

  void CheckH265Level(std::span<const Codec> codecs,
                      const std::string& expected_level) {
    for (const Codec& codec : codecs) {
      if (codec.name == "H265") {
        auto it = codec.params.find("level-id");
        ASSERT_TRUE(it != codec.params.end());
        EXPECT_EQ(it->second, expected_level);
      }
    }
  }

 protected:
  Environment env_;
  TransportDescriptionFactory tdf_offerer_;
  TransportDescriptionFactory tdf_answerer_;
  UniqueRandomIdGenerator ssrc_generator_offerer_;
  UniqueRandomIdGenerator ssrc_generator_answerer_;
  FakeSctpTransportFactory sctpf_offerer_;
  FakeSctpTransportFactory sctpf_answerer_;
  MediaSessionDescriptionFactory sf_offerer_;
  MediaSessionDescriptionFactory sf_answerer_;
  CodecLookupHelperForTesting codec_lookup_helper_offerer_;
  CodecLookupHelperForTesting codec_lookup_helper_answerer_;
};

// Both sides support H.265 level 5.2 for encoding and decoding.
// Offer: level 5.2, SendRecv
// Answer: level 5.2, SendRecv
TEST_F(VideoCodecsOfferH265LevelIdTest, TestSendRecvSymmetrical) {
  const std::vector<Codec> send_codecs(kVideoCodecsH265Level52.begin(),
                                       kVideoCodecsH265Level52.end());
  const std::vector<Codec> recv_codecs(kVideoCodecsH265Level52.begin(),
                                       kVideoCodecsH265Level52.end());
  const std::vector<Codec> sendrecv_codecs(kVideoCodecsH265Level52.begin(),
                                           kVideoCodecsH265Level52.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(send_codecs, recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(recv_codecs, send_codecs);
  EXPECT_THAT(codec_lookup_helper_offerer_.GetCodecVendor()
                  ->video_sendrecv_codecs()
                  .codecs(),
              CodecListsMatch(sendrecv_codecs, &env_.field_trials()));

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level52LevelId);
}

// Both sides support H.265 level 6.0 for encoding and decoding.
// Offer: level 6.0, SendOnly
// Answer: level 6.0, RecvOnly
TEST_F(VideoCodecsOfferH265LevelIdTest, TestSendOnlySymmetrical) {
  const std::vector<Codec> send_codecs(kVideoCodecsH265Level6.begin(),
                                       kVideoCodecsH265Level6.end());
  const std::vector<Codec> recv_codecs(kVideoCodecsH265Level6.begin(),
                                       kVideoCodecsH265Level6.end());
  const std::vector<Codec> sendrecv_codecs(kVideoCodecsH265Level6.begin(),
                                           kVideoCodecsH265Level6.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(send_codecs, recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(recv_codecs, send_codecs);
  EXPECT_THAT(codec_lookup_helper_offerer_.GetCodecVendor()
                  ->video_sendrecv_codecs()
                  .codecs(),
              CodecListsMatch(sendrecv_codecs, &env_.field_trials()));

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &opts);
  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level6LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level6LevelId);
}

// Both sides support H.265 level 5.2 for encoding and decoding.
// Offer: level 5.2, RecvOnly
// Answer: level 5.2, SendOnly
TEST_F(VideoCodecsOfferH265LevelIdTest, TestRecvOnlySymmetrical) {
  const std::vector<Codec> send_codecs(kVideoCodecsH265Level52.begin(),
                                       kVideoCodecsH265Level52.end());
  const std::vector<Codec> recv_codecs(kVideoCodecsH265Level52.begin(),
                                       kVideoCodecsH265Level52.end());
  const std::vector<Codec> sendrecv_codecs(kVideoCodecsH265Level52.begin(),
                                           kVideoCodecsH265Level52.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(send_codecs, recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(recv_codecs, send_codecs);
  EXPECT_THAT(codec_lookup_helper_offerer_.GetCodecVendor()
                  ->video_sendrecv_codecs()
                  .codecs(),
              CodecListsMatch(sendrecv_codecs, &env_.field_trials()));

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level52LevelId);
}

// Offerer encodes up to level 5.2, and decodes up to level 6.0.
// Answerer encodes up to level 6.0, and decodes up to level 5.2.
// Offer: level 5.2, SendRecv
// Answer: level 5.2, SendRecv
TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendRecvOffererEncode52Decode60AnswererEncode60Decode52) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                                kVideoCodecsH265Level52.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_THAT(codec_lookup_helper_offerer_.GetCodecVendor()
                  ->video_sendrecv_codecs()
                  .codecs(),
              CodecListsMatch(offerer_sendrecv_codecs, &env_.field_trials()));

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level52LevelId);
}

// Offerer encodes up to level 6, and decodes up to level 5.2.
// Answerer encodes up to level 5.2, and decodes up to level 6.0.
// Offer: level 5.2, SendRecv
// Answer: level 5.2, SendRecv
TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendRecvOffererEncode60Decode52AnswererEncode52Decode60) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level52.begin(),
                                                kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level52LevelId);
}

// Offerer encodes up to level 6, and decodes up to level 5.2.
// Answerer encodes up to level 3.1, and decodes up to level 5.0.
// Offer: level 5.2, SendRecv
// Answer: level 3.1, SendRecv
TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendRecvOffererEncode60Decode52AnswererEncode31Decode50) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level31.begin(),
                                                kVideoCodecsH265Level31.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level5.begin(),
                                                kVideoCodecsH265Level5.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level31, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level31LevelId);

  std::unique_ptr<SessionDescription> reoffer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(reoffer.get());
  const ContentInfo* reoffer_oc = reoffer->GetContentByName(kVideoMid);
  ASSERT_TRUE(reoffer_oc);
  const MediaContentDescription* reoffer_ocd = reoffer_oc->media_description();
  EXPECT_THAT(reoffer_ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);
}

// Offerer encodes up to level 6, and decodes up to level 5.2.
// Answerer encodes up to level 4, and decodes up to level 6.
// Offer: level 5.2, SendRecv
// Answer: level 4, SendRecv
TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendRecvOffererEncode60Decode52AnswererEncode40Decode60) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level4.begin(),
                                                kVideoCodecsH265Level4.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level4, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level4LevelId);
}

// Offerer encodes up to level 4, and decodes up to level 6.
// Answerer encodes up to level 6, and decodes up to level 5.2.
// Offer: level 4, SendRecv
// Answer: level 4, SendRecv
TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendRecvOffererEncode40Decode60AnswererEncode60Decode52) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level4.begin(),
                                               kVideoCodecsH265Level4.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level4.begin(), kVideoCodecsH265Level4.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                                kVideoCodecsH265Level52.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level4, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level4LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level4, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level4LevelId);
}

// Offerer encodes up to level 5.2, and decodes up to level 6.
// Answerer encodes up to level 6, and decodes up to level 5.2.
// Offer: level 6, RecvOnly
// Answer: level 6, SendOnly
TEST_F(VideoCodecsOfferH265LevelIdTest,
       RecvOnlyOffererEncode52Decode60AnswererEncode60Decode52) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                                kVideoCodecsH265Level52.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level6LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level6LevelId);
}

// Offerer encodes up to level 6, and decodes up to level 5.2.
// Answerer encodes up to level 5.2, and decodes up to level 6.
// Offer: level 5.2, RecvOnly
// Answer: level 5.2, SendOnly
TEST_F(VideoCodecsOfferH265LevelIdTest,
       RecvOnlyOffererEncode60Decode52AnswererEncode52Decode60) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level52.begin(),
                                                kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level52LevelId);
}

// Offerer encodes up to level 6, and decodes up to level 5.2.
// Answerer encodes up to level 3.1, and decodes up to level 5.
// Offer: level 5.2, RecvOnly
// Answer: level 3.1, SendOnly
TEST_F(VideoCodecsOfferH265LevelIdTest,
       RecvOnlyOffererEncode60Decode52AnswererEncode31Decode50) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level31.begin(),
                                                kVideoCodecsH265Level31.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level5.begin(),
                                                kVideoCodecsH265Level5.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level31, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level31LevelId);
}

// Offerer encodes up to level 6, and decodes up to level 5.2.
// Answerer encodes up to level 4, and decodes up to level 6.
// Offer: level 5.2, RecvOnly
// Answer: level 4, SendOnly
TEST_F(VideoCodecsOfferH265LevelIdTest,
       RecvOnlyOffererEncode60Decode52AnswererEncode40Decode60) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level4.begin(),
                                                kVideoCodecsH265Level4.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level4, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level4LevelId);
}

// Offerer encodes up to level 4, and decodes up to level 6.
// Answerer encodes up to level 6, and decodes up to level 5.2.
// Offer: level 6, RecvOnly
// Answer: level 6, SendOnly
TEST_F(VideoCodecsOfferH265LevelIdTest,
       RecvOnlyOffererEncode40Decode60AnswererEncode60Decode52) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level4.begin(),
                                               kVideoCodecsH265Level4.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level4.begin(), kVideoCodecsH265Level4.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                                kVideoCodecsH265Level52.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level6LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &answer_opts);
  AttachSenderToMediaDescriptionOptions(kVideoMid, MediaType::VIDEO,
                                        kVideoTrack1, {kMediaStream1}, 1,
                                        &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level6LevelId);
}

// Offerer encodes up to level 5.2, and decodes up to level 6.
// Answerer encodes up to level 6, and decodes up to level 5.2.
// Offer: level 5.2, SendOnly
// Answer: level 5.2, RecvOnly
TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendOnlyOffererEncode52Decode60AnswererEncode60Decode52) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                                kVideoCodecsH265Level52.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level52LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level52, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level52LevelId);
}

// Offerer encodes up to level 6, and decodes up to level 5.2.
// Answerer encodes up to level 5.2, and decodes up to level 6.
// Offer: level 6, SendOnly
// Answer: level 6, RecvOnly
TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendOnlyOffererEncode60Decode52AnswererEncode52Decode60) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level52.begin(),
                                                kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level6LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level6LevelId);
}

// Offerer encodes up to level 6, and decodes up to level 5.2.
// Answerer encodes up to level 3.1, and decodes up to level 5.
// Offer: level 6, SendOnly
// Answer: level 5, RecvOnly
TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendOnlyOffererEncode60Decode52AnswererEncode31Decode50) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level31.begin(),
                                                kVideoCodecsH265Level31.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level5.begin(),
                                                kVideoCodecsH265Level5.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level6LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level5, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level5LevelId);
}

// Offerer encodes up to level 6, and decodes up to level 5.2.
// Answerer encodes up to level 4, and decodes up to level 6.
// Offer: level 6, SendOnly
// Answer: level 6, RecvOnly
TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendOnlyOffererEncode60Decode52AnswererEncode40Decode60) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                               kVideoCodecsH265Level52.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level52.begin(), kVideoCodecsH265Level52.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level4.begin(),
                                                kVideoCodecsH265Level4.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level6LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level6, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level6LevelId);
}

// Offerer encodes up to level 4, and decodes up to level 6.
// Answerer encodes up to level 6, and decodes up to level 5.2.
// Offer: level 4, SendOnly
// Answer: level 4, RecvOnly
TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendOnlyOffererEncode40Decode60AnswererEncode60Decode52) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level4.begin(),
                                               kVideoCodecsH265Level4.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level4.begin(), kVideoCodecsH265Level4.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                                kVideoCodecsH265Level52.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendOnly, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level4, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level4LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level4, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level4LevelId);
}

TEST_F(VideoCodecsOfferH265LevelIdTest,
       SendOnlyOffererEncode40Decode60AnswererEncode60Decode52WithPreference) {
  const std::vector<Codec> offerer_send_codecs(kVideoCodecsH265Level4.begin(),
                                               kVideoCodecsH265Level4.end());
  const std::vector<Codec> offerer_recv_codecs(kVideoCodecsH265Level6.begin(),
                                               kVideoCodecsH265Level6.end());
  const std::vector<Codec> offerer_sendrecv_codecs(
      kVideoCodecsH265Level4.begin(), kVideoCodecsH265Level4.end());
  const std::vector<Codec> answerer_send_codecs(kVideoCodecsH265Level6.begin(),
                                                kVideoCodecsH265Level6.end());
  const std::vector<Codec> answerer_recv_codecs(kVideoCodecsH265Level52.begin(),
                                                kVideoCodecsH265Level52.end());
  codec_lookup_helper_offerer_.SetVideoCodecs(offerer_send_codecs,
                                              offerer_recv_codecs);
  codec_lookup_helper_answerer_.SetVideoCodecs(answerer_send_codecs,
                                               answerer_recv_codecs);
  EXPECT_EQ(offerer_sendrecv_codecs,
            codec_lookup_helper_offerer_.GetCodecVendor()
                ->video_sendrecv_codecs()
                .codecs());

  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);

  AttachSenderToMediaDescriptionOptions(
      kVideoMid, MediaType::VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);
  std::vector<RtpCodecCapability> preferences;
  for (const Codec& codec : codec_lookup_helper_offerer_.GetCodecVendor()
                                ->video_recv_codecs()
                                .codecs()) {
    preferences.push_back(ToRtpCodecCapability(codec));
  }
  opts.media_description_options[0].codec_preferences = preferences;

  std::unique_ptr<SessionDescription> offer =
      sf_offerer_.CreateOfferOrError(opts, nullptr).MoveValue();
  ASSERT_TRUE(offer.get());
  const ContentInfo* oc = offer->GetContentByName(kVideoMid);
  ASSERT_TRUE(oc);
  const MediaContentDescription* ocd = oc->media_description();
  EXPECT_THAT(ocd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level4, &env_.field_trials()));
  CheckH265Level(ocd->codecs(), kVideoCodecsH265Level4LevelId);

  MediaSessionOptions answer_opts;
  AddMediaDescriptionOptions(MediaType::VIDEO, kVideoMid,
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &answer_opts);

  std::unique_ptr<SessionDescription> answer =
      sf_answerer_.CreateAnswerOrError(offer.get(), answer_opts, nullptr)
          .MoveValue();
  ASSERT_TRUE(answer.get());
  const ContentInfo* ac = answer->GetContentByName(kVideoMid);
  ASSERT_TRUE(ac);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_THAT(acd->codecs(),
              CodecListsMatch(kVideoCodecsH265Level4, &env_.field_trials()));
  CheckH265Level(acd->codecs(), kVideoCodecsH265Level4LevelId);
}

#endif

}  // namespace
}  // namespace webrtc
