diff --git a/p2p/base/fake_packet_transport.h b/p2p/base/fake_packet_transport.h index f59aa39b45..a5e2abb7d6 100644 --- a/p2p/base/fake_packet_transport.h +++ b/p2p/base/fake_packet_transport.h @@ -11,6 +11,7 @@ #ifndef P2P_BASE_FAKE_PACKET_TRANSPORT_H_ #define P2P_BASE_FAKE_PACKET_TRANSPORT_H_ +#include #include #include "p2p/base/packet_transport_internal.h" @@ -81,9 +82,23 @@ class FakePacketTransport : public PacketTransportInternal { SignalSentPacket(this, sent_packet); return static_cast(len); } - int SetOption(Socket::Option opt, int value) override { return true; } - bool GetOption(Socket::Option opt, int* value) override { return true; } - int GetError() override { return 0; } + + int SetOption(Socket::Option opt, int value) override { + options_[opt] = value; + return 0; + } + + bool GetOption(Socket::Option opt, int* value) override { + auto it = options_.find(opt); + if (it == options_.end()) { + return false; + } + *value = it->second; + return true; + } + + int GetError() override { return error_; } + void SetError(int error) { error_ = error; } const CopyOnWriteBuffer* last_sent_packet() { return &last_sent_packet_; } @@ -92,6 +107,7 @@ class FakePacketTransport : public PacketTransportInternal { } void SetNetworkRoute(absl::optional network_route) { network_route_ = network_route; + SignalNetworkRouteChanged(network_route); } private: @@ -131,6 +147,9 @@ class FakePacketTransport : public PacketTransportInternal { bool writable_ = false; bool receiving_ = false; + std::map options_; + int error_ = 0; + absl::optional network_route_; }; diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 0f44f83681..1417acf66d 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -34,6 +34,8 @@ rtc_static_library("rtc_pc_base") { "channel_interface.h", "channel_manager.cc", "channel_manager.h", + "composite_rtp_transport.cc", + "composite_rtp_transport.h", "dtls_srtp_transport.cc", "dtls_srtp_transport.h", "dtls_transport.cc", @@ -255,6 +257,7 @@ if (rtc_include_tests) { sources = [ "channel_manager_unittest.cc", "channel_unittest.cc", + "composite_rtp_transport_test.cc", "dtls_srtp_transport_unittest.cc", "dtls_transport_unittest.cc", "ice_transport_unittest.cc", diff --git a/pc/composite_rtp_transport.cc b/pc/composite_rtp_transport.cc new file mode 100644 index 0000000000..1209fa5c11 --- /dev/null +++ b/pc/composite_rtp_transport.cc @@ -0,0 +1,181 @@ +/* + * Copyright 2019 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 +#include + +#include "pc/composite_rtp_transport.h" + +#include "absl/memory/memory.h" +#include "p2p/base/packet_transport_internal.h" + +namespace webrtc { + +CompositeRtpTransport::CompositeRtpTransport( + std::vector transports) + : transports_(std::move(transports)) { + RTC_DCHECK(!transports_.empty()) << "Cannot have an empty composite"; + std::vector rtp_transports; + std::vector rtcp_transports; + for (RtpTransportInternal* transport : transports_) { + RTC_DCHECK_EQ(transport->rtcp_mux_enabled(), rtcp_mux_enabled()) + << "Either all or none of the transports in a composite must enable " + "rtcp mux"; + RTC_DCHECK_EQ(transport->transport_name(), transport_name()) + << "All transports in a composite must have the same transport name"; + + transport->SignalNetworkRouteChanged.connect( + this, &CompositeRtpTransport::OnNetworkRouteChanged); + transport->SignalRtcpPacketReceived.connect( + this, &CompositeRtpTransport::OnRtcpPacketReceived); + } +} + +void CompositeRtpTransport::SetSendTransport( + RtpTransportInternal* send_transport) { + if (send_transport_ == send_transport) { + return; + } + + RTC_DCHECK(absl::c_linear_search(transports_, send_transport)) + << "Cannot set a send transport that isn't part of the composite"; + + if (send_transport_) { + send_transport_->SignalReadyToSend.disconnect(this); + send_transport_->SignalWritableState.disconnect(this); + send_transport_->SignalSentPacket.disconnect(this); + } + + send_transport_ = send_transport; + send_transport_->SignalReadyToSend.connect( + this, &CompositeRtpTransport::OnReadyToSend); + send_transport_->SignalWritableState.connect( + this, &CompositeRtpTransport::OnWritableState); + send_transport_->SignalSentPacket.connect( + this, &CompositeRtpTransport::OnSentPacket); + + SignalWritableState(send_transport_->IsWritable(/*rtcp=*/true) && + send_transport_->IsWritable(/*rtcp=*/false)); + if (send_transport_->IsReadyToSend()) { + SignalReadyToSend(true); + } +} + +const std::string& CompositeRtpTransport::transport_name() const { + return transports_.front()->transport_name(); +} + +int CompositeRtpTransport::SetRtpOption(rtc::Socket::Option opt, int value) { + int result = 0; + for (auto transport : transports_) { + result |= transport->SetRtpOption(opt, value); + } + return result; +} + +int CompositeRtpTransport::SetRtcpOption(rtc::Socket::Option opt, int value) { + int result = 0; + for (auto transport : transports_) { + result |= transport->SetRtcpOption(opt, value); + } + return result; +} + +bool CompositeRtpTransport::rtcp_mux_enabled() const { + return transports_.front()->rtcp_mux_enabled(); +} + +void CompositeRtpTransport::SetRtcpMuxEnabled(bool enabled) { + for (auto transport : transports_) { + transport->SetRtcpMuxEnabled(enabled); + } +} + +bool CompositeRtpTransport::IsReadyToSend() const { + return send_transport_ && send_transport_->IsReadyToSend(); +} + +bool CompositeRtpTransport::IsWritable(bool rtcp) const { + return send_transport_ && send_transport_->IsWritable(rtcp); +} + +bool CompositeRtpTransport::SendRtpPacket(rtc::CopyOnWriteBuffer* packet, + const rtc::PacketOptions& options, + int flags) { + if (!send_transport_) { + return false; + } + return send_transport_->SendRtpPacket(packet, options, flags); +} + +bool CompositeRtpTransport::SendRtcpPacket(rtc::CopyOnWriteBuffer* packet, + const rtc::PacketOptions& options, + int flags) { + if (!send_transport_) { + return false; + } + return send_transport_->SendRtcpPacket(packet, options, flags); +} + +void CompositeRtpTransport::UpdateRtpHeaderExtensionMap( + const cricket::RtpHeaderExtensions& header_extensions) { + for (RtpTransportInternal* transport : transports_) { + transport->UpdateRtpHeaderExtensionMap(header_extensions); + } +} + +bool CompositeRtpTransport::IsSrtpActive() const { + bool active = true; + for (RtpTransportInternal* transport : transports_) { + active &= transport->IsSrtpActive(); + } + return active; +} + +bool CompositeRtpTransport::RegisterRtpDemuxerSink( + const RtpDemuxerCriteria& criteria, + RtpPacketSinkInterface* sink) { + for (RtpTransportInternal* transport : transports_) { + transport->RegisterRtpDemuxerSink(criteria, sink); + } + return true; +} + +bool CompositeRtpTransport::UnregisterRtpDemuxerSink( + RtpPacketSinkInterface* sink) { + for (RtpTransportInternal* transport : transports_) { + transport->UnregisterRtpDemuxerSink(sink); + } + return true; +} + +void CompositeRtpTransport::OnNetworkRouteChanged( + absl::optional route) { + SignalNetworkRouteChanged(route); +} + +void CompositeRtpTransport::OnRtcpPacketReceived(rtc::CopyOnWriteBuffer* packet, + int64_t packet_time_us) { + SignalRtcpPacketReceived(packet, packet_time_us); +} + +void CompositeRtpTransport::OnWritableState(bool writable) { + SignalWritableState(writable); +} + +void CompositeRtpTransport::OnReadyToSend(bool ready_to_send) { + SignalReadyToSend(ready_to_send); +} + +void CompositeRtpTransport::OnSentPacket(const rtc::SentPacket& packet) { + SignalSentPacket(packet); +} + +} // namespace webrtc diff --git a/pc/composite_rtp_transport.h b/pc/composite_rtp_transport.h new file mode 100644 index 0000000000..deb315dcf6 --- /dev/null +++ b/pc/composite_rtp_transport.h @@ -0,0 +1,108 @@ +/* + * Copyright 2019 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. + */ + +#ifndef PC_COMPOSITE_RTP_TRANSPORT_H_ +#define PC_COMPOSITE_RTP_TRANSPORT_H_ + +#include +#include +#include + +#include "call/rtp_demuxer.h" +#include "call/rtp_packet_sink_interface.h" +#include "pc/rtp_transport_internal.h" +#include "pc/session_description.h" +#include "rtc_base/async_packet_socket.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +// Composite RTP transport capable of receiving from multiple sub-transports. +// +// CompositeRtpTransport is receive-only until the caller explicitly chooses +// which transport will be used to send and calls |SetSendTransport|. This +// choice must be made as part of the SDP negotiation process, based on receipt +// of a provisional answer. |CompositeRtpTransport| does not become writable or +// ready to send until |SetSendTransport| is called. +// +// When a full answer is received, the user should replace the composite +// transport with the single, chosen RTP transport, then delete the composite +// and all non-chosen transports. +class CompositeRtpTransport : public RtpTransportInternal { + public: + // Constructs a composite out of the given |transports|. |transports| must + // not be empty. All |transports| must outlive the composite. + explicit CompositeRtpTransport(std::vector transports); + + // Sets which transport will be used for sending packets. Once called, + // |IsReadyToSend|, |IsWritable|, and the associated signals will reflect the + // state of |send_tranpsort|. + void SetSendTransport(RtpTransportInternal* send_transport); + + // All transports within a composite must have the same name. + const std::string& transport_name() const override; + + int SetRtpOption(rtc::Socket::Option opt, int value) override; + int SetRtcpOption(rtc::Socket::Option opt, int value) override; + + // All transports within a composite must either enable or disable RTCP mux. + bool rtcp_mux_enabled() const override; + + // Enables or disables RTCP mux for all component transports. + void SetRtcpMuxEnabled(bool enabled) override; + + // The composite is ready to send if |send_transport_| is set and ready to + // send. + bool IsReadyToSend() const override; + + // The composite is writable if |send_transport_| is set and writable. + bool IsWritable(bool rtcp) const override; + + // Sends an RTP packet. May only be called after |send_transport_| is set. + bool SendRtpPacket(rtc::CopyOnWriteBuffer* packet, + const rtc::PacketOptions& options, + int flags) override; + + // Sends an RTCP packet. May only be called after |send_transport_| is set. + bool SendRtcpPacket(rtc::CopyOnWriteBuffer* packet, + const rtc::PacketOptions& options, + int flags) override; + + // Updates the mapping of RTP header extensions for all component transports. + void UpdateRtpHeaderExtensionMap( + const cricket::RtpHeaderExtensions& header_extensions) override; + + // SRTP is only active for a composite if it is active for all component + // transports. + bool IsSrtpActive() const override; + + // Registers an RTP demux sink with all component transports. + bool RegisterRtpDemuxerSink(const RtpDemuxerCriteria& criteria, + RtpPacketSinkInterface* sink) override; + bool UnregisterRtpDemuxerSink(RtpPacketSinkInterface* sink) override; + + private: + // Receive-side signals. + void OnNetworkRouteChanged(absl::optional route); + void OnRtcpPacketReceived(rtc::CopyOnWriteBuffer* packet, + int64_t packet_time_us); + + // Send-side signals. + void OnWritableState(bool writable); + void OnReadyToSend(bool ready_to_send); + void OnSentPacket(const rtc::SentPacket& packet); + + std::vector transports_; + RtpTransportInternal* send_transport_ = nullptr; +}; + +} // namespace webrtc + +#endif // PC_COMPOSITE_RTP_TRANSPORT_H_ diff --git a/pc/composite_rtp_transport_test.cc b/pc/composite_rtp_transport_test.cc new file mode 100644 index 0000000000..5264c73c63 --- /dev/null +++ b/pc/composite_rtp_transport_test.cc @@ -0,0 +1,377 @@ +/* + * Copyright 2019 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/composite_rtp_transport.h" + +#include + +#include "absl/memory/memory.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "p2p/base/fake_packet_transport.h" +#include "pc/rtp_transport.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +constexpr char kTransportName[] = "test-transport"; +constexpr char kRtcpTransportName[] = "test-transport-rtcp"; +constexpr uint8_t kRtpPayloadType = 100; + +constexpr uint8_t kRtcpPacket[] = {0x80, 73, 0, 0}; +constexpr uint8_t kRtpPacket[] = {0x80, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +class CompositeRtpTransportTest : public ::testing::Test, + public sigslot::has_slots<>, + public RtpPacketSinkInterface { + public: + CompositeRtpTransportTest() + : packet_transport_1_( + absl::make_unique(kTransportName)), + packet_transport_2_( + absl::make_unique(kTransportName)), + rtcp_transport_1_( + absl::make_unique(kRtcpTransportName)), + rtcp_transport_2_( + absl::make_unique(kRtcpTransportName)) {} + + void SetupRtpTransports(bool rtcp_mux) { + transport_1_ = absl::make_unique(rtcp_mux); + transport_2_ = absl::make_unique(rtcp_mux); + + transport_1_->SetRtpPacketTransport(packet_transport_1_.get()); + transport_2_->SetRtpPacketTransport(packet_transport_2_.get()); + if (!rtcp_mux) { + transport_1_->SetRtcpPacketTransport(rtcp_transport_1_.get()); + transport_2_->SetRtcpPacketTransport(rtcp_transport_2_.get()); + } + + composite_ = absl::make_unique( + std::vector{transport_1_.get(), + transport_2_.get()}); + + composite_->SignalReadyToSend.connect( + this, &CompositeRtpTransportTest::OnReadyToSend); + composite_->SignalWritableState.connect( + this, &CompositeRtpTransportTest::OnWritableState); + composite_->SignalSentPacket.connect( + this, &CompositeRtpTransportTest::OnSentPacket); + composite_->SignalNetworkRouteChanged.connect( + this, &CompositeRtpTransportTest::OnNetworkRouteChanged); + composite_->SignalRtcpPacketReceived.connect( + this, &CompositeRtpTransportTest::OnRtcpPacketReceived); + + RtpDemuxerCriteria criteria; + criteria.payload_types.insert(kRtpPayloadType); + composite_->RegisterRtpDemuxerSink(criteria, this); + } + + void TearDown() override { composite_->UnregisterRtpDemuxerSink(this); } + + void OnReadyToSend(bool ready) { ++ready_to_send_count_; } + void OnWritableState(bool writable) { ++writable_state_count_; } + void OnSentPacket(const rtc::SentPacket& packet) { ++sent_packet_count_; } + void OnNetworkRouteChanged(absl::optional route) { + ++network_route_count_; + last_network_route_ = route; + } + void OnRtcpPacketReceived(rtc::CopyOnWriteBuffer* buffer, + int64_t packet_time_us) { + ++rtcp_packet_count_; + last_packet_ = *buffer; + } + void OnRtpPacket(const RtpPacketReceived& packet) { + ++rtp_packet_count_; + last_packet_ = packet.Buffer(); + } + + protected: + std::unique_ptr packet_transport_1_; + std::unique_ptr packet_transport_2_; + std::unique_ptr rtcp_transport_1_; + std::unique_ptr rtcp_transport_2_; + std::unique_ptr transport_1_; + std::unique_ptr transport_2_; + std::unique_ptr composite_; + + int ready_to_send_count_ = 0; + int writable_state_count_ = 0; + int sent_packet_count_ = 0; + int network_route_count_ = 0; + int rtcp_packet_count_ = 0; + int rtp_packet_count_ = 0; + + absl::optional last_network_route_; + rtc::CopyOnWriteBuffer last_packet_; +}; + +TEST_F(CompositeRtpTransportTest, EnableRtcpMux) { + SetupRtpTransports(/*rtcp_mux=*/false); + EXPECT_FALSE(composite_->rtcp_mux_enabled()); + EXPECT_FALSE(transport_1_->rtcp_mux_enabled()); + EXPECT_FALSE(transport_2_->rtcp_mux_enabled()); + + composite_->SetRtcpMuxEnabled(true); + EXPECT_TRUE(composite_->rtcp_mux_enabled()); + EXPECT_TRUE(transport_1_->rtcp_mux_enabled()); + EXPECT_TRUE(transport_2_->rtcp_mux_enabled()); +} + +TEST_F(CompositeRtpTransportTest, DisableRtcpMux) { + SetupRtpTransports(/*rtcp_mux=*/true); + EXPECT_TRUE(composite_->rtcp_mux_enabled()); + EXPECT_TRUE(transport_1_->rtcp_mux_enabled()); + EXPECT_TRUE(transport_2_->rtcp_mux_enabled()); + + // If the component transports didn't have an RTCP transport before, they need + // to be set independently before disabling RTCP mux. There's no other sane + // way to do this, as the interface only allows sending a single RTCP + // transport, and we need one for each component. + transport_1_->SetRtcpPacketTransport(rtcp_transport_1_.get()); + transport_2_->SetRtcpPacketTransport(rtcp_transport_2_.get()); + + composite_->SetRtcpMuxEnabled(false); + EXPECT_FALSE(composite_->rtcp_mux_enabled()); + EXPECT_FALSE(transport_1_->rtcp_mux_enabled()); + EXPECT_FALSE(transport_2_->rtcp_mux_enabled()); +} + +TEST_F(CompositeRtpTransportTest, SetRtpOption) { + SetupRtpTransports(/*rtcp_mux=*/true); + EXPECT_EQ(0, composite_->SetRtpOption(rtc::Socket::OPT_DSCP, 2)); + + int value = 0; + EXPECT_TRUE(packet_transport_1_->GetOption(rtc::Socket::OPT_DSCP, &value)); + EXPECT_EQ(value, 2); + + EXPECT_TRUE(packet_transport_2_->GetOption(rtc::Socket::OPT_DSCP, &value)); + EXPECT_EQ(value, 2); +} + +TEST_F(CompositeRtpTransportTest, SetRtcpOption) { + SetupRtpTransports(/*rtcp_mux=*/false); + EXPECT_EQ(0, composite_->SetRtcpOption(rtc::Socket::OPT_DSCP, 2)); + + int value = 0; + EXPECT_TRUE(rtcp_transport_1_->GetOption(rtc::Socket::OPT_DSCP, &value)); + EXPECT_EQ(value, 2); + + EXPECT_TRUE(rtcp_transport_2_->GetOption(rtc::Socket::OPT_DSCP, &value)); + EXPECT_EQ(value, 2); +} + +TEST_F(CompositeRtpTransportTest, NeverWritableWithoutSendTransport) { + SetupRtpTransports(/*rtcp_mux=*/true); + + packet_transport_1_->SetWritable(true); + packet_transport_2_->SetWritable(true); + + EXPECT_FALSE(composite_->IsWritable(false)); + EXPECT_FALSE(composite_->IsWritable(true)); + EXPECT_FALSE(composite_->IsReadyToSend()); + EXPECT_EQ(0, ready_to_send_count_); + EXPECT_EQ(0, writable_state_count_); +} + +TEST_F(CompositeRtpTransportTest, WritableWhenSendTransportBecomesWritable) { + SetupRtpTransports(/*rtcp_mux=*/true); + + composite_->SetSendTransport(transport_1_.get()); + + EXPECT_FALSE(composite_->IsWritable(false)); + EXPECT_FALSE(composite_->IsWritable(true)); + EXPECT_FALSE(composite_->IsReadyToSend()); + EXPECT_EQ(0, ready_to_send_count_); + EXPECT_EQ(1, writable_state_count_); + + packet_transport_2_->SetWritable(true); + + EXPECT_FALSE(composite_->IsWritable(false)); + EXPECT_FALSE(composite_->IsWritable(true)); + EXPECT_FALSE(composite_->IsReadyToSend()); + EXPECT_EQ(0, ready_to_send_count_); + EXPECT_EQ(1, writable_state_count_); + + packet_transport_1_->SetWritable(true); + + EXPECT_TRUE(composite_->IsWritable(false)); + EXPECT_TRUE(composite_->IsWritable(true)); + EXPECT_TRUE(composite_->IsReadyToSend()); + EXPECT_EQ(1, ready_to_send_count_); + EXPECT_EQ(2, writable_state_count_); +} + +TEST_F(CompositeRtpTransportTest, SendTransportAlreadyWritable) { + SetupRtpTransports(/*rtcp_mux=*/true); + packet_transport_1_->SetWritable(true); + + composite_->SetSendTransport(transport_1_.get()); + + EXPECT_TRUE(composite_->IsWritable(false)); + EXPECT_TRUE(composite_->IsWritable(true)); + EXPECT_TRUE(composite_->IsReadyToSend()); + EXPECT_EQ(1, ready_to_send_count_); + EXPECT_EQ(1, writable_state_count_); +} + +TEST_F(CompositeRtpTransportTest, IsSrtpActive) { + SetupRtpTransports(/*rtcp_mux=*/true); + EXPECT_FALSE(composite_->IsSrtpActive()); +} + +TEST_F(CompositeRtpTransportTest, NetworkRouteChange) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::NetworkRoute route; + route.local_network_id = 7; + packet_transport_1_->SetNetworkRoute(route); + + EXPECT_EQ(1, network_route_count_); + EXPECT_EQ(7, last_network_route_->local_network_id); + + route.local_network_id = 8; + packet_transport_2_->SetNetworkRoute(route); + + EXPECT_EQ(2, network_route_count_); + EXPECT_EQ(8, last_network_route_->local_network_id); +} + +TEST_F(CompositeRtpTransportTest, SendRtcpBeforeSendTransportSet) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::FakePacketTransport remote("remote"); + remote.SetDestination(packet_transport_1_.get(), false); + + rtc::CopyOnWriteBuffer packet(kRtcpPacket); + EXPECT_FALSE(composite_->SendRtcpPacket(&packet, rtc::PacketOptions(), 0)); + EXPECT_EQ(0, sent_packet_count_); +} + +TEST_F(CompositeRtpTransportTest, SendRtcpOn1) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::FakePacketTransport remote("remote"); + remote.SetDestination(packet_transport_1_.get(), false); + composite_->SetSendTransport(transport_1_.get()); + + rtc::CopyOnWriteBuffer packet(kRtcpPacket); + EXPECT_TRUE(composite_->SendRtcpPacket(&packet, rtc::PacketOptions(), 0)); + EXPECT_EQ(1, sent_packet_count_); + EXPECT_EQ(packet, *packet_transport_1_->last_sent_packet()); +} + +TEST_F(CompositeRtpTransportTest, SendRtcpOn2) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::FakePacketTransport remote("remote"); + remote.SetDestination(packet_transport_2_.get(), false); + composite_->SetSendTransport(transport_2_.get()); + + rtc::CopyOnWriteBuffer packet(kRtcpPacket); + EXPECT_TRUE(composite_->SendRtcpPacket(&packet, rtc::PacketOptions(), 0)); + EXPECT_EQ(1, sent_packet_count_); + EXPECT_EQ(packet, *packet_transport_2_->last_sent_packet()); +} + +TEST_F(CompositeRtpTransportTest, SendRtpBeforeSendTransportSet) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::FakePacketTransport remote("remote"); + remote.SetDestination(packet_transport_1_.get(), false); + + rtc::CopyOnWriteBuffer packet(kRtpPacket); + EXPECT_FALSE(composite_->SendRtpPacket(&packet, rtc::PacketOptions(), 0)); + EXPECT_EQ(0, sent_packet_count_); +} + +TEST_F(CompositeRtpTransportTest, SendRtpOn1) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::FakePacketTransport remote("remote"); + remote.SetDestination(packet_transport_1_.get(), false); + composite_->SetSendTransport(transport_1_.get()); + + rtc::CopyOnWriteBuffer packet(kRtpPacket); + EXPECT_TRUE(composite_->SendRtpPacket(&packet, rtc::PacketOptions(), 0)); + EXPECT_EQ(1, sent_packet_count_); + EXPECT_EQ(packet, *packet_transport_1_->last_sent_packet()); +} + +TEST_F(CompositeRtpTransportTest, SendRtpOn2) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::FakePacketTransport remote("remote"); + remote.SetDestination(packet_transport_2_.get(), false); + composite_->SetSendTransport(transport_2_.get()); + + rtc::CopyOnWriteBuffer packet(kRtpPacket); + EXPECT_TRUE(composite_->SendRtpPacket(&packet, rtc::PacketOptions(), 0)); + EXPECT_EQ(1, sent_packet_count_); + EXPECT_EQ(packet, *packet_transport_2_->last_sent_packet()); +} + +TEST_F(CompositeRtpTransportTest, ReceiveRtcpFrom1) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::FakePacketTransport remote("remote"); + remote.SetDestination(packet_transport_1_.get(), false); + + rtc::CopyOnWriteBuffer packet(kRtcpPacket); + remote.SendPacket(packet.cdata(), packet.size(), rtc::PacketOptions(), + 0); + + EXPECT_EQ(1, rtcp_packet_count_); + EXPECT_EQ(packet, last_packet_); +} + +TEST_F(CompositeRtpTransportTest, ReceiveRtcpFrom2) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::FakePacketTransport remote("remote"); + remote.SetDestination(packet_transport_2_.get(), false); + + rtc::CopyOnWriteBuffer packet(kRtcpPacket); + remote.SendPacket(packet.cdata(), packet.size(), rtc::PacketOptions(), + 0); + + EXPECT_EQ(1, rtcp_packet_count_); + EXPECT_EQ(packet, last_packet_); +} + +TEST_F(CompositeRtpTransportTest, ReceiveRtpFrom1) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::FakePacketTransport remote("remote"); + remote.SetDestination(packet_transport_1_.get(), false); + + rtc::CopyOnWriteBuffer packet(kRtpPacket); + remote.SendPacket(packet.cdata(), packet.size(), rtc::PacketOptions(), + 0); + + EXPECT_EQ(1, rtp_packet_count_); + EXPECT_EQ(packet, last_packet_); +} + +TEST_F(CompositeRtpTransportTest, ReceiveRtpFrom2) { + SetupRtpTransports(/*rtcp_mux=*/true); + + rtc::FakePacketTransport remote("remote"); + remote.SetDestination(packet_transport_2_.get(), false); + + rtc::CopyOnWriteBuffer packet(kRtpPacket); + remote.SendPacket(packet.cdata(), packet.size(), rtc::PacketOptions(), + 0); + + EXPECT_EQ(1, rtp_packet_count_); + EXPECT_EQ(packet, last_packet_); +} + +} // namespace +} // namespace webrtc