/*
 *  Copyright 2018 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.
 */

// This file contains tests for `RtpTransceiver`.

#include "pc/rtp_transceiver.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "absl/functional/any_invocable.h"
#include "absl/strings/string_view.h"
#include "api/audio_options.h"
#include "api/crypto/crypto_options.h"
#include "api/environment/environment.h"
#include "api/jsep.h"
#include "api/make_ref_counted.h"
#include "api/media_types.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtp_parameters.h"
#include "api/rtp_transceiver_direction.h"
#include "api/scoped_refptr.h"
#include "api/test/rtc_error_matchers.h"
#include "api/video_codecs/scalability_mode.h"
#include "api/video_codecs/sdp_video_format.h"
#include "call/call.h"
#include "media/base/codec.h"
#include "media/base/codec_comparators.h"
#include "media/base/fake_media_engine.h"
#include "media/base/media_channel.h"
#include "media/base/media_config.h"
#include "media/engine/fake_webrtc_call.h"
#include "p2p/dtls/fake_dtls_transport.h"
#include "pc/codec_vendor.h"
#include "pc/connection_context.h"
#include "pc/dtls_transport.h"
#include "pc/rtp_parameters_conversion.h"
#include "pc/rtp_receiver.h"
#include "pc/rtp_receiver_proxy.h"
#include "pc/rtp_sender.h"
#include "pc/rtp_sender_proxy.h"
#include "pc/rtp_transport.h"
#include "pc/rtp_transport_internal.h"
#include "pc/scoped_operations_batcher.h"
#include "pc/session_description.h"
#include "pc/simulcast_description.h"
#include "pc/test/enable_fake_media.h"
#include "pc/test/fake_codec_lookup_helper.h"
#include "pc/test/mock_channel_interface.h"
#include "pc/test/mock_rtp_receiver_internal.h"
#include "pc/test/mock_rtp_sender_internal.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/network_route.h"
#include "rtc_base/thread.h"
#include "test/create_test_environment.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/run_loop.h"

using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::IsNull;
using ::testing::NiceMock;
using ::testing::Optional;
using ::testing::Property;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::SizeIs;

namespace webrtc {

namespace {

scoped_refptr<RtpTransceiver> CreateAudioTransceiverWithChannel(
    const Environment& env,
    Call* call,
    ConnectionContext* context,
    CodecLookupHelper* codec_lookup_helper,
    const AudioOptions& audio_options,
    absl::AnyInvocable<RtpTransportInternal*() &&> transport_lookup = []() {
      return nullptr;
    }) {
  auto transceiver = make_ref_counted<RtpTransceiver>(
      env, call, MediaConfig(), "sender", "receiver", MediaType::AUDIO, nullptr,
      std::vector<std::string>(), std::vector<RtpEncodingParameters>(), context,
      codec_lookup_helper, nullptr, nullptr, audio_options, VideoOptions(),
      CryptoOptions(), nullptr, std::vector<RtpHeaderExtensionCapability>(),
      false, std::vector<SimulcastLayer>(), [] {});

  ScopedOperationsBatcher worker_tasks(context->worker_thread());
  ScopedOperationsBatcher network_tasks(context->network_thread());
  transceiver->CreateChannel("0", call, MediaConfig(), false, CryptoOptions(),
                             audio_options, VideoOptions(), nullptr,
                             std::move(transport_lookup), worker_tasks,
                             network_tasks);

  RTC_CHECK(worker_tasks.Run().ok());
  RTC_CHECK(network_tasks.Run().ok());

  return transceiver;
}

scoped_refptr<RtpTransceiver> CreateVideoTransceiverWithChannel(
    const Environment& env,
    Call* call,
    ConnectionContext* context,
    CodecLookupHelper* codec_lookup_helper,
    const VideoOptions& video_options,
    absl::AnyInvocable<RtpTransportInternal*() &&> transport_lookup = []() {
      return nullptr;
    }) {
  auto transceiver = make_ref_counted<RtpTransceiver>(
      env, call, MediaConfig(), "sender", "receiver", MediaType::VIDEO, nullptr,
      std::vector<std::string>(), std::vector<RtpEncodingParameters>(), context,
      codec_lookup_helper, nullptr, nullptr, AudioOptions(), video_options,
      CryptoOptions(), nullptr, std::vector<RtpHeaderExtensionCapability>(),
      false, std::vector<SimulcastLayer>(), [] {});

  ScopedOperationsBatcher worker_tasks(context->worker_thread());
  ScopedOperationsBatcher network_tasks(context->network_thread());
  transceiver->CreateChannel("0", call, MediaConfig(), false, CryptoOptions(),
                             AudioOptions(), video_options, nullptr,
                             std::move(transport_lookup), worker_tasks,
                             network_tasks);

  RTC_CHECK(worker_tasks.Run().ok());
  RTC_CHECK(network_tasks.Run().ok());

  return transceiver;
}

class RtpTransceiverTest : public testing::Test {
 public:
  RtpTransceiverTest()
      : env_(CreateTestEnvironment()),
        dependencies_(MakeDependencies()),
        context_(ConnectionContext::Create(env_, &dependencies_)),
        media_engine_ref_(context_),
        codec_lookup_helper_(context_.get(), env_.field_trials()) {}

 protected:
  const Environment& env() const { return env_; }
  FakeMediaEngine* media_engine() {
    // We know this cast is safe because we supplied the fake implementation
    // in MakeDependencies().
    return static_cast<FakeMediaEngine*>(media_engine_ref_.media_engine());
  }
  ConnectionContext* context() { return context_.get(); }
  CodecLookupHelper* codec_lookup_helper() { return &codec_lookup_helper_; }
  FakeCodecLookupHelper* fake_codec_lookup_helper() {
    return &codec_lookup_helper_;
  }

 protected:
  test::RunLoop main_thread_;

 private:
  static PeerConnectionFactoryDependencies MakeDependencies() {
    PeerConnectionFactoryDependencies d;
    d.network_thread = Thread::Current();
    d.worker_thread = Thread::Current();
    d.signaling_thread = Thread::Current();
    EnableFakeMedia(d, std::make_unique<FakeMediaEngine>());
    return d;
  }

