Create a composite implementation of RtpTransportInternal.

This will be used to multiplex multiple transports during SDP
negotiation.  When the offerer watns to support multiple RTP transports,
it will combine them into a singla CompositeRtpTransport.

CompositeRtpTransport can receive from any of the offered transports
while waiting for an answer to arrive.

The choice of which transport is used to send must be driven by the SDP
answer.  If a provisional answer arrives, the composite can be set to
send using the chosen transport, while maintaining other transports in
case the peer changes its mind.  When the final answer arrives, the
composite will be deleted and replaced with the chosen transport.

Bug: webrtc:9719
Change-Id: Ib8cea77ef202f37086723bfa2c71e2aa5995a912
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/138281
Commit-Queue: Bjorn Mellem <mellem@webrtc.org>
Reviewed-by: Anton Sukhanov <sukhanov@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28093}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 0f44f83..1417acf 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -34,6 +34,8 @@
     "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 @@
     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 0000000..1209fa5
--- /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 <string>
+#include <utility>
+
+#include "pc/composite_rtp_transport.h"
+
+#include "absl/memory/memory.h"
+#include "p2p/base/packet_transport_internal.h"
+
+namespace webrtc {
+
+CompositeRtpTransport::CompositeRtpTransport(
+    std::vector<RtpTransportInternal*> transports)
+    : transports_(std::move(transports)) {
+  RTC_DCHECK(!transports_.empty()) << "Cannot have an empty composite";
+  std::vector<rtc::PacketTransportInternal*> rtp_transports;
+  std::vector<rtc::PacketTransportInternal*> 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<rtc::NetworkRoute> 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 0000000..deb315d
--- /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 <memory>
+#include <string>
+#include <vector>
+
+#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<RtpTransportInternal*> 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<rtc::NetworkRoute> 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<RtpTransportInternal*> 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 0000000..5264c73
--- /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 <memory>
+
+#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<rtc::FakePacketTransport>(kTransportName)),
+        packet_transport_2_(
+            absl::make_unique<rtc::FakePacketTransport>(kTransportName)),
+        rtcp_transport_1_(
+            absl::make_unique<rtc::FakePacketTransport>(kRtcpTransportName)),
+        rtcp_transport_2_(
+            absl::make_unique<rtc::FakePacketTransport>(kRtcpTransportName)) {}
+
+  void SetupRtpTransports(bool rtcp_mux) {
+    transport_1_ = absl::make_unique<RtpTransport>(rtcp_mux);
+    transport_2_ = absl::make_unique<RtpTransport>(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<CompositeRtpTransport>(
+        std::vector<RtpTransportInternal*>{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<rtc::NetworkRoute> 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<rtc::FakePacketTransport> packet_transport_1_;
+  std::unique_ptr<rtc::FakePacketTransport> packet_transport_2_;
+  std::unique_ptr<rtc::FakePacketTransport> rtcp_transport_1_;
+  std::unique_ptr<rtc::FakePacketTransport> rtcp_transport_2_;
+  std::unique_ptr<RtpTransport> transport_1_;
+  std::unique_ptr<RtpTransport> transport_2_;
+  std::unique_ptr<CompositeRtpTransport> 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<rtc::NetworkRoute> 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<char>(), 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<char>(), 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<char>(), 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<char>(), packet.size(), rtc::PacketOptions(),
+                    0);
+
+  EXPECT_EQ(1, rtp_packet_count_);
+  EXPECT_EQ(packet, last_packet_);
+}
+
+}  // namespace
+}  // namespace webrtc