| /* | 
 |  *  Copyright 2017 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/rtp_transport.h" | 
 |  | 
 | #include <cerrno> | 
 | #include <cstdint> | 
 | #include <optional> | 
 |  | 
 | #include "api/test/rtc_error_matchers.h" | 
 | #include "api/units/time_delta.h" | 
 | #include "call/rtp_demuxer.h" | 
 | #include "p2p/test/fake_packet_transport.h" | 
 | #include "pc/test/rtp_transport_test_util.h" | 
 | #include "rtc_base/buffer.h" | 
 | #include "rtc_base/containers/flat_set.h" | 
 | #include "rtc_base/network/ecn_marking.h" | 
 | #include "rtc_base/network/sent_packet.h" | 
 | #include "rtc_base/network_route.h" | 
 | #include "rtc_base/third_party/sigslot/sigslot.h" | 
 | #include "test/explicit_key_value_config.h" | 
 | #include "test/gmock.h" | 
 | #include "test/gtest.h" | 
 | #include "test/run_loop.h" | 
 | #include "test/wait_until.h" | 
 |  | 
 | namespace webrtc { | 
 |  | 
 | using test::ExplicitKeyValueConfig; | 
 |  | 
 | constexpr bool kMuxDisabled = false; | 
 | constexpr bool kMuxEnabled = true; | 
 | constexpr uint16_t kLocalNetId = 1; | 
 | constexpr uint16_t kRemoteNetId = 2; | 
 | constexpr int kLastPacketId = 100; | 
 | constexpr int kTransportOverheadPerPacket = 28;  // Ipv4(20) + UDP(8). | 
 |  | 
 | class SignalObserver : public sigslot::has_slots<> { | 
 |  public: | 
 |   explicit SignalObserver(RtpTransport* transport) { | 
 |     transport_ = transport; | 
 |     transport->SubscribeReadyToSend( | 
 |         this, [this](bool ready) { OnReadyToSend(ready); }); | 
 |     transport->SubscribeNetworkRouteChanged( | 
 |         this, [this](std::optional<rtc::NetworkRoute> route) { | 
 |           OnNetworkRouteChanged(route); | 
 |         }); | 
 |     if (transport->rtp_packet_transport()) { | 
 |       transport->rtp_packet_transport()->SignalSentPacket.connect( | 
 |           this, &SignalObserver::OnSentPacket); | 
 |     } | 
 |  | 
 |     if (transport->rtcp_packet_transport()) { | 
 |       transport->rtcp_packet_transport()->SignalSentPacket.connect( | 
 |           this, &SignalObserver::OnSentPacket); | 
 |     } | 
 |   } | 
 |  | 
 |   bool ready() const { return ready_; } | 
 |   void OnReadyToSend(bool ready) { ready_ = ready; } | 
 |  | 
 |   std::optional<rtc::NetworkRoute> network_route() { return network_route_; } | 
 |   void OnNetworkRouteChanged(std::optional<rtc::NetworkRoute> network_route) { | 
 |     network_route_ = network_route; | 
 |   } | 
 |  | 
 |   void OnSentPacket(rtc::PacketTransportInternal* packet_transport, | 
 |                     const rtc::SentPacket& sent_packet) { | 
 |     if (packet_transport == transport_->rtp_packet_transport()) { | 
 |       rtp_transport_sent_count_++; | 
 |     } else { | 
 |       ASSERT_EQ(transport_->rtcp_packet_transport(), packet_transport); | 
 |       rtcp_transport_sent_count_++; | 
 |     } | 
 |   } | 
 |  | 
 |   int rtp_transport_sent_count() { return rtp_transport_sent_count_; } | 
 |  | 
 |   int rtcp_transport_sent_count() { return rtcp_transport_sent_count_; } | 
 |  | 
 |  private: | 
 |   int rtp_transport_sent_count_ = 0; | 
 |   int rtcp_transport_sent_count_ = 0; | 
 |   RtpTransport* transport_ = nullptr; | 
 |   bool ready_ = false; | 
 |   std::optional<rtc::NetworkRoute> network_route_; | 
 | }; | 
 |  | 
 | TEST(RtpTransportTest, SettingRtcpAndRtpSignalsReady) { | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |  | 
 |   SignalObserver observer(&transport); | 
 |   FakePacketTransport fake_rtcp("fake_rtcp"); | 
 |   fake_rtcp.SetWritable(true); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetWritable(true); | 
 |  | 
 |   transport.SetRtcpPacketTransport(&fake_rtcp);  // rtcp ready | 
 |   EXPECT_FALSE(observer.ready()); | 
 |   transport.SetRtpPacketTransport(&fake_rtp);  // rtp ready | 
 |   EXPECT_TRUE(observer.ready()); | 
 | } | 
 |  | 
 | TEST(RtpTransportTest, SettingRtpAndRtcpSignalsReady) { | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |   SignalObserver observer(&transport); | 
 |   FakePacketTransport fake_rtcp("fake_rtcp"); | 
 |   fake_rtcp.SetWritable(true); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetWritable(true); | 
 |  | 
 |   transport.SetRtpPacketTransport(&fake_rtp);  // rtp ready | 
 |   EXPECT_FALSE(observer.ready()); | 
 |   transport.SetRtcpPacketTransport(&fake_rtcp);  // rtcp ready | 
 |   EXPECT_TRUE(observer.ready()); | 
 | } | 
 |  | 
 | TEST(RtpTransportTest, SettingRtpWithRtcpMuxEnabledSignalsReady) { | 
 |   RtpTransport transport(kMuxEnabled, ExplicitKeyValueConfig("")); | 
 |   SignalObserver observer(&transport); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetWritable(true); | 
 |  | 
 |   transport.SetRtpPacketTransport(&fake_rtp);  // rtp ready | 
 |   EXPECT_TRUE(observer.ready()); | 
 | } | 
 |  | 
 | TEST(RtpTransportTest, DisablingRtcpMuxSignalsNotReady) { | 
 |   RtpTransport transport(kMuxEnabled, ExplicitKeyValueConfig("")); | 
 |   SignalObserver observer(&transport); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetWritable(true); | 
 |  | 
 |   transport.SetRtpPacketTransport(&fake_rtp);  // rtp ready | 
 |   EXPECT_TRUE(observer.ready()); | 
 |  | 
 |   transport.SetRtcpMuxEnabled(false); | 
 |   EXPECT_FALSE(observer.ready()); | 
 | } | 
 |  | 
 | TEST(RtpTransportTest, EnablingRtcpMuxSignalsReady) { | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |   SignalObserver observer(&transport); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetWritable(true); | 
 |  | 
 |   transport.SetRtpPacketTransport(&fake_rtp);  // rtp ready | 
 |   EXPECT_FALSE(observer.ready()); | 
 |  | 
 |   transport.SetRtcpMuxEnabled(true); | 
 |   EXPECT_TRUE(observer.ready()); | 
 | } | 
 |  | 
 | // Tests the SignalNetworkRoute is fired when setting a packet transport. | 
 | TEST(RtpTransportTest, SetRtpTransportWithNetworkRouteChanged) { | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |   SignalObserver observer(&transport); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |  | 
 |   EXPECT_FALSE(observer.network_route()); | 
 |  | 
 |   rtc::NetworkRoute network_route; | 
 |   // Set a non-null RTP transport with a new network route. | 
 |   network_route.connected = true; | 
 |   network_route.local = rtc::RouteEndpoint::CreateWithNetworkId(kLocalNetId); | 
 |   network_route.remote = rtc::RouteEndpoint::CreateWithNetworkId(kRemoteNetId); | 
 |   network_route.last_sent_packet_id = kLastPacketId; | 
 |   network_route.packet_overhead = kTransportOverheadPerPacket; | 
 |   fake_rtp.SetNetworkRoute(std::optional<rtc::NetworkRoute>(network_route)); | 
 |   transport.SetRtpPacketTransport(&fake_rtp); | 
 |   ASSERT_TRUE(observer.network_route()); | 
 |   EXPECT_TRUE(observer.network_route()->connected); | 
 |   EXPECT_EQ(kLocalNetId, observer.network_route()->local.network_id()); | 
 |   EXPECT_EQ(kRemoteNetId, observer.network_route()->remote.network_id()); | 
 |   EXPECT_EQ(kTransportOverheadPerPacket, | 
 |             observer.network_route()->packet_overhead); | 
 |   EXPECT_EQ(kLastPacketId, observer.network_route()->last_sent_packet_id); | 
 |  | 
 |   // Set a null RTP transport. | 
 |   transport.SetRtpPacketTransport(nullptr); | 
 |   EXPECT_FALSE(observer.network_route()); | 
 | } | 
 |  | 
 | TEST(RtpTransportTest, SetRtcpTransportWithNetworkRouteChanged) { | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |   SignalObserver observer(&transport); | 
 |   FakePacketTransport fake_rtcp("fake_rtcp"); | 
 |  | 
 |   EXPECT_FALSE(observer.network_route()); | 
 |  | 
 |   rtc::NetworkRoute network_route; | 
 |   // Set a non-null RTCP transport with a new network route. | 
 |   network_route.connected = true; | 
 |   network_route.local = rtc::RouteEndpoint::CreateWithNetworkId(kLocalNetId); | 
 |   network_route.remote = rtc::RouteEndpoint::CreateWithNetworkId(kRemoteNetId); | 
 |   network_route.last_sent_packet_id = kLastPacketId; | 
 |   network_route.packet_overhead = kTransportOverheadPerPacket; | 
 |   fake_rtcp.SetNetworkRoute(std::optional<rtc::NetworkRoute>(network_route)); | 
 |   transport.SetRtcpPacketTransport(&fake_rtcp); | 
 |   ASSERT_TRUE(observer.network_route()); | 
 |   EXPECT_TRUE(observer.network_route()->connected); | 
 |   EXPECT_EQ(kLocalNetId, observer.network_route()->local.network_id()); | 
 |   EXPECT_EQ(kRemoteNetId, observer.network_route()->remote.network_id()); | 
 |   EXPECT_EQ(kTransportOverheadPerPacket, | 
 |             observer.network_route()->packet_overhead); | 
 |   EXPECT_EQ(kLastPacketId, observer.network_route()->last_sent_packet_id); | 
 |  | 
 |   // Set a null RTCP transport. | 
 |   transport.SetRtcpPacketTransport(nullptr); | 
 |   EXPECT_FALSE(observer.network_route()); | 
 | } | 
 |  | 
 | // Test that RTCP packets are sent over correct transport based on the RTCP-mux | 
 | // status. | 
 | TEST(RtpTransportTest, RtcpPacketSentOverCorrectTransport) { | 
 |   // If the RTCP-mux is not enabled, RTCP packets are expected to be sent over | 
 |   // the RtcpPacketTransport. | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |   FakePacketTransport fake_rtcp("fake_rtcp"); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   transport.SetRtcpPacketTransport(&fake_rtcp);  // rtcp ready | 
 |   transport.SetRtpPacketTransport(&fake_rtp);    // rtp ready | 
 |   SignalObserver observer(&transport); | 
 |  | 
 |   fake_rtp.SetDestination(&fake_rtp, true); | 
 |   fake_rtcp.SetDestination(&fake_rtcp, true); | 
 |  | 
 |   rtc::CopyOnWriteBuffer packet; | 
 |   EXPECT_TRUE(transport.SendRtcpPacket(&packet, rtc::PacketOptions(), 0)); | 
 |   EXPECT_EQ(1, observer.rtcp_transport_sent_count()); | 
 |  | 
 |   // The RTCP packets are expected to be sent over RtpPacketTransport if | 
 |   // RTCP-mux is enabled. | 
 |   transport.SetRtcpMuxEnabled(true); | 
 |   EXPECT_TRUE(transport.SendRtcpPacket(&packet, rtc::PacketOptions(), 0)); | 
 |   EXPECT_EQ(1, observer.rtp_transport_sent_count()); | 
 | } | 
 |  | 
 | TEST(RtpTransportTest, ChangingReadyToSendStateOnlySignalsWhenChanged) { | 
 |   RtpTransport transport(kMuxEnabled, ExplicitKeyValueConfig("")); | 
 |   TransportObserver observer(&transport); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetWritable(true); | 
 |  | 
 |   // State changes, so we should signal. | 
 |   transport.SetRtpPacketTransport(&fake_rtp); | 
 |   EXPECT_EQ(observer.ready_to_send_signal_count(), 1); | 
 |  | 
 |   // State does not change, so we should not signal. | 
 |   transport.SetRtpPacketTransport(&fake_rtp); | 
 |   EXPECT_EQ(observer.ready_to_send_signal_count(), 1); | 
 |  | 
 |   // State does not change, so we should not signal. | 
 |   transport.SetRtcpMuxEnabled(true); | 
 |   EXPECT_EQ(observer.ready_to_send_signal_count(), 1); | 
 |  | 
 |   // State changes, so we should signal. | 
 |   transport.SetRtcpMuxEnabled(false); | 
 |   EXPECT_EQ(observer.ready_to_send_signal_count(), 2); | 
 | } | 
 |  | 
 | // Test that SignalPacketReceived fires with rtcp=true when a RTCP packet is | 
 | // received. | 
 | TEST(RtpTransportTest, SignalDemuxedRtcp) { | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetDestination(&fake_rtp, true); | 
 |   transport.SetRtpPacketTransport(&fake_rtp); | 
 |   TransportObserver observer(&transport); | 
 |  | 
 |   // An rtcp packet. | 
 |   const unsigned char data[] = {0x80, 73, 0, 0}; | 
 |   const int len = 4; | 
 |   const rtc::PacketOptions options; | 
 |   const int flags = 0; | 
 |   fake_rtp.SendPacket(reinterpret_cast<const char*>(data), len, options, flags); | 
 |   EXPECT_EQ(0, observer.rtp_count()); | 
 |   EXPECT_EQ(1, observer.rtcp_count()); | 
 | } | 
 |  | 
 | static const unsigned char kRtpData[] = {0x80, 0x11, 0, 0, 0, 0, | 
 |                                          0,    0,    0, 0, 0, 0}; | 
 | static const int kRtpLen = 12; | 
 |  | 
 | // Test that SignalPacketReceived fires with rtcp=false when a RTP packet with a | 
 | // handled payload type is received. | 
 | TEST(RtpTransportTest, SignalHandledRtpPayloadType) { | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetDestination(&fake_rtp, true); | 
 |   transport.SetRtpPacketTransport(&fake_rtp); | 
 |   TransportObserver observer(&transport); | 
 |   RtpDemuxerCriteria demuxer_criteria; | 
 |   // Add a handled payload type. | 
 |   demuxer_criteria.payload_types().insert(0x11); | 
 |   transport.RegisterRtpDemuxerSink(demuxer_criteria, &observer); | 
 |  | 
 |   // An rtp packet. | 
 |   const rtc::PacketOptions options; | 
 |   const int flags = 0; | 
 |   rtc::Buffer rtp_data(kRtpData, kRtpLen); | 
 |   fake_rtp.SendPacket(rtp_data.data<char>(), kRtpLen, options, flags); | 
 |   EXPECT_EQ(1, observer.rtp_count()); | 
 |   EXPECT_EQ(0, observer.un_demuxable_rtp_count()); | 
 |   EXPECT_EQ(0, observer.rtcp_count()); | 
 |   // Remove the sink before destroying the transport. | 
 |   transport.UnregisterRtpDemuxerSink(&observer); | 
 | } | 
 |  | 
 | TEST(RtpTransportTest, ReceivedPacketEcnMarkingPropagatedToDemuxedPacket) { | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |   // Setup FakePacketTransport to send packets to itself. | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetDestination(&fake_rtp, true); | 
 |   transport.SetRtpPacketTransport(&fake_rtp); | 
 |   TransportObserver observer(&transport); | 
 |   RtpDemuxerCriteria demuxer_criteria; | 
 |   // Add a payload type of kRtpData. | 
 |   demuxer_criteria.payload_types().insert(0x11); | 
 |   transport.RegisterRtpDemuxerSink(demuxer_criteria, &observer); | 
 |  | 
 |   rtc::PacketOptions options; | 
 |   options.ecn_1 = true; | 
 |   const int flags = 0; | 
 |   rtc::Buffer rtp_data(kRtpData, kRtpLen); | 
 |   fake_rtp.SendPacket(rtp_data.data<char>(), kRtpLen, options, flags); | 
 |   ASSERT_EQ(observer.rtp_count(), 1); | 
 |   EXPECT_EQ(observer.last_recv_rtp_packet().ecn(), rtc::EcnMarking::kEct1); | 
 |  | 
 |   transport.UnregisterRtpDemuxerSink(&observer); | 
 | } | 
 |  | 
 | // Test that SignalPacketReceived does not fire when a RTP packet with an | 
 | // unhandled payload type is received. | 
 | TEST(RtpTransportTest, DontSignalUnhandledRtpPayloadType) { | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetDestination(&fake_rtp, true); | 
 |   transport.SetRtpPacketTransport(&fake_rtp); | 
 |   TransportObserver observer(&transport); | 
 |   RtpDemuxerCriteria demuxer_criteria; | 
 |   // Add an unhandled payload type. | 
 |   demuxer_criteria.payload_types().insert(0x12); | 
 |   transport.RegisterRtpDemuxerSink(demuxer_criteria, &observer); | 
 |  | 
 |   const rtc::PacketOptions options; | 
 |   const int flags = 0; | 
 |   rtc::Buffer rtp_data(kRtpData, kRtpLen); | 
 |   fake_rtp.SendPacket(rtp_data.data<char>(), kRtpLen, options, flags); | 
 |   EXPECT_EQ(0, observer.rtp_count()); | 
 |   EXPECT_EQ(1, observer.un_demuxable_rtp_count()); | 
 |   EXPECT_EQ(0, observer.rtcp_count()); | 
 |   // Remove the sink before destroying the transport. | 
 |   transport.UnregisterRtpDemuxerSink(&observer); | 
 | } | 
 |  | 
 | TEST(RtpTransportTest, DontChangeReadyToSendStateOnSendFailure) { | 
 |   // ReadyToSendState should only care about if transport is writable unless the | 
 |   // field trial WebRTC-SetReadyToSendFalseIfSendFail/Enabled/ is set. | 
 |   RtpTransport transport(kMuxEnabled, ExplicitKeyValueConfig("")); | 
 |   TransportObserver observer(&transport); | 
 |  | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   fake_rtp.SetDestination(&fake_rtp, true); | 
 |   transport.SetRtpPacketTransport(&fake_rtp); | 
 |   fake_rtp.SetWritable(true); | 
 |   EXPECT_TRUE(observer.ready_to_send()); | 
 |   EXPECT_EQ(observer.ready_to_send_signal_count(), 1); | 
 |   rtc::CopyOnWriteBuffer packet; | 
 |   EXPECT_TRUE(transport.SendRtpPacket(&packet, rtc::PacketOptions(), 0)); | 
 |  | 
 |   // The fake RTP will return -1 due to ENOTCONN. | 
 |   fake_rtp.SetError(ENOTCONN); | 
 |   EXPECT_FALSE(transport.SendRtpPacket(&packet, rtc::PacketOptions(), 0)); | 
 |   // Ready to send state should not have changed. | 
 |   EXPECT_TRUE(observer.ready_to_send()); | 
 |   EXPECT_EQ(observer.ready_to_send_signal_count(), 1); | 
 | } | 
 |  | 
 | TEST(RtpTransportTest, RecursiveSetSendDoesNotCrash) { | 
 |   const int kShortTimeout = 100; | 
 |   test::RunLoop loop; | 
 |  | 
 |   RtpTransport transport( | 
 |       kMuxEnabled, | 
 |       ExplicitKeyValueConfig("WebRTC-SetReadyToSendFalseIfSendFail/Enabled/")); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   transport.SetRtpPacketTransport(&fake_rtp); | 
 |   TransportObserver observer(&transport); | 
 |   observer.SetActionOnReadyToSend([&](bool ready) { | 
 |     const rtc::PacketOptions options; | 
 |     const int flags = 0; | 
 |     rtc::CopyOnWriteBuffer rtp_data(kRtpData, kRtpLen); | 
 |     transport.SendRtpPacket(&rtp_data, options, flags); | 
 |   }); | 
 |   // The fake RTP will have no destination, so will return -1. | 
 |   fake_rtp.SetError(ENOTCONN); | 
 |   fake_rtp.SetWritable(true); | 
 |   // At this point, only the initial ready-to-send is observed. | 
 |   EXPECT_TRUE(observer.ready_to_send()); | 
 |   EXPECT_EQ(observer.ready_to_send_signal_count(), 1); | 
 |   // After the wait, the ready-to-send false is observed. | 
 |   EXPECT_THAT(WaitUntil([&] { return observer.ready_to_send_signal_count(); }, | 
 |                         ::testing::Eq(2), | 
 |                         {.timeout = webrtc::TimeDelta::Millis(kShortTimeout)}), | 
 |               IsRtcOk()); | 
 |   EXPECT_FALSE(observer.ready_to_send()); | 
 | } | 
 |  | 
 | TEST(RtpTransportTest, RecursiveOnSentPacketDoesNotCrash) { | 
 |   const int kShortTimeout = 100; | 
 |   test::RunLoop loop; | 
 |   RtpTransport transport(kMuxDisabled, ExplicitKeyValueConfig("")); | 
 |   FakePacketTransport fake_rtp("fake_rtp"); | 
 |   transport.SetRtpPacketTransport(&fake_rtp); | 
 |   fake_rtp.SetDestination(&fake_rtp, true); | 
 |   TransportObserver observer(&transport); | 
 |   const rtc::PacketOptions options; | 
 |   const int flags = 0; | 
 |  | 
 |   fake_rtp.SetWritable(true); | 
 |   observer.SetActionOnSentPacket([&]() { | 
 |     rtc::CopyOnWriteBuffer rtp_data(kRtpData, kRtpLen); | 
 |     if (observer.sent_packet_count() < 2) { | 
 |       transport.SendRtpPacket(&rtp_data, options, flags); | 
 |     } | 
 |   }); | 
 |   rtc::CopyOnWriteBuffer rtp_data(kRtpData, kRtpLen); | 
 |   transport.SendRtpPacket(&rtp_data, options, flags); | 
 |   EXPECT_EQ(observer.sent_packet_count(), 1); | 
 |   EXPECT_THAT( | 
 |       WaitUntil([&] { return observer.sent_packet_count(); }, ::testing::Eq(2), | 
 |                 {.timeout = webrtc::TimeDelta::Millis(kShortTimeout)}), | 
 |       IsRtcOk()); | 
 | } | 
 |  | 
 | }  // namespace webrtc |