// 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 #include "absl/types/span.h" #include "cast/streaming/sender_report_builder.h" #include "cast/streaming/sender_report_parser.h" #include "gtest/gtest.h" namespace openscreen { namespace cast { namespace { using openscreen::operator<<; constexpr Ssrc kSenderSsrc{1}; constexpr Ssrc kReceiverSsrc{2}; class SenderReportTest : public testing::Test { public: SenderReportBuilder* builder() { return &builder_; } SenderReportParser* parser() { return &parser_; } const NtpTimeConverter& ntp_converter() const { return session_.ntp_converter(); } private: RtcpSession session_{kSenderSsrc, kReceiverSsrc, Clock::now()}; SenderReportBuilder builder_{&session_}; SenderReportParser parser_{&session_}; }; // Tests that the compound RTCP packets containing a Sender Report alongside // zero or more other messages can be parsed successfully. TEST_F(SenderReportTest, Parsing) { // clang-format off const uint8_t kSenderReportPacket[] = { 0b10000001, // Version=2, Padding=no, ItemCount=1 byte. 200, // RTCP Packet type byte. 0x00, 0x0c, // Length of remainder of packet, in 32-bit words. 0x00, 0x00, 0x00, 0x01, // SSRC of sender. 0xe0, 0x73, 0x2e, 0x54, // NTP Timestamp (late evening on 2019-04-30). 0x80, 0x00, 0x00, 0x00, 0x00, 0x14, 0x99, 0x70, // RTP Timestamp (15 seconds, 90kHz timebase). 0x00, 0x00, 0x01, 0xff, // Sender's Packet Count. 0x00, 0x07, 0x11, 0x0d, // Sender's Octet Count. 0x00, 0x00, 0x00, 0x02, // SSRC of receiver (to whom this report is for). 0x00, // Fraction lost. 0x00, 0x00, 0x02, // Cumulative Number of Packets Lost. 0x00, 0x00, 0x38, 0x40, // Highest Sequence Number Received. 0x00, 0x00, 0x03, 0x84, // Interarrival Jitter. 0xaf, 0xd3, 0xff, 0x00, // Sender Report ID. 0x00, 0x00, 0x83, 0xfa, // Delay since last Sender Report. }; constexpr NtpTimestamp kNtpTimestampInSenderReport{0xe0732e5480000000}; const uint8_t kOtherPacket[] = { 0b10000000, // Version=2, Padding=no, ItemCount=0 byte. 204, // RTCP Packet type byte. 0x00, 0x01, // Length of remainder of packet, in 32-bit words. 0x00, 0x00, 0x00, 0x02, // SSRC of receiver. }; // clang-format on // A RTCP packet only containing non-sender-reports will not provide a Sender // Report result. EXPECT_FALSE(parser()->Parse(kOtherPacket)); // A compound RTCP packet containing a Sender Report alongside other things // should be detected as "well-formed" by the parser and it should also // provide a Sender Report result. Also, it shouldn't matter what the ordering // is. const absl::Span kCompoundCombinations[2][2] = { {kSenderReportPacket, kOtherPacket}, {kOtherPacket, kSenderReportPacket}, }; for (const auto& combo : kCompoundCombinations) { uint8_t compound_packet[sizeof(kSenderReportPacket) + sizeof(kOtherPacket)]; memcpy(compound_packet, combo[0].data(), combo[0].size()); memcpy(compound_packet + combo[0].size(), combo[1].data(), combo[1].size()); const auto parsed = parser()->Parse(compound_packet); ASSERT_TRUE(parsed.has_value()); EXPECT_EQ(ToStatusReportId(kNtpTimestampInSenderReport), parsed->report_id); EXPECT_EQ(ntp_converter().ToLocalTime(kNtpTimestampInSenderReport), parsed->reference_time); EXPECT_EQ(RtpTimeTicks() + RtpTimeDelta::FromTicks(1350000), parsed->rtp_timestamp); EXPECT_EQ(uint32_t{0x1ff}, parsed->send_packet_count); EXPECT_EQ(uint32_t{0x7110d}, parsed->send_octet_count); ASSERT_TRUE(parsed->report_block.has_value()); EXPECT_EQ(kReceiverSsrc, parsed->report_block->ssrc); // Note: RtcpReportBlock parsing is unit-tested elsewhere. } } // Tests that the SenderReportParser will not try to parse an empty packet. TEST_F(SenderReportTest, WillNotParseEmptyPacket) { const uint8_t kEmptyPacket[] = {}; EXPECT_FALSE(parser()->Parse(absl::Span(kEmptyPacket, 0))); } // Tests that the SenderReportParser will not parse anything from garbage data. TEST_F(SenderReportTest, WillNotParseGarbage) { // clang-format off const uint8_t kGarbage[] = { 0x4f, 0x27, 0xeb, 0x22, 0x27, 0xeb, 0x22, 0x4f, 0xeb, 0x22, 0x4f, 0x27, 0x22, 0x4f, 0x27, 0xeb, }; // clang-format on EXPECT_FALSE(parser()->Parse(kGarbage)); } // Assuming that SenderReportTest.Parsing has been proven the implementation, // this test checks that the builder produces RTCP packets that can be parsed. TEST_F(SenderReportTest, BuildPackets) { for (int i = 0; i <= 1; ++i) { const bool with_report_block = (i == 1); RtcpSenderReport original; original.reference_time = Clock::now(); original.rtp_timestamp = RtpTimeTicks() + RtpTimeDelta::FromTicks(5); original.send_packet_count = 55; original.send_octet_count = 20044; if (with_report_block) { RtcpReportBlock& report_block = original.report_block.emplace(); report_block.ssrc = kReceiverSsrc; } uint8_t buffer[kRtcpCommonHeaderSize + kRtcpSenderReportSize + kRtcpReportBlockSize]; memset(buffer, 0, sizeof(buffer)); const auto result = builder()->BuildPacket(original, buffer); ASSERT_TRUE(result.first.data()); const int expected_packet_size = sizeof(buffer) - (with_report_block ? 0 : kRtcpReportBlockSize); EXPECT_EQ(expected_packet_size, static_cast(result.first.size())); const StatusReportId expected_status_report_id = ToStatusReportId( ntp_converter().ToNtpTimestamp(original.reference_time)); EXPECT_EQ(expected_status_report_id, result.second); const auto parsed = parser()->Parse(result.first); ASSERT_TRUE(parsed.has_value()); EXPECT_EQ(expected_status_report_id, parsed->report_id); // Note: The reference time can be off by one platform clock tick due to // a lossy conversion when going to and from the wire-format NtpTimestamps. // See the unit tests in ntp_time_unittest.cc for further discussion. EXPECT_LE( std::abs((original.reference_time - parsed->reference_time).count()), 1); EXPECT_EQ(original.rtp_timestamp, parsed->rtp_timestamp); EXPECT_EQ(original.send_packet_count, parsed->send_packet_count); EXPECT_EQ(original.send_octet_count, parsed->send_octet_count); if (with_report_block) { ASSERT_TRUE(parsed->report_block.has_value()); EXPECT_EQ(original.report_block->ssrc, parsed->report_block->ssrc); // Note: RtcpReportBlock serialization/parsing is unit-tested elsewhere. } } } TEST_F(SenderReportTest, ComputesTimePointsFromReportIds) { // Note: The time_points can be off by up to 16 µs because of the loss of // precision caused by truncating the NtpTimestamps into StatusReportIds. constexpr std::chrono::microseconds kEpsilon{16}; // Test a sampling of time points over the last 65536 seconds to confirm the // rollover correction logic is working. Clock::time_point on_or_before = Clock::now() + std::chrono::seconds(65536); constexpr int kNumIterations = 16; constexpr int kSecondsPerStep = 4096; for (int i = 0; i < kNumIterations; ++i) { const Clock::time_point expected_time = on_or_before - std::chrono::seconds(i * kSecondsPerStep); const auto report_id = ToStatusReportId(ntp_converter().ToNtpTimestamp(expected_time)); const Clock::time_point report_time = builder()->GetRecentReportTime(report_id, on_or_before); EXPECT_GE(on_or_before, report_time); const auto absolute_difference = (expected_time < report_time) ? (report_time - expected_time) : (expected_time - report_time); EXPECT_LE(absolute_difference, kEpsilon) << expected_time << " vs " << report_time; } } } // namespace } // namespace cast } // namespace openscreen