/*
 *  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 <utility>

#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/peer_connection_interface.h"
#include "api/rtp_parameters.h"
#include "media/base/fake_media_engine.h"
#include "media/base/media_engine.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/thread.h"
#include "test/gmock.h"
#include "test/gtest.h"

using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::Optional;
using ::testing::Property;
using ::testing::Return;
using ::testing::ReturnRef;

namespace webrtc {

namespace {

class RtpTransceiverTest : public testing::Test {
 public:
  RtpTransceiverTest()
      : dependencies_(MakeDependencies()),
        context_(ConnectionContext::Create(&dependencies_)) {}

 protected:
  cricket::MediaEngineInterface* media_engine() {
    return context_->media_engine();
  }
  ConnectionContext* context() { return context_.get(); }

 private:
  rtc::AutoThread main_thread_;

  static PeerConnectionFactoryDependencies MakeDependencies() {
    PeerConnectionFactoryDependencies d;
    d.network_thread = rtc::Thread::Current();
    d.worker_thread = rtc::Thread::Current();
    d.signaling_thread = rtc::Thread::Current();
    d.media_engine = std::make_unique<cricket::FakeMediaEngine>();
    return d;
  }

  PeerConnectionFactoryDependencies dependencies_;
  rtc::scoped_refptr<ConnectionContext> context_;
};

// Checks that a channel cannot be set on a stopped `RtpTransceiver`.
TEST_F(RtpTransceiverTest, CannotSetChannelOnStoppedTransceiver) {
  const std::string content_name("my_mid");
  auto transceiver = rtc::make_ref_counted<RtpTransceiver>(
      cricket::MediaType::MEDIA_TYPE_AUDIO, context());
  auto channel1 = std::make_unique<cricket::MockChannelInterface>();
  EXPECT_CALL(*channel1, media_type())
      .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
  EXPECT_CALL(*channel1, mid()).WillRepeatedly(ReturnRef(content_name));
  EXPECT_CALL(*channel1, SetFirstPacketReceivedCallback(_));
  EXPECT_CALL(*channel1, SetRtpTransport(_)).WillRepeatedly(Return(true));
  auto channel1_ptr = channel1.get();
  transceiver->SetChannel(std::move(channel1), [&](const std::string& mid) {
    EXPECT_EQ(mid, content_name);
    return nullptr;
  });
  EXPECT_EQ(channel1_ptr, transceiver->channel());

  // Stop the transceiver.
  transceiver->StopInternal();
  EXPECT_EQ(channel1_ptr, transceiver->channel());

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

  // Clear the current channel - required to allow SetChannel()
  EXPECT_CALL(*channel1_ptr, SetFirstPacketReceivedCallback(_));
  transceiver->ClearChannel();
  // Channel can no longer be set, so this call should be a no-op.
  transceiver->SetChannel(std::move(channel2),
                          [](const std::string&) { return nullptr; });
  EXPECT_EQ(nullptr, transceiver->channel());
}

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

  auto channel_ptr = channel.get();
  transceiver->SetChannel(std::move(channel), [&](const std::string& mid) {
    EXPECT_EQ(mid, content_name);
    return nullptr;
  });
  EXPECT_EQ(channel_ptr, transceiver->channel());

  // Stop the transceiver.
  transceiver->StopInternal();
  EXPECT_EQ(channel_ptr, transceiver->channel());

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

class RtpTransceiverUnifiedPlanTest : public RtpTransceiverTest {
 public:
  RtpTransceiverUnifiedPlanTest()
      : transceiver_(rtc::make_ref_counted<RtpTransceiver>(
            RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
                rtc::Thread::Current(),
                sender_),
            RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
                rtc::Thread::Current(),
                rtc::Thread::Current(),
                receiver_),
            context(),
            media_engine()->voice().GetRtpHeaderExtensions(),
            /* on_negotiation_needed= */ [] {})) {}

  static rtc::scoped_refptr<MockRtpReceiverInternal> MockReceiver() {
    auto receiver = rtc::make_ref_counted<MockRtpReceiverInternal>();
    EXPECT_CALL(*receiver.get(), media_type())
        .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
    return receiver;
  }

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

  rtc::AutoThread main_thread_;
  rtc::scoped_refptr<MockRtpReceiverInternal> receiver_ = MockReceiver();
  rtc::scoped_refptr<MockRtpSenderInternal> sender_ = MockSender();
  rtc::scoped_refptr<RtpTransceiver> transceiver_;
};

