409 lines
17 KiB
C++
409 lines
17 KiB
C++
// 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
|