| // 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 <chrono> |
| #include <limits> |
| |
| #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 <typename T> |
| void SerializeAndExpectPointerAdvanced(const T& source, |
| int num_bytes, |
| uint8_t* buffer) { |
| absl::Span<uint8_t> 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<const uint8_t>(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<uint8_t> 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<int64_t>::max() - 12, |
| std::numeric_limits<int64_t>::max())); |
| EXPECT_EQ(0, ComputeLoss(std::numeric_limits<int64_t>::max(), |
| std::numeric_limits<int64_t>::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<int64_t>::max(), |
| std::numeric_limits<int64_t>::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 |