In rare cases, it is possible to queue a call to SendData from the signaling thread on a channel being closed or already closed in the network thread. By keeping track of currently open streams, we avoid sending messages with a stream id of channels that the other side already considers closed and has already reused for a new channel. This caused rare messages to be delivered on the wrong data channel if a message was quickly sent, channel closed and a new one reopened. Bug: webrtc:14277 Change-Id: If35fed8d12d5d2c18cdc6601085d8b632c37a0ba Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/272624 Commit-Queue: Florent Castelli <orphis@webrtc.org> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Cr-Commit-Position: refs/heads/main@{#37880}
234 lines
8.0 KiB
C++
234 lines
8.0 KiB
C++
/*
|
|
* 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 "net/dcsctp/public/mock_dcsctp_socket.h"
|
|
#include "net/dcsctp/public/mock_dcsctp_socket_factory.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 {
|
|
class MockDataChannelObserver : 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));
|
|
};
|
|
|
|
class Peer {
|
|
public:
|
|
Peer() : fake_packet_transport_("transport"), simulated_clock_(1000) {
|
|
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>(
|
|
rtc::Thread::Current(), &fake_packet_transport_, &simulated_clock_,
|
|
std::move(mock_dcsctp_socket_factory));
|
|
sctp_transport_->SetDataChannelSink(&observer_);
|
|
sctp_transport_->SetOnConnectedCallback(
|
|
[this]() { observer_.OnConnected(); });
|
|
}
|
|
|
|
rtc::FakePacketTransport fake_packet_transport_;
|
|
webrtc::SimulatedClock simulated_clock_;
|
|
dcsctp::MockDcSctpSocket* socket_;
|
|
std::unique_ptr<webrtc::DcSctpTransport> sctp_transport_;
|
|
NiceMock<MockDataChannelObserver> observer_;
|
|
};
|
|
} // 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.observer_, OnReadyToSend);
|
|
EXPECT_CALL(peer_a.observer_, 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_, 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.observer_, OnChannelClosing(1)).Times(0);
|
|
EXPECT_CALL(peer_b.observer_, OnChannelClosing(1));
|
|
EXPECT_CALL(peer_a.observer_, OnChannelClosed(1));
|
|
EXPECT_CALL(peer_b.observer_, 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);
|
|
peer_b.sctp_transport_->OpenStream(1);
|
|
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.observer_, OnChannelClosing(1)).Times(0);
|
|
EXPECT_CALL(peer_b.observer_, OnChannelClosing(1)).Times(0);
|
|
EXPECT_CALL(peer_a.observer_, OnChannelClosed(1));
|
|
EXPECT_CALL(peer_b.observer_, 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);
|
|
peer_b.sctp_transport_->OpenStream(1);
|
|
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, 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);
|
|
|
|
cricket::SendDataResult result;
|
|
SendDataParams params;
|
|
rtc::CopyOnWriteBuffer payload;
|
|
bool send_data_return =
|
|
peer_a.sctp_transport_->SendData(1, params, payload, &result);
|
|
EXPECT_FALSE(send_data_return);
|
|
EXPECT_EQ(cricket::SDR_ERROR, result);
|
|
}
|
|
|
|
TEST(DcSctpTransportTest, DiscardMessageClosingChannel) {
|
|
rtc::AutoThread main_thread;
|
|
Peer peer_a;
|
|
|
|
EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(0);
|
|
|
|
peer_a.sctp_transport_->OpenStream(1);
|
|
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
|
|
peer_a.sctp_transport_->ResetStream(1);
|
|
|
|
cricket::SendDataResult result;
|
|
SendDataParams params;
|
|
rtc::CopyOnWriteBuffer payload;
|
|
|
|
bool send_data_return =
|
|
peer_a.sctp_transport_->SendData(1, params, payload, &result);
|
|
EXPECT_FALSE(send_data_return);
|
|
EXPECT_EQ(cricket::SDR_ERROR, result);
|
|
}
|
|
|
|
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);
|
|
peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
|
|
|
|
cricket::SendDataResult result;
|
|
SendDataParams params;
|
|
rtc::CopyOnWriteBuffer payload;
|
|
|
|
bool send_data_return =
|
|
peer_a.sctp_transport_->SendData(1, params, payload, &result);
|
|
EXPECT_TRUE(send_data_return);
|
|
EXPECT_EQ(cricket::SDR_SUCCESS, result);
|
|
}
|
|
|
|
} // namespace webrtc
|