// 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/rtcp_common.h" #include #include #include "absl/types/span.h" #include "gtest/gtest.h" #include "platform/api/time.h" #include "util/chrono_helpers.h" namespace openscreen { namespace cast { namespace { template void SerializeAndExpectPointerAdvanced(const T& source, int num_bytes, uint8_t* buffer) { absl::Span buffer_span(buffer, num_bytes); source.AppendFields(&buffer_span); EXPECT_EQ(buffer + num_bytes, buffer_span.data()); } // Tests that the RTCP Common Header for a packet type that includes an Item // Count is successfully serialized and re-parsed. TEST(RtcpCommonTest, SerializesAndParsesHeaderForSenderReports) { RtcpCommonHeader original; original.packet_type = RtcpPacketType::kSenderReport; original.with.report_count = 31; original.payload_size = 16; uint8_t buffer[kRtcpCommonHeaderSize]; SerializeAndExpectPointerAdvanced(original, kRtcpCommonHeaderSize, buffer); const auto parsed = RtcpCommonHeader::Parse(buffer); ASSERT_TRUE(parsed.has_value()); EXPECT_EQ(original.packet_type, parsed->packet_type); EXPECT_EQ(original.with.report_count, parsed->with.report_count); EXPECT_EQ(original.payload_size, parsed->payload_size); } // Tests that the RTCP Common Header for a packet type that includes a RTCP // Subtype is successfully serialized and re-parsed. TEST(RtcpCommonTest, SerializesAndParsesHeaderForCastFeedback) { RtcpCommonHeader original; original.packet_type = RtcpPacketType::kPayloadSpecific; original.with.subtype = RtcpSubtype::kFeedback; original.payload_size = 99 * sizeof(uint32_t); uint8_t buffer[kRtcpCommonHeaderSize]; SerializeAndExpectPointerAdvanced(original, kRtcpCommonHeaderSize, buffer); const auto parsed = RtcpCommonHeader::Parse(buffer); ASSERT_TRUE(parsed.has_value()); EXPECT_EQ(original.packet_type, parsed->packet_type); EXPECT_EQ(original.with.subtype, parsed->with.subtype); EXPECT_EQ(original.payload_size, parsed->payload_size); } // Tests that a RTCP Common Header will not be parsed from an empty buffer. TEST(RtcpCommonTest, WillNotParseHeaderFromEmptyBuffer) { const uint8_t kEmptyPacket[] = {}; EXPECT_FALSE( RtcpCommonHeader::Parse(absl::Span(kEmptyPacket, 0)) .has_value()); } // Tests that a RTCP Common Header will not be parsed from a buffer containing // garbage data. TEST(RtcpCommonTest, WillNotParseHeaderFromGarbage) { // 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(RtcpCommonHeader::Parse(kGarbage).has_value()); } // Tests whether RTCP Common Header validation logic is correct. TEST(RtcpCommonTest, WillNotParseHeaderWithInvalidData) { // clang-format off const uint8_t kCastFeedbackPacket[] = { 0b10000001, // Version=2, Padding=no, ItemCount=1 byte. 206, // RTCP Packet type byte. 0x00, 0x04, // Length of remainder of packet, in 32-bit words. 9, 8, 7, 6, // SSRC of receiver. 1, 2, 3, 4, // SSRC of sender. 'C', 'A', 'S', 'T', 0x0a, // Checkpoint Frame ID (lower 8 bits). 0x00, // Number of "Loss Fields" 0x00, 0x28, // Current Playout Delay in milliseconds. }; // clang-format on // Start with a valid packet, and expect the parse to succeed. uint8_t buffer[sizeof(kCastFeedbackPacket)]; memcpy(buffer, kCastFeedbackPacket, sizeof(buffer)); EXPECT_TRUE(RtcpCommonHeader::Parse(buffer).has_value()); // Wrong version in first byte: Expect parse failure. buffer[0] = 0b01000001; EXPECT_FALSE(RtcpCommonHeader::Parse(buffer).has_value()); buffer[0] = kCastFeedbackPacket[0]; // Wrong packet type (not in RTCP range): Expect parse failure. buffer[1] = 42; EXPECT_FALSE(RtcpCommonHeader::Parse(buffer).has_value()); buffer[1] = kCastFeedbackPacket[1]; } // Test that the Report Block optionally included in Sender Reports or Receiver // Reports can be serialized and re-parsed correctly. TEST(RtcpCommonTest, SerializesAndParsesRtcpReportBlocks) { constexpr Ssrc kSsrc{0x04050607}; RtcpReportBlock original; original.ssrc = kSsrc; original.packet_fraction_lost_numerator = 0x67; original.cumulative_packets_lost = 74536; original.extended_high_sequence_number = 0x0201fedc; original.jitter = RtpTimeDelta::FromTicks(123); original.last_status_report_id = 0x0908; original.delay_since_last_report = RtcpReportBlock::Delay(99999); uint8_t buffer[kRtcpReportBlockSize]; SerializeAndExpectPointerAdvanced(original, kRtcpReportBlockSize, buffer); // If the number of report blocks is zero, or some other SSRC is specified, // ParseOne() should not return a result. EXPECT_FALSE(RtcpReportBlock::ParseOne(buffer, 0, 0).has_value()); EXPECT_FALSE(RtcpReportBlock::ParseOne(buffer, 0, kSsrc).has_value()); EXPECT_FALSE(RtcpReportBlock::ParseOne(buffer, 1, 0).has_value()); // Expect that the report block is parsed correctly. const auto parsed = RtcpReportBlock::ParseOne(buffer, 1, kSsrc); ASSERT_TRUE(parsed.has_value()); EXPECT_EQ(original.ssrc, parsed->ssrc); EXPECT_EQ(original.packet_fraction_lost_numerator, parsed->packet_fraction_lost_numerator); EXPECT_EQ(original.cumulative_packets_lost, parsed->cumulative_packets_lost); EXPECT_EQ(original.extended_high_sequence_number, parsed->extended_high_sequence_number); EXPECT_EQ(original.jitter, parsed->jitter); EXPECT_EQ(original.last_status_report_id, parsed->last_status_report_id); EXPECT_EQ(original.delay_since_last_report, parsed->delay_since_last_report); } // Tests that the Report Block parser can, among multiple Report Blocks, find // the one with a matching recipient SSRC. TEST(RtcpCommonTest, ParsesOneReportBlockFromMultipleBlocks) { constexpr Ssrc kSsrc{0x04050607}; constexpr int kNumBlocks = 5; RtcpReportBlock expected; expected.ssrc = kSsrc; expected.packet_fraction_lost_numerator = 0x67; expected.cumulative_packets_lost = 74536; expected.extended_high_sequence_number = 0x0201fedc; expected.jitter = RtpTimeDelta::FromTicks(123); expected.last_status_report_id = 0x0908; expected.delay_since_last_report = RtcpReportBlock::Delay(99999); // Generate multiple report blocks with different recipient SSRCs. uint8_t buffer[kRtcpReportBlockSize * kNumBlocks]; absl::Span buffer_span(buffer, kRtcpReportBlockSize * kNumBlocks); for (int i = 0; i < kNumBlocks; ++i) { RtcpReportBlock another; another.ssrc = expected.ssrc + i - 2; another.packet_fraction_lost_numerator = expected.packet_fraction_lost_numerator + i - 2; another.cumulative_packets_lost = expected.cumulative_packets_lost + i - 2; another.extended_high_sequence_number = expected.extended_high_sequence_number + i - 2; another.jitter = expected.jitter + RtpTimeDelta::FromTicks(i - 2); another.last_status_report_id = expected.last_status_report_id + i - 2; another.delay_since_last_report = expected.delay_since_last_report + RtcpReportBlock::Delay(i - 2); another.AppendFields(&buffer_span); } // Expect that the desired report block is found and parsed correctly. const auto parsed = RtcpReportBlock::ParseOne(buffer, kNumBlocks, kSsrc); ASSERT_TRUE(parsed.has_value()); EXPECT_EQ(expected.ssrc, parsed->ssrc); EXPECT_EQ(expected.packet_fraction_lost_numerator, parsed->packet_fraction_lost_numerator); EXPECT_EQ(expected.cumulative_packets_lost, parsed->cumulative_packets_lost); EXPECT_EQ(expected.extended_high_sequence_number, parsed->extended_high_sequence_number); EXPECT_EQ(expected.jitter, parsed->jitter); EXPECT_EQ(expected.last_status_report_id, parsed->last_status_report_id); EXPECT_EQ(expected.delay_since_last_report, parsed->delay_since_last_report); } // Tests the helper for computing the packet fraction loss numerator, a value // that should always be between 0 and 255, in terms of absolute packet counts. TEST(RtcpCommonTest, ComputesPacketLossFractionForReportBlocks) { const auto ComputeFractionLost = [](int64_t num_apparently_sent, int64_t num_received) { RtcpReportBlock report; report.SetPacketFractionLostNumerator(num_apparently_sent, num_received); return report.packet_fraction_lost_numerator; }; // If no non-duplicate packets were sent to the Receiver, the packet loss // fraction should be zero. EXPECT_EQ(0, ComputeFractionLost(0, 0)); EXPECT_EQ(0, ComputeFractionLost(0, 1)); EXPECT_EQ(0, ComputeFractionLost(0, 999)); // If the same number or more packets were received than those apparently // sent, the packet loss fraction should be zero. EXPECT_EQ(0, ComputeFractionLost(1, 1)); EXPECT_EQ(0, ComputeFractionLost(1, 2)); EXPECT_EQ(0, ComputeFractionLost(1, 4)); EXPECT_EQ(0, ComputeFractionLost(4, 5)); EXPECT_EQ(0, ComputeFractionLost(42, 42)); EXPECT_EQ(0, ComputeFractionLost(60, 999)); // Test various partial loss scenarios. EXPECT_EQ(85, ComputeFractionLost(3, 2)); EXPECT_EQ(128, ComputeFractionLost(10, 5)); EXPECT_EQ(174, ComputeFractionLost(22, 7)); // Test various total-loss/near-total-loss scenarios. EXPECT_EQ(255, ComputeFractionLost(17, 0)); EXPECT_EQ(255, ComputeFractionLost(100, 0)); EXPECT_EQ(255, ComputeFractionLost(9876, 1)); } // Tests the helper for computing the cumulative packet loss total, a value that // should always be between 0 and 2^24 - 1, in terms of absolute packet counts. TEST(RtcpCommonTest, ComputesCumulativePacketLossForReportBlocks) { const auto ComputeLoss = [](int64_t num_apparently_sent, int64_t num_received) { RtcpReportBlock report; report.SetCumulativePacketsLost(num_apparently_sent, num_received); return report.cumulative_packets_lost; }; // Test various no-loss scenarios (including duplicate packets). EXPECT_EQ(0, ComputeLoss(0, 0)); EXPECT_EQ(0, ComputeLoss(0, 1)); EXPECT_EQ(0, ComputeLoss(3, 3)); EXPECT_EQ(0, ComputeLoss(56, 56)); EXPECT_EQ(0, ComputeLoss(std::numeric_limits::max() - 12, std::numeric_limits::max())); EXPECT_EQ(0, ComputeLoss(std::numeric_limits::max(), std::numeric_limits::max())); // Test various partial loss scenarios. EXPECT_EQ(1, ComputeLoss(2, 1)); EXPECT_EQ(2, ComputeLoss(42, 40)); EXPECT_EQ(1025, ComputeLoss(999999, 999999 - 1025)); EXPECT_EQ(1, ComputeLoss(std::numeric_limits::max(), std::numeric_limits::max() - 1)); // Test that a huge cumulative loss saturates to the maximum valid value for // the field. EXPECT_EQ((1 << 24) - 1, ComputeLoss(999999999, 1)); } // Tests the helper that converts Clock::durations to the report blocks timebase // (1/65536 sconds), and also that it saturates to to the valid range of values // (0 to 2^32 - 1 ticks). TEST(RtcpCommonTest, ComputesDelayForReportBlocks) { RtcpReportBlock report; using Delay = RtcpReportBlock::Delay; const auto ComputeDelay = [](Clock::duration delay_in_wrong_timebase) { RtcpReportBlock report; report.SetDelaySinceLastReport(delay_in_wrong_timebase); return report.delay_since_last_report; }; // A duration less than or equal to zero should clamp to zero. EXPECT_EQ(Delay::zero(), ComputeDelay(Clock::duration::min())); EXPECT_EQ(Delay::zero(), ComputeDelay(milliseconds{-1234})); EXPECT_EQ(Delay::zero(), ComputeDelay(Clock::duration::zero())); // Test conversion of various durations that should not clamp. EXPECT_EQ(Delay(32768 /* 1/2 second worth of ticks */), ComputeDelay(milliseconds(500))); EXPECT_EQ(Delay(65536 /* 1 second worth of ticks */), ComputeDelay(seconds(1))); EXPECT_EQ(Delay(655360 /* 10 seconds worth of ticks */), ComputeDelay(seconds(10))); EXPECT_EQ(Delay(4294967294), ComputeDelay(microseconds(65535999983))); EXPECT_EQ(Delay(4294967294), ComputeDelay(microseconds(65535999984))); // A too-large duration should clamp to the maximum-possible Delay value. EXPECT_EQ(Delay(4294967295), ComputeDelay(microseconds(65535999985))); EXPECT_EQ(Delay(4294967295), ComputeDelay(microseconds(65535999986))); EXPECT_EQ(Delay(4294967295), ComputeDelay(microseconds(999999000000))); EXPECT_EQ(Delay(4294967295), ComputeDelay(Clock::duration::max())); } } // namespace } // namespace cast } // namespace openscreen