  Environment env_;
  PeerConnectionFactoryDependencies dependencies_;
  scoped_refptr<ConnectionContext> context_;
  ConnectionContext::MediaEngineReference media_engine_ref_;
  FakeCodecLookupHelper codec_lookup_helper_;
};

class RtpTransceiverDoubleThreadTest : public testing::Test {
 public:
  RtpTransceiverDoubleThreadTest()
      : env_(CreateTestEnvironment()),
        network_thread_(Thread::Create()),
        worker_thread_(Thread::Create()),
        dependencies_(
            MakeDependencies(network_thread_.get(), worker_thread_.get())),
        context_(ConnectionContext::Create(env_, &dependencies_)),
        codec_lookup_helper_(context_.get(), env_.field_trials()) {
    network_thread_->Start();
    worker_thread_->Start();
    context_->worker_thread()->BlockingCall([&]() {
      media_engine_ref_ =
          std::make_unique<ConnectionContext::MediaEngineReference>(context_);
    });
  }

  ~RtpTransceiverDoubleThreadTest() override {
    context_->worker_thread()->BlockingCall(
        [&]() { media_engine_ref_.reset(); });
  }

 protected:
  const Environment& env() const { return env_; }
  FakeMediaEngine* media_engine() {
    return static_cast<FakeMediaEngine*>(media_engine_ref_->media_engine());
  }
  ConnectionContext* context() { return context_.get(); }
  CodecLookupHelper* codec_lookup_helper() { return &codec_lookup_helper_; }

 private:
  static PeerConnectionFactoryDependencies MakeDependencies(
      Thread* network_thread,
      Thread* worker_thread) {
    PeerConnectionFactoryDependencies d;
    d.network_thread = network_thread;
    d.worker_thread = worker_thread;
    d.signaling_thread = Thread::Current();
    RTC_LOG(LS_INFO) << "MakeDependencies signaling_thread="
                     << d.signaling_thread;
    EnableFakeMedia(d, std::make_unique<FakeMediaEngine>());
    return d;
  }

  test::RunLoop main_thread_;
  Environment env_;
  std::unique_ptr<Thread> network_thread_;
  std::unique_ptr<Thread> worker_thread_;
  PeerConnectionFactoryDependencies dependencies_;
  scoped_refptr<ConnectionContext> context_;
  std::unique_ptr<ConnectionContext::MediaEngineReference> media_engine_ref_;
  FakeCodecLookupHelper codec_lookup_helper_;
};

class RtpTransceiverTestWithFakeCall : public RtpTransceiverTest {
 public:
  RtpTransceiverTestWithFakeCall()
      : call_(std::make_unique<FakeCall>(env(),
                                         Thread::Current(),
                                         Thread::Current())) {}

 protected:
  std::unique_ptr<FakeCall> call_;
};

// Checks that a channel cannot be set on a stopped `RtpTransceiver`.
TEST_F(RtpTransceiverTest, CannotSetChannelOnStoppedTransceiver) {
  const std::string content_name("my_mid");
  auto transceiver = make_ref_counted<RtpTransceiver>(
      env(), MediaType::AUDIO, context(), codec_lookup_helper(), nullptr);
  transceiver->set_mid(content_name);
  auto channel1 = std::make_unique<NiceMock<MockChannelInterface>>();
  EXPECT_CALL(*channel1, media_type()).WillRepeatedly(Return(MediaType::AUDIO));
  EXPECT_CALL(*channel1, mid()).WillRepeatedly(ReturnRef(content_name));
  EXPECT_CALL(*channel1, SetRtpTransport(_)).WillRepeatedly(Return(true));

  transceiver->SetChannelForTest(std::move(channel1));
  EXPECT_TRUE(transceiver->HasChannel());

  // Stop the transceiver.
  transceiver->StopInternal();
  EXPECT_TRUE(transceiver->HasChannel());

  auto channel2 = std::make_unique<NiceMock<MockChannelInterface>>();
  EXPECT_CALL(*channel2, media_type()).WillRepeatedly(Return(MediaType::AUDIO));

  // Clear the current channel - required to allow SetChannelForTest()
  transceiver->ClearChannel();

  ASSERT_FALSE(transceiver->HasChannel());

  // Channel can no longer be set, so this call should be a no-op.
  transceiver->SetChannelForTest(std::move(channel2));
  EXPECT_FALSE(transceiver->HasChannel());
}

// Checks that a channel can be unset on a stopped `RtpTransceiver`
TEST_F(RtpTransceiverTest, CanUnsetChannelOnStoppedTransceiver) {
  const std::string content_name("my_mid");
  auto transceiver = make_ref_counted<RtpTransceiver>(
      env(), MediaType::VIDEO, context(), codec_lookup_helper(), nullptr);
  transceiver->set_mid(content_name);
  auto channel = std::make_unique<NiceMock<MockChannelInterface>>();
  EXPECT_CALL(*channel, media_type()).WillRepeatedly(Return(MediaType::VIDEO));
  EXPECT_CALL(*channel, mid()).WillRepeatedly(ReturnRef(content_name));
  EXPECT_CALL(*channel, SetRtpTransport(_)).WillRepeatedly(Return(true));

  transceiver->SetChannelForTest(std::move(channel));
  EXPECT_TRUE(transceiver->HasChannel());

  // Stop the transceiver.
  transceiver->StopInternal();
  EXPECT_TRUE(transceiver->HasChannel());

  // Set the channel to `nullptr`.
  transceiver->ClearChannel();
  EXPECT_FALSE(transceiver->HasChannel());
}

TEST_F(RtpTransceiverTestWithFakeCall, TransportNameIsUpdated) {
  const std::string content_name("my_mid");

  auto fake_dtls = std::make_unique<FakeDtlsTransport>("test_transport", false);
  auto rtp_transport = std::make_unique<RtpTransport>(/*rtcp_mux_enabled=*/true,
                                                      env().field_trials());
  rtp_transport->SetRtpPacketTransport(fake_dtls.get());

  auto transceiver = CreateAudioTransceiverWithChannel(
      env(), call_.get(), context(), codec_lookup_helper(), AudioOptions(),
      [&]() -> RtpTransportInternal* { return rtp_transport.get(); });

  EXPECT_TRUE(transceiver->HasChannel());
  EXPECT_EQ(transceiver->transport_name(), "test_transport");

  auto dtls_transport = make_ref_counted<DtlsTransport>(fake_dtls.get());
  transceiver->SetTransport(dtls_transport, "updated_transport");
  EXPECT_EQ(transceiver->transport_name(), "updated_transport");

  // Setting null transport should clear the name.
  transceiver->SetTransport(nullptr, std::nullopt);
  EXPECT_EQ(transceiver->transport_name(), std::nullopt);
  EXPECT_TRUE(transceiver->HasChannel());

  // Clearing the channel should reset the transport name to nullopt.
  transceiver->SetTransport(dtls_transport, "yadt");
  EXPECT_EQ(transceiver->transport_name(), "yadt");
  transceiver->ClearChannel();
  EXPECT_FALSE(transceiver->transport_name().has_value());
}

class RtpTransceiverUnifiedPlanTest : public RtpTransceiverTest {
 public:
  static scoped_refptr<MockRtpReceiverInternal> MockReceiver(
      MediaType media_type) {
    auto receiver = make_ref_counted<NiceMock<MockRtpReceiverInternal>>();
    EXPECT_CALL(*receiver.get(), media_type())
        .WillRepeatedly(Return(media_type));
    return receiver;
  }