// Basic tests for Stop()
TEST_F(RtpTransceiverUnifiedPlanTest, StopSetsDirection) {
  EXPECT_CALL(*receiver_.get(), Stop());
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());

  EXPECT_EQ(RtpTransceiverDirection::kInactive, transceiver_->direction());
  EXPECT_FALSE(transceiver_->current_direction());
  transceiver_->StopStandard();
  EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver_->direction());
  EXPECT_FALSE(transceiver_->current_direction());
  transceiver_->StopTransceiverProcedure();
  EXPECT_TRUE(transceiver_->current_direction());
  EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver_->direction());
  EXPECT_EQ(RtpTransceiverDirection::kStopped,
            *transceiver_->current_direction());
}

class RtpTransceiverTestForHeaderExtensions : public RtpTransceiverTest {
 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_(rtc::make_ref_counted<RtpTransceiver>(
            RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
                rtc::Thread::Current(),
                sender_),
            RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
                rtc::Thread::Current(),
                rtc::Thread::Current(),
                receiver_),
            context(),
            extensions_,
            /* on_negotiation_needed= */ [] {})) {}

  static rtc::scoped_refptr<MockRtpReceiverInternal> MockReceiver() {
    auto receiver = rtc::make_ref_counted<MockRtpReceiverInternal>();
    EXPECT_CALL(*receiver.get(), media_type())
        .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
    return receiver;
  }

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

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

  rtc::AutoThread main_thread_;
  rtc::scoped_refptr<MockRtpReceiverInternal> receiver_ = MockReceiver();
  rtc::scoped_refptr<MockRtpSenderInternal> sender_ = MockSender();

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

TEST_F(RtpTransceiverTestForHeaderExtensions, OffersChannelManagerList) {
  EXPECT_CALL(*receiver_.get(), Stop());
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());

  EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_);
}

TEST_F(RtpTransceiverTestForHeaderExtensions, ModifiesDirection) {
  EXPECT_CALL(*receiver_.get(), Stop());
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());

  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) {
  EXPECT_CALL(*receiver_.get(), Stop());
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());

  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) {
  EXPECT_CALL(*receiver_.get(), Stop());
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());

  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) {
  EXPECT_CALL(*receiver_.get(), Stop());
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());

  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) {
  EXPECT_CALL(*receiver_.get(), Stop());
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());

  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) {
  EXPECT_CALL(*receiver_.get(), Stop());
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());

  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_CALL(*receiver_.get(), Stop());
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());
  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");
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)).WillRepeatedly(Return());
  EXPECT_CALL(*receiver_.get(), Stop()).WillRepeatedly(Return());
  EXPECT_CALL(*sender_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());
  auto mock_channel = std::make_unique<cricket::MockChannelInterface>();
  auto mock_channel_ptr = mock_channel.get();
  EXPECT_CALL(*mock_channel, SetFirstPacketReceivedCallback(_));
  EXPECT_CALL(*mock_channel, media_type())
      .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
  EXPECT_CALL(*mock_channel, media_send_channel())
      .WillRepeatedly(Return(nullptr));
  EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name));
  EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true));
  transceiver_->SetChannel(std::move(mock_channel),
                           [](const std::string&) { return nullptr; });
  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)));

  EXPECT_CALL(*mock_channel_ptr, SetFirstPacketReceivedCallback(_));
  ClearChannel();
}

TEST_F(RtpTransceiverTestForHeaderExtensions, ReturnsNegotiatedHdrExts) {
  const std::string content_name("my_mid");
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)).WillRepeatedly(Return());
  EXPECT_CALL(*receiver_.get(), Stop()).WillRepeatedly(Return());
  EXPECT_CALL(*sender_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());

  auto mock_channel = std::make_unique<cricket::MockChannelInterface>();
  auto mock_channel_ptr = mock_channel.get();
  EXPECT_CALL(*mock_channel, SetFirstPacketReceivedCallback(_));
  EXPECT_CALL(*mock_channel, media_type())
      .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
  EXPECT_CALL(*mock_channel, media_send_channel())
      .WillRepeatedly(Return(nullptr));
  EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name));
  EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true));

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

  transceiver_->SetChannel(std::move(mock_channel),
                           [](const std::string&) { return nullptr; });

  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)));
  EXPECT_CALL(*mock_channel_ptr, SetFirstPacketReceivedCallback(_));
  ClearChannel();
}

TEST_F(RtpTransceiverTestForHeaderExtensions,
       ReturnsNegotiatedHdrExtsSecondTime) {
  EXPECT_CALL(*receiver_.get(), Stop());
  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
  EXPECT_CALL(*sender_.get(), Stop());

  cricket::RtpHeaderExtensions extensions = {webrtc::RtpExtension("uri1", 1),
                                             webrtc::RtpExtension("uri2", 2)};
  cricket::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 = {webrtc::RtpExtension("uri3", 4),
                webrtc::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)));
}

}  // namespace

}  // namespace webrtc
