| /* |
| * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "webrtc/call/rtcp_demuxer.h" |
| |
| #include <memory> |
| #include <set> |
| |
| #include "webrtc/call/rtcp_packet_sink_interface.h" |
| #include "webrtc/common_types.h" |
| #include "webrtc/modules/rtp_rtcp/source/rtcp_packet/bye.h" |
| #include "webrtc/rtc_base/arraysize.h" |
| #include "webrtc/rtc_base/basictypes.h" |
| #include "webrtc/rtc_base/checks.h" |
| #include "webrtc/rtc_base/ptr_util.h" |
| #include "webrtc/test/gmock.h" |
| #include "webrtc/test/gtest.h" |
| |
| namespace webrtc { |
| |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::AtLeast; |
| using ::testing::ElementsAreArray; |
| using ::testing::InSequence; |
| using ::testing::Matcher; |
| using ::testing::NiceMock; |
| |
| class MockRtcpPacketSink : public RtcpPacketSinkInterface { |
| public: |
| MOCK_METHOD1(OnRtcpPacket, void(rtc::ArrayView<const uint8_t>)); |
| }; |
| |
| class RtcpDemuxerTest : public testing::Test { |
| protected: |
| ~RtcpDemuxerTest() { |
| for (auto* sink : sinks_to_tear_down_) { |
| demuxer_.RemoveSink(sink); |
| } |
| for (auto* sink : broadcast_sinks_to_tear_down_) { |
| demuxer_.RemoveBroadcastSink(sink); |
| } |
| } |
| |
| void AddSsrcSink(uint32_t ssrc, RtcpPacketSinkInterface* sink) { |
| demuxer_.AddSink(ssrc, sink); |
| sinks_to_tear_down_.insert(sink); |
| } |
| |
| void AddRsidSink(const std::string& rsid, RtcpPacketSinkInterface* sink) { |
| demuxer_.AddSink(rsid, sink); |
| sinks_to_tear_down_.insert(sink); |
| } |
| |
| void RemoveSink(RtcpPacketSinkInterface* sink) { |
| sinks_to_tear_down_.erase(sink); |
| demuxer_.RemoveSink(sink); |
| } |
| |
| void AddBroadcastSink(RtcpPacketSinkInterface* sink) { |
| demuxer_.AddBroadcastSink(sink); |
| broadcast_sinks_to_tear_down_.insert(sink); |
| } |
| |
| void RemoveBroadcastSink(RtcpPacketSinkInterface* sink) { |
| broadcast_sinks_to_tear_down_.erase(sink); |
| demuxer_.RemoveBroadcastSink(sink); |
| } |
| |
| RtcpDemuxer demuxer_; |
| std::set<RtcpPacketSinkInterface*> sinks_to_tear_down_; |
| std::set<RtcpPacketSinkInterface*> broadcast_sinks_to_tear_down_; |
| }; |
| |
| // Produces a packet buffer representing an RTCP packet with a given SSRC, |
| // as it would look when sent over the wire. |
| // |distinguishing_string| allows different RTCP packets with the same SSRC |
| // to be distinguished. How this is set into the actual packet is |
| // unimportant, and depends on which RTCP message we choose to use. |
| rtc::Buffer CreateRtcpPacket(uint32_t ssrc, |
| const std::string& distinguishing_string = "") { |
| rtcp::Bye packet; |
| packet.SetSenderSsrc(ssrc); |
| if (distinguishing_string != "") { |
| // Actual way we use |distinguishing_string| is unimportant, so long |
| // as it ends up in the packet. |
| packet.SetReason(distinguishing_string); |
| } |
| return packet.Build(); |
| } |
| |
| static Matcher<rtc::ArrayView<const uint8_t>> SamePacketAs( |
| const rtc::Buffer& other) { |
| return ElementsAreArray(other.cbegin(), other.cend()); |
| } |
| |
| } // namespace |
| |
| TEST_F(RtcpDemuxerTest, OnRtcpPacketCalledOnCorrectSinkBySsrc) { |
| constexpr uint32_t ssrcs[] = {101, 202, 303}; |
| MockRtcpPacketSink sinks[arraysize(ssrcs)]; |
| for (size_t i = 0; i < arraysize(ssrcs); i++) { |
| AddSsrcSink(ssrcs[i], &sinks[i]); |
| } |
| |
| for (size_t i = 0; i < arraysize(ssrcs); i++) { |
| auto packet = CreateRtcpPacket(ssrcs[i]); |
| EXPECT_CALL(sinks[i], OnRtcpPacket(SamePacketAs(packet))).Times(1); |
| demuxer_.OnRtcpPacket(packet); |
| } |
| } |
| |
| TEST_F(RtcpDemuxerTest, OnRtcpPacketCalledOnResolvedRsidSink) { |
| // Set up some RSID sinks. |
| const std::string rsids[] = {"a", "b", "c"}; |
| MockRtcpPacketSink sinks[arraysize(rsids)]; |
| for (size_t i = 0; i < arraysize(rsids); i++) { |
| AddRsidSink(rsids[i], &sinks[i]); |
| } |
| |
| // Only resolve one of the sinks. |
| constexpr size_t resolved_sink_index = 0; |
| constexpr uint32_t ssrc = 345; |
| demuxer_.OnSsrcBoundToRsid(rsids[resolved_sink_index], ssrc); |
| |
| // The resolved sink gets notifications of RTCP messages with its SSRC. |
| auto packet = CreateRtcpPacket(ssrc); |
| EXPECT_CALL(sinks[resolved_sink_index], OnRtcpPacket(SamePacketAs(packet))) |
| .Times(1); |
| |
| // RTCP received; expected calls triggered. |
| demuxer_.OnRtcpPacket(packet); |
| } |
| |
| TEST_F(RtcpDemuxerTest, |
| SingleCallbackAfterResolutionOfAnRsidToAlreadyRegisteredSsrc) { |
| // Associate a sink with an SSRC. |
| MockRtcpPacketSink sink; |
| constexpr uint32_t ssrc = 999; |
| AddSsrcSink(ssrc, &sink); |
| |
| // Associate the same sink with an RSID. |
| const std::string rsid = "r"; |
| AddRsidSink(rsid, &sink); |
| |
| // Resolve the RSID to the aforementioned SSRC. |
| demuxer_.OnSsrcBoundToRsid(rsid, ssrc); |
| |
| // OnRtcpPacket still called only a single time for messages with this SSRC. |
| auto packet = CreateRtcpPacket(ssrc); |
| EXPECT_CALL(sink, OnRtcpPacket(SamePacketAs(packet))).Times(1); |
| demuxer_.OnRtcpPacket(packet); |
| } |
| |
| TEST_F(RtcpDemuxerTest, |
| OnRtcpPacketCalledOnAllBroadcastSinksForAllRtcpPackets) { |
| MockRtcpPacketSink sinks[3]; |
| for (MockRtcpPacketSink& sink : sinks) { |
| AddBroadcastSink(&sink); |
| } |
| |
| constexpr uint32_t ssrc = 747; |
| auto packet = CreateRtcpPacket(ssrc); |
| |
| for (MockRtcpPacketSink& sink : sinks) { |
| EXPECT_CALL(sink, OnRtcpPacket(SamePacketAs(packet))).Times(1); |
| } |
| |
| // RTCP received; expected calls triggered. |
| demuxer_.OnRtcpPacket(packet); |
| } |
| |
| TEST_F(RtcpDemuxerTest, PacketsDeliveredInRightOrderToNonBroadcastSink) { |
| constexpr uint32_t ssrc = 101; |
| MockRtcpPacketSink sink; |
| AddSsrcSink(ssrc, &sink); |
| |
| std::vector<rtc::Buffer> packets; |
| for (size_t i = 0; i < 5; i++) { |
| packets.push_back(CreateRtcpPacket(ssrc, std::to_string(i))); |
| } |
| |
| InSequence sequence; |
| for (const auto& packet : packets) { |
| EXPECT_CALL(sink, OnRtcpPacket(SamePacketAs(packet))).Times(1); |
| } |
| |
| for (const auto& packet : packets) { |
| demuxer_.OnRtcpPacket(packet); |
| } |
| } |
| |
| TEST_F(RtcpDemuxerTest, PacketsDeliveredInRightOrderToBroadcastSink) { |
| MockRtcpPacketSink sink; |
| AddBroadcastSink(&sink); |
| |
| std::vector<rtc::Buffer> packets; |
| for (size_t i = 0; i < 5; i++) { |
| constexpr uint32_t ssrc = 101; |
| packets.push_back(CreateRtcpPacket(ssrc, std::to_string(i))); |
| } |
| |
| InSequence sequence; |
| for (const auto& packet : packets) { |
| EXPECT_CALL(sink, OnRtcpPacket(SamePacketAs(packet))).Times(1); |
| } |
| |
| for (const auto& packet : packets) { |
| demuxer_.OnRtcpPacket(packet); |
| } |
| } |
| |
| TEST_F(RtcpDemuxerTest, MultipleSinksMappedToSameSsrc) { |
| MockRtcpPacketSink sinks[3]; |
| constexpr uint32_t ssrc = 404; |
| for (auto& sink : sinks) { |
| AddSsrcSink(ssrc, &sink); |
| } |
| |
| // Reception of an RTCP packet associated with the shared SSRC triggers the |
| // callback on all of the sinks associated with it. |
| auto packet = CreateRtcpPacket(ssrc); |
| for (auto& sink : sinks) { |
| EXPECT_CALL(sink, OnRtcpPacket(SamePacketAs(packet))); |
| } |
| |
| demuxer_.OnRtcpPacket(packet); |
| } |
| |
| TEST_F(RtcpDemuxerTest, SinkMappedToMultipleSsrcs) { |
| constexpr uint32_t ssrcs[] = {404, 505, 606}; |
| MockRtcpPacketSink sink; |
| for (uint32_t ssrc : ssrcs) { |
| AddSsrcSink(ssrc, &sink); |
| } |
| |
| // The sink which is associated with multiple SSRCs gets the callback |
| // triggered for each of those SSRCs. |
| for (uint32_t ssrc : ssrcs) { |
| auto packet = CreateRtcpPacket(ssrc); |
| EXPECT_CALL(sink, OnRtcpPacket(SamePacketAs(packet))); |
| demuxer_.OnRtcpPacket(packet); |
| } |
| } |
| |
| TEST_F(RtcpDemuxerTest, MultipleRsidsOnSameSink) { |
| // Sink associated with multiple sinks. |
| MockRtcpPacketSink sink; |
| const std::string rsids[] = {"a", "b", "c"}; |
| for (const auto& rsid : rsids) { |
| AddRsidSink(rsid, &sink); |
| } |
| |
| // RSIDs resolved to SSRCs. |
| uint32_t ssrcs[arraysize(rsids)]; |
| for (size_t i = 0; i < arraysize(rsids); i++) { |
| ssrcs[i] = 1000 + static_cast<uint32_t>(i); |
| demuxer_.OnSsrcBoundToRsid(rsids[i], ssrcs[i]); |
| } |
| |
| // Set up packets to match those RSIDs/SSRCs. |
| std::vector<rtc::Buffer> packets; |
| for (size_t i = 0; i < arraysize(rsids); i++) { |
| packets.push_back(CreateRtcpPacket(ssrcs[i])); |
| } |
| |
| // The sink expects to receive all of the packets. |
| for (const auto& packet : packets) { |
| EXPECT_CALL(sink, OnRtcpPacket(SamePacketAs(packet))).Times(1); |
| } |
| |
| // Packet demuxed correctly; OnRtcpPacket() triggered on sink. |
| for (const auto& packet : packets) { |
| demuxer_.OnRtcpPacket(packet); |
| } |
| } |
| |
| TEST_F(RtcpDemuxerTest, RsidUsedByMultipleSinks) { |
| MockRtcpPacketSink sinks[3]; |
| const std::string shared_rsid = "a"; |
| |
| for (MockRtcpPacketSink& sink : sinks) { |
| AddRsidSink(shared_rsid, &sink); |
| } |
| |
| constexpr uint32_t shared_ssrc = 888; |
| demuxer_.OnSsrcBoundToRsid(shared_rsid, shared_ssrc); |
| |
| auto packet = CreateRtcpPacket(shared_ssrc); |
| |
| for (MockRtcpPacketSink& sink : sinks) { |
| EXPECT_CALL(sink, OnRtcpPacket(SamePacketAs(packet))).Times(1); |
| } |
| |
| demuxer_.OnRtcpPacket(packet); |
| } |
| |
| TEST_F(RtcpDemuxerTest, NoCallbackOnSsrcSinkRemovedBeforeFirstPacket) { |
| constexpr uint32_t ssrc = 404; |
| MockRtcpPacketSink sink; |
| AddSsrcSink(ssrc, &sink); |
| |
| RemoveSink(&sink); |
| |
| // The removed sink does not get callbacks. |
| auto packet = CreateRtcpPacket(ssrc); |
| EXPECT_CALL(sink, OnRtcpPacket(_)).Times(0); // Not called. |
| demuxer_.OnRtcpPacket(packet); |
| } |
| |
| TEST_F(RtcpDemuxerTest, NoCallbackOnSsrcSinkRemovedAfterFirstPacket) { |
| constexpr uint32_t ssrc = 404; |
| NiceMock<MockRtcpPacketSink> sink; |
| AddSsrcSink(ssrc, &sink); |
| |
| auto before_packet = CreateRtcpPacket(ssrc); |
| demuxer_.OnRtcpPacket(before_packet); |
| |
| RemoveSink(&sink); |
| |
| // The removed sink does not get callbacks. |
| auto after_packet = CreateRtcpPacket(ssrc); |
| EXPECT_CALL(sink, OnRtcpPacket(_)).Times(0); // Not called. |
| demuxer_.OnRtcpPacket(after_packet); |
| } |
| |
| TEST_F(RtcpDemuxerTest, NoCallbackOnRsidSinkRemovedBeforeRsidResolution) { |
| const std::string rsid = "a"; |
| constexpr uint32_t ssrc = 404; |
| MockRtcpPacketSink sink; |
| AddRsidSink(rsid, &sink); |
| |
| // Removal before resolution. |
| RemoveSink(&sink); |
| demuxer_.OnSsrcBoundToRsid(rsid, ssrc); |
| |
| // The removed sink does not get callbacks. |
| auto packet = CreateRtcpPacket(ssrc); |
| EXPECT_CALL(sink, OnRtcpPacket(_)).Times(0); // Not called. |
| demuxer_.OnRtcpPacket(packet); |
| } |
| |
| TEST_F(RtcpDemuxerTest, NoCallbackOnRsidSinkRemovedAfterRsidResolution) { |
| const std::string rsid = "a"; |
| constexpr uint32_t ssrc = 404; |
| MockRtcpPacketSink sink; |
| AddRsidSink(rsid, &sink); |
| |
| // Removal after resolution. |
| demuxer_.OnSsrcBoundToRsid(rsid, ssrc); |
| RemoveSink(&sink); |
| |
| // The removed sink does not get callbacks. |
| auto packet = CreateRtcpPacket(ssrc); |
| EXPECT_CALL(sink, OnRtcpPacket(_)).Times(0); // Not called. |
| demuxer_.OnRtcpPacket(packet); |
| } |
| |
| TEST_F(RtcpDemuxerTest, NoCallbackOnBroadcastSinkRemovedBeforeFirstPacket) { |
| MockRtcpPacketSink sink; |
| AddBroadcastSink(&sink); |
| |
| RemoveBroadcastSink(&sink); |
| |
| // The removed sink does not get callbacks. |
| constexpr uint32_t ssrc = 404; |
| auto packet = CreateRtcpPacket(ssrc); |
| EXPECT_CALL(sink, OnRtcpPacket(_)).Times(0); // Not called. |
| demuxer_.OnRtcpPacket(packet); |
| } |
| |
| TEST_F(RtcpDemuxerTest, NoCallbackOnBroadcastSinkRemovedAfterFirstPacket) { |
| NiceMock<MockRtcpPacketSink> sink; |
| AddBroadcastSink(&sink); |
| |
| constexpr uint32_t ssrc = 404; |
| auto before_packet = CreateRtcpPacket(ssrc); |
| demuxer_.OnRtcpPacket(before_packet); |
| |
| RemoveBroadcastSink(&sink); |
| |
| // The removed sink does not get callbacks. |
| auto after_packet = CreateRtcpPacket(ssrc); |
| EXPECT_CALL(sink, OnRtcpPacket(_)).Times(0); // Not called. |
| demuxer_.OnRtcpPacket(after_packet); |
| } |
| |
| // The RSID to SSRC mapping should be one-to-one. If we end up receiving |
| // two (or more) packets with the same SSRC, but different RSIDs, we guarantee |
| // remembering the first one; no guarantees are made about further associations. |
| TEST_F(RtcpDemuxerTest, FirstResolutionOfRsidNotForgotten) { |
| MockRtcpPacketSink sink; |
| const std::string rsid = "a"; |
| AddRsidSink(rsid, &sink); |
| |
| constexpr uint32_t ssrc_a = 111; // First resolution - guaranteed effective. |
| demuxer_.OnSsrcBoundToRsid(rsid, ssrc_a); |
| |
| constexpr uint32_t ssrc_b = 222; // Second resolution - no guarantees. |
| demuxer_.OnSsrcBoundToRsid(rsid, ssrc_b); |
| |
| auto packet_a = CreateRtcpPacket(ssrc_a); |
| EXPECT_CALL(sink, OnRtcpPacket(SamePacketAs(packet_a))).Times(1); |
| demuxer_.OnRtcpPacket(packet_a); |
| |
| auto packet_b = CreateRtcpPacket(ssrc_b); |
| EXPECT_CALL(sink, OnRtcpPacket(SamePacketAs(packet_b))).Times(AtLeast(0)); |
| demuxer_.OnRtcpPacket(packet_b); |
| } |
| |
| #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) |
| |
| TEST_F(RtcpDemuxerTest, RepeatedSsrcToSinkAssociationsDisallowed) { |
| MockRtcpPacketSink sink; |
| |
| constexpr uint32_t ssrc = 101; |
| AddSsrcSink(ssrc, &sink); |
| EXPECT_DEATH(AddSsrcSink(ssrc, &sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, RepeatedRsidToSinkAssociationsDisallowed) { |
| MockRtcpPacketSink sink; |
| |
| const std::string rsid = "z"; |
| AddRsidSink(rsid, &sink); |
| EXPECT_DEATH(AddRsidSink(rsid, &sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, RepeatedBroadcastSinkRegistrationDisallowed) { |
| MockRtcpPacketSink sink; |
| |
| AddBroadcastSink(&sink); |
| EXPECT_DEATH(AddBroadcastSink(&sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, SsrcSinkCannotAlsoBeRegisteredAsBroadcast) { |
| MockRtcpPacketSink sink; |
| |
| constexpr uint32_t ssrc = 101; |
| AddSsrcSink(ssrc, &sink); |
| EXPECT_DEATH(AddBroadcastSink(&sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, RsidSinkCannotAlsoBeRegisteredAsBroadcast) { |
| MockRtcpPacketSink sink; |
| |
| const std::string rsid = "z"; |
| AddRsidSink(rsid, &sink); |
| EXPECT_DEATH(AddBroadcastSink(&sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, BroadcastSinkCannotAlsoBeRegisteredAsSsrcSink) { |
| MockRtcpPacketSink sink; |
| |
| AddBroadcastSink(&sink); |
| constexpr uint32_t ssrc = 101; |
| EXPECT_DEATH(AddSsrcSink(ssrc, &sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, BroadcastSinkCannotAlsoBeRegisteredAsRsidSink) { |
| MockRtcpPacketSink sink; |
| |
| AddBroadcastSink(&sink); |
| const std::string rsid = "j"; |
| EXPECT_DEATH(AddRsidSink(rsid, &sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, MayNotCallRemoveSinkOnNeverAddedSink) { |
| MockRtcpPacketSink sink; |
| EXPECT_DEATH(RemoveSink(&sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, MayNotCallRemoveBroadcastSinkOnNeverAddedSink) { |
| MockRtcpPacketSink sink; |
| EXPECT_DEATH(RemoveBroadcastSink(&sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, RsidMustBeNonEmpty) { |
| MockRtcpPacketSink sink; |
| EXPECT_DEATH(AddRsidSink("", &sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, RsidMustBeAlphaNumeric) { |
| MockRtcpPacketSink sink; |
| EXPECT_DEATH(AddRsidSink("a_3", &sink), ""); |
| } |
| |
| TEST_F(RtcpDemuxerTest, RsidMustNotExceedMaximumLength) { |
| MockRtcpPacketSink sink; |
| std::string rsid(StreamId::kMaxSize + 1, 'a'); |
| EXPECT_DEATH(AddRsidSink(rsid, &sink), ""); |
| } |
| |
| #endif |
| |
| } // namespace webrtc |