  static scoped_refptr<MockRtpSenderInternal> MockSender(MediaType media_type) {
    auto sender = make_ref_counted<NiceMock<MockRtpSenderInternal>>();
    EXPECT_CALL(*sender.get(), media_type()).WillRepeatedly(Return(media_type));
    return sender;
  }

  scoped_refptr<RtpTransceiver> CreateTransceiver(
      scoped_refptr<RtpSenderInternal> sender,
      scoped_refptr<RtpReceiverInternal> receiver) {
    return make_ref_counted<RtpTransceiver>(
        env(),
        RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
            Thread::Current(), std::move(sender)),
        RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
            Thread::Current(), Thread::Current(), std::move(receiver)),
        context(), codec_lookup_helper(),
        media_engine()->voice().GetRtpHeaderExtensions(&env().field_trials()),
        /* on_negotiation_needed= */ [] {});
  }
};

// Basic tests for Stop()
TEST_F(RtpTransceiverUnifiedPlanTest, StopSetsDirection) {
  scoped_refptr<MockRtpReceiverInternal> receiver =
      MockReceiver(MediaType::AUDIO);
  scoped_refptr<MockRtpSenderInternal> sender = MockSender(MediaType::AUDIO);
  scoped_refptr<RtpTransceiver> transceiver =
      CreateTransceiver(sender, receiver);

  EXPECT_EQ(RtpTransceiverDirection::kInactive, transceiver->direction());
  EXPECT_FALSE(transceiver->current_direction());
  transceiver->StopStandard();
  EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver->direction());
  EXPECT_FALSE(transceiver->current_direction());
  auto stop_task = transceiver->GetStopTransceiverProcedure();
  if (stop_task) {
    std::move(stop_task)();
  }
  EXPECT_TRUE(transceiver->current_direction());
  EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver->direction());
  EXPECT_EQ(RtpTransceiverDirection::kStopped,
            *transceiver->current_direction());
}

class RtpTransceiverFilteredCodecPreferencesTest
    : public RtpTransceiverUnifiedPlanTest {
 public:
  RtpTransceiverFilteredCodecPreferencesTest()
      : transceiver_(CreateTransceiver(MockSender(MediaType::VIDEO),
                                       MockReceiver(MediaType::VIDEO))) {}

  struct H264CodecCapabilities {
    Codec cricket_sendrecv_codec;
    RtpCodecCapability sendrecv_codec;
    Codec cricket_sendonly_codec;
    RtpCodecCapability sendonly_codec;
    Codec cricket_recvonly_codec;
    RtpCodecCapability recvonly_codec;
    Codec cricket_rtx_codec;
    RtpCodecCapability rtx_codec;
  };

  // This function must be called after modifying the media factory's
  // capabilities, since the transceiver picks up codecs from the factory
  // at transceiver create time.
  void RecreateTransceiver() {
    fake_codec_lookup_helper()->Reset();
    transceiver_ = CreateTransceiver(MockSender(MediaType::VIDEO),
                                     MockReceiver(MediaType::VIDEO));
  }

  // For H264, the profile and level IDs are entangled. This function uses
  // profile-level-id values that are not equal even when levels are ignored.
  H264CodecCapabilities ConfigureH264CodecCapabilities() {
    Codec cricket_sendrecv_codec =
        CreateVideoCodec(SdpVideoFormat("H264",
                                        {{"level-asymmetry-allowed", "1"},
                                         {"packetization-mode", "1"},
                                         {"profile-level-id", "42f00b"}},
                                        {ScalabilityMode::kL1T1}));
    Codec cricket_sendonly_codec =
        CreateVideoCodec(SdpVideoFormat("H264",
                                        {{"level-asymmetry-allowed", "1"},
                                         {"packetization-mode", "1"},
                                         {"profile-level-id", "640034"}},
                                        {ScalabilityMode::kL1T1}));
    Codec cricket_recvonly_codec =
        CreateVideoCodec(SdpVideoFormat("H264",
                                        {{"level-asymmetry-allowed", "1"},
                                         {"packetization-mode", "1"},
                                         {"profile-level-id", "f4001f"}},
                                        {ScalabilityMode::kL1T1}));
    Codec cricket_rtx_codec =
        CreateVideoRtxCodec(Codec::kIdNotSet, Codec::kIdNotSet);
    media_engine()->SetVideoSendCodecs(
        {cricket_sendrecv_codec, cricket_sendonly_codec, cricket_rtx_codec});
    media_engine()->SetVideoRecvCodecs(
        {cricket_sendrecv_codec, cricket_recvonly_codec, cricket_rtx_codec});
    H264CodecCapabilities capabilities = {
        .cricket_sendrecv_codec = cricket_sendrecv_codec,
        .sendrecv_codec = ToRtpCodecCapability(cricket_sendrecv_codec),
        .cricket_sendonly_codec = cricket_sendonly_codec,
        .sendonly_codec = ToRtpCodecCapability(cricket_sendonly_codec),
        .cricket_recvonly_codec = cricket_recvonly_codec,
        .recvonly_codec = ToRtpCodecCapability(cricket_recvonly_codec),
        .cricket_rtx_codec = cricket_rtx_codec,
        .rtx_codec = ToRtpCodecCapability(cricket_rtx_codec),
    };
    EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(
        capabilities.cricket_sendrecv_codec, capabilities.sendonly_codec));
    EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(
        capabilities.cricket_sendrecv_codec, capabilities.recvonly_codec));
    EXPECT_FALSE(IsSameRtpCodecIgnoringLevel(
        capabilities.cricket_sendonly_codec, capabilities.recvonly_codec));
    // Because RtpTransceiver buffers codec information in a CodecVendor,
    // we must recreate it after changing the supported codecs.
    RecreateTransceiver();
    return capabilities;
  }

