| // Copyright 2019 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/compound_rtcp_parser.h" |
| |
| #include <chrono> |
| #include <cmath> |
| |
| #include "cast/streaming/mock_compound_rtcp_parser_client.h" |
| #include "cast/streaming/rtcp_session.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "platform/api/time.h" |
| #include "util/chrono_helpers.h" |
| |
| using testing::_; |
| using testing::Mock; |
| using testing::SaveArg; |
| using testing::StrictMock; |
| |
| namespace openscreen { |
| namespace cast { |
| namespace { |
| |
| constexpr Ssrc kSenderSsrc{1}; |
| constexpr Ssrc kReceiverSsrc{2}; |
| |
| class CompoundRtcpParserTest : public testing::Test { |
| public: |
| RtcpSession* session() { return &session_; } |
| StrictMock<MockCompoundRtcpParserClient>* client() { return &client_; } |
| CompoundRtcpParser* parser() { return &parser_; } |
| |
| private: |
| RtcpSession session_{kSenderSsrc, kReceiverSsrc, Clock::now()}; |
| StrictMock<MockCompoundRtcpParserClient> client_; |
| CompoundRtcpParser parser_{&session_, &client_}; |
| }; |
| |
| TEST_F(CompoundRtcpParserTest, ProcessesEmptyPacket) { |
| const uint8_t kEmpty[0] = {}; |
| // Expect NO calls to mock client. |
| EXPECT_TRUE( |
| parser()->Parse(absl::Span<const uint8_t>(kEmpty, 0), FrameId::first())); |
| } |
| |
| TEST_F(CompoundRtcpParserTest, ReturnsErrorForGarbage) { |
| const uint8_t kGarbage[] = { |
| 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}; |
| // Expect NO calls to mock client. |
| EXPECT_FALSE(parser()->Parse(kGarbage, FrameId::first())); |
| } |
| |
| TEST_F(CompoundRtcpParserTest, ParsesReceiverReportWithoutReportBlock) { |
| // clang-format off |
| const uint8_t kReceiverReportWithoutReportBlock[] = { |
| 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. |
| }; |
| // clang-format on |
| |
| // Expect NO calls to mock client. |
| EXPECT_TRUE( |
| parser()->Parse(kReceiverReportWithoutReportBlock, FrameId::first())); |
| } |
| |
| TEST_F(CompoundRtcpParserTest, ParsesReceiverReportWithReportBlock) { |
| // clang-format off |
| const uint8_t kReceiverReportWithReportBlock[] = { |
| 0b10000001, // Version=2, Padding=no, ReportCount=1. |
| 201, // RTCP Packet type byte. |
| 0x00, 0x07, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| |
| // Report block: |
| 0x00, 0x00, 0x00, 0x01, // Sender SSRC. |
| 0x05, // Fraction Lost. |
| 0x01, 0x02, 0x03, // Cumulative # packets lost. |
| 0x09, 0x09, 0x09, 0x02, // Highest sequence number. |
| 0x00, 0x00, 0x00, 0xaa, // Interarrival Jitter. |
| 0x0b, 0x0c, 0x8f, 0xed, // Sender Report ID. |
| 0x00, 0x01, 0x00, 0x00, // Delay since last sender report. |
| }; |
| // clang-format on |
| |
| RtcpReportBlock block; |
| EXPECT_CALL(*(client()), OnReceiverReport(_)).WillOnce(SaveArg<0>(&block)); |
| EXPECT_TRUE( |
| parser()->Parse(kReceiverReportWithReportBlock, FrameId::first())); |
| Mock::VerifyAndClearExpectations(client()); |
| EXPECT_EQ(kSenderSsrc, block.ssrc); |
| EXPECT_EQ(uint8_t{5}, block.packet_fraction_lost_numerator); |
| EXPECT_EQ(0x010203, block.cumulative_packets_lost); |
| EXPECT_EQ(uint32_t{0x09090902}, block.extended_high_sequence_number); |
| EXPECT_EQ(RtpTimeDelta::FromTicks(170), block.jitter); |
| EXPECT_EQ(StatusReportId{0x0b0c8fed}, block.last_status_report_id); |
| EXPECT_EQ(RtcpReportBlock::Delay(65536), block.delay_since_last_report); |
| } |
| |
| TEST_F(CompoundRtcpParserTest, ParsesPictureLossIndicatorMessage) { |
| // clang-format off |
| const uint8_t kPictureLossIndicatorPacket[] = { |
| 0b10000000 | 1, // Version=2, Padding=no, Subtype=PLI. |
| 206, // RTCP Packet type byte. |
| 0x00, 0x02, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| 0x00, 0x00, 0x00, 0x01, // Sender SSRC. |
| }; |
| |
| const uint8_t kPictureLossIndicatorPacketWithWrongReceiverSsrc[] = { |
| 0b10000000 | 1, // Version=2, Padding=no, Subtype=PLI. |
| 206, // RTCP Packet type byte. |
| 0x00, 0x02, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x03, // WRONG Receiver SSRC. |
| 0x00, 0x00, 0x00, 0x01, // Sender SSRC. |
| }; |
| |
| const uint8_t kPictureLossIndicatorPacketWithWrongSenderSsrc[] = { |
| 0b10000000 | 1, // Version=2, Padding=no, Subtype=PLI. |
| 206, // RTCP Packet type byte. |
| 0x00, 0x02, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| 0x00, 0x00, 0x00, 0x03, // WRONG Sender SSRC. |
| }; |
| // clang-format on |
| |
| // The mock client should get a PLI notification when the packet is valid and |
| // contains the correct SSRCs. |
| EXPECT_CALL(*(client()), OnReceiverIndicatesPictureLoss()); |
| EXPECT_TRUE(parser()->Parse(kPictureLossIndicatorPacket, FrameId::first())); |
| Mock::VerifyAndClearExpectations(client()); |
| |
| // The mock client should get no PLI notifications when either of the SSRCs is |
| // incorrect. |
| EXPECT_CALL(*(client()), OnReceiverIndicatesPictureLoss()).Times(0); |
| EXPECT_TRUE(parser()->Parse(kPictureLossIndicatorPacketWithWrongReceiverSsrc, |
| FrameId::first())); |
| Mock::VerifyAndClearExpectations(client()); |
| EXPECT_CALL(*(client()), OnReceiverIndicatesPictureLoss()).Times(0); |
| EXPECT_TRUE(parser()->Parse(kPictureLossIndicatorPacketWithWrongSenderSsrc, |
| FrameId::first())); |
| Mock::VerifyAndClearExpectations(client()); |
| } |
| |
| // Tests that RTCP packets containing chronologically-old data are ignored. This |
| // test's methodology simulates a real-world possibility: A receiver sends a |
| // "Picture Loss Indicator" in one RTCP packet, and then it sends another packet |
| // ~1 second later without the PLI, indicating the problem has been resolved. |
| // However, the packets are delivered out-of-order by the network. In this case, |
| // the CompoundRtcpParser should ignore the stale packet containing the PLI. |
| TEST_F(CompoundRtcpParserTest, IgnoresStalePackets) { |
| // clang-format off |
| const uint8_t kNotStaleCompoundPacket[] = { |
| // Receiver report: |
| 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. |
| |
| // Receiver reference time report: |
| 0b10000000, // Version=2, Padding=no. |
| 207, // RTCP Packet type byte. |
| 0x00, 0x04, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| 0x04, // Block type = Receiver Reference Time Report |
| 0x00, // Reserved byte. |
| 0x00, 0x02, // Block length = 2. |
| 0xe0, 0x73, 0x2e, 0x54, // NTP Timestamp (late evening on 2019-04-30). |
| 0x80, 0x00, 0x00, 0x00, |
| }; |
| |
| const uint8_t kStaleCompoundPacketWithPli[] = { |
| // Picture loss indicator: |
| 0b10000000 | 1, // Version=2, Padding=no, Subtype=PLI. |
| 206, // RTCP Packet type byte. |
| 0x00, 0x02, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| 0x00, 0x00, 0x00, 0x01, // Sender SSRC. |
| |
| // Receiver reference time report: |
| 0b10000000, // Version=2, Padding=no. |
| 207, // RTCP Packet type byte. |
| 0x00, 0x04, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| 0x04, // Block type = Receiver Reference Time Report |
| 0x00, // Reserved byte. |
| 0x00, 0x02, // Block length = 2. |
| 0xe0, 0x73, 0x2e, 0x53, // NTP Timestamp (late evening on 2019-04-30). |
| 0x42, 0x31, 0x20, 0x00, |
| }; |
| // clang-format on |
| |
| const auto expected_timestamp = |
| session()->ntp_converter().ToLocalTime(NtpTimestamp{0xe0732e5480000000}); |
| EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(expected_timestamp)); |
| EXPECT_CALL(*(client()), OnReceiverIndicatesPictureLoss()).Times(0); |
| EXPECT_TRUE(parser()->Parse(kNotStaleCompoundPacket, FrameId::first())); |
| EXPECT_TRUE(parser()->Parse(kStaleCompoundPacketWithPli, FrameId::first())); |
| } |
| |
| // Tests that unknown RTCP extended reports are ignored, but known ones are |
| // still parsed when sent alongside the unknown ones. |
| TEST_F(CompoundRtcpParserTest, IgnoresUnknownExtendedReports) { |
| // clang-format off |
| const uint8_t kPacketWithThreeExtendedReports[] = { |
| 0b10000000, // Version=2, Padding=no. |
| 207, // RTCP Packet type byte. |
| 0x00, 0x0c, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| |
| // Unknown extended report: |
| 0x02, // Block type = unknown (2) |
| 0x00, // Reserved byte. |
| 0x00, 0x06, // Block length = 6 words. |
| 0x01, 0x01, 0x01, 0x01, |
| 0x02, 0x02, 0x02, 0x02, |
| 0x03, 0x03, 0x03, 0x03, |
| 0x04, 0x04, 0x04, 0x04, |
| 0x05, 0x05, 0x05, 0x05, |
| 0x06, 0x06, 0x06, 0x06, |
| |
| // Receiver Reference Time Report: |
| 0x04, // Block type = RRTR |
| 0x00, // Reserved byte. |
| 0x00, 0x02, // Block length = 2 words. |
| 0xe0, 0x73, 0x2e, 0x55, // NTP Timestamp (late evening on 2019-04-30). |
| 0x00, 0x00, 0x00, 0x00, |
| |
| // Another unknown extended report: |
| 0x00, // Block type = unknown (0) |
| 0x00, // Reserved byte. |
| 0x00, 0x00, // Block length = 0 words. |
| }; |
| // clang-format on |
| |
| const auto expected_timestamp = |
| session()->ntp_converter().ToLocalTime(NtpTimestamp{0xe0732e5500000000}); |
| EXPECT_CALL(*(client()), OnReceiverReferenceTimeAdvanced(expected_timestamp)); |
| EXPECT_TRUE( |
| parser()->Parse(kPacketWithThreeExtendedReports, FrameId::first())); |
| } |
| |
| // Tests that a simple Cast Feedback packet is parsed, and the checkpoint frame |
| // ID is properly bit-extended, based on the current state of the Sender. |
| TEST_F(CompoundRtcpParserTest, ParsesSimpleFeedback) { |
| // clang-format off |
| const uint8_t kFeedbackPacket[] = { |
| 0b10000000 | 15, // Version=2, Padding=no, Subtype=Feedback. |
| 206, // RTCP Packet type byte. |
| 0x00, 0x04, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| 0x00, 0x00, 0x00, 0x01, // Sender SSRC. |
| 'C', 'A', 'S', 'T', |
| 0x0a, // Checkpoint Frame ID = 10. |
| 0x00, // No NACKs. |
| 0x02, 0x26, // Playout delay = 550 ms. |
| }; |
| // clang-format on |
| |
| // First scenario: Valid range of FrameIds is [0,42]. |
| const auto kMaxFeedbackFrameId0 = FrameId::first() + 42; |
| const auto expected_frame_id0 = FrameId::first() + 10; |
| const auto expected_playout_delay = milliseconds(550); |
| EXPECT_CALL(*(client()), |
| OnReceiverCheckpoint(expected_frame_id0, expected_playout_delay)); |
| EXPECT_TRUE(parser()->Parse(kFeedbackPacket, kMaxFeedbackFrameId0)); |
| Mock::VerifyAndClearExpectations(client()); |
| |
| // Second scenario: Valid range of FrameIds is [299,554]. Note: 544 == 0x22a. |
| const auto kMaxFeedbackFrameId1 = FrameId::first() + 0x22a; |
| const auto expected_frame_id1 = FrameId::first() + 0x20a; |
| EXPECT_CALL(*(client()), |
| OnReceiverCheckpoint(expected_frame_id1, expected_playout_delay)); |
| EXPECT_TRUE(parser()->Parse(kFeedbackPacket, kMaxFeedbackFrameId1)); |
| Mock::VerifyAndClearExpectations(client()); |
| } |
| |
| // Tests NACK feedback parsing, and that redundant NACKs are de-duped, and that |
| // the results are delivered to the client sorted. |
| TEST_F(CompoundRtcpParserTest, ParsesFeedbackWithNacks) { |
| // clang-format off |
| const uint8_t kFeedbackPacket[] = { |
| 0b10000000 | 15, // Version=2, Padding=no, Subtype=Feedback. |
| 206, // RTCP Packet type byte. |
| 0x00, 0x0b, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| 0x00, 0x00, 0x00, 0x01, // Sender SSRC. |
| 'C', 'A', 'S', 'T', |
| 0x0a, // Checkpoint Frame ID = 10. |
| 0x07, // Seven NACKs. |
| 0x02, 0x28, // Playout delay = 552 ms. |
| 0x0b, 0x00, 0x03, 0b00000000, // NACK Packet 3 in Frame 11. |
| 0x0b, 0x00, 0x07, 0b10001101, // NACK Packet 7-8, 10-11, 15 in Frame 11. |
| 0x0d, 0xff, 0xff, 0b00000000, // NACK all packets in Frame 13. |
| 0x0b, 0x00, 0x0b, 0b00000000, // Redundant: NACK packet 11 in Frame 11. |
| 0x0c, 0xff, 0xff, 0b00000000, // NACK all packets in Frame 12. |
| 0x0d, 0x00, 0x01, 0b00000000, // Redundant: NACK packet 1 in Frame 13. |
| 0x0e, 0x00, 0x00, 0b01000010, // NACK packets 0, 2, 7 in Frame 14. |
| }; |
| // clang-format on |
| |
| // The de-duped and sorted list of the frame/packet NACKs expected when |
| // parsing kFeedbackPacket: |
| const std::vector<PacketNack> kMissingPackets = { |
| {FrameId::first() + 11, 3}, |
| {FrameId::first() + 11, 7}, |
| {FrameId::first() + 11, 8}, |
| {FrameId::first() + 11, 10}, |
| {FrameId::first() + 11, 11}, |
| {FrameId::first() + 11, 15}, |
| {FrameId::first() + 12, kAllPacketsLost}, |
| {FrameId::first() + 13, kAllPacketsLost}, |
| {FrameId::first() + 14, 0}, |
| {FrameId::first() + 14, 2}, |
| {FrameId::first() + 14, 7}, |
| }; |
| |
| const auto kMaxFeedbackFrameId = FrameId::first() + 42; |
| const auto expected_frame_id = FrameId::first() + 10; |
| const auto expected_playout_delay = milliseconds(552); |
| EXPECT_CALL(*(client()), |
| OnReceiverCheckpoint(expected_frame_id, expected_playout_delay)); |
| EXPECT_CALL(*(client()), OnReceiverIsMissingPackets(kMissingPackets)); |
| EXPECT_TRUE(parser()->Parse(kFeedbackPacket, kMaxFeedbackFrameId)); |
| } |
| |
| // Tests the CST2 "later frame ACK" parsing: Both the common "2 bytes of bit |
| // vector" case, and a "multiple words of bit vector" case. |
| TEST_F(CompoundRtcpParserTest, ParsesFeedbackWithAcks) { |
| // clang-format off |
| const uint8_t kSmallerFeedbackPacket[] = { |
| 0b10000000 | 15, // Version=2, Padding=no, Subtype=Feedback. |
| 206, // RTCP Packet type byte. |
| 0x00, 0x07, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| 0x00, 0x00, 0x00, 0x01, // Sender SSRC. |
| 'C', 'A', 'S', 'T', |
| 0x0a, // Checkpoint Frame ID = 10. |
| 0x01, // One NACK. |
| 0x01, 0x26, // Playout delay = 294 ms. |
| 0x0b, 0x00, 0x03, 0b00000000, // NACK Packet 3 in Frame 11. |
| 'C', 'S', 'T', '2', |
| 0x99, // Feedback counter. |
| 0x02, // 2 bytes of ACK bit vector. |
| 0b00000010, 0b00000000, // ACK only frame 13. |
| }; |
| |
| const uint8_t kLargerFeedbackPacket[] = { |
| 0b10000000 | 15, // Version=2, Padding=no, Subtype=Feedback. |
| 206, // RTCP Packet type byte. |
| 0x00, 0x08, // Length of remainder of packet, in 32-bit words. |
| 0x00, 0x00, 0x00, 0x02, // Receiver SSRC. |
| 0x00, 0x00, 0x00, 0x01, // Sender SSRC. |
| 'C', 'A', 'S', 'T', |
| 0x0a, // Checkpoint Frame ID = 10. |
| 0x00, // Zero NACKs. |
| 0x01, 0x26, // Playout delay = 294 ms. |
| 'C', 'S', 'T', '2', |
| 0x99, // Feedback counter. |
| 0x0a, // 10 bytes of ACK bit vector. |
| 0b11111111, 0b11111111, // ACK frames 12-27. |
| 0b00000000, 0b00000001, 0b00000000, 0b00000000, // ACK frame 36. |
| 0b00000000, 0b00000000, 0b00000000, 0b10000000, // ACK frame 91. |
| }; |
| // clang-format on |
| |
| // From the smaller packet: The single frame ACK and single packet NACK. |
| const std::vector<FrameId> kFrame13Only = {FrameId::first() + 13}; |
| const std::vector<PacketNack> kFrame11Packet3Only = { |
| {FrameId::first() + 11, 3}}; |
| |
| // From the larger packet: Many frame ACKs. |
| const std::vector<FrameId> kManyFrames = { |
| FrameId::first() + 12, FrameId::first() + 13, FrameId::first() + 14, |
| FrameId::first() + 15, FrameId::first() + 16, FrameId::first() + 17, |
| FrameId::first() + 18, FrameId::first() + 19, FrameId::first() + 20, |
| FrameId::first() + 21, FrameId::first() + 22, FrameId::first() + 23, |
| FrameId::first() + 24, FrameId::first() + 25, FrameId::first() + 26, |
| FrameId::first() + 27, FrameId::first() + 36, FrameId::first() + 91, |
| }; |
| |
| // Test the smaller packet. |
| const auto kMaxFeedbackFrameId = FrameId::first() + 100; |
| const auto expected_frame_id = FrameId::first() + 10; |
| const auto expected_playout_delay = milliseconds(294); |
| EXPECT_CALL(*(client()), |
| OnReceiverCheckpoint(expected_frame_id, expected_playout_delay)); |
| EXPECT_CALL(*(client()), OnReceiverHasFrames(kFrame13Only)); |
| EXPECT_CALL(*(client()), OnReceiverIsMissingPackets(kFrame11Packet3Only)); |
| EXPECT_TRUE(parser()->Parse(kSmallerFeedbackPacket, kMaxFeedbackFrameId)); |
| Mock::VerifyAndClearExpectations(client()); |
| |
| // Test the larger ACK packet. |
| EXPECT_CALL(*(client()), |
| OnReceiverCheckpoint(expected_frame_id, expected_playout_delay)); |
| EXPECT_CALL(*(client()), OnReceiverHasFrames(kManyFrames)); |
| EXPECT_TRUE(parser()->Parse(kLargerFeedbackPacket, kMaxFeedbackFrameId)); |
| Mock::VerifyAndClearExpectations(client()); |
| } |
| |
| } // namespace |
| } // namespace cast |
| } // namespace openscreen |