// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "cast/streaming/sender_packet_router.h" #include #include "cast/streaming/constants.h" #include "cast/streaming/mock_environment.h" #include "cast/streaming/testing/simple_socket_subscriber.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "platform/base/ip_address.h" #include "platform/test/fake_clock.h" #include "platform/test/fake_task_runner.h" #include "util/big_endian.h" #include "util/chrono_helpers.h" #include "util/osp_logging.h" using testing::_; using testing::Invoke; using testing::Mock; using testing::Return; namespace openscreen { namespace cast { namespace { const IPEndpoint kRemoteEndpoint{ // Use a random IPv6 address in the range reserved for "documentation // purposes." IPAddress::Parse("2001:db8:0d93:69c2:fd1a:49a6:a7c0:e8a6").value(), 25476}; const IPEndpoint kUnexpectedEndpoint{ IPAddress::Parse("2001:db8:0d93:69c2:fd1a:49a6:a7c0:e8a7").value(), 25476}; // Limited burst parameters to simplify unit testing. constexpr int kMaxPacketsPerBurst = 3; constexpr auto kBurstInterval = milliseconds(10); constexpr Ssrc kAudioReceiverSsrc = 2; constexpr Ssrc kVideoReceiverSsrc = 32; const uint8_t kGarbagePacket[] = { 0x42, 0x61, 0x16, 0x17, 0x26, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x72, 0x74, 0x63, 0x70, 0x5f}; // clang-format off const uint8_t kValidAudioRtcpPacket[] = { 0b10000000, // Version=2, Padding=no, ReportCount=0. 201, // RTCP Packet type byte. 0x00, 0x01, // Length of remainder of packet, in 32-bit words. 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. }; const uint8_t kValidAudioRtpPacket[] = { 0b10000000, // Version/Padding byte. 96, // Payload type byte. 0xbe, 0xef, // Sequence number. 9, 8, 7, 6, // RTP timestamp. 0, 0, 0, 2, // SSRC. 0b10000000, // Is key frame, no extensions. 5, // Frame ID. 0xa, 0xb, // Packet ID. 0xa, 0xc, // Max packet ID. 0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, // Payload. }; // clang-format on // Returns a copy of an |original| RTCP packet, but with its send-to SSRC // modified to the given |alternate_ssrc|. std::vector MakeRtcpPacketWithAlternateReceiverSsrc( absl::Span original, Ssrc alternate_ssrc) { constexpr int kOffsetToSsrcField = 4; std::vector out(original.begin(), original.end()); OSP_CHECK_GE(out.size(), kOffsetToSsrcField + sizeof(uint32_t)); WriteBigEndian(uint32_t{alternate_ssrc}, out.data() + kOffsetToSsrcField); return out; } // Serializes the |flag| and |send_time| into the front of |buffer| so the tests // can make unique packets and confirm their identities after passing through // various components. absl::Span MakeFakePacketWithFlag(char flag, Clock::time_point send_time, absl::Span buffer) { const Clock::duration::rep ticks = send_time.time_since_epoch().count(); const auto packet_size = sizeof(ticks) + sizeof(flag); buffer = buffer.subspan(0, packet_size); OSP_CHECK_EQ(buffer.size(), packet_size); WriteBigEndian(ticks, buffer.data()); buffer[sizeof(ticks)] = flag; return buffer; } // Same as MakeFakePacketWithFlag(), but for tests that don't use the flag. absl::Span MakeFakePacket(Clock::time_point send_time, absl::Span buffer) { return MakeFakePacketWithFlag('?', send_time, buffer); } // Returns the flag that was placed in the given |fake_packet|, or '?' if // unknown. char ParseFlag(absl::Span fake_packet) { constexpr auto kFlagOffset = sizeof(Clock::duration::rep); if (fake_packet.size() == (kFlagOffset + sizeof(char))) { return static_cast(fake_packet[kFlagOffset]); } return '?'; } // Deserializes and returns the timestamp that was placed in the given |packet| // by MakeFakePacketWithFlag(). Clock::time_point ParseTimestamp(absl::Span fake_packet) { Clock::duration::rep ticks = 0; if (fake_packet.size() >= sizeof(ticks)) { ticks = ReadBigEndian(fake_packet.data()); } return Clock::time_point() + Clock::duration(ticks); } // Returns an empty version of |buffer|. absl::Span ToEmptyPacketBuffer(Clock::time_point send_time, absl::Span buffer) { return buffer.subspan(0, 0); } class MockSender : public SenderPacketRouter::Sender { public: MockSender() = default; ~MockSender() override = default; MOCK_METHOD(void, OnReceivedRtcpPacket, (Clock::time_point arrival_time, absl::Span packet), (override)); MOCK_METHOD(absl::Span, GetRtcpPacketForImmediateSend, (Clock::time_point send_time, absl::Span buffer), (override)); MOCK_METHOD(absl::Span, GetRtpPacketForImmediateSend, (Clock::time_point send_time, absl::Span buffer), (override)); MOCK_METHOD(Clock::time_point, GetRtpResumeTime, (), (override)); }; class SenderPacketRouterTest : public testing::Test { public: SenderPacketRouterTest() : clock_(Clock::now()), task_runner_(&clock_), env_(&FakeClock::now, &task_runner_), router_(&env_, kMaxPacketsPerBurst, kBurstInterval) { env_.SetSocketSubscriber(&socket_subscriber_); } ~SenderPacketRouterTest() override = default; MockEnvironment* env() { return &env_; } SenderPacketRouter* router() { return &router_; } MockSender* audio_sender() { return &audio_sender_; } MockSender* video_sender() { return &video_sender_; } void SimulatePacketArrivedNow(const IPEndpoint& source, absl::Span packet) { static_cast(&router_)->OnReceivedPacket( source, env_.now(), std::vector(packet.begin(), packet.end())); } void AdvanceClockAndRunTasks(Clock::duration delta) { clock_.Advance(delta); } void RunTasksUntilIdle() { task_runner_.RunTasksUntilIdle(); } private: FakeClock clock_; FakeTaskRunner task_runner_; testing::NiceMock env_; SenderPacketRouter router_; testing::NiceMock audio_sender_; testing::NiceMock video_sender_; SimpleSubscriber socket_subscriber_; }; // Tests that the SenderPacketRouter is correctly configured from the specific // burst parameters that were passed to its constructor. This confirms internal // calculations based on these parameters. TEST_F(SenderPacketRouterTest, IsConfiguredFromBurstParameters) { EXPECT_EQ(env()->GetMaxPacketSize(), router()->max_packet_size()); // The following lower-bound/upper-bound values were hand-calculated based on // the arguments that were passed to the SenderPacketRouter constructor, and // assuming a packet size anywhere from 256 bytes to one megabyte. // // The exact value for max_burst_bitrate() is not known here because // Environment::GetMaxPacketSize() depends on the platform and network medium. // To test for an exact value would require duplicating the math in // SenderPacketRouter::ComputeMaxBurstBitrate() here (and then *what* would we // be testing?). EXPECT_LE(614400, router()->max_burst_bitrate()); EXPECT_GE(2147483647, router()->max_burst_bitrate()); } TEST_F(SenderPacketRouterTest, IgnoresPacketsFromUnexpectedSources) { env()->set_remote_endpoint(kRemoteEndpoint); router()->OnSenderCreated(kAudioReceiverSsrc, audio_sender()); EXPECT_CALL(*audio_sender(), OnReceivedRtcpPacket(_, _)).Times(0); SimulatePacketArrivedNow(kUnexpectedEndpoint, absl::Span(kValidAudioRtcpPacket)); router()->OnSenderDestroyed(kAudioReceiverSsrc); } TEST_F(SenderPacketRouterTest, IgnoresInboundPacketsContainingGarbage) { env()->set_remote_endpoint(kRemoteEndpoint); router()->OnSenderCreated(kAudioReceiverSsrc, audio_sender()); EXPECT_CALL(*audio_sender(), OnReceivedRtcpPacket(_, _)).Times(0); SimulatePacketArrivedNow(kUnexpectedEndpoint, absl::Span(kGarbagePacket)); SimulatePacketArrivedNow(kRemoteEndpoint, absl::Span(kGarbagePacket)); router()->OnSenderDestroyed(kAudioReceiverSsrc); } // Note: RTP packets should be ignored since it wouldn't make sense for a // Receiver to stream media to a Sender. TEST_F(SenderPacketRouterTest, IgnoresInboundRtpPackets) { env()->set_remote_endpoint(kRemoteEndpoint); router()->OnSenderCreated(kAudioReceiverSsrc, audio_sender()); EXPECT_CALL(*audio_sender(), OnReceivedRtcpPacket(_, _)).Times(0); SimulatePacketArrivedNow(kUnexpectedEndpoint, absl::Span(kValidAudioRtpPacket)); SimulatePacketArrivedNow(kRemoteEndpoint, absl::Span(kValidAudioRtpPacket)); router()->OnSenderDestroyed(kAudioReceiverSsrc); } TEST_F(SenderPacketRouterTest, IgnoresInboundRtcpPacketsFromUnknownReceivers) { env()->set_remote_endpoint(kRemoteEndpoint); router()->OnSenderCreated(kAudioReceiverSsrc, audio_sender()); const std::vector rtcp_packet_not_for_me = MakeRtcpPacketWithAlternateReceiverSsrc(kValidAudioRtcpPacket, kAudioReceiverSsrc + 1); EXPECT_CALL(*audio_sender(), OnReceivedRtcpPacket(_, _)).Times(0); SimulatePacketArrivedNow(kUnexpectedEndpoint, absl::Span(rtcp_packet_not_for_me)); SimulatePacketArrivedNow(kRemoteEndpoint, absl::Span(rtcp_packet_not_for_me)); router()->OnSenderDestroyed(kAudioReceiverSsrc); } // Tests that the SenderPacketRouter forwards packets from Receivers to the // appropriate Sender. TEST_F(SenderPacketRouterTest, RoutesRTCPPacketsFromReceivers) { EXPECT_CALL(*env(), SendPacket(_)).Times(0); const absl::Span audio_rtcp_packet(kValidAudioRtcpPacket); std::vector video_rtcp_packet = MakeRtcpPacketWithAlternateReceiverSsrc(audio_rtcp_packet, kVideoReceiverSsrc); env()->set_remote_endpoint(kRemoteEndpoint); router()->OnSenderCreated(kAudioReceiverSsrc, audio_sender()); // It should route a valid audio RTCP packet to the audio Sender, and ignore a // valid video RTCP packet (since the video Sender is not yet known to the // SenderPacketRouter). { Clock::time_point arrival_time{}; std::vector received_packet; EXPECT_CALL(*audio_sender(), OnReceivedRtcpPacket(_, _)) .WillOnce(Invoke( [&](Clock::time_point when, absl::Span packet) { arrival_time = when; received_packet.assign(packet.begin(), packet.end()); })); EXPECT_CALL(*video_sender(), OnReceivedRtcpPacket(_, _)).Times(0); const Clock::time_point expected_arrival_time = env()->now(); SimulatePacketArrivedNow(kRemoteEndpoint, audio_rtcp_packet); SimulatePacketArrivedNow(kRemoteEndpoint, video_rtcp_packet); Mock::VerifyAndClear(audio_sender()); EXPECT_EQ(expected_arrival_time, arrival_time); EXPECT_EQ(audio_rtcp_packet, received_packet); Mock::VerifyAndClear(video_sender()); } AdvanceClockAndRunTasks(seconds(1)); // Register the video Sender with the router. Now, confirm audio RTCP packets // still go to the audio Sender and video RTCP packets go to the video Sender. router()->OnSenderCreated(kVideoReceiverSsrc, video_sender()); { Clock::time_point audio_arrival_time{}, video_arrival_time{}; std::vector received_audio_packet, received_video_packet; EXPECT_CALL(*audio_sender(), OnReceivedRtcpPacket(_, _)) .WillOnce(Invoke( [&](Clock::time_point when, absl::Span packet) { audio_arrival_time = when; received_audio_packet.assign(packet.begin(), packet.end()); })); EXPECT_CALL(*video_sender(), OnReceivedRtcpPacket(_, _)) .WillOnce(Invoke( [&](Clock::time_point when, absl::Span packet) { video_arrival_time = when; received_video_packet.assign(packet.begin(), packet.end()); })); const Clock::time_point expected_audio_arrival_time = env()->now(); SimulatePacketArrivedNow(kRemoteEndpoint, audio_rtcp_packet); AdvanceClockAndRunTasks(milliseconds(11)); const Clock::time_point expected_video_arrival_time = env()->now(); SimulatePacketArrivedNow(kRemoteEndpoint, video_rtcp_packet); Mock::VerifyAndClear(audio_sender()); EXPECT_EQ(expected_audio_arrival_time, audio_arrival_time); EXPECT_EQ(audio_rtcp_packet, received_audio_packet); Mock::VerifyAndClear(video_sender()); EXPECT_EQ(expected_video_arrival_time, video_arrival_time); EXPECT_EQ(video_rtcp_packet, received_video_packet); } router()->OnSenderDestroyed(kAudioReceiverSsrc); router()->OnSenderDestroyed(kVideoReceiverSsrc); } // Tests that the SenderPacketRouter schedules periodic RTCP packet sends, // starting once the Sender requests the first RTCP send. TEST_F(SenderPacketRouterTest, SchedulesPeriodicTransmissionOfRTCPPackets) { env()->set_remote_endpoint(kRemoteEndpoint); router()->OnSenderCreated(kAudioReceiverSsrc, audio_sender()); constexpr int kNumIterations = 5; EXPECT_CALL(*audio_sender(), OnReceivedRtcpPacket(_, _)).Times(0); EXPECT_CALL(*audio_sender(), GetRtcpPacketForImmediateSend(_, _)) .Times(kNumIterations) .WillRepeatedly(Invoke(&MakeFakePacket)); EXPECT_CALL(*audio_sender(), GetRtpPacketForImmediateSend(_, _)).Times(0); ON_CALL(*audio_sender(), GetRtpResumeTime()) .WillByDefault(Return(SenderPacketRouter::kNever)); // Capture every packet sent for analysis at the end of this test. std::vector> packets_sent; EXPECT_CALL(*env(), SendPacket(_)) .WillRepeatedly(Invoke([&](absl::Span packet) { packets_sent.emplace_back(packet.begin(), packet.end()); })); const Clock::time_point first_send_time = env()->now(); router()->RequestRtcpSend(kAudioReceiverSsrc); RunTasksUntilIdle(); // The first RTCP packet should be sent immediately. for (int i = 1; i < kNumIterations; ++i) { AdvanceClockAndRunTasks(kRtcpReportInterval); } // Ensure each RTCP packet was sent and in-sequence. Mock::VerifyAndClear(env()); ASSERT_EQ(kNumIterations, static_cast(packets_sent.size())); for (int i = 0; i < kNumIterations; ++i) { const Clock::time_point expected_send_time = first_send_time + i * kRtcpReportInterval; EXPECT_EQ(expected_send_time, ParseTimestamp(packets_sent[i])); } router()->OnSenderDestroyed(kAudioReceiverSsrc); } // Tests that the SenderPacketRouter schedules RTP packet bursts from a single // Sender. TEST_F(SenderPacketRouterTest, SchedulesAndTransmitsRTPBursts) { env()->set_remote_endpoint(kRemoteEndpoint); router()->OnSenderCreated(kVideoReceiverSsrc, video_sender()); // Capture every packet sent for analysis at the end of this test. std::vector> packets_sent; EXPECT_CALL(*env(), SendPacket(_)) .WillRepeatedly(Invoke([&](absl::Span packet) { packets_sent.emplace_back(packet.begin(), packet.end()); })); // Simulate a typical video Sender RTP at-startup sending sequence: First, at // t=0ms, the Sender wants to send its large 10-packet key frame. This will // require four bursts, since only 3 packets can be sent per burst. // // While the first frame is being sent, a smaller 4-packet frame is enqueued, // and the Sender will want to start sending this immediately after the first // frame. Part of this second frame will be sent in the fourth burst, and the // rest in the fifth burst. // // After the fifth burst, the Sender will schedule a "kickstart packet" for // 25ms later. However, when the SenderPacketRouter later asks the Sender for // that packet, the Sender will change its mind and decide not to send // anything. // // At t=100ms, the next frame of video is enqueued in the Sender and it // requests that RTP sending resume for that. This is a small 1-packet frame. const Clock::time_point start_time = env()->now(); int num_get_rtp_calls = 0; EXPECT_CALL(*video_sender(), GetRtpPacketForImmediateSend(_, _)) .Times(14 + 2) .WillRepeatedly( Invoke([&](Clock::time_point send_time, absl::Span buffer) { ++num_get_rtp_calls; // 14 packets are sent: The first through fourth bursts send three // packets each, and the fifth burst sends two. if (num_get_rtp_calls <= 14) { return MakeFakePacket(send_time, buffer); } // 2 "done signals" are then sent: One is at the end of the fifth // burst, one is for a "nothing to send" sixth burst. return ToEmptyPacketBuffer(send_time, buffer); })); const Clock::time_point kickstart_time = start_time + 4 * kBurstInterval + milliseconds(25); int num_get_resume_calls = 0; EXPECT_CALL(*video_sender(), GetRtpResumeTime()) .Times(4 + 1 + 1) .WillRepeatedly(Invoke([&] { ++num_get_resume_calls; // After each of the first through fourth bursts, the Sender wants to // transmit more right away. if (num_get_resume_calls <= 4) { return env()->now(); } // After the fifth burst, the Sender requests resuming for kickstart // later. if (num_get_resume_calls == 5) { return kickstart_time; } // After the sixth burst, the Sender pauses RTP sending indefinitely. return SenderPacketRouter::kNever; })); router()->RequestRtpSend(kVideoReceiverSsrc); // Execute first burst. RunTasksUntilIdle(); // Execute second through fifth bursts. for (int i = 1; i <= 4; ++i) { AdvanceClockAndRunTasks(kBurstInterval); } // Execute the sixth burst at the kickstart time. AdvanceClockAndRunTasks(kickstart_time - env()->now()); Mock::VerifyAndClear(video_sender()); // Now, resume RTP sending for one more 1-packet frame, and then pause RTP // sending again. EXPECT_CALL(*video_sender(), GetRtpPacketForImmediateSend(_, _)) .WillOnce(Invoke(&MakeFakePacket)) // Frame 2, only packet. .WillOnce(Invoke(&ToEmptyPacketBuffer)); // Done for now. // After the seventh burst, the Sender pauses RTP sending again. EXPECT_CALL(*video_sender(), GetRtpResumeTime()) .WillOnce(Return(SenderPacketRouter::kNever)); // Advance to the resume time. Nothing should happen until RequestRtpSend() is // called. const Clock::time_point resume_time = start_time + milliseconds(100); AdvanceClockAndRunTasks(resume_time - env()->now()); router()->RequestRtpSend(kVideoReceiverSsrc); // Execute seventh burst. RunTasksUntilIdle(); // Run for one more second, but nothing should be happening since sending is // paused. AdvanceClockAndRunTasks(seconds(1)); Mock::VerifyAndClear(video_sender()); // Confirm 15 packets got sent and contain the expected data (which tracks // when they were sent). ASSERT_EQ(15, static_cast(packets_sent.size())); Clock::time_point expected_time; int packet_idx = 0; // First burst through fourth burst. for (int burst_number = 0; burst_number < 4; ++burst_number) { expected_time = start_time + burst_number * kBurstInterval; EXPECT_EQ(expected_time, ParseTimestamp(packets_sent[packet_idx++])); EXPECT_EQ(expected_time, ParseTimestamp(packets_sent[packet_idx++])); EXPECT_EQ(expected_time, ParseTimestamp(packets_sent[packet_idx++])); } // Fifth burst. expected_time += kBurstInterval; EXPECT_EQ(expected_time, ParseTimestamp(packets_sent[packet_idx++])); EXPECT_EQ(expected_time, ParseTimestamp(packets_sent[packet_idx++])); // Seventh burst (sixth burst sent nothing). EXPECT_EQ(resume_time, ParseTimestamp(packets_sent[packet_idx++])); router()->OnSenderDestroyed(kVideoReceiverSsrc); } // Tests that the SenderPacketRouter schedules packet sends based on transmit // prority: RTCP before RTP, and the audio Sender's packets before the video // Sender's. TEST_F(SenderPacketRouterTest, SchedulesAndTransmitsAccountingForPriority) { env()->set_remote_endpoint(kRemoteEndpoint); ASSERT_LT(ComparePriority(kAudioReceiverSsrc, kVideoReceiverSsrc), 0); router()->OnSenderCreated(kVideoReceiverSsrc, video_sender()); router()->OnSenderCreated(kAudioReceiverSsrc, audio_sender()); // Capture every packet sent for analysis at the end of this test. std::vector> packets_sent; EXPECT_CALL(*env(), SendPacket(_)) .WillRepeatedly(Invoke([&](absl::Span packet) { packets_sent.emplace_back(packet.begin(), packet.end()); })); // These indicate how often one packet will be sent from each Sender. constexpr Clock::duration kAudioRtpInterval = milliseconds(10); constexpr Clock::duration kVideoRtpInterval = milliseconds(33); // Note: The priority flags used in this test ('0'..'3') indicate // lowest-to-highest priority. EXPECT_CALL(*audio_sender(), GetRtcpPacketForImmediateSend(_, _)) .WillRepeatedly( Invoke([](Clock::time_point send_time, absl::Span buffer) { return MakeFakePacketWithFlag('3', send_time, buffer); })); int num_audio_get_rtp_calls = 0; EXPECT_CALL(*audio_sender(), GetRtpPacketForImmediateSend(_, _)) .WillRepeatedly( Invoke([&](Clock::time_point send_time, absl::Span buffer) { // Alternate between returning a single packet and a "done for now" // signal. ++num_audio_get_rtp_calls; if (num_audio_get_rtp_calls % 2) { return MakeFakePacketWithFlag('1', send_time, buffer); } return buffer.subspan(0, 0); })); EXPECT_CALL(*video_sender(), GetRtcpPacketForImmediateSend(_, _)) .WillRepeatedly( Invoke([](Clock::time_point send_time, absl::Span buffer) { return MakeFakePacketWithFlag('2', send_time, buffer); })); int num_video_get_rtp_calls = 0; EXPECT_CALL(*video_sender(), GetRtpPacketForImmediateSend(_, _)) .WillRepeatedly( Invoke([&](Clock::time_point send_time, absl::Span buffer) { // Alternate between returning a single packet and a "done for now" // signal. ++num_video_get_rtp_calls; if (num_video_get_rtp_calls % 2) { return MakeFakePacketWithFlag('0', send_time, buffer); } return buffer.subspan(0, 0); })); EXPECT_CALL(*audio_sender(), GetRtpResumeTime()).WillRepeatedly(Invoke([&] { return env()->now() + kAudioRtpInterval; })); EXPECT_CALL(*video_sender(), GetRtpResumeTime()).WillRepeatedly(Invoke([&] { return env()->now() + kVideoRtpInterval; })); // Request starting both RTCP and RTP sends for both Senders, in a random // order. router()->RequestRtcpSend(kVideoReceiverSsrc); router()->RequestRtpSend(kAudioReceiverSsrc); router()->RequestRtcpSend(kAudioReceiverSsrc); router()->RequestRtpSend(kVideoReceiverSsrc); // Run the SenderPacketRouter for 3 seconds. constexpr Clock::duration kSimulationDuration = seconds(3); constexpr Clock::duration kSimulationStepPeriod = milliseconds(1); const Clock::time_point start_time = env()->now(); RunTasksUntilIdle(); const Clock::time_point end_time = start_time + kSimulationDuration; while (env()->now() <= end_time) { AdvanceClockAndRunTasks(kSimulationStepPeriod); } // Examine the packets that were actually sent, and confirm that the priority // ordering was maintained. ASSERT_EQ(384, static_cast(packets_sent.size())); // The very first packet sent should be an audio RTCP packet. EXPECT_EQ('3', ParseFlag(packets_sent[0])); EXPECT_EQ(start_time, ParseTimestamp(packets_sent[0])); // Scan the rest, checking that packets sent in the same burst (i.e., having // the same send timestamp) were sent in priority order. char last_priority_flag = '3'; Clock::time_point last_timestamp = start_time; for (int i = 1; i < static_cast(packets_sent.size()) && !testing::Test::HasFailure(); ++i) { const char priority_flag = ParseFlag(packets_sent[i]); const Clock::time_point timestamp = ParseTimestamp(packets_sent[i]); EXPECT_LE(last_timestamp, timestamp) << "packet[" << i << ']'; if (timestamp == last_timestamp) { EXPECT_GT(last_priority_flag, priority_flag) << "packet[" << i << ']'; } last_priority_flag = priority_flag; last_timestamp = timestamp; } router()->OnSenderDestroyed(kVideoReceiverSsrc); router()->OnSenderDestroyed(kAudioReceiverSsrc); } } // namespace } // namespace cast } // namespace openscreen