#ifdef RTC_ENABLE_H265
  struct H265CodecCapabilities {
    // The level-id from sender getCapabilities() or receiver getCapabilities().
    static constexpr const char* kSendOnlyLevel = "180";
    static constexpr const char* kRecvOnlyLevel = "156";
    // A valid H265 level-id, but one not present in either getCapabilities().
    static constexpr const char* kLevelNotInCapabilities = "135";

    Codec cricket_sendonly_codec;
    RtpCodecCapability sendonly_codec;
    Codec cricket_recvonly_codec;
    RtpCodecCapability recvonly_codec;
  };

  // For H265, the profile and level IDs are separate and are ignored by
  // IsSameRtpCodecIgnoringLevel().
  H265CodecCapabilities ConfigureH265CodecCapabilities() {
    Codec cricket_sendonly_codec = CreateVideoCodec(
        SdpVideoFormat("H265",
                       {{"profile-id", "1"},
                        {"tier-flag", "0"},
                        {"level-id", H265CodecCapabilities::kSendOnlyLevel},
                        {"tx-mode", "SRST"}},
                       {ScalabilityMode::kL1T1}));
    Codec cricket_recvonly_codec = CreateVideoCodec(
        SdpVideoFormat("H265",
                       {{"profile-id", "1"},
                        {"tier-flag", "0"},
                        {"level-id", H265CodecCapabilities::kRecvOnlyLevel},
                        {"tx-mode", "SRST"}},
                       {ScalabilityMode::kL1T1}));
    media_engine()->SetVideoSendCodecs({cricket_sendonly_codec});
    media_engine()->SetVideoRecvCodecs({cricket_recvonly_codec});
    // Because RtpTransceiver buffers codec information in a CodecVendor,
    // we must recreate it after changing the supported codecs.
    RecreateTransceiver();
    return {
        .cricket_sendonly_codec = cricket_sendonly_codec,
        .sendonly_codec = ToRtpCodecCapability(cricket_sendonly_codec),
        .cricket_recvonly_codec = cricket_recvonly_codec,
        .recvonly_codec = ToRtpCodecCapability(cricket_recvonly_codec),
    };
  }
#endif  // RTC_ENABLE_H265

 protected:
  scoped_refptr<RtpTransceiver> transceiver_;
};

TEST_F(RtpTransceiverFilteredCodecPreferencesTest, EmptyByDefault) {
  ConfigureH264CodecCapabilities();

  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));

  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));

  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));

  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kInactive),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
}

TEST_F(RtpTransceiverFilteredCodecPreferencesTest, OrderIsMaintained) {
  const auto codecs = ConfigureH264CodecCapabilities();
  std::vector<RtpCodecCapability> codec_capabilities = {codecs.sendrecv_codec,
                                                        codecs.rtx_codec};
  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codec_capabilities[0], codec_capabilities[1]));
  // Reverse order.
  codec_capabilities = {codecs.rtx_codec, codecs.sendrecv_codec};
  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codec_capabilities[0], codec_capabilities[1]));
}

TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
       FiltersCodecsBasedOnDirection) {
  const auto codecs = ConfigureH264CodecCapabilities();
  std::vector<RtpCodecCapability> codec_capabilities = {
      codecs.sendonly_codec, codecs.sendrecv_codec, codecs.recvonly_codec};
  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());

  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codecs.sendrecv_codec));

  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codecs.sendonly_codec, codecs.sendrecv_codec));

  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codecs.sendrecv_codec, codecs.recvonly_codec));

  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kInactive),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codecs.sendrecv_codec));
}

TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
       RtxIsIncludedAfterFiltering) {
  const auto codecs = ConfigureH264CodecCapabilities();
  std::vector<RtpCodecCapability> codec_capabilities = {codecs.recvonly_codec,
                                                        codecs.rtx_codec};
  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());

  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codecs.recvonly_codec, codecs.rtx_codec));
}

TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
       NoMediaIsTheSameAsNoPreference) {
  const auto codecs = ConfigureH264CodecCapabilities();
  std::vector<RtpCodecCapability> codec_capabilities = {codecs.recvonly_codec,
                                                        codecs.rtx_codec};
  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());

  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
      IsRtcOk());
  // After filtering the only codec that remains is RTX which is not a media
  // codec, this is the same as not having any preferences.
  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));

  // But the preferences are remembered in case the direction changes such that
  // we do have a media codec.
  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codecs.recvonly_codec, codecs.rtx_codec));
}

TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
       H264LevelIdsIgnoredByFilter) {
  // Baseline 3.1 and 5.2 are compatible when ignoring level IDs.
  Codec baseline_3_1 =
      CreateVideoCodec(SdpVideoFormat("H264",
                                      {{"level-asymmetry-allowed", "1"},
                                       {"packetization-mode", "1"},
                                       {"profile-level-id", "42001f"}},
                                      {ScalabilityMode::kL1T1}));
  Codec baseline_5_2 =
      CreateVideoCodec(SdpVideoFormat("H264",
                                      {{"level-asymmetry-allowed", "1"},
                                       {"packetization-mode", "1"},
                                       {"profile-level-id", "420034"}},
                                      {ScalabilityMode::kL1T1}));
  // High is NOT compatible with baseline.
  Codec high_3_1 =
      CreateVideoCodec(SdpVideoFormat("H264",
                                      {{"level-asymmetry-allowed", "1"},
                                       {"packetization-mode", "1"},
                                       {"profile-level-id", "64001f"}},
                                      {ScalabilityMode::kL1T1}));
  // Configure being able to both send and receive Baseline but using different
  // level IDs in either direction, while the High profile is "truly" recvonly.
  media_engine()->SetVideoSendCodecs({baseline_3_1});
  media_engine()->SetVideoRecvCodecs({baseline_5_2, high_3_1});
  // Because RtpTransceiver buffers codec information in a CodecVendor,
  // we must recreate it after changing the supported codecs.
  RecreateTransceiver();

  // Prefer to "sendrecv" Baseline 5.2. Even though we can only send 3.1 this
  // codec is not filtered out due to 5.2 and 3.1 being compatible when ignoring
  // level IDs.
  std::vector<RtpCodecCapability> codec_capabilities = {
      ToRtpCodecCapability(baseline_5_2)};
  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codec_capabilities[0]));
  // Prefer to "sendrecv" High 3.1. This gets filtered out because we cannot
  // send it (Baseline 3.1 is not compatible with it).
  codec_capabilities = {ToRtpCodecCapability(high_3_1)};
  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
  // Change direction to "recvonly" to avoid High 3.1 being filtered out.
  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codec_capabilities[0]));
}

