2021-04-08 09:56:59 +02:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2021 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 "net/dcsctp/socket/dcsctp_socket.h"
|
|
|
|
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <deque>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <utility>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
2021-06-20 22:57:26 +02:00
|
|
|
#include "absl/flags/flag.h"
|
2021-04-08 09:56:59 +02:00
|
|
|
#include "absl/memory/memory.h"
|
|
|
|
|
#include "absl/strings/string_view.h"
|
|
|
|
|
#include "absl/types/optional.h"
|
|
|
|
|
#include "api/array_view.h"
|
2021-09-20 11:35:59 +02:00
|
|
|
#include "net/dcsctp/common/handover_testing.h"
|
2021-04-08 09:56:59 +02:00
|
|
|
#include "net/dcsctp/packet/chunk/chunk.h"
|
|
|
|
|
#include "net/dcsctp/packet/chunk/cookie_echo_chunk.h"
|
|
|
|
|
#include "net/dcsctp/packet/chunk/data_chunk.h"
|
|
|
|
|
#include "net/dcsctp/packet/chunk/data_common.h"
|
|
|
|
|
#include "net/dcsctp/packet/chunk/error_chunk.h"
|
|
|
|
|
#include "net/dcsctp/packet/chunk/heartbeat_ack_chunk.h"
|
|
|
|
|
#include "net/dcsctp/packet/chunk/heartbeat_request_chunk.h"
|
|
|
|
|
#include "net/dcsctp/packet/chunk/idata_chunk.h"
|
|
|
|
|
#include "net/dcsctp/packet/chunk/init_chunk.h"
|
|
|
|
|
#include "net/dcsctp/packet/chunk/sack_chunk.h"
|
2021-05-07 11:22:50 +02:00
|
|
|
#include "net/dcsctp/packet/chunk/shutdown_chunk.h"
|
2021-04-08 09:56:59 +02:00
|
|
|
#include "net/dcsctp/packet/error_cause/error_cause.h"
|
|
|
|
|
#include "net/dcsctp/packet/error_cause/unrecognized_chunk_type_cause.h"
|
|
|
|
|
#include "net/dcsctp/packet/parameter/heartbeat_info_parameter.h"
|
dcsctp: Handle rapid closing of streams
When streams were to be reset, but there was already an ongoing
stream reset command in-flight, those streams wouldn't be properly
reset. When multiple streams were reset close to each other (within
an RTT), some streams would not have their SSNs reset, which resulted
in the stream resuming the SSN sequence. This could result in ordered
streams not delivering all messages as the receiver wouldn't deliver any
messages with SSN different from the expected SSN=0.
In WebRTC data channels, this would be triggered if multiple channels
were closed at roughly the same time, then re-opened, and continued
to be used in ordered mode. Unordered messages would still be delivered,
but the stream state could be wrong as the DATA_CHANNEL_ACK message is
sent ordered, and possibly not delivered.
There were unit tests for this, but not on the socket level using
real components, but just on the stream reset handler using mocks,
where this issue wasn't found. Also, those mocks didn't validate that
the correct parameters were provided, so that's fixed now.
The root cause was the PrepareResetStreams was only called if there
wasn't an ongoing stream reset operation in progress. One may try to
solve it by always calling PrepareResetStreams also when there is an
ongoing request, or to call it when the request has finished. One would
then realize that when the response of the outgoing stream request is
received, and CommitResetStreams is called, it would reset all paused
and (prepared) to-be-reset streams - not just the ones in the outgoing
stream request.
One cause of this was the lack of a single source of truth of the stream
states. The SendQueue kept track of which streams that were paused, but
the stream reset handler kept track of which streams that were
resetting. As that's error prone, this CL moves the source of truth
completely to the SendQueue and defining explicit stream pause states. A
stream can be in one of these possible states:
* Not paused. This is the default for an active stream.
* Pending to be paused. This is when it's about to be reset, but
there is a message that has been partly sent, with fragments
remaining to be sent before it can be paused.
* Paused, with no partly sent message. In this state, it's ready to
be reset.
* Resetting. A stream transitions into this state when it has been
paused and has been included in an outgoing stream reset request.
When this request has been responded to, the stream can really be
reset (SSN=0, MID=0).
This CL also improves logging, and adds socket tests to catch this
issue.
Bug: webrtc:13994, chromium:1320194
Change-Id: I883570d1f277bc01e52b1afad62d6be2aca930a2
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/261180
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36771}
2022-05-02 17:15:57 +02:00
|
|
|
#include "net/dcsctp/packet/parameter/outgoing_ssn_reset_request_parameter.h"
|
2021-04-08 09:56:59 +02:00
|
|
|
#include "net/dcsctp/packet/parameter/parameter.h"
|
|
|
|
|
#include "net/dcsctp/packet/sctp_packet.h"
|
|
|
|
|
#include "net/dcsctp/packet/tlv_trait.h"
|
|
|
|
|
#include "net/dcsctp/public/dcsctp_message.h"
|
|
|
|
|
#include "net/dcsctp/public/dcsctp_options.h"
|
|
|
|
|
#include "net/dcsctp/public/dcsctp_socket.h"
|
2021-06-20 22:57:26 +02:00
|
|
|
#include "net/dcsctp/public/text_pcap_packet_observer.h"
|
2021-05-05 16:22:29 +02:00
|
|
|
#include "net/dcsctp/public/types.h"
|
2021-04-08 09:56:59 +02:00
|
|
|
#include "net/dcsctp/rx/reassembly_queue.h"
|
|
|
|
|
#include "net/dcsctp/socket/mock_dcsctp_socket_callbacks.h"
|
|
|
|
|
#include "net/dcsctp/testing/testing_macros.h"
|
|
|
|
|
#include "rtc_base/gunit.h"
|
|
|
|
|
#include "test/gmock.h"
|
|
|
|
|
|
2021-06-20 22:57:26 +02:00
|
|
|
ABSL_FLAG(bool, dcsctp_capture_packets, false, "Print packet capture.");
|
|
|
|
|
|
2021-04-08 09:56:59 +02:00
|
|
|
namespace dcsctp {
|
|
|
|
|
namespace {
|
|
|
|
|
using ::testing::_;
|
|
|
|
|
using ::testing::AllOf;
|
|
|
|
|
using ::testing::ElementsAre;
|
|
|
|
|
using ::testing::HasSubstr;
|
|
|
|
|
using ::testing::IsEmpty;
|
|
|
|
|
using ::testing::SizeIs;
|
2022-03-31 17:15:03 +02:00
|
|
|
using ::testing::UnorderedElementsAre;
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
constexpr SendOptions kSendOptions;
|
2021-05-05 16:22:29 +02:00
|
|
|
constexpr size_t kLargeMessageSize = DcSctpOptions::kMaxSafeMTUSize * 20;
|
2021-08-11 20:43:47 +02:00
|
|
|
constexpr size_t kSmallMessageSize = 10;
|
|
|
|
|
constexpr int kMaxBurstPackets = 4;
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2021-07-07 19:38:43 +02:00
|
|
|
MATCHER_P(HasDataChunkWithStreamId, stream_id, "") {
|
|
|
|
|
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg);
|
|
|
|
|
if (!packet.has_value()) {
|
|
|
|
|
*result_listener << "data didn't parse as an SctpPacket";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (packet->descriptors()[0].type != DataChunk::kType) {
|
|
|
|
|
*result_listener << "the first chunk in the packet is not a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::optional<DataChunk> dc =
|
|
|
|
|
DataChunk::Parse(packet->descriptors()[0].data);
|
|
|
|
|
if (!dc.has_value()) {
|
|
|
|
|
*result_listener << "The first chunk didn't parse as a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dc->stream_id() != stream_id) {
|
|
|
|
|
*result_listener << "the stream_id is " << *dc->stream_id();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-09 20:24:51 +02:00
|
|
|
MATCHER_P(HasDataChunkWithPPID, ppid, "") {
|
|
|
|
|
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg);
|
|
|
|
|
if (!packet.has_value()) {
|
|
|
|
|
*result_listener << "data didn't parse as an SctpPacket";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (packet->descriptors()[0].type != DataChunk::kType) {
|
|
|
|
|
*result_listener << "the first chunk in the packet is not a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::optional<DataChunk> dc =
|
|
|
|
|
DataChunk::Parse(packet->descriptors()[0].data);
|
|
|
|
|
if (!dc.has_value()) {
|
|
|
|
|
*result_listener << "The first chunk didn't parse as a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dc->ppid() != ppid) {
|
|
|
|
|
*result_listener << "the ppid is " << *dc->ppid();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-08 09:56:59 +02:00
|
|
|
MATCHER_P(HasDataChunkWithSsn, ssn, "") {
|
|
|
|
|
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg);
|
|
|
|
|
if (!packet.has_value()) {
|
|
|
|
|
*result_listener << "data didn't parse as an SctpPacket";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (packet->descriptors()[0].type != DataChunk::kType) {
|
|
|
|
|
*result_listener << "the first chunk in the packet is not a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::optional<DataChunk> dc =
|
|
|
|
|
DataChunk::Parse(packet->descriptors()[0].data);
|
|
|
|
|
if (!dc.has_value()) {
|
|
|
|
|
*result_listener << "The first chunk didn't parse as a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dc->ssn() != ssn) {
|
|
|
|
|
*result_listener << "the ssn is " << *dc->ssn();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MATCHER_P(HasDataChunkWithMid, mid, "") {
|
|
|
|
|
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg);
|
|
|
|
|
if (!packet.has_value()) {
|
|
|
|
|
*result_listener << "data didn't parse as an SctpPacket";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (packet->descriptors()[0].type != IDataChunk::kType) {
|
|
|
|
|
*result_listener << "the first chunk in the packet is not an i-data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::optional<IDataChunk> dc =
|
|
|
|
|
IDataChunk::Parse(packet->descriptors()[0].data);
|
|
|
|
|
if (!dc.has_value()) {
|
|
|
|
|
*result_listener << "The first chunk didn't parse as an i-data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dc->message_id() != mid) {
|
|
|
|
|
*result_listener << "the mid is " << *dc->message_id();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MATCHER_P(HasSackWithCumAckTsn, tsn, "") {
|
|
|
|
|
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg);
|
|
|
|
|
if (!packet.has_value()) {
|
|
|
|
|
*result_listener << "data didn't parse as an SctpPacket";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (packet->descriptors()[0].type != SackChunk::kType) {
|
|
|
|
|
*result_listener << "the first chunk in the packet is not a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::optional<SackChunk> sc =
|
|
|
|
|
SackChunk::Parse(packet->descriptors()[0].data);
|
|
|
|
|
if (!sc.has_value()) {
|
|
|
|
|
*result_listener << "The first chunk didn't parse as a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sc->cumulative_tsn_ack() != tsn) {
|
|
|
|
|
*result_listener << "the cum_ack_tsn is " << *sc->cumulative_tsn_ack();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MATCHER(HasSackWithNoGapAckBlocks, "") {
|
|
|
|
|
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg);
|
|
|
|
|
if (!packet.has_value()) {
|
|
|
|
|
*result_listener << "data didn't parse as an SctpPacket";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (packet->descriptors()[0].type != SackChunk::kType) {
|
|
|
|
|
*result_listener << "the first chunk in the packet is not a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::optional<SackChunk> sc =
|
|
|
|
|
SackChunk::Parse(packet->descriptors()[0].data);
|
|
|
|
|
if (!sc.has_value()) {
|
|
|
|
|
*result_listener << "The first chunk didn't parse as a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!sc->gap_ack_blocks().empty()) {
|
|
|
|
|
*result_listener << "there are gap ack blocks";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
dcsctp: Handle rapid closing of streams
When streams were to be reset, but there was already an ongoing
stream reset command in-flight, those streams wouldn't be properly
reset. When multiple streams were reset close to each other (within
an RTT), some streams would not have their SSNs reset, which resulted
in the stream resuming the SSN sequence. This could result in ordered
streams not delivering all messages as the receiver wouldn't deliver any
messages with SSN different from the expected SSN=0.
In WebRTC data channels, this would be triggered if multiple channels
were closed at roughly the same time, then re-opened, and continued
to be used in ordered mode. Unordered messages would still be delivered,
but the stream state could be wrong as the DATA_CHANNEL_ACK message is
sent ordered, and possibly not delivered.
There were unit tests for this, but not on the socket level using
real components, but just on the stream reset handler using mocks,
where this issue wasn't found. Also, those mocks didn't validate that
the correct parameters were provided, so that's fixed now.
The root cause was the PrepareResetStreams was only called if there
wasn't an ongoing stream reset operation in progress. One may try to
solve it by always calling PrepareResetStreams also when there is an
ongoing request, or to call it when the request has finished. One would
then realize that when the response of the outgoing stream request is
received, and CommitResetStreams is called, it would reset all paused
and (prepared) to-be-reset streams - not just the ones in the outgoing
stream request.
One cause of this was the lack of a single source of truth of the stream
states. The SendQueue kept track of which streams that were paused, but
the stream reset handler kept track of which streams that were
resetting. As that's error prone, this CL moves the source of truth
completely to the SendQueue and defining explicit stream pause states. A
stream can be in one of these possible states:
* Not paused. This is the default for an active stream.
* Pending to be paused. This is when it's about to be reset, but
there is a message that has been partly sent, with fragments
remaining to be sent before it can be paused.
* Paused, with no partly sent message. In this state, it's ready to
be reset.
* Resetting. A stream transitions into this state when it has been
paused and has been included in an outgoing stream reset request.
When this request has been responded to, the stream can really be
reset (SSN=0, MID=0).
This CL also improves logging, and adds socket tests to catch this
issue.
Bug: webrtc:13994, chromium:1320194
Change-Id: I883570d1f277bc01e52b1afad62d6be2aca930a2
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/261180
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36771}
2022-05-02 17:15:57 +02:00
|
|
|
MATCHER_P(HasReconfigWithStreams, streams_matcher, "") {
|
|
|
|
|
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg);
|
|
|
|
|
if (!packet.has_value()) {
|
|
|
|
|
*result_listener << "data didn't parse as an SctpPacket";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (packet->descriptors()[0].type != ReConfigChunk::kType) {
|
|
|
|
|
*result_listener << "the first chunk in the packet is not a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::optional<ReConfigChunk> reconfig =
|
|
|
|
|
ReConfigChunk::Parse(packet->descriptors()[0].data);
|
|
|
|
|
if (!reconfig.has_value()) {
|
|
|
|
|
*result_listener << "The first chunk didn't parse as a data chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Parameters& parameters = reconfig->parameters();
|
|
|
|
|
if (parameters.descriptors().size() != 1 ||
|
|
|
|
|
parameters.descriptors()[0].type !=
|
|
|
|
|
OutgoingSSNResetRequestParameter::kType) {
|
|
|
|
|
*result_listener << "Expected the reconfig chunk to have an outgoing SSN "
|
|
|
|
|
"reset request parameter";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::optional<OutgoingSSNResetRequestParameter> p =
|
|
|
|
|
OutgoingSSNResetRequestParameter::Parse(parameters.descriptors()[0].data);
|
|
|
|
|
testing::Matcher<rtc::ArrayView<const StreamID>> matcher = streams_matcher;
|
|
|
|
|
if (!matcher.MatchAndExplain(p->stream_ids(), result_listener)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-01 08:46:32 +00:00
|
|
|
MATCHER_P(HasReconfigWithResponse, result, "") {
|
|
|
|
|
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg);
|
|
|
|
|
if (!packet.has_value()) {
|
|
|
|
|
*result_listener << "data didn't parse as an SctpPacket";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (packet->descriptors()[0].type != ReConfigChunk::kType) {
|
|
|
|
|
*result_listener << "the first chunk in the packet is not a reconfig chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::optional<ReConfigChunk> reconfig =
|
|
|
|
|
ReConfigChunk::Parse(packet->descriptors()[0].data);
|
|
|
|
|
if (!reconfig.has_value()) {
|
|
|
|
|
*result_listener << "The first chunk didn't parse as a reconfig chunk";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Parameters& parameters = reconfig->parameters();
|
|
|
|
|
if (parameters.descriptors().size() != 1 ||
|
|
|
|
|
parameters.descriptors()[0].type !=
|
|
|
|
|
ReconfigurationResponseParameter::kType) {
|
|
|
|
|
*result_listener << "Expected the reconfig chunk to have a "
|
|
|
|
|
"ReconfigurationResponse Parameter";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
absl::optional<ReconfigurationResponseParameter> p =
|
|
|
|
|
ReconfigurationResponseParameter::Parse(parameters.descriptors()[0].data);
|
|
|
|
|
if (p->result() != result) {
|
|
|
|
|
*result_listener << "ReconfigurationResponse Parameter doesn't contain the "
|
|
|
|
|
"expected result";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-08 09:56:59 +02:00
|
|
|
TSN AddTo(TSN tsn, int delta) {
|
|
|
|
|
return TSN(*tsn + delta);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
DcSctpOptions FixupOptions(DcSctpOptions options = {}) {
|
|
|
|
|
DcSctpOptions fixup = options;
|
2021-04-08 09:56:59 +02:00
|
|
|
// To make the interval more predictable in tests.
|
2022-03-04 20:11:44 +01:00
|
|
|
fixup.heartbeat_interval_include_rtt = false;
|
|
|
|
|
fixup.max_burst = kMaxBurstPackets;
|
|
|
|
|
return fixup;
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-20 22:57:26 +02:00
|
|
|
std::unique_ptr<PacketObserver> GetPacketObserver(absl::string_view name) {
|
|
|
|
|
if (absl::GetFlag(FLAGS_dcsctp_capture_packets)) {
|
|
|
|
|
return std::make_unique<TextPcapPacketObserver>(name);
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
struct SocketUnderTest {
|
|
|
|
|
explicit SocketUnderTest(absl::string_view name,
|
|
|
|
|
const DcSctpOptions& opts = {})
|
|
|
|
|
: options(FixupOptions(opts)),
|
|
|
|
|
cb(name),
|
|
|
|
|
socket(name, cb, GetPacketObserver(name), options) {}
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
const DcSctpOptions options;
|
|
|
|
|
testing::NiceMock<MockDcSctpSocketCallbacks> cb;
|
|
|
|
|
DcSctpSocket socket;
|
|
|
|
|
};
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
void ExchangeMessages(SocketUnderTest& a, SocketUnderTest& z) {
|
|
|
|
|
bool delivered_packet = false;
|
|
|
|
|
do {
|
|
|
|
|
delivered_packet = false;
|
|
|
|
|
std::vector<uint8_t> packet_from_a = a.cb.ConsumeSentPacket();
|
|
|
|
|
if (!packet_from_a.empty()) {
|
|
|
|
|
delivered_packet = true;
|
|
|
|
|
z.socket.ReceivePacket(std::move(packet_from_a));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> packet_from_z = z.cb.ConsumeSentPacket();
|
|
|
|
|
if (!packet_from_z.empty()) {
|
|
|
|
|
delivered_packet = true;
|
|
|
|
|
a.socket.ReceivePacket(std::move(packet_from_z));
|
|
|
|
|
}
|
|
|
|
|
} while (delivered_packet);
|
|
|
|
|
}
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
void RunTimers(SocketUnderTest& s) {
|
|
|
|
|
for (;;) {
|
|
|
|
|
absl::optional<TimeoutID> timeout_id = s.cb.GetNextExpiredTimeout();
|
|
|
|
|
if (!timeout_id.has_value()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
s.socket.HandleTimeout(*timeout_id);
|
dcsctp: Expire timers just before triggering them
In real life, when a Timeout expires, the caller is supposed to call
DcSctpSocket::HandleTimeout directly, as the Timeout that just expired
is stopped (it just expired), but the Timer still believes it's running.
The system is not in a consistent state.
In tests, all timeouts were evaluated at the same time, which, if two
timeouts expired at the same time, would put them both as "not running",
and with their timers believing they were running. So if you would do
any operation on a timer whose timeout had just expired, the timeout
would assert saying that "you can't stop a stopped timeout" or similar.
This isn't relevant in non-test scenarios.
Solved by expiring timeouts one by one.
Bug: webrtc:12614
Change-Id: I79d006f4d3e96854d77cec3eb0080aa23b8569cb
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/217560
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33925}
2021-05-05 14:00:50 +02:00
|
|
|
}
|
2022-03-04 20:11:44 +01:00
|
|
|
}
|
dcsctp: Expire timers just before triggering them
In real life, when a Timeout expires, the caller is supposed to call
DcSctpSocket::HandleTimeout directly, as the Timeout that just expired
is stopped (it just expired), but the Timer still believes it's running.
The system is not in a consistent state.
In tests, all timeouts were evaluated at the same time, which, if two
timeouts expired at the same time, would put them both as "not running",
and with their timers believing they were running. So if you would do
any operation on a timer whose timeout had just expired, the timeout
would assert saying that "you can't stop a stopped timeout" or similar.
This isn't relevant in non-test scenarios.
Solved by expiring timeouts one by one.
Bug: webrtc:12614
Change-Id: I79d006f4d3e96854d77cec3eb0080aa23b8569cb
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/217560
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33925}
2021-05-05 14:00:50 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
void AdvanceTime(SocketUnderTest& a, SocketUnderTest& z, DurationMs duration) {
|
|
|
|
|
a.cb.AdvanceTime(duration);
|
|
|
|
|
z.cb.AdvanceTime(duration);
|
2021-05-07 10:32:36 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
RunTimers(a);
|
|
|
|
|
RunTimers(z);
|
|
|
|
|
}
|
2021-05-07 10:32:36 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
// Calls Connect() on `sock_a_` and make the connection established.
|
|
|
|
|
void ConnectSockets(SocketUnderTest& a, SocketUnderTest& z) {
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnected).Times(1);
|
2021-05-07 10:32:36 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Connect();
|
|
|
|
|
// Z reads INIT, INIT_ACK, COOKIE_ECHO, COOKIE_ACK
|
|
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
|
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
|
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
|
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnected);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<SocketUnderTest> HandoverSocket(
|
|
|
|
|
std::unique_ptr<SocketUnderTest> sut) {
|
|
|
|
|
EXPECT_EQ(sut->socket.GetHandoverReadiness(), HandoverReadinessStatus());
|
|
|
|
|
|
|
|
|
|
bool is_closed = sut->socket.state() == SocketState::kClosed;
|
|
|
|
|
if (!is_closed) {
|
|
|
|
|
EXPECT_CALL(sut->cb, OnClosed).Times(1);
|
|
|
|
|
}
|
|
|
|
|
absl::optional<DcSctpSocketHandoverState> handover_state =
|
|
|
|
|
sut->socket.GetHandoverStateAndClose();
|
|
|
|
|
EXPECT_TRUE(handover_state.has_value());
|
|
|
|
|
g_handover_state_transformer_for_test(&*handover_state);
|
|
|
|
|
|
|
|
|
|
auto handover_socket = std::make_unique<SocketUnderTest>("H", sut->options);
|
|
|
|
|
if (!is_closed) {
|
|
|
|
|
EXPECT_CALL(handover_socket->cb, OnConnected).Times(1);
|
|
|
|
|
}
|
|
|
|
|
handover_socket->socket.RestoreFromState(*handover_state);
|
|
|
|
|
return handover_socket;
|
|
|
|
|
}
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-05-12 22:40:04 +02:00
|
|
|
std::vector<uint32_t> GetReceivedMessagePpids(SocketUnderTest& z) {
|
|
|
|
|
std::vector<uint32_t> ppids;
|
|
|
|
|
for (;;) {
|
|
|
|
|
absl::optional<DcSctpMessage> msg = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
if (!msg.has_value()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
ppids.push_back(*msg->ppid());
|
|
|
|
|
}
|
|
|
|
|
return ppids;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
// Test parameter that controls whether to perform handovers during the test. A
|
|
|
|
|
// test can have multiple points where it conditionally hands over socket Z.
|
|
|
|
|
// Either socket Z will be handed over at all those points or handed over never.
|
|
|
|
|
enum class HandoverMode {
|
|
|
|
|
kNoHandover,
|
|
|
|
|
kPerformHandovers,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class DcSctpSocketParametrizedTest
|
2022-03-04 20:11:44 +01:00
|
|
|
: public ::testing::Test,
|
2021-09-17 15:32:48 +02:00
|
|
|
public ::testing::WithParamInterface<HandoverMode> {
|
|
|
|
|
protected:
|
2022-03-04 20:11:44 +01:00
|
|
|
// Trigger handover for `sut` depending on the current test param.
|
|
|
|
|
std::unique_ptr<SocketUnderTest> MaybeHandoverSocket(
|
|
|
|
|
std::unique_ptr<SocketUnderTest> sut) {
|
2021-09-17 15:32:48 +02:00
|
|
|
if (GetParam() == HandoverMode::kPerformHandovers) {
|
2022-03-04 20:11:44 +01:00
|
|
|
return HandoverSocket(std::move(sut));
|
2021-09-17 15:32:48 +02:00
|
|
|
}
|
2022-03-04 20:11:44 +01:00
|
|
|
return sut;
|
2021-09-17 15:32:48 +02:00
|
|
|
}
|
2022-03-04 20:11:44 +01:00
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
// Trigger handover for socket Z depending on the current test param.
|
|
|
|
|
// Then checks message passing to verify the handed over socket is functional.
|
2022-03-04 20:11:44 +01:00
|
|
|
void MaybeHandoverSocketAndSendMessage(SocketUnderTest& a,
|
|
|
|
|
std::unique_ptr<SocketUnderTest> z) {
|
2021-09-17 15:32:48 +02:00
|
|
|
if (GetParam() == HandoverMode::kPerformHandovers) {
|
2022-03-04 20:11:44 +01:00
|
|
|
z = HandoverSocket(std::move(z));
|
2021-09-17 15:32:48 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
ExchangeMessages(a, *z);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg = z->cb.ConsumeReceivedMessage();
|
2021-09-17 15:32:48 +02:00
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->stream_id(), StreamID(1));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
INSTANTIATE_TEST_SUITE_P(Handovers,
|
|
|
|
|
DcSctpSocketParametrizedTest,
|
|
|
|
|
testing::Values(HandoverMode::kNoHandover,
|
|
|
|
|
HandoverMode::kPerformHandovers),
|
|
|
|
|
[](const auto& test_info) {
|
|
|
|
|
return test_info.param ==
|
|
|
|
|
HandoverMode::kPerformHandovers
|
|
|
|
|
? "WithHandovers"
|
|
|
|
|
: "NoHandover";
|
|
|
|
|
});
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, EstablishConnection) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnectionRestarted).Times(0);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnectionRestarted).Times(0);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
// Z reads INIT, produces INIT_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// A reads INIT_ACK, produces COOKIE_ECHO
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// Z reads COOKIE_ECHO, produces COOKIE_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// A reads COOKIE_ACK.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnected);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, EstablishConnectionWithSetupCollision) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnectionRestarted).Times(0);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnectionRestarted).Times(0);
|
|
|
|
|
a.socket.Connect();
|
|
|
|
|
z.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, z);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnected);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, ShuttingDownWhileEstablishingConnection) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnected).Times(0);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnected).Times(1);
|
|
|
|
|
a.socket.Connect();
|
2021-05-07 10:56:52 +02:00
|
|
|
|
|
|
|
|
// Z reads INIT, produces INIT_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-05-07 10:56:52 +02:00
|
|
|
// A reads INIT_ACK, produces COOKIE_ECHO
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-05-07 10:56:52 +02:00
|
|
|
// Z reads COOKIE_ECHO, produces COOKIE_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-05-07 10:56:52 +02:00
|
|
|
// Drop COOKIE_ACK, just to more easily verify shutdown protocol.
|
2022-03-04 20:11:44 +01:00
|
|
|
z.cb.ConsumeSentPacket();
|
2021-05-07 10:56:52 +02:00
|
|
|
|
|
|
|
|
// As Socket A has received INIT_ACK, it has a TCB and is connected, while
|
|
|
|
|
// Socket Z needs to receive COOKIE_ECHO to get there. Socket A still has
|
|
|
|
|
// timers running at this point.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnecting);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
2021-05-07 10:56:52 +02:00
|
|
|
|
|
|
|
|
// Socket A is now shut down, which should make it stop those timers.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Shutdown();
|
2021-05-07 10:56:52 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnClosed).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnClosed).Times(1);
|
2021-05-07 10:56:52 +02:00
|
|
|
|
|
|
|
|
// Z reads SHUTDOWN, produces SHUTDOWN_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-05-07 10:56:52 +02:00
|
|
|
// A reads SHUTDOWN_ACK, produces SHUTDOWN_COMPLETE
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-05-07 10:56:52 +02:00
|
|
|
// Z reads SHUTDOWN_COMPLETE.
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-05-07 10:56:52 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_TRUE(a.cb.ConsumeSentPacket().empty());
|
|
|
|
|
EXPECT_TRUE(z.cb.ConsumeSentPacket().empty());
|
2021-05-07 10:56:52 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kClosed);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kClosed);
|
2021-05-07 10:56:52 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, EstablishSimultaneousConnection) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnectionRestarted).Times(0);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnectionRestarted).Times(0);
|
|
|
|
|
a.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// INIT isn't received by Z, as it wasn't ready yet.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// A reads INIT, produces INIT_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Z reads INIT_ACK, sends COOKIE_ECHO
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// A reads COOKIE_ECHO - establishes connection.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnected);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Proceed with the remaining packets.
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, z);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnected);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, EstablishConnectionLostCookieAck) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnectionRestarted).Times(0);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnectionRestarted).Times(0);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
// Z reads INIT, produces INIT_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// A reads INIT_ACK, produces COOKIE_ECHO
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// Z reads COOKIE_ECHO, produces COOKIE_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// COOKIE_ACK is lost.
|
2022-03-04 20:11:44 +01:00
|
|
|
z.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnecting);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// This will make A re-send the COOKIE_ECHO
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, z, DurationMs(a.options.t1_cookie_timeout));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Z reads COOKIE_ECHO, produces COOKIE_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// A reads COOKIE_ACK.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnected);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, ResendInitAndEstablishConnection) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
a.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
// INIT is never received by Z.
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_EQ(init_packet.descriptors()[0].type, InitChunk::kType);
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, z, a.options.t1_init_timeout);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Z reads INIT, produces INIT_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// A reads INIT_ACK, produces COOKIE_ECHO
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// Z reads COOKIE_ECHO, produces COOKIE_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// A reads COOKIE_ACK.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnected);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, ResendingInitTooManyTimesAborts) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
a.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// INIT is never received by Z.
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_EQ(init_packet.descriptors()[0].type, InitChunk::kType);
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
for (int i = 0; i < *a.options.max_init_retransmits; ++i) {
|
|
|
|
|
AdvanceTime(a, z, a.options.t1_init_timeout * (1 << i));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// INIT is resent
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket resent_init_packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_EQ(resent_init_packet.descriptors()[0].type, InitChunk::kType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Another timeout, after the max init retransmits.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnAborted).Times(1);
|
|
|
|
|
AdvanceTime(
|
|
|
|
|
a, z, a.options.t1_init_timeout * (1 << *a.options.max_init_retransmits));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kClosed);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, ResendCookieEchoAndEstablishConnection) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
a.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Z reads INIT, produces INIT_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// A reads INIT_ACK, produces COOKIE_ECHO
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// COOKIE_ECHO is never received by Z.
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_EQ(init_packet.descriptors()[0].type, CookieEchoChunk::kType);
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, z, a.options.t1_init_timeout);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Z reads COOKIE_ECHO, produces COOKIE_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// A reads COOKIE_ACK.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnected);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, ResendingCookieEchoTooManyTimesAborts) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
a.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Z reads INIT, produces INIT_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// A reads INIT_ACK, produces COOKIE_ECHO
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// COOKIE_ECHO is never received by Z.
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_EQ(init_packet.descriptors()[0].type, CookieEchoChunk::kType);
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
for (int i = 0; i < *a.options.max_init_retransmits; ++i) {
|
|
|
|
|
AdvanceTime(a, z, a.options.t1_cookie_timeout * (1 << i));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// COOKIE_ECHO is resent
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket resent_init_packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_EQ(resent_init_packet.descriptors()[0].type, CookieEchoChunk::kType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Another timeout, after the max init retransmits.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnAborted).Times(1);
|
|
|
|
|
AdvanceTime(
|
|
|
|
|
a, z,
|
|
|
|
|
a.options.t1_cookie_timeout * (1 << *a.options.max_init_retransmits));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kClosed);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, DoesntSendMorePacketsUntilCookieAckHasBeenReceived) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
2021-09-17 15:32:48 +02:00
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Connect();
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
|
|
|
|
|
// Z reads INIT, produces INIT_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
// A reads INIT_ACK, produces COOKIE_ECHO
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
|
|
|
|
|
// COOKIE_ECHO is never received by Z.
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket cookie_echo_packet1,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
EXPECT_THAT(cookie_echo_packet1.descriptors(), SizeIs(2));
|
|
|
|
|
EXPECT_EQ(cookie_echo_packet1.descriptors()[0].type, CookieEchoChunk::kType);
|
|
|
|
|
EXPECT_EQ(cookie_echo_packet1.descriptors()[1].type, DataChunk::kType);
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_THAT(a.cb.ConsumeSentPacket(), IsEmpty());
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
|
|
|
|
|
// There are DATA chunks in the sent packet (that was lost), which means that
|
|
|
|
|
// the T3-RTX timer is running, but as the socket is in kCookieEcho state, it
|
|
|
|
|
// will be T1-COOKIE that drives retransmissions, so when the T3-RTX expires,
|
|
|
|
|
// nothing should be retransmitted.
|
2022-03-04 20:11:44 +01:00
|
|
|
ASSERT_TRUE(a.options.rto_initial < a.options.t1_cookie_timeout);
|
|
|
|
|
AdvanceTime(a, z, a.options.rto_initial);
|
|
|
|
|
EXPECT_THAT(a.cb.ConsumeSentPacket(), IsEmpty());
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
|
|
|
|
|
// When T1-COOKIE expires, both the COOKIE-ECHO and DATA should be present.
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, z, a.options.t1_cookie_timeout - a.options.rto_initial);
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
|
|
|
|
|
// And this COOKIE-ECHO and DATA is also lost - never received by Z.
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket cookie_echo_packet2,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
EXPECT_THAT(cookie_echo_packet2.descriptors(), SizeIs(2));
|
|
|
|
|
EXPECT_EQ(cookie_echo_packet2.descriptors()[0].type, CookieEchoChunk::kType);
|
|
|
|
|
EXPECT_EQ(cookie_echo_packet2.descriptors()[1].type, DataChunk::kType);
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_THAT(a.cb.ConsumeSentPacket(), IsEmpty());
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
|
|
|
|
|
// COOKIE_ECHO has exponential backoff.
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, z, a.options.t1_cookie_timeout * 2);
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
|
|
|
|
|
// Z reads COOKIE_ECHO, produces COOKIE_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
// A reads COOKIE_ACK.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnected);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
EXPECT_THAT(z.cb.ConsumeReceivedMessage()->payload(),
|
dcsctp: Don't sent more packets before COOKIE ACK
While in the COOKIE ECHO state, there is a TCB and there might be data
in the send buffer, and RFC4960 allows the COOKIE ECHO chunk to bundle
additional DATA chunks in the same packet, but there mustn't be more
than one such packet sent, and that packet must have a COOKIE ECHO chunk
as the first chunk in it.
When the COOKIE ACK chunk has been received, the socket is allowed to
send multiple packets.
Previously, this was state managed by the socket and not the TCB, as
the socket is responsible for moving between the different states. And
when the COOKIE ECHO chunk was sent, the TCB was instructed to only send
a single packet by the socket.
However, if there were retransmissions or anything else that could
result in calling TransmissionControlBlock::SendBufferedChunks, it would
do as instructed and send those, even if the socket was in a state where
that wasn't allowed.
When the peer was dcSCTP, this didn't cause any issues as dcSCTP tries
to be tolerant in what it receives (but strict in what it sends, except
for when there are bugs). When the peer was usrsctp, it would send an
ABORT for each received packet that didn't have a COOKIE ECHO as the
first chunk, and then restart the handshake (sending an INIT). So this
resulted in a longer handshake, but the connection would eventually be
correctly established and any DATA chunks that resulted in the ABORTs
would've been retransmitted.
By making the TCB aware of that particular state, and to make it
responsible for creating the SCTP packet with the COOKIE ECHO chunk
first, and also to only send a single packet when it is in that state,
there will not be any way to bypass this limitation.
Also, while not explicitly mentioned in the RFC, the retransmission
timer will not affect resending any outstanding DATA chunks that were
bundled together with the COOKIE ECHO chunk, as then there would be two
timers that both would drive resending COOKIE ECHO and DATA chunks.
Bug: webrtc:12880
Change-Id: I76f215a03cceab5bafe9f16eb4775f3dc68a6f05
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/222645
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34329}
2021-06-16 12:52:42 +02:00
|
|
|
SizeIs(kLargeMessageSize));
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, ShutdownConnection) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
RTC_LOG(LS_INFO) << "Shutting down";
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(z->cb, OnClosed).Times(1);
|
|
|
|
|
a.socket.Shutdown();
|
2021-04-08 09:56:59 +02:00
|
|
|
// Z reads SHUTDOWN, produces SHUTDOWN_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// A reads SHUTDOWN_ACK, produces SHUTDOWN_COMPLETE
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// Z reads SHUTDOWN_COMPLETE.
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kClosed);
|
|
|
|
|
EXPECT_EQ(z->socket.state(), SocketState::kClosed);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
|
|
|
|
EXPECT_EQ(z->socket.state(), SocketState::kClosed);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, ShutdownTimerExpiresTooManyTimeClosesConnection) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
2021-05-07 11:22:50 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
a.socket.Shutdown();
|
2021-05-07 11:22:50 +02:00
|
|
|
// Drop first SHUTDOWN packet.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.cb.ConsumeSentPacket();
|
2021-05-07 11:22:50 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kShuttingDown);
|
2021-05-07 11:22:50 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
for (int i = 0; i < *a.options.max_retransmissions; ++i) {
|
|
|
|
|
AdvanceTime(a, z, DurationMs(a.options.rto_initial * (1 << i)));
|
2021-05-07 11:22:50 +02:00
|
|
|
|
|
|
|
|
// Dropping every shutdown chunk.
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-05-07 11:22:50 +02:00
|
|
|
EXPECT_EQ(packet.descriptors()[0].type, ShutdownChunk::kType);
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_TRUE(a.cb.ConsumeSentPacket().empty());
|
2021-05-07 11:22:50 +02:00
|
|
|
}
|
|
|
|
|
// The last expiry, makes it abort the connection.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnAborted).Times(1);
|
|
|
|
|
AdvanceTime(a, z,
|
|
|
|
|
a.options.rto_initial * (1 << *a.options.max_retransmissions));
|
2021-05-07 11:22:50 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kClosed);
|
2021-05-07 11:22:50 +02:00
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-05-07 11:22:50 +02:00
|
|
|
EXPECT_EQ(packet.descriptors()[0].type, AbortChunk::kType);
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_TRUE(a.cb.ConsumeSentPacket().empty());
|
2021-05-07 11:22:50 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, EstablishConnectionWhileSendingData) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
a.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Z reads INIT, produces INIT_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// // A reads INIT_ACK, produces COOKIE_ECHO
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// // Z reads COOKIE_ECHO, produces COOKIE_ACK
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// // A reads COOKIE_ACK.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kConnected);
|
|
|
|
|
EXPECT_EQ(z.socket.state(), SocketState::kConnected);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg = z.cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->stream_id(), StreamID(1));
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, SendMessageAfterEstablished) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ConnectSockets(a, z);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
|
|
|
|
|
|
|
|
|
absl::optional<DcSctpMessage> msg = z.cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->stream_id(), StreamID(1));
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, TimeoutResendsPacket) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
RTC_LOG(LS_INFO) << "Advancing time";
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, a.options.rto_initial);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg = z->cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->stream_id(), StreamID(1));
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, SendALotOfBytesMissedSecondPacket) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2021-05-05 16:22:29 +02:00
|
|
|
std::vector<uint8_t> payload(kLargeMessageSize);
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), kSendOptions);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// First DATA
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// Second DATA (lost)
|
2022-03-04 20:11:44 +01:00
|
|
|
a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Retransmit and handle the rest
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg = z->cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->stream_id(), StreamID(1));
|
|
|
|
|
EXPECT_THAT(msg->payload(), testing::ElementsAreArray(payload));
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, SendingHeartbeatAnswersWithAck) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Inject a HEARTBEAT chunk
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Builder b(a.socket.verification_tag(), DcSctpOptions());
|
2021-04-08 09:56:59 +02:00
|
|
|
uint8_t info[] = {1, 2, 3, 4};
|
|
|
|
|
Parameters::Builder params_builder;
|
|
|
|
|
params_builder.Add(HeartbeatInfoParameter(info));
|
|
|
|
|
b.Add(HeartbeatRequestChunk(params_builder.Build()));
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(b.Build());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// HEARTBEAT_ACK is sent as a reply. Capture it.
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket ack_packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_THAT(ack_packet.descriptors(), SizeIs(1));
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(
|
|
|
|
|
HeartbeatAckChunk ack,
|
|
|
|
|
HeartbeatAckChunk::Parse(ack_packet.descriptors()[0].data));
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(HeartbeatInfoParameter info_param, ack.info());
|
|
|
|
|
EXPECT_THAT(info_param.info(), ElementsAre(1, 2, 3, 4));
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, ExpectHeartbeatToBeSent) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_THAT(a.cb.ConsumeSentPacket(), IsEmpty());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, a.options.heartbeat_interval);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> hb_packet_raw = a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket hb_packet,
|
|
|
|
|
SctpPacket::Parse(hb_packet_raw));
|
|
|
|
|
ASSERT_THAT(hb_packet.descriptors(), SizeIs(1));
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(
|
|
|
|
|
HeartbeatRequestChunk hb,
|
|
|
|
|
HeartbeatRequestChunk::Parse(hb_packet.descriptors()[0].data));
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(HeartbeatInfoParameter info_param, hb.info());
|
|
|
|
|
|
|
|
|
|
// The info is a single 64-bit number.
|
|
|
|
|
EXPECT_THAT(hb.info()->info(), SizeIs(8));
|
|
|
|
|
|
|
|
|
|
// Feed it to Sock-z and expect a HEARTBEAT_ACK that will be propagated back.
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(hb_packet_raw);
|
|
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest,
|
|
|
|
|
CloseConnectionAfterTooManyLostHeartbeats) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(z->cb, OnClosed).Times(1);
|
|
|
|
|
EXPECT_THAT(a.cb.ConsumeSentPacket(), testing::IsEmpty());
|
2021-04-08 09:56:59 +02:00
|
|
|
// Force-close socket Z so that it doesn't interfere from now on.
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.Close();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
DurationMs time_to_next_hearbeat = a.options.heartbeat_interval;
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
for (int i = 0; i < *a.options.max_retransmissions; ++i) {
|
2021-04-08 09:56:59 +02:00
|
|
|
RTC_LOG(LS_INFO) << "Letting HEARTBEAT interval timer expire - sending...";
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, time_to_next_hearbeat);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Dropping every heartbeat.
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket hb_packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_EQ(hb_packet.descriptors()[0].type, HeartbeatRequestChunk::kType);
|
|
|
|
|
|
|
|
|
|
RTC_LOG(LS_INFO) << "Letting the heartbeat expire.";
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, DurationMs(1000));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
time_to_next_hearbeat = a.options.heartbeat_interval - DurationMs(1000);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTC_LOG(LS_INFO) << "Letting HEARTBEAT interval timer expire - sending...";
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, time_to_next_hearbeat);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Last heartbeat
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_THAT(a.cb.ConsumeSentPacket(), Not(IsEmpty()));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnAborted).Times(1);
|
2021-04-08 09:56:59 +02:00
|
|
|
// Should suffice as exceeding RTO
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, DurationMs(1000));
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, RecoversAfterASuccessfulAck) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(a.cb.ConsumeSentPacket(), testing::IsEmpty());
|
|
|
|
|
EXPECT_CALL(z->cb, OnClosed).Times(1);
|
2021-04-08 09:56:59 +02:00
|
|
|
// Force-close socket Z so that it doesn't interfere from now on.
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.Close();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
DurationMs time_to_next_hearbeat = a.options.heartbeat_interval;
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
for (int i = 0; i < *a.options.max_retransmissions; ++i) {
|
|
|
|
|
AdvanceTime(a, *z, time_to_next_hearbeat);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Dropping every heartbeat.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
RTC_LOG(LS_INFO) << "Letting the heartbeat expire.";
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, DurationMs(1000));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
time_to_next_hearbeat = a.options.heartbeat_interval - DurationMs(1000);
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RTC_LOG(LS_INFO) << "Getting the last heartbeat - and acking it";
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, time_to_next_hearbeat);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> hb_packet_raw = a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket hb_packet,
|
|
|
|
|
SctpPacket::Parse(hb_packet_raw));
|
|
|
|
|
ASSERT_THAT(hb_packet.descriptors(), SizeIs(1));
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(
|
|
|
|
|
HeartbeatRequestChunk hb,
|
|
|
|
|
HeartbeatRequestChunk::Parse(hb_packet.descriptors()[0].data));
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Builder b(a.socket.verification_tag(), a.options);
|
2021-04-08 09:56:59 +02:00
|
|
|
b.Add(HeartbeatAckChunk(std::move(hb).extract_parameters()));
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(b.Build());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Should suffice as exceeding RTO - which will not fire.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnAborted).Times(0);
|
|
|
|
|
AdvanceTime(a, *z, DurationMs(1000));
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(a.cb.ConsumeSentPacket(), IsEmpty());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Verify that we get new heartbeats again.
|
|
|
|
|
RTC_LOG(LS_INFO) << "Expecting a new heartbeat";
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, time_to_next_hearbeat);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket another_packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_EQ(another_packet.descriptors()[0].type, HeartbeatRequestChunk::kType);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, ResetStream) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), {});
|
|
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg = z->cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->stream_id(), StreamID(1));
|
|
|
|
|
|
|
|
|
|
// Handle SACK
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Reset the outgoing stream. This will directly send a RE-CONFIG.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(1)}));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Receiving the packet will trigger a callback, indicating that A has
|
|
|
|
|
// reset its stream. It will also send a RE-CONFIG with a response.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(z->cb, OnIncomingStreamsReset).Times(1);
|
|
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Receiving a response will trigger a callback. Streams are now reset.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnStreamsResetPerformed).Times(1);
|
|
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, ResetStreamWillMakeChunksStartAtZeroSsn) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> payload(a.options.mtu - 100);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
|
|
|
|
|
|
|
|
|
|
auto packet1 = a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_THAT(packet1, HasDataChunkWithSsn(SSN(0)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(packet1);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
auto packet2 = a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_THAT(packet2, HasDataChunkWithSsn(SSN(1)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(packet2);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Handle SACK
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg1 = z->cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_TRUE(msg1.has_value());
|
|
|
|
|
EXPECT_EQ(msg1->stream_id(), StreamID(1));
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg2 = z->cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_TRUE(msg2.has_value());
|
|
|
|
|
EXPECT_EQ(msg2->stream_id(), StreamID(1));
|
|
|
|
|
|
|
|
|
|
// Reset the outgoing stream. This will directly send a RE-CONFIG.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(1)}));
|
2021-04-08 09:56:59 +02:00
|
|
|
// RE-CONFIG, req
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// RE-CONFIG, resp
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
auto packet3 = a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_THAT(packet3, HasDataChunkWithSsn(SSN(0)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(packet3);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
auto packet4 = a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_THAT(packet4, HasDataChunkWithSsn(SSN(1)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(packet4);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Handle SACK
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest,
|
|
|
|
|
ResetStreamWillOnlyResetTheRequestedStreams) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-07-07 19:38:43 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> payload(a.options.mtu - 100);
|
2021-07-07 19:38:43 +02:00
|
|
|
|
|
|
|
|
// Send two ordered messages on SID 1
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
|
2021-07-07 19:38:43 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
auto packet1 = a.cb.ConsumeSentPacket();
|
2021-07-07 19:38:43 +02:00
|
|
|
EXPECT_THAT(packet1, HasDataChunkWithStreamId(StreamID(1)));
|
|
|
|
|
EXPECT_THAT(packet1, HasDataChunkWithSsn(SSN(0)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(packet1);
|
2021-07-07 19:38:43 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
auto packet2 = a.cb.ConsumeSentPacket();
|
2021-07-07 19:38:43 +02:00
|
|
|
EXPECT_THAT(packet1, HasDataChunkWithStreamId(StreamID(1)));
|
|
|
|
|
EXPECT_THAT(packet2, HasDataChunkWithSsn(SSN(1)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(packet2);
|
2021-07-07 19:38:43 +02:00
|
|
|
|
|
|
|
|
// Handle SACK
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-07-07 19:38:43 +02:00
|
|
|
|
|
|
|
|
// Do the same, for SID 3
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(3), PPID(53), payload), {});
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(3), PPID(53), payload), {});
|
|
|
|
|
auto packet3 = a.cb.ConsumeSentPacket();
|
2021-07-07 19:38:43 +02:00
|
|
|
EXPECT_THAT(packet3, HasDataChunkWithStreamId(StreamID(3)));
|
|
|
|
|
EXPECT_THAT(packet3, HasDataChunkWithSsn(SSN(0)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(packet3);
|
|
|
|
|
auto packet4 = a.cb.ConsumeSentPacket();
|
2021-07-07 19:38:43 +02:00
|
|
|
EXPECT_THAT(packet4, HasDataChunkWithStreamId(StreamID(3)));
|
|
|
|
|
EXPECT_THAT(packet4, HasDataChunkWithSsn(SSN(1)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(packet4);
|
|
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-07-07 19:38:43 +02:00
|
|
|
|
|
|
|
|
// Receive all messages.
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg1 = z->cb.ConsumeReceivedMessage();
|
2021-07-07 19:38:43 +02:00
|
|
|
ASSERT_TRUE(msg1.has_value());
|
|
|
|
|
EXPECT_EQ(msg1->stream_id(), StreamID(1));
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg2 = z->cb.ConsumeReceivedMessage();
|
2021-07-07 19:38:43 +02:00
|
|
|
ASSERT_TRUE(msg2.has_value());
|
|
|
|
|
EXPECT_EQ(msg2->stream_id(), StreamID(1));
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg3 = z->cb.ConsumeReceivedMessage();
|
2021-07-07 19:38:43 +02:00
|
|
|
ASSERT_TRUE(msg3.has_value());
|
|
|
|
|
EXPECT_EQ(msg3->stream_id(), StreamID(3));
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg4 = z->cb.ConsumeReceivedMessage();
|
2021-07-07 19:38:43 +02:00
|
|
|
ASSERT_TRUE(msg4.has_value());
|
|
|
|
|
EXPECT_EQ(msg4->stream_id(), StreamID(3));
|
|
|
|
|
|
|
|
|
|
// Reset SID 1. This will directly send a RE-CONFIG.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(3)}));
|
2021-07-07 19:38:43 +02:00
|
|
|
// RE-CONFIG, req
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-07-07 19:38:43 +02:00
|
|
|
// RE-CONFIG, resp
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-07-07 19:38:43 +02:00
|
|
|
|
|
|
|
|
// Send a message on SID 1 and 3 - SID 1 should not be reset, but 3 should.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
|
2021-07-07 19:38:43 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(3), PPID(53), payload), {});
|
2021-07-07 19:38:43 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
auto packet5 = a.cb.ConsumeSentPacket();
|
2021-07-07 19:38:43 +02:00
|
|
|
EXPECT_THAT(packet5, HasDataChunkWithStreamId(StreamID(1)));
|
|
|
|
|
EXPECT_THAT(packet5, HasDataChunkWithSsn(SSN(2))); // Unchanged.
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(packet5);
|
2021-07-07 19:38:43 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
auto packet6 = a.cb.ConsumeSentPacket();
|
2021-07-07 19:38:43 +02:00
|
|
|
EXPECT_THAT(packet6, HasDataChunkWithStreamId(StreamID(3)));
|
|
|
|
|
EXPECT_THAT(packet6, HasDataChunkWithSsn(SSN(0))); // Reset.
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(packet6);
|
2021-07-07 19:38:43 +02:00
|
|
|
|
|
|
|
|
// Handle SACK
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-07-07 19:38:43 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, OnePeerReconnects) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnConnectionRestarted).Times(1);
|
2021-04-08 09:56:59 +02:00
|
|
|
// Let's be evil here - reconnect while a fragmented packet was about to be
|
|
|
|
|
// sent. The receiving side should get it in full.
|
2021-05-05 16:22:29 +02:00
|
|
|
std::vector<uint8_t> payload(kLargeMessageSize);
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), kSendOptions);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// First DATA
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Create a new association, z2 - and don't use z anymore.
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest z2("Z2");
|
|
|
|
|
z2.socket.Connect();
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Retransmit and handle the rest. As there will be some chunks in-flight that
|
|
|
|
|
// have the wrong verification tag, those will yield errors.
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, z2);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg = z2.cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->stream_id(), StreamID(1));
|
|
|
|
|
EXPECT_THAT(msg->payload(), testing::ElementsAreArray(payload));
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, SendMessageWithLimitedRtx) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
SendOptions send_options;
|
|
|
|
|
send_options.max_retransmissions = 0;
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> payload(a.options.mtu - 100);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(51), payload), send_options);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(52), payload), send_options);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), send_options);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// First DATA
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
// Second DATA (lost)
|
2022-03-04 20:11:44 +01:00
|
|
|
a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
// Third DATA
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2021-07-09 20:24:51 +02:00
|
|
|
// Handle SACK for first DATA
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-07-09 20:24:51 +02:00
|
|
|
|
|
|
|
|
// Handle delayed SACK for third DATA
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, a.options.delayed_ack_max_timeout);
|
2021-07-09 20:24:51 +02:00
|
|
|
|
|
|
|
|
// Handle SACK for second DATA
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Now the missing data chunk will be marked as nacked, but it might still be
|
|
|
|
|
// in-flight and the reported gap could be due to out-of-order delivery. So
|
|
|
|
|
// the RetransmissionQueue will not mark it as "to be retransmitted" until
|
|
|
|
|
// after the t3-rtx timer has expired.
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, a.options.rto_initial);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// The chunk will be marked as retransmitted, and then as abandoned, which
|
|
|
|
|
// will trigger a FORWARD-TSN to be sent.
|
|
|
|
|
|
|
|
|
|
// FORWARD-TSN (third)
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2021-07-09 20:24:51 +02:00
|
|
|
// Which will trigger a SACK
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg1 = z->cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_TRUE(msg1.has_value());
|
|
|
|
|
EXPECT_EQ(msg1->ppid(), PPID(51));
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg2 = z->cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_TRUE(msg2.has_value());
|
|
|
|
|
EXPECT_EQ(msg2->ppid(), PPID(53));
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
absl::optional<DcSctpMessage> msg3 = z->cb.ConsumeReceivedMessage();
|
2021-04-08 09:56:59 +02:00
|
|
|
EXPECT_FALSE(msg3.has_value());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, SendManyFragmentedMessagesWithLimitedRtx) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-07-09 20:24:51 +02:00
|
|
|
|
|
|
|
|
SendOptions send_options;
|
|
|
|
|
send_options.unordered = IsUnordered(true);
|
|
|
|
|
send_options.max_retransmissions = 0;
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> payload(a.options.mtu * 2 - 100 /* margin */);
|
2021-07-09 20:24:51 +02:00
|
|
|
// Sending first message
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(51), payload), send_options);
|
2021-07-09 20:24:51 +02:00
|
|
|
// Sending second message
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(52), payload), send_options);
|
2021-07-09 20:24:51 +02:00
|
|
|
// Sending third message
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), send_options);
|
2021-07-09 20:24:51 +02:00
|
|
|
// Sending fourth message
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(54), payload), send_options);
|
2021-07-09 20:24:51 +02:00
|
|
|
|
|
|
|
|
// First DATA, first fragment
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> packet = a.cb.ConsumeSentPacket();
|
2021-07-09 20:24:51 +02:00
|
|
|
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(51)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(std::move(packet));
|
2021-07-09 20:24:51 +02:00
|
|
|
|
|
|
|
|
// First DATA, second fragment (lost)
|
2022-03-04 20:11:44 +01:00
|
|
|
packet = a.cb.ConsumeSentPacket();
|
2021-07-09 20:24:51 +02:00
|
|
|
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(51)));
|
|
|
|
|
|
|
|
|
|
// Second DATA, first fragment
|
2022-03-04 20:11:44 +01:00
|
|
|
packet = a.cb.ConsumeSentPacket();
|
2021-07-09 20:24:51 +02:00
|
|
|
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(52)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(std::move(packet));
|
2021-07-09 20:24:51 +02:00
|
|
|
|
|
|
|
|
// Second DATA, second fragment (lost)
|
2022-03-04 20:11:44 +01:00
|
|
|
packet = a.cb.ConsumeSentPacket();
|
2021-07-09 20:24:51 +02:00
|
|
|
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(52)));
|
|
|
|
|
EXPECT_THAT(packet, HasDataChunkWithSsn(SSN(0)));
|
|
|
|
|
|
|
|
|
|
// Third DATA, first fragment
|
2022-03-04 20:11:44 +01:00
|
|
|
packet = a.cb.ConsumeSentPacket();
|
2021-07-09 20:24:51 +02:00
|
|
|
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(53)));
|
|
|
|
|
EXPECT_THAT(packet, HasDataChunkWithSsn(SSN(0)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(std::move(packet));
|
2021-07-09 20:24:51 +02:00
|
|
|
|
|
|
|
|
// Third DATA, second fragment (lost)
|
2022-03-04 20:11:44 +01:00
|
|
|
packet = a.cb.ConsumeSentPacket();
|
2021-07-09 20:24:51 +02:00
|
|
|
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(53)));
|
|
|
|
|
EXPECT_THAT(packet, HasDataChunkWithSsn(SSN(0)));
|
|
|
|
|
|
|
|
|
|
// Fourth DATA, first fragment
|
2022-03-04 20:11:44 +01:00
|
|
|
packet = a.cb.ConsumeSentPacket();
|
2021-07-09 20:24:51 +02:00
|
|
|
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(54)));
|
|
|
|
|
EXPECT_THAT(packet, HasDataChunkWithSsn(SSN(0)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(std::move(packet));
|
2021-07-09 20:24:51 +02:00
|
|
|
|
|
|
|
|
// Fourth DATA, second fragment
|
2022-03-04 20:11:44 +01:00
|
|
|
packet = a.cb.ConsumeSentPacket();
|
2021-07-09 20:24:51 +02:00
|
|
|
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(54)));
|
|
|
|
|
EXPECT_THAT(packet, HasDataChunkWithSsn(SSN(0)));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(std::move(packet));
|
2021-07-09 20:24:51 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-07-09 20:24:51 +02:00
|
|
|
|
|
|
|
|
// Let the RTX timer expire, and exchange FORWARD-TSN/SACKs
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, *z, a.options.rto_initial);
|
2021-07-09 20:24:51 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
|
|
|
|
|
|
|
|
|
absl::optional<DcSctpMessage> msg1 = z->cb.ConsumeReceivedMessage();
|
2021-07-09 20:24:51 +02:00
|
|
|
ASSERT_TRUE(msg1.has_value());
|
|
|
|
|
EXPECT_EQ(msg1->ppid(), PPID(54));
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ASSERT_FALSE(z->cb.ConsumeReceivedMessage().has_value());
|
2021-09-21 07:30:12 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-07-09 20:24:51 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-08 09:56:59 +02:00
|
|
|
struct FakeChunkConfig : ChunkConfig {
|
|
|
|
|
static constexpr int kType = 0x49;
|
|
|
|
|
static constexpr size_t kHeaderSize = 4;
|
|
|
|
|
static constexpr int kVariableLengthAlignment = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class FakeChunk : public Chunk, public TLVTrait<FakeChunkConfig> {
|
|
|
|
|
public:
|
|
|
|
|
FakeChunk() {}
|
|
|
|
|
|
|
|
|
|
FakeChunk(FakeChunk&& other) = default;
|
|
|
|
|
FakeChunk& operator=(FakeChunk&& other) = default;
|
|
|
|
|
|
|
|
|
|
void SerializeTo(std::vector<uint8_t>& out) const override {
|
|
|
|
|
AllocateTLV(out);
|
|
|
|
|
}
|
|
|
|
|
std::string ToString() const override { return "FAKE"; }
|
|
|
|
|
};
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, ReceivingUnknownChunkRespondsWithError) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Inject a FAKE chunk
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Builder b(a.socket.verification_tag(), DcSctpOptions());
|
2021-04-08 09:56:59 +02:00
|
|
|
b.Add(FakeChunk());
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(b.Build());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// ERROR is sent as a reply. Capture it.
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket reply_packet,
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Parse(a.cb.ConsumeSentPacket()));
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_THAT(reply_packet.descriptors(), SizeIs(1));
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(
|
|
|
|
|
ErrorChunk error, ErrorChunk::Parse(reply_packet.descriptors()[0].data));
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(
|
|
|
|
|
UnrecognizedChunkTypeCause cause,
|
|
|
|
|
error.error_causes().get<UnrecognizedChunkTypeCause>());
|
|
|
|
|
EXPECT_THAT(cause.unrecognized_chunk(), ElementsAre(0x49, 0x00, 0x00, 0x04));
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, ReceivingErrorChunkReportsAsCallback) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Inject a ERROR chunk
|
2022-03-04 20:11:44 +01:00
|
|
|
SctpPacket::Builder b(a.socket.verification_tag(), DcSctpOptions());
|
2021-04-08 09:56:59 +02:00
|
|
|
b.Add(
|
|
|
|
|
ErrorChunk(Parameters::Builder()
|
|
|
|
|
.Add(UnrecognizedChunkTypeCause({0x49, 0x00, 0x00, 0x04}))
|
|
|
|
|
.Build()));
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnError(ErrorKind::kPeerReported,
|
|
|
|
|
HasSubstr("Unrecognized Chunk Type")));
|
|
|
|
|
a.socket.ReceivePacket(b.Build());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, PassingHighWatermarkWillOnlyAcceptCumAckTsn) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
|
2021-08-12 10:48:11 +02:00
|
|
|
constexpr size_t kReceiveWindowBufferSize = 2000;
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest z(
|
|
|
|
|
"Z", {.mtu = 3000,
|
|
|
|
|
.max_receiver_window_buffer_size = kReceiveWindowBufferSize});
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(z.cb, OnClosed).Times(0);
|
|
|
|
|
EXPECT_CALL(z.cb, OnAborted).Times(0);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Connect();
|
|
|
|
|
std::vector<uint8_t> init_data = a.cb.ConsumeSentPacket();
|
2021-04-08 09:56:59 +02:00
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet,
|
|
|
|
|
SctpPacket::Parse(init_data));
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(
|
|
|
|
|
InitChunk init_chunk,
|
|
|
|
|
InitChunk::Parse(init_packet.descriptors()[0].data));
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(init_data);
|
|
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
|
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
|
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Fill up Z2 to the high watermark limit.
|
2021-08-12 10:48:11 +02:00
|
|
|
constexpr size_t kWatermarkLimit =
|
|
|
|
|
kReceiveWindowBufferSize * ReassemblyQueue::kHighWatermarkLimit;
|
|
|
|
|
constexpr size_t kRemainingSize = kReceiveWindowBufferSize - kWatermarkLimit;
|
|
|
|
|
|
2021-04-08 09:56:59 +02:00
|
|
|
TSN tsn = init_chunk.initial_tsn();
|
|
|
|
|
AnyDataChunk::Options opts;
|
|
|
|
|
opts.is_beginning = Data::IsBeginning(true);
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(
|
|
|
|
|
SctpPacket::Builder(z.socket.verification_tag(), z.options)
|
2021-12-01 18:57:22 +00:00
|
|
|
.Add(DataChunk(tsn, StreamID(1), SSN(0), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(kWatermarkLimit + 1), opts))
|
2021-04-08 09:56:59 +02:00
|
|
|
.Build());
|
|
|
|
|
|
|
|
|
|
// First DATA will always trigger a SACK. It's not interesting.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_THAT(z.cb.ConsumeSentPacket(),
|
2021-04-08 09:56:59 +02:00
|
|
|
AllOf(HasSackWithCumAckTsn(tsn), HasSackWithNoGapAckBlocks()));
|
|
|
|
|
|
|
|
|
|
// This DATA should be accepted - it's advancing cum ack tsn.
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(
|
|
|
|
|
SctpPacket::Builder(z.socket.verification_tag(), z.options)
|
|
|
|
|
.Add(DataChunk(AddTo(tsn, 1), StreamID(1), SSN(0), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(1),
|
|
|
|
|
/*options=*/{}))
|
|
|
|
|
.Build());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// The receiver might have moved into delayed ack mode.
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, z, z.options.rto_initial);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
EXPECT_THAT(
|
2022-03-04 20:11:44 +01:00
|
|
|
z.cb.ConsumeSentPacket(),
|
2021-04-08 09:56:59 +02:00
|
|
|
AllOf(HasSackWithCumAckTsn(AddTo(tsn, 1)), HasSackWithNoGapAckBlocks()));
|
|
|
|
|
|
|
|
|
|
// This DATA will not be accepted - it's not advancing cum ack tsn.
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(
|
|
|
|
|
SctpPacket::Builder(z.socket.verification_tag(), z.options)
|
|
|
|
|
.Add(DataChunk(AddTo(tsn, 3), StreamID(1), SSN(0), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(1),
|
|
|
|
|
/*options=*/{}))
|
|
|
|
|
.Build());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Sack will be sent in IMMEDIATE mode when this is happening.
|
|
|
|
|
EXPECT_THAT(
|
2022-03-04 20:11:44 +01:00
|
|
|
z.cb.ConsumeSentPacket(),
|
2021-04-08 09:56:59 +02:00
|
|
|
AllOf(HasSackWithCumAckTsn(AddTo(tsn, 1)), HasSackWithNoGapAckBlocks()));
|
|
|
|
|
|
|
|
|
|
// This DATA will not be accepted either.
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(
|
|
|
|
|
SctpPacket::Builder(z.socket.verification_tag(), z.options)
|
|
|
|
|
.Add(DataChunk(AddTo(tsn, 4), StreamID(1), SSN(0), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(1),
|
|
|
|
|
/*options=*/{}))
|
|
|
|
|
.Build());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// Sack will be sent in IMMEDIATE mode when this is happening.
|
|
|
|
|
EXPECT_THAT(
|
2022-03-04 20:11:44 +01:00
|
|
|
z.cb.ConsumeSentPacket(),
|
2021-04-08 09:56:59 +02:00
|
|
|
AllOf(HasSackWithCumAckTsn(AddTo(tsn, 1)), HasSackWithNoGapAckBlocks()));
|
|
|
|
|
|
|
|
|
|
// This DATA should be accepted, and it fills the reassembly queue.
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(
|
|
|
|
|
SctpPacket::Builder(z.socket.verification_tag(), z.options)
|
2021-12-01 18:57:22 +00:00
|
|
|
.Add(DataChunk(AddTo(tsn, 2), StreamID(1), SSN(0), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(kRemainingSize),
|
|
|
|
|
/*options=*/{}))
|
2021-05-20 19:34:18 +02:00
|
|
|
.Build());
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// The receiver might have moved into delayed ack mode.
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, z, z.options.rto_initial);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
EXPECT_THAT(
|
2022-03-04 20:11:44 +01:00
|
|
|
z.cb.ConsumeSentPacket(),
|
2021-04-08 09:56:59 +02:00
|
|
|
AllOf(HasSackWithCumAckTsn(AddTo(tsn, 2)), HasSackWithNoGapAckBlocks()));
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(z.cb, OnAborted(ErrorKind::kResourceExhaustion, _));
|
|
|
|
|
EXPECT_CALL(z.cb, OnClosed).Times(0);
|
2021-04-08 09:56:59 +02:00
|
|
|
|
|
|
|
|
// This DATA will make the connection close. It's too full now.
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(
|
|
|
|
|
SctpPacket::Builder(z.socket.verification_tag(), z.options)
|
2021-12-01 18:57:22 +00:00
|
|
|
.Add(DataChunk(AddTo(tsn, 3), StreamID(1), SSN(0), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize),
|
|
|
|
|
/*options=*/{}))
|
2021-05-20 19:34:18 +02:00
|
|
|
.Build());
|
2021-04-08 09:56:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, SetMaxMessageSize) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
|
|
|
|
|
a.socket.SetMaxMessageSize(42u);
|
|
|
|
|
EXPECT_EQ(a.socket.options().max_message_size, 42u);
|
2021-05-04 20:12:52 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, SendsMessagesWithLowLifetime) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-05-05 16:22:29 +02:00
|
|
|
|
|
|
|
|
// Mock that the time always goes forward.
|
|
|
|
|
TimeMs now(0);
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, TimeMillis).WillRepeatedly([&]() {
|
2021-05-05 16:22:29 +02:00
|
|
|
now += DurationMs(3);
|
|
|
|
|
return now;
|
|
|
|
|
});
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(z->cb, TimeMillis).WillRepeatedly([&]() {
|
2021-05-05 16:22:29 +02:00
|
|
|
now += DurationMs(3);
|
|
|
|
|
return now;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Queue a few small messages with low lifetime, both ordered and unordered,
|
|
|
|
|
// and validate that all are delivered.
|
|
|
|
|
static constexpr int kIterations = 100;
|
|
|
|
|
for (int i = 0; i < kIterations; ++i) {
|
|
|
|
|
SendOptions send_options;
|
|
|
|
|
send_options.unordered = IsUnordered((i % 2) == 0);
|
|
|
|
|
send_options.lifetime = DurationMs(i % 3); // 0, 1, 2 ms
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), send_options);
|
2021-05-05 16:22:29 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-05-05 16:22:29 +02:00
|
|
|
|
|
|
|
|
for (int i = 0; i < kIterations; ++i) {
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_TRUE(z->cb.ConsumeReceivedMessage().has_value());
|
2021-05-05 16:22:29 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_FALSE(z->cb.ConsumeReceivedMessage().has_value());
|
2021-05-05 16:22:29 +02:00
|
|
|
|
|
|
|
|
// Validate that the sockets really make the time move forward.
|
|
|
|
|
EXPECT_GE(*now, kIterations * 2);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-05-05 16:22:29 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest,
|
|
|
|
|
DiscardsMessagesWithLowLifetimeIfMustBuffer) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-05-05 16:22:29 +02:00
|
|
|
|
|
|
|
|
SendOptions lifetime_0;
|
|
|
|
|
lifetime_0.unordered = IsUnordered(true);
|
|
|
|
|
lifetime_0.lifetime = DurationMs(0);
|
|
|
|
|
|
|
|
|
|
SendOptions lifetime_1;
|
|
|
|
|
lifetime_1.unordered = IsUnordered(true);
|
|
|
|
|
lifetime_1.lifetime = DurationMs(1);
|
|
|
|
|
|
|
|
|
|
// Mock that the time always goes forward.
|
|
|
|
|
TimeMs now(0);
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, TimeMillis).WillRepeatedly([&]() {
|
2021-05-05 16:22:29 +02:00
|
|
|
now += DurationMs(3);
|
|
|
|
|
return now;
|
|
|
|
|
});
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(z->cb, TimeMillis).WillRepeatedly([&]() {
|
2021-05-05 16:22:29 +02:00
|
|
|
now += DurationMs(3);
|
|
|
|
|
return now;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Fill up the send buffer with a large message.
|
|
|
|
|
std::vector<uint8_t> payload(kLargeMessageSize);
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), kSendOptions);
|
2021-05-05 16:22:29 +02:00
|
|
|
|
|
|
|
|
// And queue a few small messages with lifetime=0 or 1 ms - can't be sent.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2, 3}), lifetime_0);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {4, 5, 6}), lifetime_1);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {7, 8, 9}), lifetime_0);
|
2021-05-05 16:22:29 +02:00
|
|
|
|
|
|
|
|
// Handle all that was sent until congestion window got full.
|
|
|
|
|
for (;;) {
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> packet_from_a = a.cb.ConsumeSentPacket();
|
2021-05-05 16:22:29 +02:00
|
|
|
if (packet_from_a.empty()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(std::move(packet_from_a));
|
2021-05-05 16:22:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shouldn't be enough to send that large message.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_FALSE(z->cb.ConsumeReceivedMessage().has_value());
|
2021-05-05 16:22:29 +02:00
|
|
|
|
|
|
|
|
// Exchange the rest of the messages, with the time ever increasing.
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-05-05 16:22:29 +02:00
|
|
|
|
|
|
|
|
// The large message should be delivered. It was sent reliably.
|
2022-03-04 20:11:44 +01:00
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(DcSctpMessage m1, z->cb.ConsumeReceivedMessage());
|
2021-05-05 16:22:29 +02:00
|
|
|
EXPECT_EQ(m1.stream_id(), StreamID(1));
|
|
|
|
|
EXPECT_THAT(m1.payload(), SizeIs(kLargeMessageSize));
|
|
|
|
|
|
|
|
|
|
// But none of the smaller messages.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_FALSE(z->cb.ConsumeReceivedMessage().has_value());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-05-05 16:22:29 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, HasReasonableBufferedAmountValues) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.buffered_amount(StreamID(1)), 0u);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
2021-09-17 15:32:48 +02:00
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
kSendOptions);
|
2021-05-20 19:34:18 +02:00
|
|
|
// Sending a small message will directly send it as a single packet, so
|
|
|
|
|
// nothing is left in the queue.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.buffered_amount(StreamID(1)), 0u);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
2021-09-17 15:32:48 +02:00
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
|
|
|
|
// Sending a message will directly start sending a few packets, so the
|
|
|
|
|
// buffered amount is not the full message size.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_GT(a.socket.buffered_amount(StreamID(1)), 0u);
|
|
|
|
|
EXPECT_LT(a.socket.buffered_amount(StreamID(1)), kLargeMessageSize);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, HasDefaultOnBufferedAmountLowValueZero) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
EXPECT_EQ(a.socket.buffered_amount_low_threshold(StreamID(1)), 0u);
|
2021-05-20 19:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest,
|
|
|
|
|
TriggersOnBufferedAmountLowWithDefaultValueZero) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow).Times(0);
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow(StreamID(1)));
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
2021-09-17 15:32:48 +02:00
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
kSendOptions);
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow).WillRepeatedly(testing::Return());
|
|
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest,
|
|
|
|
|
DoesntTriggerOnBufferedAmountLowIfBelowThreshold) {
|
2021-05-20 19:34:18 +02:00
|
|
|
static constexpr size_t kMessageSize = 1000;
|
|
|
|
|
static constexpr size_t kBufferedAmountLowThreshold = kMessageSize * 10;
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
a.socket.SetBufferedAmountLowThreshold(StreamID(1),
|
2021-09-17 15:32:48 +02:00
|
|
|
kBufferedAmountLowThreshold);
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow).Times(0);
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow(StreamID(1))).Times(0);
|
|
|
|
|
a.socket.Send(
|
2021-05-20 19:34:18 +02:00
|
|
|
DcSctpMessage(StreamID(1), PPID(53), std::vector<uint8_t>(kMessageSize)),
|
|
|
|
|
kSendOptions);
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(
|
2021-05-20 19:34:18 +02:00
|
|
|
DcSctpMessage(StreamID(1), PPID(53), std::vector<uint8_t>(kMessageSize)),
|
|
|
|
|
kSendOptions);
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, TriggersOnBufferedAmountMultipleTimes) {
|
2021-05-20 19:34:18 +02:00
|
|
|
static constexpr size_t kMessageSize = 1000;
|
|
|
|
|
static constexpr size_t kBufferedAmountLowThreshold = kMessageSize / 2;
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
a.socket.SetBufferedAmountLowThreshold(StreamID(1),
|
2021-09-17 15:32:48 +02:00
|
|
|
kBufferedAmountLowThreshold);
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow).Times(0);
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow(StreamID(1))).Times(3);
|
|
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow(StreamID(2))).Times(2);
|
|
|
|
|
a.socket.Send(
|
2021-05-20 19:34:18 +02:00
|
|
|
DcSctpMessage(StreamID(1), PPID(53), std::vector<uint8_t>(kMessageSize)),
|
|
|
|
|
kSendOptions);
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(
|
2021-05-20 19:34:18 +02:00
|
|
|
DcSctpMessage(StreamID(2), PPID(53), std::vector<uint8_t>(kMessageSize)),
|
|
|
|
|
kSendOptions);
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(
|
2021-05-20 19:34:18 +02:00
|
|
|
DcSctpMessage(StreamID(1), PPID(53), std::vector<uint8_t>(kMessageSize)),
|
|
|
|
|
kSendOptions);
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(
|
2021-05-20 19:34:18 +02:00
|
|
|
DcSctpMessage(StreamID(2), PPID(53), std::vector<uint8_t>(kMessageSize)),
|
|
|
|
|
kSendOptions);
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(
|
2021-05-20 19:34:18 +02:00
|
|
|
DcSctpMessage(StreamID(1), PPID(53), std::vector<uint8_t>(kMessageSize)),
|
|
|
|
|
kSendOptions);
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest,
|
|
|
|
|
TriggersOnBufferedAmountLowOnlyWhenCrossingThreshold) {
|
2021-05-20 19:34:18 +02:00
|
|
|
static constexpr size_t kMessageSize = 1000;
|
|
|
|
|
static constexpr size_t kBufferedAmountLowThreshold = kMessageSize * 1.5;
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
a.socket.SetBufferedAmountLowThreshold(StreamID(1),
|
2021-09-17 15:32:48 +02:00
|
|
|
kBufferedAmountLowThreshold);
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow).Times(0);
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow).Times(0);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
|
|
|
|
// Add a few messages to fill up the congestion window. When that is full,
|
|
|
|
|
// messages will start to be fully buffered.
|
2022-03-04 20:11:44 +01:00
|
|
|
while (a.socket.buffered_amount(StreamID(1)) <= kBufferedAmountLowThreshold) {
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
2021-09-17 15:32:48 +02:00
|
|
|
std::vector<uint8_t>(kMessageSize)),
|
|
|
|
|
kSendOptions);
|
2021-05-20 19:34:18 +02:00
|
|
|
}
|
2022-03-04 20:11:44 +01:00
|
|
|
size_t initial_buffered = a.socket.buffered_amount(StreamID(1));
|
2021-08-12 10:48:11 +02:00
|
|
|
ASSERT_GT(initial_buffered, kBufferedAmountLowThreshold);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
|
|
|
|
// Start ACKing packets, which will empty the send queue, and trigger the
|
|
|
|
|
// callback.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnBufferedAmountLow(StreamID(1))).Times(1);
|
|
|
|
|
ExchangeMessages(a, *z);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest,
|
|
|
|
|
DoesntTriggerOnTotalBufferAmountLowWhenBelow) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnTotalBufferedAmountLow).Times(0);
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
2021-09-17 15:32:48 +02:00
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest,
|
|
|
|
|
TriggersOnTotalBufferAmountLowWhenCrossingThreshold) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnTotalBufferedAmountLow).Times(0);
|
2021-05-20 19:34:18 +02:00
|
|
|
|
|
|
|
|
// Fill up the send queue completely.
|
|
|
|
|
for (;;) {
|
2022-03-04 20:11:44 +01:00
|
|
|
if (a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
2021-09-17 15:32:48 +02:00
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions) == SendStatus::kErrorResourceExhaustion) {
|
2021-05-20 19:34:18 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_CALL(a.cb, OnTotalBufferedAmountLow).Times(1);
|
|
|
|
|
ExchangeMessages(a, *z);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-05-20 19:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
2022-05-13 14:27:55 +02:00
|
|
|
TEST(DcSctpSocketTest, InitialMetricsAreUnset) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_FALSE(a.socket.GetMetrics().has_value());
|
2021-08-09 12:26:32 +02:00
|
|
|
}
|
|
|
|
|
|
2022-05-13 15:31:14 +02:00
|
|
|
TEST(DcSctpSocketTest, MessageInterleavingMetricsAreSet) {
|
|
|
|
|
std::vector<std::pair<bool, bool>> combinations = {
|
|
|
|
|
{false, false}, {false, true}, {true, false}, {true, true}};
|
|
|
|
|
for (const auto& [a_enable, z_enable] : combinations) {
|
|
|
|
|
DcSctpOptions a_options = {.enable_message_interleaving = a_enable};
|
|
|
|
|
DcSctpOptions z_options = {.enable_message_interleaving = z_enable};
|
|
|
|
|
|
|
|
|
|
SocketUnderTest a("A", a_options);
|
|
|
|
|
SocketUnderTest z("Z", z_options);
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->uses_message_interleaving,
|
|
|
|
|
a_enable && z_enable);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, RxAndTxPacketMetricsIncrease) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
const size_t initial_a_rwnd = a.options.max_receiver_window_buffer_size *
|
2021-08-09 12:26:32 +02:00
|
|
|
ReassemblyQueue::kHighWatermarkLimit;
|
|
|
|
|
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->tx_packets_count, 2u);
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->rx_packets_count, 2u);
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->tx_messages_count, 0u);
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->cwnd_bytes,
|
2022-03-04 20:11:44 +01:00
|
|
|
a.options.cwnd_mtus_initial * a.options.mtu);
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->unack_data_count, 0u);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(z.socket.GetMetrics()->rx_packets_count, 2u);
|
|
|
|
|
EXPECT_EQ(z.socket.GetMetrics()->rx_messages_count, 0u);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions);
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->unack_data_count, 1u);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket()); // DATA
|
|
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket()); // SACK
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->peer_rwnd_bytes, initial_a_rwnd);
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->unack_data_count, 0u);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_TRUE(z.cb.ConsumeReceivedMessage().has_value());
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->tx_packets_count, 3u);
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->rx_packets_count, 3u);
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->tx_messages_count, 1u);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(z.socket.GetMetrics()->rx_packets_count, 3u);
|
|
|
|
|
EXPECT_EQ(z.socket.GetMetrics()->rx_messages_count, 1u);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
|
|
|
|
// Send one more (large - fragmented), and receive the delayed SACK.
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(a.options.mtu * 2 + 1)),
|
2021-09-17 15:32:48 +02:00
|
|
|
kSendOptions);
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->unack_data_count, 3u);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket()); // DATA
|
|
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket()); // DATA
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket()); // SACK
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->unack_data_count, 1u);
|
|
|
|
|
EXPECT_GT(a.socket.GetMetrics()->peer_rwnd_bytes, 0u);
|
|
|
|
|
EXPECT_LT(a.socket.GetMetrics()->peer_rwnd_bytes, initial_a_rwnd);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket()); // DATA
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_TRUE(z.cb.ConsumeReceivedMessage().has_value());
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->tx_packets_count, 6u);
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->rx_packets_count, 4u);
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->tx_messages_count, 2u);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(z.socket.GetMetrics()->rx_packets_count, 6u);
|
|
|
|
|
EXPECT_EQ(z.socket.GetMetrics()->rx_messages_count, 2u);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
|
|
|
|
// Delayed sack
|
2022-03-04 20:11:44 +01:00
|
|
|
AdvanceTime(a, z, a.options.delayed_ack_max_timeout);
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket()); // SACK
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->unack_data_count, 0u);
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->rx_packets_count, 5u);
|
|
|
|
|
EXPECT_EQ(a.socket.GetMetrics()->peer_rwnd_bytes, initial_a_rwnd);
|
2021-08-09 12:26:32 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, UnackDataAlsoIncludesSendQueue) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
2021-09-17 15:32:48 +02:00
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
2021-08-09 12:26:32 +02:00
|
|
|
size_t payload_bytes =
|
2022-03-04 20:11:44 +01:00
|
|
|
a.options.mtu - SctpPacket::kHeaderSize - DataChunk::kHeaderSize;
|
2021-08-09 12:26:32 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
size_t expected_sent_packets = a.options.cwnd_mtus_initial;
|
2021-08-09 12:26:32 +02:00
|
|
|
|
|
|
|
|
size_t expected_queued_bytes =
|
|
|
|
|
kLargeMessageSize - expected_sent_packets * payload_bytes;
|
|
|
|
|
|
|
|
|
|
size_t expected_queued_packets = expected_queued_bytes / payload_bytes;
|
|
|
|
|
|
|
|
|
|
// Due to alignment, padding etc, it's hard to calculate the exact number, but
|
|
|
|
|
// it should be in this range.
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_GE(a.socket.GetMetrics()->unack_data_count,
|
2021-08-09 12:26:32 +02:00
|
|
|
expected_sent_packets + expected_queued_packets);
|
|
|
|
|
|
2022-05-13 14:27:55 +02:00
|
|
|
EXPECT_LE(a.socket.GetMetrics()->unack_data_count,
|
2021-08-09 12:26:32 +02:00
|
|
|
expected_sent_packets + expected_queued_packets + 2);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-08-09 12:26:32 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, DoesntSendMoreThanMaxBurstPackets) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-08-11 20:43:47 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
2021-09-17 15:32:48 +02:00
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
2021-08-11 20:43:47 +02:00
|
|
|
|
|
|
|
|
for (int i = 0; i < kMaxBurstPackets; ++i) {
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> packet = a.cb.ConsumeSentPacket();
|
2021-08-11 20:43:47 +02:00
|
|
|
EXPECT_THAT(packet, Not(IsEmpty()));
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(std::move(packet)); // DATA
|
2021-08-11 20:43:47 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_THAT(a.cb.ConsumeSentPacket(), IsEmpty());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, *z);
|
|
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-08-11 20:43:47 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-17 15:32:48 +02:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, SendsOnlyLargePackets) {
|
2022-03-04 20:11:44 +01:00
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
2021-08-12 10:48:11 +02:00
|
|
|
|
|
|
|
|
// A really large message, to ensure that the congestion window is often full.
|
|
|
|
|
constexpr size_t kMessageSize = 100000;
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(
|
2021-08-12 10:48:11 +02:00
|
|
|
DcSctpMessage(StreamID(1), PPID(53), std::vector<uint8_t>(kMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
|
|
|
|
|
bool delivered_packet = false;
|
|
|
|
|
std::vector<size_t> data_packet_sizes;
|
|
|
|
|
do {
|
|
|
|
|
delivered_packet = false;
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> packet_from_a = a.cb.ConsumeSentPacket();
|
2021-08-12 10:48:11 +02:00
|
|
|
if (!packet_from_a.empty()) {
|
|
|
|
|
data_packet_sizes.push_back(packet_from_a.size());
|
|
|
|
|
delivered_packet = true;
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.ReceivePacket(std::move(packet_from_a));
|
2021-08-12 10:48:11 +02:00
|
|
|
}
|
2022-03-04 20:11:44 +01:00
|
|
|
std::vector<uint8_t> packet_from_z = z->cb.ConsumeSentPacket();
|
2021-08-12 10:48:11 +02:00
|
|
|
if (!packet_from_z.empty()) {
|
|
|
|
|
delivered_packet = true;
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.ReceivePacket(std::move(packet_from_z));
|
2021-08-12 10:48:11 +02:00
|
|
|
}
|
|
|
|
|
} while (delivered_packet);
|
|
|
|
|
|
|
|
|
|
size_t packet_payload_bytes =
|
2022-03-04 20:11:44 +01:00
|
|
|
a.options.mtu - SctpPacket::kHeaderSize - DataChunk::kHeaderSize;
|
2021-08-12 10:48:11 +02:00
|
|
|
// +1 accounts for padding, and rounding up.
|
|
|
|
|
size_t expected_packets =
|
|
|
|
|
(kMessageSize + packet_payload_bytes - 1) / packet_payload_bytes + 1;
|
|
|
|
|
EXPECT_THAT(data_packet_sizes, SizeIs(expected_packets));
|
|
|
|
|
|
|
|
|
|
// Remove the last size - it will be the remainder. But all other sizes should
|
|
|
|
|
// be large.
|
|
|
|
|
data_packet_sizes.pop_back();
|
|
|
|
|
|
|
|
|
|
for (size_t size : data_packet_sizes) {
|
|
|
|
|
// The 4 is for padding/alignment.
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_GE(size, a.options.mtu - 4);
|
2021-08-12 10:48:11 +02:00
|
|
|
}
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
2021-08-12 10:48:11 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, SendMessagesAfterHandover) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
2021-09-17 15:32:48 +02:00
|
|
|
|
|
|
|
|
// Send message before handover to move socket to a not initial state
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
|
|
|
|
z->cb.ConsumeReceivedMessage();
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
z = HandoverSocket(std::move(z));
|
2021-09-17 15:32:48 +02:00
|
|
|
|
|
|
|
|
absl::optional<DcSctpMessage> msg;
|
|
|
|
|
|
|
|
|
|
RTC_LOG(LS_INFO) << "Sending A #1";
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {3, 4}), kSendOptions);
|
|
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
msg = z->cb.ConsumeReceivedMessage();
|
2021-09-17 15:32:48 +02:00
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->stream_id(), StreamID(1));
|
|
|
|
|
EXPECT_THAT(msg->payload(), testing::ElementsAre(3, 4));
|
|
|
|
|
|
|
|
|
|
RTC_LOG(LS_INFO) << "Sending A #2";
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(53), {5, 6}), kSendOptions);
|
|
|
|
|
z->socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
msg = z->cb.ConsumeReceivedMessage();
|
2021-09-17 15:32:48 +02:00
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->stream_id(), StreamID(2));
|
|
|
|
|
EXPECT_THAT(msg->payload(), testing::ElementsAre(5, 6));
|
|
|
|
|
|
|
|
|
|
RTC_LOG(LS_INFO) << "Sending Z #1";
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
z->socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2, 3}), kSendOptions);
|
|
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket()); // ack
|
|
|
|
|
a.socket.ReceivePacket(z->cb.ConsumeSentPacket()); // data
|
2021-09-17 15:32:48 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
msg = a.cb.ConsumeReceivedMessage();
|
2021-09-17 15:32:48 +02:00
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->stream_id(), StreamID(1));
|
|
|
|
|
EXPECT_THAT(msg->payload(), testing::ElementsAre(1, 2, 3));
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, CanDetectDcsctpImplementation) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
2021-09-24 23:01:21 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(a.socket.peer_implementation(), SctpImplementation::kDcsctp);
|
2021-09-24 23:01:21 +02:00
|
|
|
|
|
|
|
|
// As A initiated the connection establishment, Z will not receive enough
|
|
|
|
|
// information to know about A's implementation
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(z.socket.peer_implementation(), SctpImplementation::kUnknown);
|
2021-09-24 23:01:21 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
TEST(DcSctpSocketTest, BothCanDetectDcsctpImplementation) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnected).Times(1);
|
|
|
|
|
a.socket.Connect();
|
|
|
|
|
z.socket.Connect();
|
2021-09-24 23:01:21 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
ExchangeMessages(a, z);
|
2021-09-24 23:01:21 +02:00
|
|
|
|
2022-03-04 20:11:44 +01:00
|
|
|
EXPECT_EQ(a.socket.peer_implementation(), SctpImplementation::kDcsctp);
|
|
|
|
|
EXPECT_EQ(z.socket.peer_implementation(), SctpImplementation::kDcsctp);
|
2021-09-24 23:01:21 +02:00
|
|
|
}
|
2022-03-08 09:36:55 +01:00
|
|
|
|
|
|
|
|
TEST_P(DcSctpSocketParametrizedTest, CanLoseFirstOrderedMessage) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
|
|
|
|
|
|
|
|
|
SendOptions send_options;
|
|
|
|
|
send_options.unordered = IsUnordered(false);
|
|
|
|
|
send_options.max_retransmissions = 0;
|
|
|
|
|
std::vector<uint8_t> payload(a.options.mtu - 100);
|
|
|
|
|
|
|
|
|
|
// Send a first message (SID=1, SSN=0)
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(51), payload), send_options);
|
|
|
|
|
|
|
|
|
|
// First DATA is lost, and retransmission timer will delete it.
|
|
|
|
|
a.cb.ConsumeSentPacket();
|
|
|
|
|
AdvanceTime(a, *z, a.options.rto_initial);
|
|
|
|
|
ExchangeMessages(a, *z);
|
|
|
|
|
|
|
|
|
|
// Send a second message (SID=0, SSN=1).
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(52), payload), send_options);
|
|
|
|
|
ExchangeMessages(a, *z);
|
|
|
|
|
|
|
|
|
|
// The Z socket should receive the second message, but not the first.
|
|
|
|
|
absl::optional<DcSctpMessage> msg = z->cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg.has_value());
|
|
|
|
|
EXPECT_EQ(msg->ppid(), PPID(52));
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(z->cb.ConsumeReceivedMessage().has_value());
|
|
|
|
|
|
|
|
|
|
MaybeHandoverSocketAndSendMessage(a, std::move(z));
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-20 19:59:03 +01:00
|
|
|
TEST(DcSctpSocketTest, ReceiveBothUnorderedAndOrderedWithSameTSN) {
|
|
|
|
|
/* This issue was found by fuzzing. */
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
a.socket.Connect();
|
|
|
|
|
std::vector<uint8_t> init_data = a.cb.ConsumeSentPacket();
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet,
|
|
|
|
|
SctpPacket::Parse(init_data));
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(
|
|
|
|
|
InitChunk init_chunk,
|
|
|
|
|
InitChunk::Parse(init_packet.descriptors()[0].data));
|
|
|
|
|
z.socket.ReceivePacket(init_data);
|
|
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
|
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
|
|
|
|
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
|
|
|
|
|
|
|
|
|
|
// Receive a short unordered message with tsn=INITIAL_TSN+1
|
|
|
|
|
TSN tsn = init_chunk.initial_tsn();
|
|
|
|
|
AnyDataChunk::Options opts;
|
|
|
|
|
opts.is_beginning = Data::IsBeginning(true);
|
|
|
|
|
opts.is_end = Data::IsEnd(true);
|
|
|
|
|
opts.is_unordered = IsUnordered(true);
|
|
|
|
|
z.socket.ReceivePacket(
|
|
|
|
|
SctpPacket::Builder(z.socket.verification_tag(), z.options)
|
|
|
|
|
.Add(DataChunk(TSN(*tsn + 1), StreamID(1), SSN(0), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(10), opts))
|
|
|
|
|
.Build());
|
|
|
|
|
|
|
|
|
|
// Now receive a longer _ordered_ message with [INITIAL_TSN, INITIAL_TSN+1].
|
|
|
|
|
// This isn't allowed as it reuses TSN=53 with different properties, but it
|
|
|
|
|
// shouldn't cause any issues.
|
|
|
|
|
opts.is_unordered = IsUnordered(false);
|
|
|
|
|
opts.is_end = Data::IsEnd(false);
|
|
|
|
|
z.socket.ReceivePacket(
|
|
|
|
|
SctpPacket::Builder(z.socket.verification_tag(), z.options)
|
|
|
|
|
.Add(DataChunk(tsn, StreamID(1), SSN(0), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(10), opts))
|
|
|
|
|
.Build());
|
|
|
|
|
|
|
|
|
|
opts.is_beginning = Data::IsBeginning(false);
|
|
|
|
|
opts.is_end = Data::IsEnd(true);
|
|
|
|
|
z.socket.ReceivePacket(
|
|
|
|
|
SctpPacket::Builder(z.socket.verification_tag(), z.options)
|
|
|
|
|
.Add(DataChunk(TSN(*tsn + 1), StreamID(1), SSN(0), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(10), opts))
|
|
|
|
|
.Build());
|
|
|
|
|
}
|
2022-03-31 17:15:03 +02:00
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, CloseTwoStreamsAtTheSameTime) {
|
|
|
|
|
// Reported as https://crbug.com/1312009.
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(z.cb, OnIncomingStreamsReset(ElementsAre(StreamID(1)))).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnIncomingStreamsReset(ElementsAre(StreamID(2)))).Times(1);
|
|
|
|
|
EXPECT_CALL(a.cb, OnStreamsResetPerformed(ElementsAre(StreamID(1)))).Times(1);
|
|
|
|
|
EXPECT_CALL(a.cb, OnStreamsResetPerformed(ElementsAre(StreamID(2)))).Times(1);
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(1)}));
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(2)}));
|
|
|
|
|
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, CloseThreeStreamsAtTheSameTime) {
|
|
|
|
|
// Similar to CloseTwoStreamsAtTheSameTime, but ensuring that the two
|
|
|
|
|
// remaining streams are reset at the same time in the second request.
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(z.cb, OnIncomingStreamsReset(ElementsAre(StreamID(1)))).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnIncomingStreamsReset(
|
|
|
|
|
UnorderedElementsAre(StreamID(2), StreamID(3))))
|
|
|
|
|
.Times(1);
|
|
|
|
|
EXPECT_CALL(a.cb, OnStreamsResetPerformed(ElementsAre(StreamID(1)))).Times(1);
|
|
|
|
|
EXPECT_CALL(a.cb, OnStreamsResetPerformed(
|
|
|
|
|
UnorderedElementsAre(StreamID(2), StreamID(3))))
|
|
|
|
|
.Times(1);
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(3), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(1)}));
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(2)}));
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(3)}));
|
|
|
|
|
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
}
|
dcsctp: Handle rapid closing of streams
When streams were to be reset, but there was already an ongoing
stream reset command in-flight, those streams wouldn't be properly
reset. When multiple streams were reset close to each other (within
an RTT), some streams would not have their SSNs reset, which resulted
in the stream resuming the SSN sequence. This could result in ordered
streams not delivering all messages as the receiver wouldn't deliver any
messages with SSN different from the expected SSN=0.
In WebRTC data channels, this would be triggered if multiple channels
were closed at roughly the same time, then re-opened, and continued
to be used in ordered mode. Unordered messages would still be delivered,
but the stream state could be wrong as the DATA_CHANNEL_ACK message is
sent ordered, and possibly not delivered.
There were unit tests for this, but not on the socket level using
real components, but just on the stream reset handler using mocks,
where this issue wasn't found. Also, those mocks didn't validate that
the correct parameters were provided, so that's fixed now.
The root cause was the PrepareResetStreams was only called if there
wasn't an ongoing stream reset operation in progress. One may try to
solve it by always calling PrepareResetStreams also when there is an
ongoing request, or to call it when the request has finished. One would
then realize that when the response of the outgoing stream request is
received, and CommitResetStreams is called, it would reset all paused
and (prepared) to-be-reset streams - not just the ones in the outgoing
stream request.
One cause of this was the lack of a single source of truth of the stream
states. The SendQueue kept track of which streams that were paused, but
the stream reset handler kept track of which streams that were
resetting. As that's error prone, this CL moves the source of truth
completely to the SendQueue and defining explicit stream pause states. A
stream can be in one of these possible states:
* Not paused. This is the default for an active stream.
* Pending to be paused. This is when it's about to be reset, but
there is a message that has been partly sent, with fragments
remaining to be sent before it can be paused.
* Paused, with no partly sent message. In this state, it's ready to
be reset.
* Resetting. A stream transitions into this state when it has been
paused and has been included in an outgoing stream reset request.
When this request has been responded to, the stream can really be
reset (SSN=0, MID=0).
This CL also improves logging, and adds socket tests to catch this
issue.
Bug: webrtc:13994, chromium:1320194
Change-Id: I883570d1f277bc01e52b1afad62d6be2aca930a2
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/261180
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36771}
2022-05-02 17:15:57 +02:00
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, CloseStreamsWithPendingRequest) {
|
|
|
|
|
// Checks that stream reset requests are properly paused when they can't be
|
|
|
|
|
// immediately reset - i.e. when there is already an ongoing stream reset
|
|
|
|
|
// request (and there can only be a single one in-flight).
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(z.cb, OnIncomingStreamsReset(ElementsAre(StreamID(1)))).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnIncomingStreamsReset(
|
|
|
|
|
UnorderedElementsAre(StreamID(2), StreamID(3))))
|
|
|
|
|
.Times(1);
|
|
|
|
|
EXPECT_CALL(a.cb, OnStreamsResetPerformed(ElementsAre(StreamID(1)))).Times(1);
|
|
|
|
|
EXPECT_CALL(a.cb, OnStreamsResetPerformed(
|
|
|
|
|
UnorderedElementsAre(StreamID(2), StreamID(3))))
|
|
|
|
|
.Times(1);
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
SendOptions send_options = {.unordered = IsUnordered(false)};
|
|
|
|
|
|
|
|
|
|
// Send a few ordered messages
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), send_options);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(53), {1, 2}), send_options);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(3), PPID(53), {1, 2}), send_options);
|
|
|
|
|
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
// Receive these messages
|
|
|
|
|
absl::optional<DcSctpMessage> msg1 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg1.has_value());
|
|
|
|
|
EXPECT_EQ(msg1->stream_id(), StreamID(1));
|
|
|
|
|
absl::optional<DcSctpMessage> msg2 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg2.has_value());
|
|
|
|
|
EXPECT_EQ(msg2->stream_id(), StreamID(2));
|
|
|
|
|
absl::optional<DcSctpMessage> msg3 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg3.has_value());
|
|
|
|
|
EXPECT_EQ(msg3->stream_id(), StreamID(3));
|
|
|
|
|
|
|
|
|
|
// Reset the streams - not all at once.
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(1)}));
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> packet = a.cb.ConsumeSentPacket();
|
|
|
|
|
EXPECT_THAT(packet, HasReconfigWithStreams(ElementsAre(StreamID(1))));
|
|
|
|
|
z.socket.ReceivePacket(std::move(packet));
|
|
|
|
|
|
|
|
|
|
// Sending more reset requests while this one is ongoing.
|
|
|
|
|
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(2)}));
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(3)}));
|
|
|
|
|
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
// Send a few more ordered messages
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), send_options);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(53), {1, 2}), send_options);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(3), PPID(53), {1, 2}), send_options);
|
|
|
|
|
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
// Receive these messages
|
|
|
|
|
absl::optional<DcSctpMessage> msg4 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg4.has_value());
|
|
|
|
|
EXPECT_EQ(msg4->stream_id(), StreamID(1));
|
|
|
|
|
absl::optional<DcSctpMessage> msg5 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg5.has_value());
|
|
|
|
|
EXPECT_EQ(msg5->stream_id(), StreamID(2));
|
|
|
|
|
absl::optional<DcSctpMessage> msg6 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg6.has_value());
|
|
|
|
|
EXPECT_EQ(msg6->stream_id(), StreamID(3));
|
2022-05-02 13:04:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, StreamsHaveInitialPriority) {
|
|
|
|
|
DcSctpOptions options = {.default_stream_priority = StreamPriority(42)};
|
|
|
|
|
SocketUnderTest a("A", options);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(a.socket.GetStreamPriority(StreamID(1)),
|
|
|
|
|
options.default_stream_priority);
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(a.socket.GetStreamPriority(StreamID(2)),
|
|
|
|
|
options.default_stream_priority);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, CanChangeStreamPriority) {
|
|
|
|
|
DcSctpOptions options = {.default_stream_priority = StreamPriority(42)};
|
|
|
|
|
SocketUnderTest a("A", options);
|
|
|
|
|
|
|
|
|
|
a.socket.SetStreamPriority(StreamID(1), StreamPriority(43));
|
|
|
|
|
EXPECT_EQ(a.socket.GetStreamPriority(StreamID(1)), StreamPriority(43));
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
|
|
|
|
|
a.socket.SetStreamPriority(StreamID(2), StreamPriority(43));
|
|
|
|
|
EXPECT_EQ(a.socket.GetStreamPriority(StreamID(2)), StreamPriority(43));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_P(DcSctpSocketParametrizedTest, WillHandoverPriority) {
|
|
|
|
|
DcSctpOptions options = {.default_stream_priority = StreamPriority(42)};
|
|
|
|
|
auto a = std::make_unique<SocketUnderTest>("A", options);
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(*a, z);
|
|
|
|
|
|
|
|
|
|
a->socket.SetStreamPriority(StreamID(1), StreamPriority(43));
|
|
|
|
|
a->socket.Send(DcSctpMessage(StreamID(2), PPID(53), {1, 2}), kSendOptions);
|
|
|
|
|
a->socket.SetStreamPriority(StreamID(2), StreamPriority(43));
|
|
|
|
|
|
|
|
|
|
ExchangeMessages(*a, z);
|
|
|
|
|
|
|
|
|
|
a = MaybeHandoverSocket(std::move(a));
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(a->socket.GetStreamPriority(StreamID(1)), StreamPriority(43));
|
|
|
|
|
EXPECT_EQ(a->socket.GetStreamPriority(StreamID(2)), StreamPriority(43));
|
|
|
|
|
}
|
2022-05-05 12:08:37 +02:00
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, ReconnectSocketWithPendingStreamReset) {
|
|
|
|
|
// This is an issue found by fuzzing, and doesn't really make sense in WebRTC
|
|
|
|
|
// data channels as a SCTP connection is never ever closed and then
|
|
|
|
|
// reconnected. SCTP connections are closed when the peer connection is
|
|
|
|
|
// deleted, and then it doesn't do more with SCTP.
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(1)}));
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(z.cb, OnAborted).Times(1);
|
|
|
|
|
a.socket.Close();
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(a.socket.state(), SocketState::kClosed);
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnConnected).Times(1);
|
|
|
|
|
EXPECT_CALL(z.cb, OnConnected).Times(1);
|
|
|
|
|
a.socket.Connect();
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(2)}));
|
|
|
|
|
}
|
2022-05-12 22:40:04 +02:00
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, SmallSentMessagesWithPrioWillArriveInSpecificOrder) {
|
|
|
|
|
DcSctpOptions options = {.enable_message_interleaving = true};
|
|
|
|
|
SocketUnderTest a("A", options);
|
|
|
|
|
SocketUnderTest z("A", options);
|
|
|
|
|
|
|
|
|
|
a.socket.SetStreamPriority(StreamID(1), StreamPriority(700));
|
|
|
|
|
a.socket.SetStreamPriority(StreamID(2), StreamPriority(200));
|
|
|
|
|
a.socket.SetStreamPriority(StreamID(3), StreamPriority(100));
|
|
|
|
|
|
|
|
|
|
// Enqueue messages before connecting the socket, to ensure they aren't send
|
|
|
|
|
// as soon as Send() is called.
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(3), PPID(301),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(101),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(201),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(102),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(103),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
std::vector<uint32_t> received_ppids;
|
|
|
|
|
for (;;) {
|
|
|
|
|
absl::optional<DcSctpMessage> msg = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
if (!msg.has_value()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
received_ppids.push_back(*msg->ppid());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(received_ppids, ElementsAre(101, 102, 103, 201, 301));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, LargeSentMessagesWithPrioWillArriveInSpecificOrder) {
|
|
|
|
|
DcSctpOptions options = {.enable_message_interleaving = true};
|
|
|
|
|
SocketUnderTest a("A", options);
|
|
|
|
|
SocketUnderTest z("A", options);
|
|
|
|
|
|
|
|
|
|
a.socket.SetStreamPriority(StreamID(1), StreamPriority(700));
|
|
|
|
|
a.socket.SetStreamPriority(StreamID(2), StreamPriority(200));
|
|
|
|
|
a.socket.SetStreamPriority(StreamID(3), StreamPriority(100));
|
|
|
|
|
|
|
|
|
|
// Enqueue messages before connecting the socket, to ensure they aren't send
|
|
|
|
|
// as soon as Send() is called.
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(3), PPID(301),
|
|
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(101),
|
|
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(201),
|
|
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(102),
|
|
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(GetReceivedMessagePpids(z), ElementsAre(101, 102, 201, 301));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, MessageWithHigherPrioWillInterruptLowerPrioMessage) {
|
|
|
|
|
DcSctpOptions options = {.enable_message_interleaving = true};
|
|
|
|
|
SocketUnderTest a("A", options);
|
|
|
|
|
SocketUnderTest z("Z", options);
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
a.socket.SetStreamPriority(StreamID(2), StreamPriority(128));
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(201),
|
|
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
|
|
|
|
|
// Due to a non-zero initial congestion window, the message will already start
|
|
|
|
|
// to send, but will not succeed to be sent completely before filling the
|
|
|
|
|
// congestion window or stopping due to reaching how many packets that can be
|
|
|
|
|
// sent at once (max burst). The important thing is that the entire message
|
|
|
|
|
// doesn't get sent in full.
|
|
|
|
|
|
|
|
|
|
// Now enqueue two messages; one small and one large higher priority message.
|
|
|
|
|
a.socket.SetStreamPriority(StreamID(1), StreamPriority(512));
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(101),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(102),
|
|
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(GetReceivedMessagePpids(z), ElementsAre(101, 102, 201));
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-27 14:44:57 +02:00
|
|
|
TEST(DcSctpSocketTest, LifecycleEventsAreGeneratedForAckedMessages) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(101),
|
|
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
{.lifecycle_id = LifecycleId(41)});
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(102),
|
|
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
kSendOptions);
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(2), PPID(103),
|
|
|
|
|
std::vector<uint8_t>(kLargeMessageSize)),
|
|
|
|
|
{.lifecycle_id = LifecycleId(42)});
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleMessageDelivered(LifecycleId(41)));
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleEnd(LifecycleId(41)));
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleMessageDelivered(LifecycleId(42)));
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleEnd(LifecycleId(42)));
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
// In case of delayed ack.
|
|
|
|
|
AdvanceTime(a, z, a.options.delayed_ack_max_timeout);
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(GetReceivedMessagePpids(z), ElementsAre(101, 102, 103));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, LifecycleEventsForFailMaxRetransmissions) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> payload(a.options.mtu - 100);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(51), payload),
|
|
|
|
|
{
|
|
|
|
|
.max_retransmissions = 0,
|
|
|
|
|
.lifecycle_id = LifecycleId(1),
|
|
|
|
|
});
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(52), payload),
|
|
|
|
|
{
|
|
|
|
|
.max_retransmissions = 0,
|
|
|
|
|
.lifecycle_id = LifecycleId(2),
|
|
|
|
|
});
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload),
|
|
|
|
|
{
|
|
|
|
|
.max_retransmissions = 0,
|
|
|
|
|
.lifecycle_id = LifecycleId(3),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// First DATA
|
|
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
|
|
|
|
// Second DATA (lost)
|
|
|
|
|
a.cb.ConsumeSentPacket();
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleMessageDelivered(LifecycleId(1)));
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleEnd(LifecycleId(1)));
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleMessageExpired(LifecycleId(2),
|
|
|
|
|
/*maybe_delivered=*/true));
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleEnd(LifecycleId(2)));
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleMessageDelivered(LifecycleId(3)));
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleEnd(LifecycleId(3)));
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
// Handle delayed SACK.
|
|
|
|
|
AdvanceTime(a, z, a.options.delayed_ack_max_timeout);
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
// The chunk is now NACKed. Let the RTO expire, to discard the message.
|
|
|
|
|
AdvanceTime(a, z, a.options.rto_initial);
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
// Handle delayed SACK.
|
|
|
|
|
AdvanceTime(a, z, a.options.delayed_ack_max_timeout);
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(GetReceivedMessagePpids(z), ElementsAre(51, 53));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, LifecycleEventsForExpiredMessageWithRetransmitLimit) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
// Will not be able to send it in full within the congestion window, but will
|
|
|
|
|
// need to wait for SACKs to be received for more fragments to be sent.
|
|
|
|
|
std::vector<uint8_t> payload(kLargeMessageSize);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(51), payload),
|
|
|
|
|
{
|
|
|
|
|
.max_retransmissions = 0,
|
|
|
|
|
.lifecycle_id = LifecycleId(1),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// First DATA
|
|
|
|
|
z.socket.ReceivePacket(a.cb.ConsumeSentPacket());
|
|
|
|
|
// Second DATA (lost)
|
|
|
|
|
a.cb.ConsumeSentPacket();
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleMessageExpired(LifecycleId(1),
|
|
|
|
|
/*maybe_delivered=*/false));
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleEnd(LifecycleId(1)));
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(GetReceivedMessagePpids(z), IsEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, LifecycleEventsForExpiredMessageWithLifetimeLimit) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
// Send it before the socket is connected, to prevent it from being sent too
|
|
|
|
|
// quickly. The idea is that it should be expired before even attempting to
|
|
|
|
|
// send it in full.
|
|
|
|
|
std::vector<uint8_t> payload(kSmallMessageSize);
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(51), payload),
|
|
|
|
|
{
|
|
|
|
|
.lifetime = DurationMs(100),
|
|
|
|
|
.lifecycle_id = LifecycleId(1),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
AdvanceTime(a, z, DurationMs(200));
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleMessageExpired(LifecycleId(1),
|
|
|
|
|
/*maybe_delivered=*/false));
|
|
|
|
|
EXPECT_CALL(a.cb, OnLifecycleEnd(LifecycleId(1)));
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(GetReceivedMessagePpids(z), IsEmpty());
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-18 21:12:31 +00:00
|
|
|
TEST_P(DcSctpSocketParametrizedTest, ExposesTheNumberOfNegotiatedStreams) {
|
|
|
|
|
DcSctpOptions options_a = {
|
|
|
|
|
.announced_maximum_incoming_streams = 12,
|
|
|
|
|
.announced_maximum_outgoing_streams = 45,
|
|
|
|
|
};
|
|
|
|
|
SocketUnderTest a("A", options_a);
|
|
|
|
|
|
|
|
|
|
DcSctpOptions options_z = {
|
|
|
|
|
.announced_maximum_incoming_streams = 23,
|
|
|
|
|
.announced_maximum_outgoing_streams = 34,
|
|
|
|
|
};
|
|
|
|
|
auto z = std::make_unique<SocketUnderTest>("Z", options_z);
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, *z);
|
|
|
|
|
z = MaybeHandoverSocket(std::move(z));
|
|
|
|
|
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(Metrics metrics_a, a.socket.GetMetrics());
|
|
|
|
|
EXPECT_EQ(metrics_a.negotiated_maximum_incoming_streams, 12);
|
|
|
|
|
EXPECT_EQ(metrics_a.negotiated_maximum_outgoing_streams, 23);
|
|
|
|
|
|
|
|
|
|
ASSERT_HAS_VALUE_AND_ASSIGN(Metrics metrics_z, z->socket.GetMetrics());
|
|
|
|
|
EXPECT_EQ(metrics_z.negotiated_maximum_incoming_streams, 23);
|
|
|
|
|
EXPECT_EQ(metrics_z.negotiated_maximum_outgoing_streams, 12);
|
|
|
|
|
}
|
2022-09-01 08:46:32 +00:00
|
|
|
|
|
|
|
|
TEST(DcSctpSocketTest, ResetStreamsDeferred) {
|
|
|
|
|
// Guaranteed to be fragmented into two fragments.
|
|
|
|
|
constexpr size_t kTwoFragmentsSize = DcSctpOptions::kMaxSafeMTUSize + 100;
|
|
|
|
|
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53),
|
|
|
|
|
std::vector<uint8_t>(kTwoFragmentsSize)),
|
|
|
|
|
{});
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(54),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
{});
|
|
|
|
|
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(1)}));
|
|
|
|
|
|
|
|
|
|
auto data1 = a.cb.ConsumeSentPacket();
|
|
|
|
|
auto data2 = a.cb.ConsumeSentPacket();
|
|
|
|
|
auto data3 = a.cb.ConsumeSentPacket();
|
|
|
|
|
auto reconfig = a.cb.ConsumeSentPacket();
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(data1, HasDataChunkWithSsn(SSN(0)));
|
|
|
|
|
EXPECT_THAT(data2, HasDataChunkWithSsn(SSN(0)));
|
|
|
|
|
EXPECT_THAT(data3, HasDataChunkWithSsn(SSN(1)));
|
|
|
|
|
EXPECT_THAT(reconfig, HasReconfigWithStreams(ElementsAre(StreamID(1))));
|
|
|
|
|
|
|
|
|
|
// Receive them slightly out of order to make stream resetting deferred.
|
|
|
|
|
z.socket.ReceivePacket(reconfig);
|
|
|
|
|
|
|
|
|
|
z.socket.ReceivePacket(data1);
|
|
|
|
|
z.socket.ReceivePacket(data2);
|
|
|
|
|
z.socket.ReceivePacket(data3);
|
|
|
|
|
|
|
|
|
|
absl::optional<DcSctpMessage> msg1 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg1.has_value());
|
|
|
|
|
EXPECT_EQ(msg1->stream_id(), StreamID(1));
|
|
|
|
|
EXPECT_EQ(msg1->ppid(), PPID(53));
|
|
|
|
|
EXPECT_EQ(msg1->payload().size(), kTwoFragmentsSize);
|
|
|
|
|
|
|
|
|
|
absl::optional<DcSctpMessage> msg2 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg2.has_value());
|
|
|
|
|
EXPECT_EQ(msg2->stream_id(), StreamID(1));
|
|
|
|
|
EXPECT_EQ(msg2->ppid(), PPID(54));
|
|
|
|
|
EXPECT_EQ(msg2->payload().size(), kSmallMessageSize);
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnStreamsResetPerformed(ElementsAre(StreamID(1))));
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
// Z sent "in progress", which will make A buffer packets until it's sure
|
|
|
|
|
// that the reconfiguration has been applied. A will retry - wait for that.
|
|
|
|
|
AdvanceTime(a, z, a.options.rto_initial);
|
|
|
|
|
|
|
|
|
|
auto reconfig2 = a.cb.ConsumeSentPacket();
|
|
|
|
|
EXPECT_THAT(reconfig2, HasReconfigWithStreams(ElementsAre(StreamID(1))));
|
|
|
|
|
EXPECT_CALL(z.cb, OnIncomingStreamsReset(ElementsAre(StreamID(1))));
|
|
|
|
|
z.socket.ReceivePacket(reconfig2);
|
|
|
|
|
|
|
|
|
|
auto reconfig3 = z.cb.ConsumeSentPacket();
|
|
|
|
|
EXPECT_THAT(reconfig3,
|
|
|
|
|
HasReconfigWithResponse(
|
|
|
|
|
ReconfigurationResponseParameter::Result::kSuccessPerformed));
|
|
|
|
|
a.socket.ReceivePacket(reconfig3);
|
|
|
|
|
|
|
|
|
|
EXPECT_THAT(data1, HasDataChunkWithSsn(SSN(0)));
|
|
|
|
|
EXPECT_THAT(data2, HasDataChunkWithSsn(SSN(0)));
|
|
|
|
|
EXPECT_THAT(data3, HasDataChunkWithSsn(SSN(1)));
|
|
|
|
|
EXPECT_THAT(reconfig, HasReconfigWithStreams(ElementsAre(StreamID(1))));
|
|
|
|
|
|
|
|
|
|
// Send a new message after the stream has been reset.
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(55),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
{});
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
absl::optional<DcSctpMessage> msg3 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg3.has_value());
|
|
|
|
|
EXPECT_EQ(msg3->stream_id(), StreamID(1));
|
|
|
|
|
EXPECT_EQ(msg3->ppid(), PPID(55));
|
|
|
|
|
EXPECT_EQ(msg3->payload().size(), kSmallMessageSize);
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-01 13:00:28 +00:00
|
|
|
TEST(DcSctpSocketTest, ResetStreamsWithPausedSenderResumesWhenPerformed) {
|
|
|
|
|
SocketUnderTest a("A");
|
|
|
|
|
SocketUnderTest z("Z");
|
|
|
|
|
|
|
|
|
|
ConnectSockets(a, z);
|
|
|
|
|
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(51),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
{});
|
|
|
|
|
|
|
|
|
|
a.socket.ResetStreams(std::vector<StreamID>({StreamID(1)}));
|
|
|
|
|
|
|
|
|
|
// Will be queued, as the stream has an outstanding reset operation.
|
|
|
|
|
a.socket.Send(DcSctpMessage(StreamID(1), PPID(52),
|
|
|
|
|
std::vector<uint8_t>(kSmallMessageSize)),
|
|
|
|
|
{});
|
|
|
|
|
|
|
|
|
|
EXPECT_CALL(a.cb, OnStreamsResetPerformed(ElementsAre(StreamID(1))));
|
|
|
|
|
EXPECT_CALL(z.cb, OnIncomingStreamsReset(ElementsAre(StreamID(1))));
|
|
|
|
|
ExchangeMessages(a, z);
|
|
|
|
|
|
|
|
|
|
absl::optional<DcSctpMessage> msg1 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg1.has_value());
|
|
|
|
|
EXPECT_EQ(msg1->stream_id(), StreamID(1));
|
|
|
|
|
EXPECT_EQ(msg1->ppid(), PPID(51));
|
|
|
|
|
EXPECT_EQ(msg1->payload().size(), kSmallMessageSize);
|
|
|
|
|
|
|
|
|
|
absl::optional<DcSctpMessage> msg2 = z.cb.ConsumeReceivedMessage();
|
|
|
|
|
ASSERT_TRUE(msg2.has_value());
|
|
|
|
|
EXPECT_EQ(msg2->stream_id(), StreamID(1));
|
|
|
|
|
EXPECT_EQ(msg2->ppid(), PPID(52));
|
|
|
|
|
EXPECT_EQ(msg2->payload().size(), kSmallMessageSize);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-08 09:56:59 +02:00
|
|
|
} // namespace
|
|
|
|
|
} // namespace dcsctp
|