Source code
Revision control
Copy as Markdown
Other Tools
/*
* 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 "media/sctp/dcsctp_transport.h"
#include <memory>
#include <utility>
#include "api/environment/environment.h"
#include "api/environment/environment_factory.h"
#include "api/priority.h"
#include "net/dcsctp/public/mock_dcsctp_socket.h"
#include "net/dcsctp/public/mock_dcsctp_socket_factory.h"
#include "net/dcsctp/public/types.h"
#include "p2p/base/fake_packet_transport.h"
#include "test/gtest.h"
using ::testing::_;
using ::testing::ByMove;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::ReturnPointee;
namespace webrtc {
namespace {
const PriorityValue kDefaultPriority = PriorityValue(Priority::kLow);
class MockDataChannelSink : public DataChannelSink {
public:
MOCK_METHOD(void, OnConnected, ());
// DataChannelSink
MOCK_METHOD(void,
OnDataReceived,
(int, DataMessageType, const rtc::CopyOnWriteBuffer&));
MOCK_METHOD(void, OnChannelClosing, (int));
MOCK_METHOD(void, OnChannelClosed, (int));
MOCK_METHOD(void, OnReadyToSend, ());
MOCK_METHOD(void, OnTransportClosed, (RTCError));
MOCK_METHOD(void, OnBufferedAmountLow, (int channel_id), (override));
};
static_assert(!std::is_abstract_v<MockDataChannelSink>);
class Peer {
public:
Peer()
: fake_packet_transport_("transport"),
simulated_clock_(1000),
env_(CreateEnvironment(&simulated_clock_)) {
auto socket_ptr = std::make_unique<dcsctp::MockDcSctpSocket>();
socket_ = socket_ptr.get();
auto mock_dcsctp_socket_factory =
std::make_unique<dcsctp::MockDcSctpSocketFactory>();
EXPECT_CALL(*mock_dcsctp_socket_factory, Create)
.Times(1)
.WillOnce(Return(ByMove(std::move(socket_ptr))));
sctp_transport_ = std::make_unique<webrtc::DcSctpTransport>(
env_, rtc::Thread::Current(), &fake_packet_transport_,
std::move(mock_dcsctp_socket_factory));
sctp_transport_->SetDataChannelSink(&sink_);
sctp_transport_->SetOnConnectedCallback([this]() { sink_.OnConnected(); });
}
rtc::FakePacketTransport fake_packet_transport_;
webrtc::SimulatedClock simulated_clock_;
Environment env_;
dcsctp::MockDcSctpSocket* socket_;
std::unique_ptr<webrtc::DcSctpTransport> sctp_transport_;
NiceMock<MockDataChannelSink> sink_;
};
} // namespace
TEST(DcSctpTransportTest, OpenSequence) {
rtc::AutoThread main_thread;
Peer peer_a;
peer_a.fake_packet_transport_.SetWritable(true);
EXPECT_CALL(*peer_a.socket_, Connect)
.Times(1)
.WillOnce(Invoke(peer_a.sctp_transport_.get(),
&dcsctp::DcSctpSocketCallbacks::OnConnected));
EXPECT_CALL(peer_a.sink_, OnReadyToSend);
EXPECT_CALL(peer_a.sink_, OnConnected);
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
}
// Tests that the close sequence invoked from one end results in the stream to
// be reset from both ends and all the proper signals are sent.
TEST(DcSctpTransportTest, CloseSequence) {
rtc::AutoThread main_thread;
Peer peer_a;
Peer peer_b;
peer_a.fake_packet_transport_.SetDestination(&peer_b.fake_packet_transport_,
false);
{
InSequence sequence;
EXPECT_CALL(
*peer_a.socket_,
SetStreamPriority(dcsctp::StreamID(1),
dcsctp::StreamPriority(kDefaultPriority.value())));
EXPECT_CALL(*peer_a.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1))))
.WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed));
EXPECT_CALL(*peer_b.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1))))
.WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed));
EXPECT_CALL(peer_a.sink_, OnChannelClosing(1)).Times(0);
EXPECT_CALL(peer_b.sink_, OnChannelClosing(1));
EXPECT_CALL(peer_a.sink_, OnChannelClosed(1));
EXPECT_CALL(peer_b.sink_, OnChannelClosed(1));
}
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
peer_b.sctp_transport_->Start(5000, 5000, 256 * 1024);
peer_a.sctp_transport_->OpenStream(1, kDefaultPriority);
peer_b.sctp_transport_->OpenStream(1, kDefaultPriority);
peer_a.sctp_transport_->ResetStream(1);
// Simulate the callbacks from the stream resets
dcsctp::StreamID streams[1] = {dcsctp::StreamID(1)};
static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
->OnStreamsResetPerformed(streams);
static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get())
->OnIncomingStreamsReset(streams);
static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
->OnIncomingStreamsReset(streams);
static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get())
->OnStreamsResetPerformed(streams);
}
// Tests that the close sequence initiated from both peers at the same time
// terminates properly. Both peers will think they initiated it, so no
// OnClosingProcedureStartedRemotely should be called.
TEST(DcSctpTransportTest, CloseSequenceSimultaneous) {
rtc::AutoThread main_thread;
Peer peer_a;
Peer peer_b;
peer_a.fake_packet_transport_.SetDestination(&peer_b.fake_packet_transport_,
false);
{
InSequence sequence;
EXPECT_CALL(*peer_a.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1))))
.WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed));
EXPECT_CALL(*peer_b.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1))))
.WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed));
EXPECT_CALL(peer_a.sink_, OnChannelClosing(1)).Times(0);
EXPECT_CALL(peer_b.sink_, OnChannelClosing(1)).Times(0);
EXPECT_CALL(peer_a.sink_, OnChannelClosed(1));
EXPECT_CALL(peer_b.sink_, OnChannelClosed(1));
}
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
peer_b.sctp_transport_->Start(5000, 5000, 256 * 1024);
peer_a.sctp_transport_->OpenStream(1, kDefaultPriority);
peer_b.sctp_transport_->OpenStream(1, kDefaultPriority);
peer_a.sctp_transport_->ResetStream(1);
peer_b.sctp_transport_->ResetStream(1);
// Simulate the callbacks from the stream resets
dcsctp::StreamID streams[1] = {dcsctp::StreamID(1)};
static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
->OnStreamsResetPerformed(streams);
static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get())
->OnStreamsResetPerformed(streams);
static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
->OnIncomingStreamsReset(streams);
static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get())
->OnIncomingStreamsReset(streams);
}
TEST(DcSctpTransportTest, SetStreamPriority) {
rtc::AutoThread main_thread;
Peer peer_a;
{
InSequence sequence;
EXPECT_CALL(
*peer_a.socket_,
SetStreamPriority(dcsctp::StreamID(1), dcsctp::StreamPriority(1337)));
EXPECT_CALL(
*peer_a.socket_,
SetStreamPriority(dcsctp::StreamID(2), dcsctp::StreamPriority(3141)));
}
EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(0);
peer_a.sctp_transport_->OpenStream(1, PriorityValue(1337));
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
peer_a.sctp_transport_->OpenStream(2, PriorityValue(3141));
}
TEST(DcSctpTransportTest, DiscardMessageClosedChannel) {
rtc::AutoThread main_thread;
Peer peer_a;
EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(0);
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
SendDataParams params;
rtc::CopyOnWriteBuffer payload;
EXPECT_EQ(peer_a.sctp_transport_->SendData(1, params, payload).type(),
RTCErrorType::INVALID_STATE);
}
TEST(DcSctpTransportTest, DiscardMessageClosingChannel) {
rtc::AutoThread main_thread;
Peer peer_a;
EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(0);
peer_a.sctp_transport_->OpenStream(1, kDefaultPriority);
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
peer_a.sctp_transport_->ResetStream(1);
SendDataParams params;
rtc::CopyOnWriteBuffer payload;
EXPECT_EQ(peer_a.sctp_transport_->SendData(1, params, payload).type(),
RTCErrorType::INVALID_STATE);
}
TEST(DcSctpTransportTest, SendDataOpenChannel) {
rtc::AutoThread main_thread;
Peer peer_a;
dcsctp::DcSctpOptions options;
EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(1);
EXPECT_CALL(*peer_a.socket_, options()).WillOnce(ReturnPointee(&options));
peer_a.sctp_transport_->OpenStream(1, kDefaultPriority);
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
SendDataParams params;
rtc::CopyOnWriteBuffer payload;
EXPECT_TRUE(peer_a.sctp_transport_->SendData(1, params, payload).ok());
}
TEST(DcSctpTransportTest, DeliversMessage) {
rtc::AutoThread main_thread;
Peer peer_a;
EXPECT_CALL(peer_a.sink_,
OnDataReceived(1, webrtc::DataMessageType::kBinary, _))
.Times(1);
peer_a.sctp_transport_->OpenStream(1, kDefaultPriority);
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
->OnMessageReceived(
dcsctp::DcSctpMessage(dcsctp::StreamID(1), dcsctp::PPID(53), {0}));
}
TEST(DcSctpTransportTest, DropMessageWithUnknownPpid) {
rtc::AutoThread main_thread;
Peer peer_a;
EXPECT_CALL(peer_a.sink_, OnDataReceived(_, _, _)).Times(0);
peer_a.sctp_transport_->OpenStream(1, kDefaultPriority);
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
->OnMessageReceived(
dcsctp::DcSctpMessage(dcsctp::StreamID(1), dcsctp::PPID(1337), {0}));
}
} // namespace webrtc