#ifdef RTC_ENABLE_H265
TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
       H265LevelIdIsIgnoredByFilter) {
  const auto codecs = ConfigureH265CodecCapabilities();
  std::vector<RtpCodecCapability> codec_capabilities = {codecs.sendonly_codec,
                                                        codecs.recvonly_codec};
  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
  // Regardless of direction, both codecs are preferred due to ignoring levels.
  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codec_capabilities[0], codec_capabilities[1]));
  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codec_capabilities[0], codec_capabilities[1]));
  EXPECT_THAT(
      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
      IsRtcOk());
  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
              ElementsAre(codec_capabilities[0], codec_capabilities[1]));
}

TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
       H265LevelIdHasToBeFromSenderOrReceiverCapabilities) {
  ConfigureH265CodecCapabilities();
  Codec cricket_codec = CreateVideoCodec(SdpVideoFormat(
      "H265",
      {{"profile-id", "1"},
       {"tier-flag", "0"},
       {"level-id", H265CodecCapabilities::kLevelNotInCapabilities},
       {"tx-mode", "SRST"}},
      {ScalabilityMode::kL1T1}));

  std::vector<RtpCodecCapability> codec_capabilities = {
      ToRtpCodecCapability(cricket_codec)};
  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities),
              IsRtcErrorWithTypeAndMessage(
                  RTCErrorType::INVALID_MODIFICATION,
                  "Invalid codec preferences: Missing codec from codec "
                  "capabilities."));
}
#endif  // RTC_ENABLE_H265

class RtpTransceiverTestForHeaderExtensions
    : public RtpTransceiverUnifiedPlanTest {
 public:
  RtpTransceiverTestForHeaderExtensions()
      : extensions_(
            {RtpHeaderExtensionCapability("uri1",
                                          1,
                                          RtpTransceiverDirection::kSendOnly),
             RtpHeaderExtensionCapability("uri2",
                                          2,
                                          RtpTransceiverDirection::kRecvOnly),
             RtpHeaderExtensionCapability(RtpExtension::kMidUri,
                                          3,
                                          RtpTransceiverDirection::kSendRecv),
             RtpHeaderExtensionCapability(RtpExtension::kVideoRotationUri,
                                          4,
                                          RtpTransceiverDirection::kSendRecv)}),
        transceiver_(make_ref_counted<RtpTransceiver>(
            env(),
            RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
                Thread::Current(),
                sender_),
            RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
                Thread::Current(),
                Thread::Current(),
                receiver_),
            context(),
            codec_lookup_helper(),
            extensions_,
            /* on_negotiation_needed= */ [] {})) {}

  void ClearChannel() {
    EXPECT_CALL(*sender_.get(), SetMediaChannel(IsNull()))
        .WillRepeatedly(Return());
    transceiver_->ClearChannel();
  }

 protected:
  scoped_refptr<MockRtpReceiverInternal> receiver_ =
      MockReceiver(MediaType::AUDIO);
  scoped_refptr<MockRtpSenderInternal> sender_ = MockSender(MediaType::AUDIO);

  std::vector<RtpHeaderExtensionCapability> extensions_;
  scoped_refptr<RtpTransceiver> transceiver_;
};

TEST_F(RtpTransceiverTestForHeaderExtensions, OffersChannelManagerList) {
  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}

TEST_F(RtpTransceiverTestForHeaderExtensions, ModifiesDirection) {
  auto modified_extensions = extensions_;
  modified_extensions[0].direction = RtpTransceiverDirection::kSendOnly;
  EXPECT_TRUE(
      transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(),
            modified_extensions);
  modified_extensions[0].direction = RtpTransceiverDirection::kRecvOnly;
  EXPECT_TRUE(
      transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(),
            modified_extensions);
  modified_extensions[0].direction = RtpTransceiverDirection::kSendRecv;
  EXPECT_TRUE(
      transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(),
            modified_extensions);
  modified_extensions[0].direction = RtpTransceiverDirection::kInactive;
  EXPECT_TRUE(
      transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(),
            modified_extensions);
}

TEST_F(RtpTransceiverTestForHeaderExtensions, AcceptsStoppedExtension) {
  auto modified_extensions = extensions_;
  modified_extensions[0].direction = RtpTransceiverDirection::kStopped;
  EXPECT_TRUE(
      transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok());
  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(),
            modified_extensions);
}

TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsDifferentSize) {
  auto modified_extensions = extensions_;
  modified_extensions.pop_back();

  EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions),
              Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION));
  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}

TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsChangedUri) {
  auto modified_extensions = extensions_;
  ASSERT_TRUE(!modified_extensions.empty());
  modified_extensions[0].uri = "http://webrtc.org";

  EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions),
              Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION));
  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}

TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsReorder) {
  auto modified_extensions = extensions_;
  ASSERT_GE(modified_extensions.size(), 2u);
  std::swap(modified_extensions[0], modified_extensions[1]);

  EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions),
              Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION));
  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}

TEST_F(RtpTransceiverTestForHeaderExtensions,
       RejectsStoppedMandatoryExtensions) {
  std::vector<RtpHeaderExtensionCapability> modified_extensions = extensions_;
  // Attempting to stop the mandatory MID extension.
  modified_extensions[2].direction = RtpTransceiverDirection::kStopped;
  EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions),
              Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION));
  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}

TEST_F(RtpTransceiverTestForHeaderExtensions,
       NoNegotiatedHdrExtsWithoutChannel) {
  EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
              ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped)));
}

TEST_F(RtpTransceiverTestForHeaderExtensions,
       NoNegotiatedHdrExtsWithChannelWithoutNegotiation) {
  const std::string content_name("my_mid");
  transceiver_->set_mid(content_name);
  auto mock_channel = std::make_unique<NiceMock<MockChannelInterface>>();
  // Raw ptr for updating expectations later since `mock_channel` will be moved
  // to `SetChannel`.
  EXPECT_CALL(*mock_channel, media_type())
      .WillRepeatedly(Return(MediaType::AUDIO));
  EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name));
  EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true));

  transceiver_->SetChannelForTest(std::move(mock_channel));
  EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
              ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped)));

  ClearChannel();
}

