Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/libwebrtc/pc/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 211 kB image not shown  

Quelle  media_session_unittest.cc   Sprache: C

 
/*
 *  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 <stddef.h>

#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#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/media_types.h"
#include "api/rtp_parameters.h"
#include "api/rtp_transceiver_direction.h"
#include "media/base/codec.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 "media/sctp/sctp_transport_internal.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/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/unique_id_generator.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"

namespace cricket {
namespace {

using ::rtc::UniqueRandomIdGenerator;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Pointwise;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAreArray;
using ::testing::Values;
using ::testing::ValuesIn;
using ::webrtc::RtpExtension;
using ::webrtc::RtpTransceiverDirection;

using Candidates = std::vector<Candidate>;

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 Codec kAudioCodecs1[] = {CreateAudioCodec(111, "opus", 48000, 2),
                               CreateRedAudioCodec("111"),
                               CreateAudioCodec(102, "iLBC", 8000, 1),
                               CreateAudioCodec(0, "PCMU", 8000, 1),
                               CreateAudioCodec(8, "PCMA", 8000, 1),
                               CreateAudioCodec(107, "CN", 48000, 1)};

const Codec kAudioCodecs2[] = {
    CreateAudioCodec(126, "foo", 16000, 1),
    CreateAudioCodec(0, "PCMU", 8000, 1),
    CreateAudioCodec(127, "iLBC", 8000, 1),
};

const Codec kAudioCodecsAnswer[] = {
    CreateAudioCodec(102, "iLBC", 8000, 1),
    CreateAudioCodec(0, "PCMU", 8000, 1),
};

const Codec kVideoCodecs1[] = {CreateVideoCodec(96, "H264-SVC"),
                               CreateVideoCodec(97, "H264")};

const Codec kVideoCodecs1Reverse[] = {CreateVideoCodec(97, "H264"),
                                      CreateVideoCodec(96, "H264-SVC")};

const Codec kVideoCodecs2[] = {CreateVideoCodec(126, "H264"),
                               CreateVideoCodec(127, "H263")};

const Codec kVideoCodecsAnswer[] = {CreateVideoCodec(97, "H264")};

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

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

const RtpExtension 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 RtpExtension 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 RtpExtension kAudioRtpExtension3[] = {
    RtpExtension("http://google.com/testing/audio_something", 2),
    RtpExtension("http://google.com/testing/both_audio_and_video", 3),
};

const RtpExtension 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 RtpExtension 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 RtpExtension kAudioRtpExtensionAnswer[] = {
    RtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", 8),
};

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

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

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

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

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

const RtpExtension 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 RtpExtension 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 RtpExtension kVideoRtpExtension3[] = {
    RtpExtension("http://google.com/testing/video_something", 4),
    RtpExtension("http://google.com/testing/both_audio_and_video", 5),
};

const RtpExtension 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 RtpExtension kVideoRtpExtensionAnswer[] = {
    RtpExtension("urn:ietf:params:rtp-hdrext:toffset", 14),
};

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

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

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

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

const RtpExtension 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 RtpExtension kRtpExtensionTransportSequenceNumber02[] = {
    RtpExtension(
        "http://www.webrtc.org/experiments/rtp-hdrext/transport-wide-cc-02",
        2),
};

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

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

const char kMediaStream1[] = "stream_1";
const char kMediaStream2[] = "stream_2";
const char kVideoTrack1[] = "video_1";
const char kVideoTrack2[] = "video_2";
const char kAudioTrack1[] = "audio_1";
const char kAudioTrack2[] = "audio_2";
const 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) {
  rtc::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(const std::vector<Codec>& codecs) {
  std::vector<std::string> codec_names;
  codec_names.reserve(codecs.size());
  for (const auto& 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(MEDIA_TYPE_AUDIO, "audio", direction, kActive,
                             opts);
  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video", direction, kActive,
                             opts);
}

void AddDataSection(RtpTransceiverDirection direction,
                    MediaSessionOptions* opts) {
  AddMediaDescriptionOptions(MEDIA_TYPE_DATA, "data", direction, kActive, opts);
}

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

void AttachSenderToMediaDescriptionOptions(
    const std::string& mid,
    MediaType type,
    const std::string& track_id,
    const std::vector<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 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(MEDIA_TYPE_AUDIO, "audio",
                             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()
      : tdf1_(field_trials),
        tdf2_(field_trials),
        f1_(nullptr, false, &ssrc_generator1, &tdf1_, nullptr),
        f2_(nullptr, false, &ssrc_generator2, &tdf2_, nullptr) {
    f1_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs1),
                         MAKE_VECTOR(kAudioCodecs1));
    f1_.set_video_codecs(MAKE_VECTOR(kVideoCodecs1),
                         MAKE_VECTOR(kVideoCodecs1));
    f2_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs2),
                         MAKE_VECTOR(kAudioCodecs2));
    f2_.set_video_codecs(MAKE_VECTOR(kVideoCodecs2),
                         MAKE_VECTOR(kVideoCodecs2));
    tdf1_.set_certificate(rtc::RTCCertificate::Create(
        std::unique_ptr<rtc::SSLIdentity>(new rtc::FakeSSLIdentity("id1"))));
    tdf2_.set_certificate(rtc::RTCCertificate::Create(
        std::unique_ptr<rtc::SSLIdentity>(new rtc::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(
          "audio",
          TransportDescription(current_audio_ufrag, current_audio_pwd)));
      current_desc->AddTransportInfo(TransportInfo(
          "video",
          TransportDescription(current_video_ufrag, current_video_pwd)));
      current_desc->AddTransportInfo(TransportInfo(
          "data", 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("audio");
    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_EQ(static_cast<size_t>(ICE_UFRAG_LENGTH),
                  ti_audio->description.ice_ufrag.size());
        EXPECT_EQ(static_cast<size_t>(ICE_PWD_LENGTH),
                  ti_audio->description.ice_pwd.size());
      }
      auto media_desc_options_it =
          FindFirstMediaDescriptionByMid("audio", options);
      EXPECT_EQ(
          media_desc_options_it->transport_options.enable_ice_renomination,
          GetIceRenomination(ti_audio));
    }
    const TransportInfo* ti_video = desc->GetTransportInfoByName("video");
    if (options.has_video()) {
      auto media_desc_options_it =
          FindFirstMediaDescriptionByMid("video", 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_EQ(static_cast<size_t>(ICE_UFRAG_LENGTH),
                    ti_video->description.ice_ufrag.size());
          EXPECT_EQ(static_cast<size_t>(ICE_PWD_LENGTH),
                    ti_video->description.ice_pwd.size());
        }
      }
      EXPECT_EQ(
          media_desc_options_it->transport_options.enable_ice_renomination,
          GetIceRenomination(ti_video));
    }
    const TransportInfo* ti_data = desc->GetTransportInfoByName("data");
    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_EQ(static_cast<size_t>(ICE_UFRAG_LENGTH),
                    ti_data->description.ice_ufrag.size());
          EXPECT_EQ(static_cast<size_t>(ICE_PWD_LENGTH),
                    ti_data->description.ice_pwd.size());
        }
      }
      auto media_desc_options_it =
          FindFirstMediaDescriptionByMid("data", 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("audio");
    ASSERT_TRUE(ac_offer);
    ContentInfo* vc_offer = offer->GetContentByName("video");
    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(
      const RtpHeaderExtensions& local,
      const RtpHeaderExtensions& offered,
      const RtpHeaderExtensions& 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<webrtc::RtpHeaderExtensionCapability>
  HeaderExtensionCapabilitiesFromRtpExtensions(RtpHeaderExtensions extensions) {
    std::vector<webrtc::RtpHeaderExtensionCapability> capabilities;
    for (const auto& extension : extensions) {
      webrtc::RtpHeaderExtensionCapability capability(
          extension.uri, extension.id, extension.encrypt,
          webrtc::RtpTransceiverDirection::kSendRecv);
      capabilities.push_back(capability);
    }
    return capabilities;
  }

  void SetAudioVideoRtpHeaderExtensions(RtpHeaderExtensions audio_exts,
                                        RtpHeaderExtensions video_exts,
                                        MediaSessionOptions* opts) {
    std::vector<webrtc::RtpHeaderExtensionCapability> audio_caps =
        HeaderExtensionCapabilitiesFromRtpExtensions(audio_exts);
    std::vector<webrtc::RtpHeaderExtensionCapability> video_caps =
        HeaderExtensionCapabilitiesFromRtpExtensions(video_exts);
    for (auto& entry : opts->media_description_options) {
      switch (entry.type) {
        case MEDIA_TYPE_AUDIO:
          entry.header_extensions = audio_caps;
          break;
        case MEDIA_TYPE_VIDEO:
          entry.header_extensions = video_caps;
          break;
        default:
          break;
      }
    }
  }

 protected:
  webrtc::test::ScopedKeyValueConfig field_trials;
  UniqueRandomIdGenerator ssrc_generator1;
  UniqueRandomIdGenerator ssrc_generator2;
  TransportDescriptionFactory tdf1_;
  TransportDescriptionFactory tdf2_;
  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("audio");
  const ContentInfo* vc = offer->GetContentByName("video");
  ASSERT_TRUE(ac);
  EXPECT_FALSE(vc);
  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
  EXPECT_THAT(f1_.audio_sendrecv_codecs(), ElementsAreArray(acd->codecs()));
  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached.
  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
  EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
  EXPECT_EQ(kMediaProtocolDtlsSavpf, acd->protocol());
}

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

  auto 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("audio");
  const ContentInfo* vc = offer->GetContentByName("video");
  ASSERT_TRUE(ac != NULL);
  ASSERT_TRUE(vc == NULL);
  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
  EXPECT_EQ(2U, acd->codecs().size());
  EXPECT_EQ("opus", acd->codecs()[0].name);
  EXPECT_EQ("red", acd->codecs()[1].name);
}

// 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<webrtc::RtpCodecCapability> preferences;
  preferences.push_back(
      webrtc::ToRtpCodecCapability(f1_.audio_sendrecv_codecs()[1]));
  preferences.push_back(
      webrtc::ToRtpCodecCapability(f1_.audio_sendrecv_codecs()[0]));
  EXPECT_EQ("red", preferences[0].name);
  EXPECT_EQ("opus", preferences[1].name);

  auto 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("audio");
  const ContentInfo* vc = offer->GetContentByName("video");
  ASSERT_TRUE(ac != NULL);
  ASSERT_TRUE(vc == NULL);
  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
  EXPECT_EQ(2U, acd->codecs().size());
  EXPECT_EQ("red", acd->codecs()[0].name);
  EXPECT_EQ("opus", acd->codecs()[1].name);
}

// 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("audio");
  const ContentInfo* vc = offer->GetContentByName("video");
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
  EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();
  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
  EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs());
  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
  EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
  EXPECT_EQ(kMediaProtocolDtlsSavpf, acd->protocol());
  EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
  EXPECT_EQ(f1_.video_sendrecv_codecs(), vcd->codecs());
  EXPECT_EQ(0U, vcd->first_ssrc());             // no sender is attached
  EXPECT_EQ(kAutoBandwidth, vcd->bandwidth());  // default bandwidth (auto)
  EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on
  EXPECT_EQ(kMediaProtocolDtlsSavpf, vcd->protocol());
}

TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferWithCustomCodecs) {
  MediaSessionOptions opts;

  webrtc::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(
      MEDIA_TYPE_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(
      MEDIA_TYPE_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(MediaProtocolType::kRtp, ac->type);
  EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();
  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
  ASSERT_EQ(acd->codecs().size(), 1U);
  // Fields in codec are set during the gen process, so simple compare
  // does not work.
  EXPECT_EQ(acd->codecs()[0].name, custom_audio_codec.name);
  RTC_LOG(LS_ERROR) << "DEBUG: audio PT assigned is " << acd->codecs()[0].id;

  EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
  ASSERT_EQ(vcd->codecs().size(), 1U);
  EXPECT_EQ(vcd->codecs()[0].name, 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.
  webrtc::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(
      MEDIA_TYPE_AUDIO, "audio", 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(
      MEDIA_TYPE_VIDEO, "video", 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("audio");
  const ContentInfo* vc = answer->GetContentByName("video");
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
  EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();
  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
  ASSERT_EQ(acd->codecs().size(), 1U);
  // Fields in codec are set during the gen process, so simple compare
  // does not work.
  EXPECT_EQ(acd->codecs()[0].name, custom_audio_codec.name);

  EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
  ASSERT_EQ(vcd->codecs().size(), 1U);
  EXPECT_EQ(vcd->codecs()[0].name, 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) {
  const Codec& offered_video_codec = f2_.video_sendrecv_codecs()[0];
  const Codec& offered_audio_codec = f2_.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(MEDIA_TYPE_AUDIO, "audio",
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
                             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(kMediaProtocolDtlsSavpf, acd->protocol());
  EXPECT_EQ(kMediaProtocolDtlsSavpf, vcd->protocol());
}

// 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("data"));
  auto dcd = GetFirstSctpDataContentDescription(offer.get());
  ASSERT_TRUE(dcd);
  // Since this transport is insecure, the protocol should be "SCTP".
  EXPECT_EQ(kMediaProtocolUdpDtlsSctp, dcd->protocol());
}

// 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("data"));
  auto dcd = GetFirstSctpDataContentDescription(offer.get());
  ASSERT_TRUE(dcd);
  // The protocol should now be "UDP/DTLS/SCTP"
  EXPECT_EQ(kMediaProtocolUdpDtlsSctp, dcd->protocol());
}

// 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("data");
  ASSERT_TRUE(data);
  ASSERT_EQ(kMediaProtocolUdpDtlsSctp, data->media_description()->protocol());

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

// 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(MEDIA_TYPE_AUDIO, "audio",
                             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(MEDIA_TYPE_AUDIO, "audio",
                             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(MEDIA_TYPE_AUDIO, "audio",
                             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(MEDIA_TYPE_VIDEO, "video",
                             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("audio"));
  EXPECT_TRUE(bundle_group->HasContentName("video"));
}

// 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(MEDIA_TYPE_AUDIO, "audio",
                             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(MEDIA_TYPE_VIDEO, "video",
                             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("audio"));
  EXPECT_TRUE(bundle_group->HasContentName("video"));
}

TEST_F(MediaSessionDescriptionFactoryTest,
       CreateAnswerForOfferWithMultipleBundleGroups) {
  // Create an offer with 4 m= sections, initially without BUNDLE groups.
  MediaSessionOptions opts;
  opts.bundle_enabled = false;
  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "1",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "2",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "3",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  AddMediaDescriptionOptions(MEDIA_TYPE_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("audio");
  ASSERT_TRUE(offer_tagged);
  const TransportDescription* reoffer_tagged =
      reoffer->GetTransportDescriptionByName("video");
  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("audio");
  ASSERT_TRUE(answer_tagged);
  const TransportDescription* reanswer_tagged =
      reanswer->GetTransportDescriptionByName("video");
  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("audio");
  const ContentInfo* vc = offer->GetContentByName("video");
  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("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
                                        {kMediaStream1}, 1, &opts);
  AttachSenderToMediaDescriptionOptions("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
                                        {kMediaStream1}, 1, &opts);

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

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

// 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(1u, offer1->contents().size());
  EXPECT_TRUE(IsMediaContentOfType(&offer1->contents()[0], MEDIA_TYPE_DATA));

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

  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
                             RtpTransceiverDirection::kRecvOnly, kActive,
                             &opts);
  std::unique_ptr<SessionDescription> offer3(
      f1_.CreateOfferOrError(opts, offer2.get()).MoveValue());
  ASSERT_TRUE(offer3.get());
  EXPECT_EQ(3u, offer3->contents().size());
  EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[0], MEDIA_TYPE_DATA));
  EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[1], MEDIA_TYPE_VIDEO));
  EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[2], MEDIA_TYPE_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("audio");
  const ContentInfo* vc = answer->GetContentByName("video");
  ASSERT_TRUE(ac);
  EXPECT_FALSE(vc);
  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
  EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
  EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
  EXPECT_EQ(kMediaProtocolDtlsSavpf, acd->protocol());
}

// 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("audio");
  const ContentInfo* vc = answer->GetContentByName("video");
  ASSERT_TRUE(ac);
  EXPECT_FALSE(vc);
  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
  const MediaContentDescription* acd = ac->media_description();
  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
  EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
  EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
  EXPECT_EQ(kMediaProtocolDtlsSavpf, acd->protocol());
}

// Create an audio answer with no common codecs, and ensure it is rejected.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateAudioAnswerWithNoCommonCodecs) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::vector f1_codecs = {CreateAudioCodec(96, "opus", 48000, 1)};
  f1_.set_audio_codecs(f1_codecs, f1_codecs);

  std::vector f2_codecs = {CreateAudioCodec(0, "PCMU", 8000, 1)};
  f2_.set_audio_codecs(f2_codecs, f2_codecs);

  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("audio");
  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("audio");
  const ContentInfo* vc = answer->GetContentByName("video");
  ASSERT_TRUE(ac);
  ASSERT_TRUE(vc);
  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
  EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
  const MediaContentDescription* acd = ac->media_description();
  const MediaContentDescription* vcd = vc->media_description();
  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
  EXPECT_THAT(acd->codecs(), ElementsAreArray(kAudioCodecsAnswer));
  EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
  EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
  EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
  EXPECT_THAT(vcd->codecs(), ElementsAreArray(kVideoCodecsAnswer));
  EXPECT_EQ(0U, vcd->first_ssrc());  // no sender is attached
  EXPECT_TRUE(vcd->rtcp_mux());      // negotiated rtcp-mux
  EXPECT_EQ(kMediaProtocolDtlsSavpf, vcd->protocol());
}

// Create a video answer with no common codecs, and ensure it is rejected.
TEST_F(MediaSessionDescriptionFactoryTest,
       TestCreateVideoAnswerWithNoCommonCodecs) {
  MediaSessionOptions opts;
  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::vector f1_codecs = {CreateVideoCodec(96, "H264")};
  f1_.set_video_codecs(f1_codecs, f1_codecs);

  std::vector f2_codecs = {CreateVideoCodec(97, "VP8")};
  f2_.set_video_codecs(f2_codecs, f2_codecs);

  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("video");
  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(MEDIA_TYPE_VIDEO, "video",
                             RtpTransceiverDirection::kSendRecv, kActive,
                             &opts);
  std::vector f1_codecs = {CreateVideoCodec(96, "H264"),
                           CreateVideoCodec(118, "flexfec-03")};
  f1_.set_video_codecs(f1_codecs, f1_codecs);

  std::vector f2_codecs = {CreateVideoCodec(97, "VP8"),
                           CreateVideoCodec(118, "flexfec-03")};
  f2_.set_video_codecs(f2_codecs, f2_codecs);

  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("video");
  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("data");
  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("data");
  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("data");
  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("data");
  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("data");
  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("data");
    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("data");
  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("data");
  ASSERT_TRUE(dc_answer);
  const SctpDataContentDescription* dcd_answer =
      dc_answer->media_description()->as_sctp();
  EXPECT_FALSE(dc_answer->rejected);
  EXPECT_EQ(1234, dcd_answer->max_message_size());
}

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("data");
  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();
--> --------------------

--> maximum size reached

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

Messung V0.5
C=92 H=98 G=94

¤ Dauer der Verarbeitung: 0.15 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.