diff options
Diffstat (limited to 'third_party/libwebrtc/pc/data_channel_controller_unittest.cc')
-rw-r--r-- | third_party/libwebrtc/pc/data_channel_controller_unittest.cc | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/third_party/libwebrtc/pc/data_channel_controller_unittest.cc b/third_party/libwebrtc/pc/data_channel_controller_unittest.cc new file mode 100644 index 0000000000..3b8adb6819 --- /dev/null +++ b/third_party/libwebrtc/pc/data_channel_controller_unittest.cc @@ -0,0 +1,214 @@ +/* + * Copyright 2022 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 "pc/data_channel_controller.h" + +#include <memory> + +#include "pc/peer_connection_internal.h" +#include "pc/sctp_data_channel.h" +#include "pc/test/mock_peer_connection_internal.h" +#include "rtc_base/null_socket_server.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/run_loop.h" + +namespace webrtc { + +namespace { + +using ::testing::NiceMock; +using ::testing::Return; + +class MockDataChannelTransport : public webrtc::DataChannelTransportInterface { + public: + ~MockDataChannelTransport() override {} + + MOCK_METHOD(RTCError, OpenChannel, (int channel_id), (override)); + MOCK_METHOD(RTCError, + SendData, + (int channel_id, + const SendDataParams& params, + const rtc::CopyOnWriteBuffer& buffer), + (override)); + MOCK_METHOD(RTCError, CloseChannel, (int channel_id), (override)); + MOCK_METHOD(void, SetDataSink, (DataChannelSink * sink), (override)); + MOCK_METHOD(bool, IsReadyToSend, (), (const, override)); +}; + +// Convenience class for tests to ensure that shutdown methods for DCC +// are consistently called. In practice SdpOfferAnswerHandler will call +// TeardownDataChannelTransport_n on the network thread when destroying the +// data channel transport and PeerConnection calls PrepareForShutdown() from +// within PeerConnection::Close(). The DataChannelControllerForTest class mimics +// behavior by calling those methods from within its destructor. +class DataChannelControllerForTest : public DataChannelController { + public: + explicit DataChannelControllerForTest( + PeerConnectionInternal* pc, + DataChannelTransportInterface* transport = nullptr) + : DataChannelController(pc) { + if (transport) { + network_thread()->BlockingCall( + [&] { SetupDataChannelTransport_n(transport); }); + } + } + + ~DataChannelControllerForTest() override { + network_thread()->BlockingCall( + [&] { TeardownDataChannelTransport_n(RTCError::OK()); }); + PrepareForShutdown(); + } +}; + +class DataChannelControllerTest : public ::testing::Test { + protected: + DataChannelControllerTest() + : network_thread_(std::make_unique<rtc::NullSocketServer>()) { + network_thread_.Start(); + pc_ = rtc::make_ref_counted<NiceMock<MockPeerConnectionInternal>>(); + ON_CALL(*pc_, signaling_thread) + .WillByDefault(Return(rtc::Thread::Current())); + ON_CALL(*pc_, network_thread).WillByDefault(Return(&network_thread_)); + } + + ~DataChannelControllerTest() override { + run_loop_.Flush(); + network_thread_.Stop(); + } + + test::RunLoop run_loop_; + rtc::Thread network_thread_; + rtc::scoped_refptr<NiceMock<MockPeerConnectionInternal>> pc_; +}; + +TEST_F(DataChannelControllerTest, CreateAndDestroy) { + DataChannelControllerForTest dcc(pc_.get()); +} + +TEST_F(DataChannelControllerTest, CreateDataChannelEarlyRelease) { + DataChannelControllerForTest dcc(pc_.get()); + auto ret = dcc.InternalCreateDataChannelWithProxy( + "label", InternalDataChannelInit(DataChannelInit())); + ASSERT_TRUE(ret.ok()); + auto channel = ret.MoveValue(); + // DCC still holds a reference to the channel. Release this reference early. + channel = nullptr; +} + +TEST_F(DataChannelControllerTest, CreateDataChannelEarlyClose) { + DataChannelControllerForTest dcc(pc_.get()); + EXPECT_FALSE(dcc.HasDataChannels()); + EXPECT_FALSE(dcc.HasUsedDataChannels()); + auto ret = dcc.InternalCreateDataChannelWithProxy( + "label", InternalDataChannelInit(DataChannelInit())); + ASSERT_TRUE(ret.ok()); + auto channel = ret.MoveValue(); + EXPECT_TRUE(dcc.HasDataChannels()); + EXPECT_TRUE(dcc.HasUsedDataChannels()); + channel->Close(); + run_loop_.Flush(); + EXPECT_FALSE(dcc.HasDataChannels()); + EXPECT_TRUE(dcc.HasUsedDataChannels()); +} + +TEST_F(DataChannelControllerTest, CreateDataChannelLateRelease) { + auto dcc = std::make_unique<DataChannelControllerForTest>(pc_.get()); + auto ret = dcc->InternalCreateDataChannelWithProxy( + "label", InternalDataChannelInit(DataChannelInit())); + ASSERT_TRUE(ret.ok()); + auto channel = ret.MoveValue(); + dcc.reset(); + channel = nullptr; +} + +TEST_F(DataChannelControllerTest, CloseAfterControllerDestroyed) { + auto dcc = std::make_unique<DataChannelControllerForTest>(pc_.get()); + auto ret = dcc->InternalCreateDataChannelWithProxy( + "label", InternalDataChannelInit(DataChannelInit())); + ASSERT_TRUE(ret.ok()); + auto channel = ret.MoveValue(); + dcc.reset(); + channel->Close(); +} + +// Allocate the maximum number of data channels and then one more. +// The last allocation should fail. +TEST_F(DataChannelControllerTest, MaxChannels) { + NiceMock<MockDataChannelTransport> transport; + int channel_id = 0; + + ON_CALL(*pc_, GetSctpSslRole_n).WillByDefault([&]() { + return absl::optional<rtc::SSLRole>((channel_id & 1) ? rtc::SSL_SERVER + : rtc::SSL_CLIENT); + }); + + DataChannelControllerForTest dcc(pc_.get(), &transport); + + // Allocate the maximum number of channels + 1. Inside the loop, the creation + // process will allocate a stream id for each channel. + for (channel_id = 0; channel_id <= cricket::kMaxSctpStreams; ++channel_id) { + auto ret = dcc.InternalCreateDataChannelWithProxy( + "label", InternalDataChannelInit(DataChannelInit())); + if (channel_id == cricket::kMaxSctpStreams) { + // We've reached the maximum and the previous call should have failed. + EXPECT_FALSE(ret.ok()); + } else { + // We're still working on saturating the pool. Things should be working. + EXPECT_TRUE(ret.ok()); + } + } +} + +// Test that while a data channel is in the `kClosing` state, its StreamId does +// not get re-used for new channels. Only once the state reaches `kClosed` +// should a StreamId be available again for allocation. +TEST_F(DataChannelControllerTest, NoStreamIdReuseWhileClosing) { + ON_CALL(*pc_, GetSctpSslRole_n).WillByDefault([&]() { + return rtc::SSL_CLIENT; + }); + + NiceMock<MockDataChannelTransport> transport; // Wider scope than `dcc`. + DataChannelControllerForTest dcc(pc_.get(), &transport); + + // Create the first channel and check that we got the expected, first sid. + auto channel1 = dcc.InternalCreateDataChannelWithProxy( + "label", InternalDataChannelInit(DataChannelInit())) + .MoveValue(); + ASSERT_EQ(channel1->id(), 0); + + // Start closing the channel and make sure its state is `kClosing` + channel1->Close(); + ASSERT_EQ(channel1->state(), DataChannelInterface::DataState::kClosing); + + // Create a second channel and make sure we get a new StreamId, not the same + // as that of channel1. + auto channel2 = dcc.InternalCreateDataChannelWithProxy( + "label2", InternalDataChannelInit(DataChannelInit())) + .MoveValue(); + ASSERT_NE(channel2->id(), channel1->id()); // In practice the id will be 2. + + // Simulate the acknowledgement of the channel closing from the transport. + // This completes the closing operation of channel1. + pc_->network_thread()->BlockingCall([&] { dcc.OnChannelClosed(0); }); + run_loop_.Flush(); + ASSERT_EQ(channel1->state(), DataChannelInterface::DataState::kClosed); + + // Now create a third channel. This time, the id of the first channel should + // be available again and therefore the ids of the first and third channels + // should be the same. + auto channel3 = dcc.InternalCreateDataChannelWithProxy( + "label3", InternalDataChannelInit(DataChannelInit())) + .MoveValue(); + EXPECT_EQ(channel3->id(), channel1->id()); +} + +} // namespace +} // namespace webrtc |