dcsctp: Cleanup use of matchers

More general matches that can be reused, less specialized ones.

Bug: None
Change-Id: I12ea98caf4f566168566173a509c204bd25e5a13
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/321123
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40804}
This commit is contained in:
Victor Boivie 2023-09-21 14:33:18 +02:00 committed by WebRTC LUCI CQ
parent 7892f05411
commit 77df7ffd0b

View File

@ -23,6 +23,7 @@
#include "api/array_view.h"
#include "net/dcsctp/common/handover_testing.h"
#include "net/dcsctp/common/math.h"
#include "net/dcsctp/packet/chunk/abort_chunk.h"
#include "net/dcsctp/packet/chunk/chunk.h"
#include "net/dcsctp/packet/chunk/cookie_ack_chunk.h"
#include "net/dcsctp/packet/chunk/cookie_echo_chunk.h"
@ -34,6 +35,7 @@
#include "net/dcsctp/packet/chunk/idata_chunk.h"
#include "net/dcsctp/packet/chunk/init_ack_chunk.h"
#include "net/dcsctp/packet/chunk/init_chunk.h"
#include "net/dcsctp/packet/chunk/reconfig_chunk.h"
#include "net/dcsctp/packet/chunk/sack_chunk.h"
#include "net/dcsctp/packet/chunk/shutdown_chunk.h"
#include "net/dcsctp/packet/error_cause/error_cause.h"
@ -41,6 +43,7 @@
#include "net/dcsctp/packet/parameter/heartbeat_info_parameter.h"
#include "net/dcsctp/packet/parameter/outgoing_ssn_reset_request_parameter.h"
#include "net/dcsctp/packet/parameter/parameter.h"
#include "net/dcsctp/packet/parameter/reconfiguration_response_parameter.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/packet/tlv_trait.h"
#include "net/dcsctp/public/dcsctp_message.h"
@ -65,6 +68,7 @@ using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Property;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
@ -74,243 +78,135 @@ constexpr size_t kSmallMessageSize = 10;
constexpr int kMaxBurstPackets = 4;
constexpr DcSctpOptions kDefaultOptions;
MATCHER_P(HasDataChunkWithStreamId, stream_id, "") {
MATCHER_P(HasChunks, chunks, "") {
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg, kDefaultOptions);
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;
return ExplainMatchResult(chunks, packet->descriptors(), result_listener);
}
MATCHER_P(HasDataChunkWithPPID, ppid, "") {
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg, kDefaultOptions);
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;
MATCHER_P(IsChunkType, chunk_type, "") {
return ExplainMatchResult(chunk_type, arg.type, result_listener);
}
MATCHER_P(HasDataChunkWithSsn, ssn, "") {
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg, kDefaultOptions);
if (!packet.has_value()) {
*result_listener << "data didn't parse as an SctpPacket";
MATCHER_P(IsDataChunk, properties, "") {
if (arg.type != DataChunk::kType) {
*result_listener << "the chunk is not a data chunk";
return false;
}
if (packet->descriptors()[0].type != DataChunk::kType) {
*result_listener << "the first chunk in the packet is not a data chunk";
absl::optional<DataChunk> chunk = DataChunk::Parse(arg.data);
if (!chunk.has_value()) {
*result_listener << "The chunk didn't parse as 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;
return ExplainMatchResult(properties, *chunk, result_listener);
}
MATCHER_P(HasDataChunkWithMid, mid, "") {
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg, kDefaultOptions);
if (!packet.has_value()) {
*result_listener << "data didn't parse as an SctpPacket";
MATCHER_P(IsSack, properties, "") {
if (arg.type != SackChunk::kType) {
*result_listener << "the chunk is not a sack chunk";
return false;
}
if (packet->descriptors()[0].type != IDataChunk::kType) {
*result_listener << "the first chunk in the packet is not an i-data chunk";
absl::optional<SackChunk> chunk = SackChunk::Parse(arg.data);
if (!chunk.has_value()) {
*result_listener << "The chunk didn't parse as a sack 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;
return ExplainMatchResult(properties, *chunk, result_listener);
}
MATCHER_P(HasSackWithCumAckTsn, tsn, "") {
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg, kDefaultOptions);
if (!packet.has_value()) {
*result_listener << "data didn't parse as an SctpPacket";
MATCHER_P(IsReConfig, properties, "") {
if (arg.type != ReConfigChunk::kType) {
*result_listener << "the chunk is not a re-config chunk";
return false;
}
if (packet->descriptors()[0].type != SackChunk::kType) {
*result_listener << "the first chunk in the packet is not a data chunk";
absl::optional<ReConfigChunk> chunk = ReConfigChunk::Parse(arg.data);
if (!chunk.has_value()) {
*result_listener << "The chunk didn't parse as a re-config 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;
return ExplainMatchResult(properties, *chunk, result_listener);
}
MATCHER(HasSackWithNoGapAckBlocks, "") {
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg, kDefaultOptions);
if (!packet.has_value()) {
*result_listener << "data didn't parse as an SctpPacket";
MATCHER_P(IsHeartbeatAck, properties, "") {
if (arg.type != HeartbeatAckChunk::kType) {
*result_listener << "the chunk is not a HeartbeatAckChunk";
return false;
}
if (packet->descriptors()[0].type != SackChunk::kType) {
*result_listener << "the first chunk in the packet is not a data chunk";
absl::optional<HeartbeatAckChunk> chunk = HeartbeatAckChunk::Parse(arg.data);
if (!chunk.has_value()) {
*result_listener << "The chunk didn't parse as a HeartbeatAckChunk";
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;
return ExplainMatchResult(properties, *chunk, result_listener);
}
MATCHER_P(HasReconfigWithStreams, streams_matcher, "") {
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg, kDefaultOptions);
if (!packet.has_value()) {
*result_listener << "data didn't parse as an SctpPacket";
MATCHER_P(IsHeartbeatRequest, properties, "") {
if (arg.type != HeartbeatRequestChunk::kType) {
*result_listener << "the chunk is not a HeartbeatRequestChunk";
return false;
}
if (packet->descriptors()[0].type != ReConfigChunk::kType) {
*result_listener << "the first chunk in the packet is not a data chunk";
absl::optional<HeartbeatRequestChunk> chunk =
HeartbeatRequestChunk::Parse(arg.data);
if (!chunk.has_value()) {
*result_listener << "The chunk didn't parse as a HeartbeatRequestChunk";
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;
return ExplainMatchResult(properties, *chunk, result_listener);
}
MATCHER_P(HasReconfigWithResponse, result, "") {
absl::optional<SctpPacket> packet = SctpPacket::Parse(arg, kDefaultOptions);
if (!packet.has_value()) {
*result_listener << "data didn't parse as an SctpPacket";
MATCHER_P(HasParameters, parameters, "") {
return ExplainMatchResult(parameters, arg.parameters().descriptors(),
result_listener);
}
MATCHER_P(IsOutgoingResetRequest, properties, "") {
if (arg.type != OutgoingSSNResetRequestParameter::kType) {
*result_listener
<< "the parameter is not an OutgoingSSNResetRequestParameter";
return false;
}
if (packet->descriptors()[0].type != ReConfigChunk::kType) {
*result_listener << "the first chunk in the packet is not a reconfig chunk";
absl::optional<OutgoingSSNResetRequestParameter> parameter =
OutgoingSSNResetRequestParameter::Parse(arg.data);
if (!parameter.has_value()) {
*result_listener
<< "The parameter didn't parse as an OutgoingSSNResetRequestParameter";
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 ExplainMatchResult(properties, *parameter, result_listener);
}
MATCHER_P(IsReconfigurationResponse, properties, "") {
if (arg.type != ReconfigurationResponseParameter::kType) {
*result_listener
<< "the parameter is not an ReconfigurationResponseParameter";
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";
absl::optional<ReconfigurationResponseParameter> parameter =
ReconfigurationResponseParameter::Parse(arg.data);
if (!parameter.has_value()) {
*result_listener
<< "The parameter didn't parse as an ReconfigurationResponseParameter";
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;
return ExplainMatchResult(properties, *parameter, result_listener);
}
TSN AddTo(TSN tsn, int delta) {
@ -634,10 +530,8 @@ TEST(DcSctpSocketTest, ResendInitAndEstablishConnection) {
a.socket.Connect();
// INIT is never received by Z.
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket init_packet,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z.options));
EXPECT_EQ(init_packet.descriptors()[0].type, InitChunk::kType);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
AdvanceTime(a, z, a.options.t1_init_timeout);
@ -661,19 +555,15 @@ TEST(DcSctpSocketTest, ResendingInitTooManyTimesAborts) {
a.socket.Connect();
// INIT is never received by Z.
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket init_packet,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z.options));
EXPECT_EQ(init_packet.descriptors()[0].type, InitChunk::kType);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
for (int i = 0; i < *a.options.max_init_retransmits; ++i) {
AdvanceTime(a, z, a.options.t1_init_timeout * (1 << i));
// INIT is resent
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket resent_init_packet,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z.options));
EXPECT_EQ(resent_init_packet.descriptors()[0].type, InitChunk::kType);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsChunkType(InitChunk::kType))));
}
// Another timeout, after the max init retransmits.
@ -696,10 +586,8 @@ TEST(DcSctpSocketTest, ResendCookieEchoAndEstablishConnection) {
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
// COOKIE_ECHO is never received by Z.
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket init_packet,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z.options));
EXPECT_EQ(init_packet.descriptors()[0].type, CookieEchoChunk::kType);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsChunkType(CookieEchoChunk::kType))));
AdvanceTime(a, z, a.options.t1_init_timeout);
@ -724,19 +612,15 @@ TEST(DcSctpSocketTest, ResendingCookieEchoTooManyTimesAborts) {
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
// COOKIE_ECHO is never received by Z.
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket init_packet,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z.options));
EXPECT_EQ(init_packet.descriptors()[0].type, CookieEchoChunk::kType);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsChunkType(CookieEchoChunk::kType))));
for (int i = 0; i < *a.options.max_init_retransmits; ++i) {
AdvanceTime(a, z, a.options.t1_cookie_timeout * (1 << i));
// COOKIE_ECHO is resent
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket resent_init_packet,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z.options));
EXPECT_EQ(resent_init_packet.descriptors()[0].type, CookieEchoChunk::kType);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsChunkType(CookieEchoChunk::kType))));
}
// Another timeout, after the max init retransmits.
@ -763,12 +647,9 @@ TEST(DcSctpSocketTest, DoesntSendMorePacketsUntilCookieAckHasBeenReceived) {
a.socket.ReceivePacket(z.cb.ConsumeSentPacket());
// COOKIE_ECHO is never received by Z.
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket cookie_echo_packet1,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z.options));
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);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsChunkType(CookieEchoChunk::kType),
IsDataChunk(_))));
EXPECT_THAT(a.cb.ConsumeSentPacket(), IsEmpty());
@ -784,12 +665,9 @@ TEST(DcSctpSocketTest, DoesntSendMorePacketsUntilCookieAckHasBeenReceived) {
AdvanceTime(a, z, a.options.t1_cookie_timeout - a.options.rto_initial);
// And this COOKIE-ECHO and DATA is also lost - never received by Z.
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket cookie_echo_packet2,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z.options));
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);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsChunkType(CookieEchoChunk::kType),
IsDataChunk(_))));
EXPECT_THAT(a.cb.ConsumeSentPacket(), IsEmpty());
@ -850,10 +728,8 @@ TEST(DcSctpSocketTest, ShutdownTimerExpiresTooManyTimeClosesConnection) {
AdvanceTime(a, z, DurationMs(a.options.rto_initial * (1 << i)));
// Dropping every shutdown chunk.
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket packet,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z.options));
EXPECT_EQ(packet.descriptors()[0].type, ShutdownChunk::kType);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsChunkType(ShutdownChunk::kType))));
EXPECT_TRUE(a.cb.ConsumeSentPacket().empty());
}
// The last expiry, makes it abort the connection.
@ -862,10 +738,8 @@ TEST(DcSctpSocketTest, ShutdownTimerExpiresTooManyTimeClosesConnection) {
a.options.rto_initial * (1 << *a.options.max_retransmissions));
EXPECT_EQ(a.socket.state(), SocketState::kClosed);
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket packet,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z.options));
EXPECT_EQ(packet.descriptors()[0].type, AbortChunk::kType);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsChunkType(AbortChunk::kType))));
EXPECT_TRUE(a.cb.ConsumeSentPacket().empty());
}
@ -972,15 +846,11 @@ TEST_P(DcSctpSocketParametrizedTest, SendingHeartbeatAnswersWithAck) {
a.socket.ReceivePacket(b.Build());
// HEARTBEAT_ACK is sent as a reply. Capture it.
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket ack_packet,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z->options));
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));
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsHeartbeatAck(
Property(&HeartbeatAckChunk::info,
Optional(Property(&HeartbeatInfoParameter::info,
ElementsAre(1, 2, 3, 4))))))));
MaybeHandoverSocketAndSendMessage(a, std::move(z));
}
@ -996,20 +866,16 @@ TEST_P(DcSctpSocketParametrizedTest, ExpectHeartbeatToBeSent) {
AdvanceTime(a, *z, a.options.heartbeat_interval);
std::vector<uint8_t> hb_packet_raw = a.cb.ConsumeSentPacket();
ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket hb_packet,
SctpPacket::Parse(hb_packet_raw, z->options));
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());
std::vector<uint8_t> packet = a.cb.ConsumeSentPacket();
// The info is a single 64-bit number.
EXPECT_THAT(hb.info()->info(), SizeIs(8));
EXPECT_THAT(
packet,
HasChunks(ElementsAre(IsHeartbeatRequest(Property(
&HeartbeatRequestChunk::info,
Optional(Property(&HeartbeatInfoParameter::info, SizeIs(8))))))));
// Feed it to Sock-z and expect a HEARTBEAT_ACK that will be propagated back.
z->socket.ReceivePacket(hb_packet_raw);
z->socket.ReceivePacket(packet);
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
MaybeHandoverSocketAndSendMessage(a, std::move(z));
@ -1110,10 +976,8 @@ TEST_P(DcSctpSocketParametrizedTest, RecoversAfterASuccessfulAck) {
RTC_LOG(LS_INFO) << "Expecting a new heartbeat";
AdvanceTime(a, *z, time_to_next_hearbeat);
ASSERT_HAS_VALUE_AND_ASSIGN(
SctpPacket another_packet,
SctpPacket::Parse(a.cb.ConsumeSentPacket(), z->options));
EXPECT_EQ(another_packet.descriptors()[0].type, HeartbeatRequestChunk::kType);
EXPECT_THAT(a.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsHeartbeatRequest(_))));
}
TEST_P(DcSctpSocketParametrizedTest, ResetStream) {
@ -1161,11 +1025,15 @@ TEST_P(DcSctpSocketParametrizedTest, ResetStreamWillMakeChunksStartAtZeroSsn) {
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
auto packet1 = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet1, HasDataChunkWithSsn(SSN(0)));
EXPECT_THAT(
packet1,
HasChunks(ElementsAre(IsDataChunk(Property(&DataChunk::ssn, SSN(0))))));
z->socket.ReceivePacket(packet1);
auto packet2 = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet2, HasDataChunkWithSsn(SSN(1)));
EXPECT_THAT(
packet2,
HasChunks(ElementsAre(IsDataChunk(Property(&DataChunk::ssn, SSN(1))))));
z->socket.ReceivePacket(packet2);
// Handle SACK
@ -1191,11 +1059,15 @@ TEST_P(DcSctpSocketParametrizedTest, ResetStreamWillMakeChunksStartAtZeroSsn) {
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
auto packet3 = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet3, HasDataChunkWithSsn(SSN(0)));
EXPECT_THAT(
packet3,
HasChunks(ElementsAre(IsDataChunk(Property(&DataChunk::ssn, SSN(0))))));
z->socket.ReceivePacket(packet3);
auto packet4 = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet4, HasDataChunkWithSsn(SSN(1)));
EXPECT_THAT(
packet4,
HasChunks(ElementsAre(IsDataChunk(Property(&DataChunk::ssn, SSN(1))))));
z->socket.ReceivePacket(packet4);
// Handle SACK
@ -1219,13 +1091,15 @@ TEST_P(DcSctpSocketParametrizedTest,
a.socket.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {});
auto packet1 = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet1, HasDataChunkWithStreamId(StreamID(1)));
EXPECT_THAT(packet1, HasDataChunkWithSsn(SSN(0)));
EXPECT_THAT(packet1, HasChunks(ElementsAre(IsDataChunk(
AllOf(Property(&DataChunk::stream_id, StreamID(1)),
Property(&DataChunk::ssn, SSN(0)))))));
z->socket.ReceivePacket(packet1);
auto packet2 = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet1, HasDataChunkWithStreamId(StreamID(1)));
EXPECT_THAT(packet2, HasDataChunkWithSsn(SSN(1)));
EXPECT_THAT(packet2, HasChunks(ElementsAre(IsDataChunk(
AllOf(Property(&DataChunk::stream_id, StreamID(1)),
Property(&DataChunk::ssn, SSN(1)))))));
z->socket.ReceivePacket(packet2);
// Handle SACK
@ -1235,12 +1109,14 @@ TEST_P(DcSctpSocketParametrizedTest,
a.socket.Send(DcSctpMessage(StreamID(3), PPID(53), payload), {});
a.socket.Send(DcSctpMessage(StreamID(3), PPID(53), payload), {});
auto packet3 = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet3, HasDataChunkWithStreamId(StreamID(3)));
EXPECT_THAT(packet3, HasDataChunkWithSsn(SSN(0)));
EXPECT_THAT(packet3, HasChunks(ElementsAre(IsDataChunk(
AllOf(Property(&DataChunk::stream_id, StreamID(3)),
Property(&DataChunk::ssn, SSN(0)))))));
z->socket.ReceivePacket(packet3);
auto packet4 = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet4, HasDataChunkWithStreamId(StreamID(3)));
EXPECT_THAT(packet4, HasDataChunkWithSsn(SSN(1)));
EXPECT_THAT(packet4, HasChunks(ElementsAre(IsDataChunk(
AllOf(Property(&DataChunk::stream_id, StreamID(3)),
Property(&DataChunk::ssn, SSN(1)))))));
z->socket.ReceivePacket(packet4);
a.socket.ReceivePacket(z->cb.ConsumeSentPacket());
@ -1274,13 +1150,16 @@ TEST_P(DcSctpSocketParametrizedTest,
a.socket.Send(DcSctpMessage(StreamID(3), PPID(53), payload), {});
auto packet5 = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet5, HasDataChunkWithStreamId(StreamID(1)));
EXPECT_THAT(packet5, HasDataChunkWithSsn(SSN(2))); // Unchanged.
EXPECT_THAT(packet5,
HasChunks(ElementsAre(IsDataChunk(
AllOf(Property(&DataChunk::stream_id, StreamID(1)),
Property(&DataChunk::ssn, SSN(2))))))); // Unchanged.
z->socket.ReceivePacket(packet5);
auto packet6 = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet6, HasDataChunkWithStreamId(StreamID(3)));
EXPECT_THAT(packet6, HasDataChunkWithSsn(SSN(0))); // Reset.
EXPECT_THAT(packet6, HasChunks(ElementsAre(IsDataChunk(AllOf(
Property(&DataChunk::stream_id, StreamID(3)),
Property(&DataChunk::ssn, SSN(0))))))); // Reset
z->socket.ReceivePacket(packet6);
// Handle SACK
@ -1400,44 +1279,52 @@ TEST_P(DcSctpSocketParametrizedTest, SendManyFragmentedMessagesWithLimitedRtx) {
// First DATA, first fragment
std::vector<uint8_t> packet = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(51)));
EXPECT_THAT(packet, HasChunks(ElementsAre(
IsDataChunk(Property(&DataChunk::ppid, PPID(51))))));
z->socket.ReceivePacket(std::move(packet));
// First DATA, second fragment (lost)
packet = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(51)));
EXPECT_THAT(packet, HasChunks(ElementsAre(
IsDataChunk(Property(&DataChunk::ppid, PPID(51))))));
// Second DATA, first fragment
packet = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(52)));
EXPECT_THAT(packet, HasChunks(ElementsAre(
IsDataChunk(Property(&DataChunk::ppid, PPID(52))))));
z->socket.ReceivePacket(std::move(packet));
// Second DATA, second fragment (lost)
packet = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(52)));
EXPECT_THAT(packet, HasDataChunkWithSsn(SSN(0)));
EXPECT_THAT(packet, HasChunks(ElementsAre(IsDataChunk(
AllOf(Property(&DataChunk::ppid, PPID(52)),
Property(&DataChunk::ssn, SSN(0)))))));
// Third DATA, first fragment
packet = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(53)));
EXPECT_THAT(packet, HasDataChunkWithSsn(SSN(0)));
EXPECT_THAT(packet, HasChunks(ElementsAre(IsDataChunk(
AllOf(Property(&DataChunk::ppid, PPID(53)),
Property(&DataChunk::ssn, SSN(0)))))));
z->socket.ReceivePacket(std::move(packet));
// Third DATA, second fragment (lost)
packet = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(53)));
EXPECT_THAT(packet, HasDataChunkWithSsn(SSN(0)));
EXPECT_THAT(packet, HasChunks(ElementsAre(IsDataChunk(
AllOf(Property(&DataChunk::ppid, PPID(53)),
Property(&DataChunk::ssn, SSN(0)))))));
// Fourth DATA, first fragment
packet = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(54)));
EXPECT_THAT(packet, HasDataChunkWithSsn(SSN(0)));
EXPECT_THAT(packet, HasChunks(ElementsAre(IsDataChunk(
AllOf(Property(&DataChunk::ppid, PPID(54)),
Property(&DataChunk::ssn, SSN(0)))))));
z->socket.ReceivePacket(std::move(packet));
// Fourth DATA, second fragment
packet = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet, HasDataChunkWithPPID(PPID(54)));
EXPECT_THAT(packet, HasDataChunkWithSsn(SSN(0)));
EXPECT_THAT(packet, HasChunks(ElementsAre(IsDataChunk(
AllOf(Property(&DataChunk::ppid, PPID(54)),
Property(&DataChunk::ssn, SSN(0)))))));
z->socket.ReceivePacket(std::move(packet));
ExchangeMessages(a, *z);
@ -1562,7 +1449,9 @@ TEST(DcSctpSocketTest, PassingHighWatermarkWillOnlyAcceptCumAckTsn) {
// First DATA will always trigger a SACK. It's not interesting.
EXPECT_THAT(z.cb.ConsumeSentPacket(),
AllOf(HasSackWithCumAckTsn(tsn), HasSackWithNoGapAckBlocks()));
HasChunks(ElementsAre(IsSack(
AllOf(Property(&SackChunk::cumulative_tsn_ack, tsn),
Property(&SackChunk::gap_ack_blocks, IsEmpty()))))));
// This DATA should be accepted - it's advancing cum ack tsn.
z.socket.ReceivePacket(
@ -1575,9 +1464,10 @@ TEST(DcSctpSocketTest, PassingHighWatermarkWillOnlyAcceptCumAckTsn) {
// The receiver might have moved into delayed ack mode.
AdvanceTime(a, z, z.options.rto_initial);
EXPECT_THAT(
z.cb.ConsumeSentPacket(),
AllOf(HasSackWithCumAckTsn(AddTo(tsn, 1)), HasSackWithNoGapAckBlocks()));
EXPECT_THAT(z.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsSack(
AllOf(Property(&SackChunk::cumulative_tsn_ack, AddTo(tsn, 1)),
Property(&SackChunk::gap_ack_blocks, IsEmpty()))))));
// This DATA will not be accepted - it's not advancing cum ack tsn.
z.socket.ReceivePacket(
@ -1588,9 +1478,10 @@ TEST(DcSctpSocketTest, PassingHighWatermarkWillOnlyAcceptCumAckTsn) {
.Build());
// Sack will be sent in IMMEDIATE mode when this is happening.
EXPECT_THAT(
z.cb.ConsumeSentPacket(),
AllOf(HasSackWithCumAckTsn(AddTo(tsn, 1)), HasSackWithNoGapAckBlocks()));
EXPECT_THAT(z.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsSack(
AllOf(Property(&SackChunk::cumulative_tsn_ack, AddTo(tsn, 1)),
Property(&SackChunk::gap_ack_blocks, IsEmpty()))))));
// This DATA will not be accepted either.
z.socket.ReceivePacket(
@ -1601,9 +1492,10 @@ TEST(DcSctpSocketTest, PassingHighWatermarkWillOnlyAcceptCumAckTsn) {
.Build());
// Sack will be sent in IMMEDIATE mode when this is happening.
EXPECT_THAT(
z.cb.ConsumeSentPacket(),
AllOf(HasSackWithCumAckTsn(AddTo(tsn, 1)), HasSackWithNoGapAckBlocks()));
EXPECT_THAT(z.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsSack(
AllOf(Property(&SackChunk::cumulative_tsn_ack, AddTo(tsn, 1)),
Property(&SackChunk::gap_ack_blocks, IsEmpty()))))));
// This DATA should be accepted, and it fills the reassembly queue.
z.socket.ReceivePacket(
@ -1616,9 +1508,10 @@ TEST(DcSctpSocketTest, PassingHighWatermarkWillOnlyAcceptCumAckTsn) {
// The receiver might have moved into delayed ack mode.
AdvanceTime(a, z, z.options.rto_initial);
EXPECT_THAT(
z.cb.ConsumeSentPacket(),
AllOf(HasSackWithCumAckTsn(AddTo(tsn, 2)), HasSackWithNoGapAckBlocks()));
EXPECT_THAT(z.cb.ConsumeSentPacket(),
HasChunks(ElementsAre(IsSack(
AllOf(Property(&SackChunk::cumulative_tsn_ack, AddTo(tsn, 2)),
Property(&SackChunk::gap_ack_blocks, IsEmpty()))))));
EXPECT_CALL(z.cb, OnAborted(ErrorKind::kResourceExhaustion, _));
EXPECT_CALL(z.cb, OnClosed).Times(0);
@ -2431,7 +2324,10 @@ TEST(DcSctpSocketTest, CloseStreamsWithPendingRequest) {
a.socket.ResetStreams(std::vector<StreamID>({StreamID(1)}));
std::vector<uint8_t> packet = a.cb.ConsumeSentPacket();
EXPECT_THAT(packet, HasReconfigWithStreams(ElementsAre(StreamID(1))));
EXPECT_THAT(packet, HasChunks(ElementsAre(IsReConfig(HasParameters(
ElementsAre(IsOutgoingResetRequest(Property(
&OutgoingSSNResetRequestParameter::stream_ids,
ElementsAre(StreamID(1))))))))));
z.socket.ReceivePacket(std::move(packet));
// Sending more reset requests while this one is ongoing.
@ -2813,10 +2709,19 @@ TEST(DcSctpSocketTest, ResetStreamsDeferred) {
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))));
EXPECT_THAT(
data1,
HasChunks(ElementsAre(IsDataChunk(Property(&DataChunk::ssn, SSN(0))))));
EXPECT_THAT(
data2,
HasChunks(ElementsAre(IsDataChunk(Property(&DataChunk::ssn, SSN(0))))));
EXPECT_THAT(
data3,
HasChunks(ElementsAre(IsDataChunk(Property(&DataChunk::ssn, SSN(1))))));
EXPECT_THAT(reconfig, HasChunks(ElementsAre(IsReConfig(HasParameters(
ElementsAre(IsOutgoingResetRequest(Property(
&OutgoingSSNResetRequestParameter::stream_ids,
ElementsAre(StreamID(1))))))))));
// Receive them slightly out of order to make stream resetting deferred.
z.socket.ReceivePacket(reconfig);
@ -2845,20 +2750,34 @@ TEST(DcSctpSocketTest, ResetStreamsDeferred) {
AdvanceTime(a, z, a.options.rto_initial);
auto reconfig2 = a.cb.ConsumeSentPacket();
EXPECT_THAT(reconfig2, HasReconfigWithStreams(ElementsAre(StreamID(1))));
EXPECT_THAT(reconfig2, HasChunks(ElementsAre(IsReConfig(HasParameters(
ElementsAre(IsOutgoingResetRequest(Property(
&OutgoingSSNResetRequestParameter::stream_ids,
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));
EXPECT_THAT(reconfig3, HasChunks(ElementsAre(IsReConfig(HasParameters(
ElementsAre(IsReconfigurationResponse(Property(
&ReconfigurationResponseParameter::result,
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))));
EXPECT_THAT(
data1,
HasChunks(ElementsAre(IsDataChunk(Property(&DataChunk::ssn, SSN(0))))));
EXPECT_THAT(
data2,
HasChunks(ElementsAre(IsDataChunk(Property(&DataChunk::ssn, SSN(0))))));
EXPECT_THAT(
data3,
HasChunks(ElementsAre(IsDataChunk(Property(&DataChunk::ssn, SSN(1))))));
EXPECT_THAT(reconfig, HasChunks(ElementsAre(IsReConfig(HasParameters(
ElementsAre(IsOutgoingResetRequest(Property(
&OutgoingSSNResetRequestParameter::stream_ids,
ElementsAre(StreamID(1))))))))));
// Send a new message after the stream has been reset.
a.socket.Send(DcSctpMessage(StreamID(1), PPID(55),