You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
307 lines
12 KiB
307 lines
12 KiB
// 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
|