TEST_F(RtpTransceiverTestForHeaderExtensions, ReturnsNegotiatedHdrExts) {
  const std::string content_name("my_mid");
  transceiver_->set_mid(content_name);
  auto mock_channel = std::make_unique<NiceMock<MockChannelInterface>>();
  EXPECT_CALL(*mock_channel, media_type())
      .WillRepeatedly(Return(MediaType::AUDIO));
  EXPECT_CALL(*mock_channel, voice_media_send_channel())
      .WillRepeatedly(Return(nullptr));
  EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name));
  EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true));

  RtpHeaderExtensions extensions = {RtpExtension("uri1", 1),
                                    RtpExtension("uri2", 2)};
  AudioContentDescription description;
  description.set_rtp_header_extensions(extensions);
  transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description);

  transceiver_->SetChannelForTest(std::move(mock_channel));

  EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
              ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kSendRecv),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kSendRecv),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped)));

  ClearChannel();
}

TEST_F(RtpTransceiverTestForHeaderExtensions,
       ReturnsNegotiatedHdrExtsOnPrAnswer) {
  const std::string content_name("my_mid");
  transceiver_->set_mid(content_name);
  auto mock_channel = std::make_unique<NiceMock<MockChannelInterface>>();
  EXPECT_CALL(*mock_channel, media_type())
      .WillRepeatedly(Return(MediaType::AUDIO));
  EXPECT_CALL(*mock_channel, voice_media_send_channel())
      .WillRepeatedly(Return(nullptr));
  EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name));
  EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true));

  RtpHeaderExtensions extensions = {RtpExtension("uri1", 1),
                                    RtpExtension("uri2", 2)};
  AudioContentDescription description;
  description.set_rtp_header_extensions(extensions);
  transceiver_->OnNegotiationUpdate(SdpType::kPrAnswer, &description);

  transceiver_->SetChannelForTest(std::move(mock_channel));

  EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
              ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kSendRecv),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kSendRecv),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped)));

  ClearChannel();
}

TEST_F(RtpTransceiverTestForHeaderExtensions,
       AnswerCanUseOtherHdrExtensionsThanPrAnswer) {
  const std::string content_name("my_mid");
  transceiver_->set_mid(content_name);
  auto mock_channel = std::make_unique<NiceMock<MockChannelInterface>>();

  EXPECT_CALL(*mock_channel, media_type())
      .WillRepeatedly(Return(MediaType::AUDIO));
  EXPECT_CALL(*mock_channel, voice_media_send_channel())
      .WillRepeatedly(Return(nullptr));
  EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name));
  EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true));

  transceiver_->SetChannelForTest(std::move(mock_channel));

  AudioContentDescription description_pr_answer;
  description_pr_answer.set_rtp_header_extensions({RtpExtension("uri1", 1)});
  transceiver_->OnNegotiationUpdate(SdpType::kPrAnswer, &description_pr_answer);

  EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
              ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kSendRecv),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped)));

  AudioContentDescription description_answer;
  description_answer.set_rtp_header_extensions(
      {RtpExtension("uri1", 1), RtpExtension("uri2", 2)});
  transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description_answer);

  EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
              ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kSendRecv),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kSendRecv),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped)));

  ClearChannel();
}

TEST_F(RtpTransceiverTestForHeaderExtensions,
       ReturnsNegotiatedHdrExtsSecondTime) {
  RtpHeaderExtensions extensions = {RtpExtension("uri1", 1),
                                    RtpExtension("uri2", 2)};
  AudioContentDescription description;
  description.set_rtp_header_extensions(extensions);
  transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description);

  EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
              ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kSendRecv),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kSendRecv),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped)));
  extensions = {RtpExtension("uri3", 4), RtpExtension("uri5", 6)};
  description.set_rtp_header_extensions(extensions);
  transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description);

  EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(),
              ElementsAre(Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped),
                          Field(&RtpHeaderExtensionCapability::direction,
                                RtpTransceiverDirection::kStopped)));
}

TEST_F(RtpTransceiverTestForHeaderExtensions,
       SimulcastOrSvcEnablesExtensionsByDefault) {
  std::vector<RtpHeaderExtensionCapability> extensions = {
      {RtpExtension::kDependencyDescriptorUri, 1,
       RtpTransceiverDirection::kStopped},
      {RtpExtension::kVideoLayersAllocationUri, 2,
       RtpTransceiverDirection::kStopped},
  };

  // Default is stopped.
  auto sender = MockSender(MediaType::VIDEO);
  auto receiver = MockReceiver(MediaType::VIDEO);
  auto transceiver = make_ref_counted<RtpTransceiver>(
      env(),
      RtpSenderProxyWithInternal<RtpSenderInternal>::Create(Thread::Current(),
                                                            sender),
      RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
          Thread::Current(), Thread::Current(), receiver),
      context(), codec_lookup_helper(), extensions,
      /* on_negotiation_needed= */ [] {});
  ASSERT_EQ(sender->media_type(), MediaType::VIDEO);
  std::vector<RtpHeaderExtensionCapability> header_extensions =
      transceiver->GetHeaderExtensionsToNegotiate();
  ASSERT_EQ(header_extensions.size(), 2u);
  EXPECT_EQ(header_extensions[0].uri, RtpExtension::kDependencyDescriptorUri);
  EXPECT_EQ(header_extensions[0].direction, RtpTransceiverDirection::kStopped);
  EXPECT_EQ(header_extensions[1].uri, RtpExtension::kVideoLayersAllocationUri);
  EXPECT_EQ(header_extensions[1].direction, RtpTransceiverDirection::kStopped);

  // Simulcast, i.e. more than one encoding.
  RtpParameters simulcast_parameters;
  simulcast_parameters.encodings.resize(2);
  auto simulcast_sender = MockSender(MediaType::VIDEO);
  EXPECT_CALL(*simulcast_sender, GetParametersInternal(_, _))
      .WillRepeatedly(Return(simulcast_parameters));
  auto simulcast_transceiver = make_ref_counted<RtpTransceiver>(
      env(),
      RtpSenderProxyWithInternal<RtpSenderInternal>::Create(Thread::Current(),
                                                            simulcast_sender),
      RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
          Thread::Current(), Thread::Current(), receiver),
      context(), codec_lookup_helper(), extensions,
      /* on_negotiation_needed= */ [] {});
  auto simulcast_extensions =
      simulcast_transceiver->GetHeaderExtensionsToNegotiate();
  ASSERT_EQ(simulcast_extensions.size(), 2u);
  EXPECT_EQ(simulcast_extensions[0].uri,
            RtpExtension::kDependencyDescriptorUri);
  EXPECT_EQ(simulcast_extensions[0].direction,
            RtpTransceiverDirection::kSendRecv);
  EXPECT_EQ(simulcast_extensions[1].uri,
            RtpExtension::kVideoLayersAllocationUri);
  EXPECT_EQ(simulcast_extensions[1].direction,
            RtpTransceiverDirection::kSendRecv);

  // SVC, a single encoding with a scalabilityMode other than L1T1.
  RtpParameters svc_parameters;
  svc_parameters.encodings.resize(1);
  svc_parameters.encodings[0].scalability_mode = "L3T3";

  auto svc_sender = MockSender(MediaType::VIDEO);
  EXPECT_CALL(*svc_sender, GetParametersInternal(_, _))
      .WillRepeatedly(Return(svc_parameters));
  auto svc_transceiver = make_ref_counted<RtpTransceiver>(
      env(),
      RtpSenderProxyWithInternal<RtpSenderInternal>::Create(Thread::Current(),
                                                            svc_sender),
      RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
          Thread::Current(), Thread::Current(), receiver),
      context(), codec_lookup_helper(), extensions,
      /* on_negotiation_needed= */ [] {});
  std::vector<RtpHeaderExtensionCapability> svc_extensions =
      svc_transceiver->GetHeaderExtensionsToNegotiate();
  ASSERT_EQ(svc_extensions.size(), 2u);
  EXPECT_EQ(svc_extensions[0].uri, RtpExtension::kDependencyDescriptorUri);
  EXPECT_EQ(svc_extensions[0].direction, RtpTransceiverDirection::kSendRecv);
  EXPECT_EQ(svc_extensions[1].uri, RtpExtension::kVideoLayersAllocationUri);
  EXPECT_EQ(svc_extensions[1].direction, RtpTransceiverDirection::kSendRecv);
}

TEST_F(RtpTransceiverTestWithFakeCall,
       SetChannelAppliesAudioOptionsToSendChannel) {
  AudioOptions audio_options;
  audio_options.audio_network_adaptor = true;

  auto transceiver = CreateAudioTransceiverWithChannel(
      env(), call_.get(), context(), codec_lookup_helper(), audio_options);

  ASSERT_TRUE(transceiver->HasChannel());
  auto* voice_channel = transceiver->voice_media_send_channel();
  ASSERT_TRUE(voice_channel);
  auto* fake_channel = static_cast<FakeVoiceMediaSendChannel*>(voice_channel);
  EXPECT_TRUE(fake_channel->options().audio_network_adaptor);

  ScopedOperationsBatcher worker_tasks(context()->worker_thread());
  ScopedOperationsBatcher network_tasks(context()->network_thread());
  network_tasks.Add(transceiver->GetClearChannelNetworkTask());
  worker_tasks.Add(
      transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true));
}

TEST_F(RtpTransceiverTestWithFakeCall, OnNetworkRouteChangedForwardsToChannel) {
  const std::string content_name("my_mid");

  auto fake_dtls = std::make_unique<FakeDtlsTransport>("test_transport", false);
  auto rtp_transport = std::make_unique<RtpTransport>(/*rtcp_mux_enabled=*/true,
                                                      env().field_trials());
  rtp_transport->SetRtpPacketTransport(fake_dtls.get());

  auto transceiver = CreateAudioTransceiverWithChannel(
      env(), call_.get(), context(), codec_lookup_helper(), AudioOptions(),
      [&]() -> RtpTransportInternal* { return rtp_transport.get(); });

  ASSERT_TRUE(transceiver->HasChannel());
  auto* voice_channel = transceiver->voice_media_send_channel();
  ASSERT_TRUE(voice_channel);
  auto* fake_channel = static_cast<FakeVoiceMediaSendChannel*>(voice_channel);

  NetworkRoute network_route;
  network_route.connected = true;
  network_route.local = RouteEndpoint::CreateWithNetworkId(1);
  network_route.remote = RouteEndpoint::CreateWithNetworkId(2);
  network_route.last_sent_packet_id = 100;
  network_route.packet_overhead = 28;

  // Trigger network route change on the transport on the network thread.
  context()->network_thread()->BlockingCall([&]() {
    fake_dtls->ice_transport()->NotifyNetworkRouteChanged(
        std::optional<NetworkRoute>(network_route));
  });

  // Verify that the channel received it.
  EXPECT_EQ(1, fake_channel->num_network_route_changes());
  EXPECT_TRUE(fake_channel->last_network_route().connected);
  EXPECT_EQ(1, fake_channel->last_network_route().local.network_id());
  EXPECT_EQ(2, fake_channel->last_network_route().remote.network_id());
  EXPECT_EQ(100, fake_channel->last_network_route().last_sent_packet_id);
  EXPECT_EQ(28, fake_channel->transport_overhead_per_packet());

  ScopedOperationsBatcher worker_tasks(context()->worker_thread());
  ScopedOperationsBatcher network_tasks(context()->network_thread());
  network_tasks.Add(transceiver->GetClearChannelNetworkTask());
  worker_tasks.Add(
      transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true));
}

TEST_F(RtpTransceiverTestWithFakeCall,
       OnNetworkRouteChangedForwardsToVideoChannel) {
  const std::string content_name("my_mid");

  auto fake_dtls = std::make_unique<FakeDtlsTransport>("test_transport", false);
  auto rtp_transport = std::make_unique<RtpTransport>(/*rtcp_mux_enabled=*/true,
                                                      env().field_trials());
  rtp_transport->SetRtpPacketTransport(fake_dtls.get());

  auto transceiver = CreateVideoTransceiverWithChannel(
      env(), call_.get(), context(), codec_lookup_helper(), VideoOptions(),
      [&]() -> RtpTransportInternal* { return rtp_transport.get(); });

  ASSERT_TRUE(transceiver->HasChannel());
  auto* video_channel = transceiver->video_media_send_channel();
  ASSERT_TRUE(video_channel);
  auto* fake_channel = static_cast<FakeVideoMediaSendChannel*>(video_channel);

  NetworkRoute network_route;
  network_route.connected = true;
  network_route.local = RouteEndpoint::CreateWithNetworkId(1);
  network_route.remote = RouteEndpoint::CreateWithNetworkId(2);
  network_route.last_sent_packet_id = 100;
  network_route.packet_overhead = 28;

  // Trigger network route change on the transport on the network thread.
  context()->network_thread()->BlockingCall([&]() {
    fake_dtls->ice_transport()->NotifyNetworkRouteChanged(
        std::optional<NetworkRoute>(network_route));
  });

  // Verify that the channel received it.
  EXPECT_EQ(1, fake_channel->num_network_route_changes());
  EXPECT_TRUE(fake_channel->last_network_route().connected);
  EXPECT_EQ(1, fake_channel->last_network_route().local.network_id());
  EXPECT_EQ(2, fake_channel->last_network_route().remote.network_id());
  EXPECT_EQ(100, fake_channel->last_network_route().last_sent_packet_id);
  EXPECT_EQ(28, fake_channel->transport_overhead_per_packet());

  ScopedOperationsBatcher worker_tasks(context()->worker_thread());
  ScopedOperationsBatcher network_tasks(context()->network_thread());
  network_tasks.Add(transceiver->GetClearChannelNetworkTask());
  worker_tasks.Add(
      transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true));
}

TEST_F(RtpTransceiverDoubleThreadTest,
       OnNetworkRouteChangedForwardsToChannel_DoubleThread) {
  const std::string content_name("my_mid");
  RTC_LOG(LS_INFO) << "TestBody current_thread=" << Thread::Current();
  RTC_LOG(LS_INFO) << "TestBody signaling_thread="
                   << context()->signaling_thread();
  RTC_LOG(LS_INFO) << "TestBody network_thread=" << context()->network_thread();

  std::unique_ptr<FakeDtlsTransport> fake_dtls;
  auto rtp_transport = std::make_unique<RtpTransport>(/*rtcp_mux_enabled=*/true,
                                                      env().field_trials());
  context()->network_thread()->BlockingCall([&]() {
    fake_dtls = std::make_unique<FakeDtlsTransport>(
        "test_transport", 0, context()->network_thread());
    rtp_transport->SetRtpPacketTransport(fake_dtls.get());
  });

  auto call = std::make_unique<FakeCall>(env(), context()->worker_thread(),
                                         context()->network_thread());

  auto transceiver = CreateAudioTransceiverWithChannel(
      env(), call.get(), context(), codec_lookup_helper(), AudioOptions(),
      [&]() -> RtpTransportInternal* { return rtp_transport.get(); });

  ASSERT_TRUE(transceiver->HasChannel());
  auto* voice_channel = transceiver->voice_media_send_channel();
  ASSERT_TRUE(voice_channel);
  auto* fake_channel = static_cast<FakeVoiceMediaSendChannel*>(voice_channel);

  NetworkRoute network_route;
  network_route.connected = true;
  network_route.local = RouteEndpoint::CreateWithNetworkId(1);
  network_route.remote = RouteEndpoint::CreateWithNetworkId(2);
  network_route.last_sent_packet_id = 100;
  network_route.packet_overhead = 28;

  // Trigger network route change on the transport on the network thread.
  context()->network_thread()->BlockingCall([&]() {
    fake_dtls->ice_transport()->NotifyNetworkRouteChanged(
        std::optional<NetworkRoute>(network_route));
  });

  // Verify that the channel received it.
  int changes = context()->network_thread()->BlockingCall(
      [&]() { return fake_channel->num_network_route_changes(); });
  EXPECT_EQ(1, changes);

  NetworkRoute last_route = context()->network_thread()->BlockingCall(
      [&]() { return fake_channel->last_network_route(); });
  EXPECT_TRUE(last_route.connected);
  EXPECT_EQ(1, last_route.local.network_id());
  EXPECT_EQ(2, last_route.remote.network_id());
  EXPECT_EQ(100, last_route.last_sent_packet_id);

  int overhead = context()->network_thread()->BlockingCall(
      [&]() { return fake_channel->transport_overhead_per_packet(); });
  EXPECT_EQ(28, overhead);

  ScopedOperationsBatcher worker_tasks(context()->worker_thread());
  ScopedOperationsBatcher network_tasks(context()->network_thread());
  network_tasks.Add(transceiver->GetClearChannelNetworkTask());
  worker_tasks.Add(
      transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true));

  RTC_CHECK(network_tasks.Run().ok());
  RTC_CHECK(worker_tasks.Run().ok());

  context()->network_thread()->BlockingCall([&]() { fake_dtls.reset(); });
}

// Sframe tests

TEST_F(RtpTransceiverUnifiedPlanTest, SframeEnabledIsNulloptByDefault) {
  scoped_refptr<RtpTransceiver> transceiver = CreateTransceiver(
      MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO));

  EXPECT_EQ(transceiver->SframeEnabled(), std::nullopt);
}

TEST_F(RtpTransceiverUnifiedPlanTest, TryToEnableSframeSetsValueToTrue) {
  scoped_refptr<RtpTransceiver> transceiver = CreateTransceiver(
      MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO));

  EXPECT_TRUE(transceiver->TryToEnableSframe().ok());
  EXPECT_THAT(transceiver->SframeEnabled(), Optional(true));
}

TEST_F(RtpTransceiverUnifiedPlanTest,
       TryToEnableSframeCanBeCalledMultipleTimes) {
  scoped_refptr<RtpTransceiver> transceiver = CreateTransceiver(
      MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO));

  EXPECT_TRUE(transceiver->TryToEnableSframe().ok());
  EXPECT_TRUE(transceiver->TryToEnableSframe().ok());
  EXPECT_THAT(transceiver->SframeEnabled(), Optional(true));
}

TEST_F(RtpTransceiverUnifiedPlanTest,
       TryToEnableSframeFailsAfterExplicitlySetToFalse) {
  scoped_refptr<RtpTransceiver> transceiver = CreateTransceiver(
      MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO));

  // Simulate the transceiver having been explicitly set to false (e.g. via
  // ApplySframeEnabled during SDP application).
  transceiver->ApplySframeEnabled(false);
  EXPECT_THAT(transceiver->SframeEnabled(), Optional(false));

  RTCError error = transceiver->TryToEnableSframe();
  EXPECT_FALSE(error.ok());
  EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
  EXPECT_THAT(transceiver->SframeEnabled(), Optional(false));
}

TEST_F(RtpTransceiverUnifiedPlanTest, ApplySframeEnabledTrueSetsState) {
  scoped_refptr<RtpTransceiver> transceiver = CreateTransceiver(
      MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO));

  transceiver->ApplySframeEnabled(true);
  EXPECT_THAT(transceiver->SframeEnabled(), Optional(true));
}

TEST_F(RtpTransceiverUnifiedPlanTest, ApplySframeEnabledFalseSetsState) {
  scoped_refptr<RtpTransceiver> transceiver = CreateTransceiver(
      MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO));

  transceiver->ApplySframeEnabled(false);
  EXPECT_THAT(transceiver->SframeEnabled(), Optional(false));
}

}  // namespace

}  // namespace webrtc
