/* * 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 #include #include #include #include #include #include "absl/memory/memory.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "api/array_view.h" #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" #include "net/dcsctp/packet/chunk/shutdown_chunk.h" #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" #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" #include "net/dcsctp/public/types.h" #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" namespace dcsctp { namespace { using ::testing::_; using ::testing::AllOf; using ::testing::ElementsAre; using ::testing::HasSubstr; using ::testing::IsEmpty; using ::testing::SizeIs; constexpr SendOptions kSendOptions; constexpr size_t kLargeMessageSize = DcSctpOptions::kMaxSafeMTUSize * 20; static constexpr size_t kSmallMessageSize = 10; MATCHER_P(HasDataChunkWithSsn, ssn, "") { absl::optional 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 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 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 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 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 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 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 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; } TSN AddTo(TSN tsn, int delta) { return TSN(*tsn + delta); } DcSctpOptions MakeOptionsForTest(bool enable_message_interleaving) { DcSctpOptions options; // To make the interval more predictable in tests. options.heartbeat_interval_include_rtt = false; options.enable_message_interleaving = enable_message_interleaving; return options; } class DcSctpSocketTest : public testing::Test { protected: explicit DcSctpSocketTest(bool enable_message_interleaving = false) : options_(MakeOptionsForTest(enable_message_interleaving)), cb_a_("A"), cb_z_("Z"), sock_a_("A", cb_a_, nullptr, options_), sock_z_("Z", cb_z_, nullptr, options_) {} void AdvanceTime(DurationMs duration) { cb_a_.AdvanceTime(duration); cb_z_.AdvanceTime(duration); } static void ExchangeMessages(DcSctpSocket& sock_a, MockDcSctpSocketCallbacks& cb_a, DcSctpSocket& sock_z, MockDcSctpSocketCallbacks& cb_z) { bool delivered_packet = false; do { delivered_packet = false; std::vector packet_from_a = cb_a.ConsumeSentPacket(); if (!packet_from_a.empty()) { delivered_packet = true; sock_z.ReceivePacket(std::move(packet_from_a)); } std::vector packet_from_z = cb_z.ConsumeSentPacket(); if (!packet_from_z.empty()) { delivered_packet = true; sock_a.ReceivePacket(std::move(packet_from_z)); } } while (delivered_packet); } void RunTimers(MockDcSctpSocketCallbacks& cb, DcSctpSocket& socket) { for (;;) { absl::optional timeout_id = cb.GetNextExpiredTimeout(); if (!timeout_id.has_value()) { break; } socket.HandleTimeout(*timeout_id); } } void RunTimers() { RunTimers(cb_a_, sock_a_); RunTimers(cb_z_, sock_z_); } // Calls Connect() on `sock_a_` and make the connection established. void ConnectSockets() { EXPECT_CALL(cb_a_, OnConnected).Times(1); EXPECT_CALL(cb_z_, OnConnected).Times(1); sock_a_.Connect(); // Z reads INIT, INIT_ACK, COOKIE_ECHO, COOKIE_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); EXPECT_EQ(sock_a_.state(), SocketState::kConnected); EXPECT_EQ(sock_z_.state(), SocketState::kConnected); } const DcSctpOptions options_; testing::NiceMock cb_a_; testing::NiceMock cb_z_; DcSctpSocket sock_a_; DcSctpSocket sock_z_; }; TEST_F(DcSctpSocketTest, EstablishConnection) { EXPECT_CALL(cb_a_, OnConnected).Times(1); EXPECT_CALL(cb_z_, OnConnected).Times(1); EXPECT_CALL(cb_a_, OnConnectionRestarted).Times(0); EXPECT_CALL(cb_z_, OnConnectionRestarted).Times(0); sock_a_.Connect(); // Z reads INIT, produces INIT_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads INIT_ACK, produces COOKIE_ECHO sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // Z reads COOKIE_ECHO, produces COOKIE_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads COOKIE_ACK. sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); EXPECT_EQ(sock_a_.state(), SocketState::kConnected); EXPECT_EQ(sock_z_.state(), SocketState::kConnected); } TEST_F(DcSctpSocketTest, EstablishConnectionWithSetupCollision) { EXPECT_CALL(cb_a_, OnConnected).Times(1); EXPECT_CALL(cb_z_, OnConnected).Times(1); EXPECT_CALL(cb_a_, OnConnectionRestarted).Times(0); EXPECT_CALL(cb_z_, OnConnectionRestarted).Times(0); sock_a_.Connect(); sock_z_.Connect(); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); EXPECT_EQ(sock_a_.state(), SocketState::kConnected); EXPECT_EQ(sock_z_.state(), SocketState::kConnected); } TEST_F(DcSctpSocketTest, ShuttingDownWhileEstablishingConnection) { EXPECT_CALL(cb_a_, OnConnected).Times(0); EXPECT_CALL(cb_z_, OnConnected).Times(1); sock_a_.Connect(); // Z reads INIT, produces INIT_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads INIT_ACK, produces COOKIE_ECHO sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // Z reads COOKIE_ECHO, produces COOKIE_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // Drop COOKIE_ACK, just to more easily verify shutdown protocol. cb_z_.ConsumeSentPacket(); // 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. EXPECT_EQ(sock_a_.state(), SocketState::kConnecting); EXPECT_EQ(sock_z_.state(), SocketState::kConnected); // Socket A is now shut down, which should make it stop those timers. sock_a_.Shutdown(); EXPECT_CALL(cb_a_, OnClosed).Times(1); EXPECT_CALL(cb_z_, OnClosed).Times(1); // Z reads SHUTDOWN, produces SHUTDOWN_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads SHUTDOWN_ACK, produces SHUTDOWN_COMPLETE sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // Z reads SHUTDOWN_COMPLETE. sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); EXPECT_TRUE(cb_a_.ConsumeSentPacket().empty()); EXPECT_TRUE(cb_z_.ConsumeSentPacket().empty()); EXPECT_EQ(sock_a_.state(), SocketState::kClosed); EXPECT_EQ(sock_z_.state(), SocketState::kClosed); } TEST_F(DcSctpSocketTest, EstablishSimultaneousConnection) { EXPECT_CALL(cb_a_, OnConnected).Times(1); EXPECT_CALL(cb_z_, OnConnected).Times(1); EXPECT_CALL(cb_a_, OnConnectionRestarted).Times(0); EXPECT_CALL(cb_z_, OnConnectionRestarted).Times(0); sock_a_.Connect(); // INIT isn't received by Z, as it wasn't ready yet. cb_a_.ConsumeSentPacket(); sock_z_.Connect(); // A reads INIT, produces INIT_ACK sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // Z reads INIT_ACK, sends COOKIE_ECHO sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads COOKIE_ECHO - establishes connection. sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); EXPECT_EQ(sock_a_.state(), SocketState::kConnected); // Proceed with the remaining packets. ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); EXPECT_EQ(sock_a_.state(), SocketState::kConnected); EXPECT_EQ(sock_z_.state(), SocketState::kConnected); } TEST_F(DcSctpSocketTest, EstablishConnectionLostCookieAck) { EXPECT_CALL(cb_a_, OnConnected).Times(1); EXPECT_CALL(cb_z_, OnConnected).Times(1); EXPECT_CALL(cb_a_, OnConnectionRestarted).Times(0); EXPECT_CALL(cb_z_, OnConnectionRestarted).Times(0); sock_a_.Connect(); // Z reads INIT, produces INIT_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads INIT_ACK, produces COOKIE_ECHO sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // Z reads COOKIE_ECHO, produces COOKIE_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // COOKIE_ACK is lost. cb_z_.ConsumeSentPacket(); EXPECT_EQ(sock_a_.state(), SocketState::kConnecting); EXPECT_EQ(sock_z_.state(), SocketState::kConnected); // This will make A re-send the COOKIE_ECHO AdvanceTime(DurationMs(options_.t1_cookie_timeout)); RunTimers(); // Z reads COOKIE_ECHO, produces COOKIE_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads COOKIE_ACK. sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); EXPECT_EQ(sock_a_.state(), SocketState::kConnected); EXPECT_EQ(sock_z_.state(), SocketState::kConnected); } TEST_F(DcSctpSocketTest, ResendInitAndEstablishConnection) { sock_a_.Connect(); // INIT is never received by Z. ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); EXPECT_EQ(init_packet.descriptors()[0].type, InitChunk::kType); AdvanceTime(options_.t1_init_timeout); RunTimers(); // Z reads INIT, produces INIT_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads INIT_ACK, produces COOKIE_ECHO sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // Z reads COOKIE_ECHO, produces COOKIE_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads COOKIE_ACK. sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); EXPECT_EQ(sock_a_.state(), SocketState::kConnected); EXPECT_EQ(sock_z_.state(), SocketState::kConnected); } TEST_F(DcSctpSocketTest, ResendingInitTooManyTimesAborts) { sock_a_.Connect(); // INIT is never received by Z. ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); EXPECT_EQ(init_packet.descriptors()[0].type, InitChunk::kType); for (int i = 0; i < options_.max_init_retransmits; ++i) { AdvanceTime(options_.t1_init_timeout * (1 << i)); RunTimers(); // INIT is resent ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket resent_init_packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); EXPECT_EQ(resent_init_packet.descriptors()[0].type, InitChunk::kType); } // Another timeout, after the max init retransmits. AdvanceTime(options_.t1_init_timeout * (1 << options_.max_init_retransmits)); EXPECT_CALL(cb_a_, OnAborted).Times(1); RunTimers(); EXPECT_EQ(sock_a_.state(), SocketState::kClosed); } TEST_F(DcSctpSocketTest, ResendCookieEchoAndEstablishConnection) { sock_a_.Connect(); // Z reads INIT, produces INIT_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads INIT_ACK, produces COOKIE_ECHO sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // COOKIE_ECHO is never received by Z. ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); EXPECT_EQ(init_packet.descriptors()[0].type, CookieEchoChunk::kType); AdvanceTime(options_.t1_init_timeout); RunTimers(); // Z reads COOKIE_ECHO, produces COOKIE_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads COOKIE_ACK. sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); EXPECT_EQ(sock_a_.state(), SocketState::kConnected); EXPECT_EQ(sock_z_.state(), SocketState::kConnected); } TEST_F(DcSctpSocketTest, ResendingCookieEchoTooManyTimesAborts) { sock_a_.Connect(); // Z reads INIT, produces INIT_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads INIT_ACK, produces COOKIE_ECHO sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // COOKIE_ECHO is never received by Z. ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket init_packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); EXPECT_EQ(init_packet.descriptors()[0].type, CookieEchoChunk::kType); for (int i = 0; i < options_.max_init_retransmits; ++i) { AdvanceTime(options_.t1_cookie_timeout * (1 << i)); RunTimers(); // COOKIE_ECHO is resent ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket resent_init_packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); EXPECT_EQ(resent_init_packet.descriptors()[0].type, CookieEchoChunk::kType); } // Another timeout, after the max init retransmits. AdvanceTime(options_.t1_cookie_timeout * (1 << options_.max_init_retransmits)); EXPECT_CALL(cb_a_, OnAborted).Times(1); RunTimers(); EXPECT_EQ(sock_a_.state(), SocketState::kClosed); } TEST_F(DcSctpSocketTest, ShutdownConnection) { ConnectSockets(); RTC_LOG(LS_INFO) << "Shutting down"; sock_a_.Shutdown(); // Z reads SHUTDOWN, produces SHUTDOWN_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // A reads SHUTDOWN_ACK, produces SHUTDOWN_COMPLETE sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // Z reads SHUTDOWN_COMPLETE. sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); EXPECT_EQ(sock_a_.state(), SocketState::kClosed); EXPECT_EQ(sock_z_.state(), SocketState::kClosed); } TEST_F(DcSctpSocketTest, ShutdownTimerExpiresTooManyTimeClosesConnection) { ConnectSockets(); sock_a_.Shutdown(); // Drop first SHUTDOWN packet. cb_a_.ConsumeSentPacket(); EXPECT_EQ(sock_a_.state(), SocketState::kShuttingDown); for (int i = 0; i < options_.max_retransmissions; ++i) { AdvanceTime(DurationMs(options_.rto_initial * (1 << i))); RunTimers(); // Dropping every shutdown chunk. ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); EXPECT_EQ(packet.descriptors()[0].type, ShutdownChunk::kType); EXPECT_TRUE(cb_a_.ConsumeSentPacket().empty()); } // The last expiry, makes it abort the connection. AdvanceTime(options_.rto_initial * (1 << options_.max_retransmissions)); EXPECT_CALL(cb_a_, OnAborted).Times(1); RunTimers(); EXPECT_EQ(sock_a_.state(), SocketState::kClosed); ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); EXPECT_EQ(packet.descriptors()[0].type, AbortChunk::kType); EXPECT_TRUE(cb_a_.ConsumeSentPacket().empty()); } TEST_F(DcSctpSocketTest, EstablishConnectionWhileSendingData) { sock_a_.Connect(); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions); // Z reads INIT, produces INIT_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // // A reads INIT_ACK, produces COOKIE_ECHO sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // // Z reads COOKIE_ECHO, produces COOKIE_ACK sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // // A reads COOKIE_ACK. sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); EXPECT_EQ(sock_a_.state(), SocketState::kConnected); EXPECT_EQ(sock_z_.state(), SocketState::kConnected); absl::optional msg = cb_z_.ConsumeReceivedMessage(); ASSERT_TRUE(msg.has_value()); EXPECT_EQ(msg->stream_id(), StreamID(1)); } TEST_F(DcSctpSocketTest, SendMessageAfterEstablished) { ConnectSockets(); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions); sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); absl::optional msg = cb_z_.ConsumeReceivedMessage(); ASSERT_TRUE(msg.has_value()); EXPECT_EQ(msg->stream_id(), StreamID(1)); } TEST_F(DcSctpSocketTest, TimeoutResendsPacket) { ConnectSockets(); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), kSendOptions); cb_a_.ConsumeSentPacket(); RTC_LOG(LS_INFO) << "Advancing time"; AdvanceTime(options_.rto_initial); RunTimers(); sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); absl::optional msg = cb_z_.ConsumeReceivedMessage(); ASSERT_TRUE(msg.has_value()); EXPECT_EQ(msg->stream_id(), StreamID(1)); } TEST_F(DcSctpSocketTest, SendALotOfBytesMissedSecondPacket) { ConnectSockets(); std::vector payload(kLargeMessageSize); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), payload), kSendOptions); // First DATA sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // Second DATA (lost) cb_a_.ConsumeSentPacket(); // Retransmit and handle the rest ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); absl::optional msg = cb_z_.ConsumeReceivedMessage(); ASSERT_TRUE(msg.has_value()); EXPECT_EQ(msg->stream_id(), StreamID(1)); EXPECT_THAT(msg->payload(), testing::ElementsAreArray(payload)); } TEST_F(DcSctpSocketTest, SendingHeartbeatAnswersWithAck) { ConnectSockets(); // Inject a HEARTBEAT chunk SctpPacket::Builder b(sock_a_.verification_tag(), DcSctpOptions()); uint8_t info[] = {1, 2, 3, 4}; Parameters::Builder params_builder; params_builder.Add(HeartbeatInfoParameter(info)); b.Add(HeartbeatRequestChunk(params_builder.Build())); sock_a_.ReceivePacket(b.Build()); // HEARTBEAT_ACK is sent as a reply. Capture it. ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket ack_packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); 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)); } TEST_F(DcSctpSocketTest, ExpectHeartbeatToBeSent) { ConnectSockets(); EXPECT_THAT(cb_a_.ConsumeSentPacket(), IsEmpty()); AdvanceTime(options_.heartbeat_interval); RunTimers(); std::vector hb_packet_raw = cb_a_.ConsumeSentPacket(); 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. sock_z_.ReceivePacket(hb_packet_raw); sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); } TEST_F(DcSctpSocketTest, CloseConnectionAfterTooManyLostHeartbeats) { ConnectSockets(); EXPECT_THAT(cb_a_.ConsumeSentPacket(), testing::IsEmpty()); // Force-close socket Z so that it doesn't interfere from now on. sock_z_.Close(); DurationMs time_to_next_hearbeat = options_.heartbeat_interval; for (int i = 0; i < options_.max_retransmissions; ++i) { RTC_LOG(LS_INFO) << "Letting HEARTBEAT interval timer expire - sending..."; AdvanceTime(time_to_next_hearbeat); RunTimers(); // Dropping every heartbeat. ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket hb_packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); EXPECT_EQ(hb_packet.descriptors()[0].type, HeartbeatRequestChunk::kType); RTC_LOG(LS_INFO) << "Letting the heartbeat expire."; AdvanceTime(DurationMs(1000)); RunTimers(); time_to_next_hearbeat = options_.heartbeat_interval - DurationMs(1000); } RTC_LOG(LS_INFO) << "Letting HEARTBEAT interval timer expire - sending..."; AdvanceTime(time_to_next_hearbeat); RunTimers(); // Last heartbeat EXPECT_THAT(cb_a_.ConsumeSentPacket(), Not(IsEmpty())); EXPECT_CALL(cb_a_, OnAborted).Times(1); // Should suffice as exceeding RTO AdvanceTime(DurationMs(1000)); RunTimers(); } TEST_F(DcSctpSocketTest, RecoversAfterASuccessfulAck) { ConnectSockets(); EXPECT_THAT(cb_a_.ConsumeSentPacket(), testing::IsEmpty()); // Force-close socket Z so that it doesn't interfere from now on. sock_z_.Close(); DurationMs time_to_next_hearbeat = options_.heartbeat_interval; for (int i = 0; i < options_.max_retransmissions; ++i) { AdvanceTime(time_to_next_hearbeat); RunTimers(); // Dropping every heartbeat. cb_a_.ConsumeSentPacket(); RTC_LOG(LS_INFO) << "Letting the heartbeat expire."; AdvanceTime(DurationMs(1000)); RunTimers(); time_to_next_hearbeat = options_.heartbeat_interval - DurationMs(1000); } RTC_LOG(LS_INFO) << "Getting the last heartbeat - and acking it"; AdvanceTime(time_to_next_hearbeat); RunTimers(); std::vector hb_packet_raw = cb_a_.ConsumeSentPacket(); 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)); SctpPacket::Builder b(sock_a_.verification_tag(), options_); b.Add(HeartbeatAckChunk(std::move(hb).extract_parameters())); sock_a_.ReceivePacket(b.Build()); // Should suffice as exceeding RTO - which will not fire. EXPECT_CALL(cb_a_, OnAborted).Times(0); AdvanceTime(DurationMs(1000)); RunTimers(); EXPECT_THAT(cb_a_.ConsumeSentPacket(), IsEmpty()); // Verify that we get new heartbeats again. RTC_LOG(LS_INFO) << "Expecting a new heartbeat"; AdvanceTime(time_to_next_hearbeat); RunTimers(); ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket another_packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); EXPECT_EQ(another_packet.descriptors()[0].type, HeartbeatRequestChunk::kType); } TEST_F(DcSctpSocketTest, ResetStream) { ConnectSockets(); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), {}); sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); absl::optional msg = cb_z_.ConsumeReceivedMessage(); ASSERT_TRUE(msg.has_value()); EXPECT_EQ(msg->stream_id(), StreamID(1)); // Handle SACK sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // Reset the outgoing stream. This will directly send a RE-CONFIG. sock_a_.ResetStreams(std::vector({StreamID(1)})); // Receiving the packet will trigger a callback, indicating that A has // reset its stream. It will also send a RE-CONFIG with a response. EXPECT_CALL(cb_z_, OnIncomingStreamsReset).Times(1); sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // Receiving a response will trigger a callback. Streams are now reset. EXPECT_CALL(cb_a_, OnStreamsResetPerformed).Times(1); sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); } TEST_F(DcSctpSocketTest, ResetStreamWillMakeChunksStartAtZeroSsn) { ConnectSockets(); std::vector payload(options_.mtu - 100); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {}); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {}); auto packet1 = cb_a_.ConsumeSentPacket(); EXPECT_THAT(packet1, HasDataChunkWithSsn(SSN(0))); sock_z_.ReceivePacket(packet1); auto packet2 = cb_a_.ConsumeSentPacket(); EXPECT_THAT(packet2, HasDataChunkWithSsn(SSN(1))); sock_z_.ReceivePacket(packet2); // Handle SACK sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); absl::optional msg1 = cb_z_.ConsumeReceivedMessage(); ASSERT_TRUE(msg1.has_value()); EXPECT_EQ(msg1->stream_id(), StreamID(1)); absl::optional msg2 = cb_z_.ConsumeReceivedMessage(); ASSERT_TRUE(msg2.has_value()); EXPECT_EQ(msg2->stream_id(), StreamID(1)); // Reset the outgoing stream. This will directly send a RE-CONFIG. sock_a_.ResetStreams(std::vector({StreamID(1)})); // RE-CONFIG, req sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // RE-CONFIG, resp sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {}); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), payload), {}); auto packet3 = cb_a_.ConsumeSentPacket(); EXPECT_THAT(packet3, HasDataChunkWithSsn(SSN(0))); sock_z_.ReceivePacket(packet3); auto packet4 = cb_a_.ConsumeSentPacket(); EXPECT_THAT(packet4, HasDataChunkWithSsn(SSN(1))); sock_z_.ReceivePacket(packet4); // Handle SACK sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); } TEST_F(DcSctpSocketTest, OnePeerReconnects) { ConnectSockets(); EXPECT_CALL(cb_a_, OnConnectionRestarted).Times(1); // Let's be evil here - reconnect while a fragmented packet was about to be // sent. The receiving side should get it in full. std::vector payload(kLargeMessageSize); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), payload), kSendOptions); // First DATA sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // Create a new association, z2 - and don't use z anymore. testing::NiceMock cb_z2("Z2"); DcSctpSocket sock_z2("Z2", cb_z2, nullptr, options_); sock_z2.Connect(); // Retransmit and handle the rest. As there will be some chunks in-flight that // have the wrong verification tag, those will yield errors. ExchangeMessages(sock_a_, cb_a_, sock_z2, cb_z2); absl::optional msg = cb_z2.ConsumeReceivedMessage(); ASSERT_TRUE(msg.has_value()); EXPECT_EQ(msg->stream_id(), StreamID(1)); EXPECT_THAT(msg->payload(), testing::ElementsAreArray(payload)); } TEST_F(DcSctpSocketTest, SendMessageWithLimitedRtx) { ConnectSockets(); SendOptions send_options; send_options.max_retransmissions = 0; std::vector payload(options_.mtu - 100); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(51), payload), send_options); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(52), payload), send_options); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), payload), send_options); // First DATA sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // Second DATA (lost) cb_a_.ConsumeSentPacket(); // Third DATA sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // Handle SACK sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); // 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. AdvanceTime(options_.rto_initial); RunTimers(); // The chunk will be marked as retransmitted, and then as abandoned, which // will trigger a FORWARD-TSN to be sent. // FORWARD-TSN (third) sock_z_.ReceivePacket(cb_a_.ConsumeSentPacket()); // The receiver might have moved into delayed ack mode. AdvanceTime(options_.rto_initial); RunTimers(); // Handle SACK sock_a_.ReceivePacket(cb_z_.ConsumeSentPacket()); absl::optional msg1 = cb_z_.ConsumeReceivedMessage(); ASSERT_TRUE(msg1.has_value()); EXPECT_EQ(msg1->ppid(), PPID(51)); absl::optional msg2 = cb_z_.ConsumeReceivedMessage(); ASSERT_TRUE(msg2.has_value()); EXPECT_EQ(msg2->ppid(), PPID(53)); absl::optional msg3 = cb_z_.ConsumeReceivedMessage(); EXPECT_FALSE(msg3.has_value()); } 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 { public: FakeChunk() {} FakeChunk(FakeChunk&& other) = default; FakeChunk& operator=(FakeChunk&& other) = default; void SerializeTo(std::vector& out) const override { AllocateTLV(out); } std::string ToString() const override { return "FAKE"; } }; TEST_F(DcSctpSocketTest, ReceivingUnknownChunkRespondsWithError) { ConnectSockets(); // Inject a FAKE chunk SctpPacket::Builder b(sock_a_.verification_tag(), DcSctpOptions()); b.Add(FakeChunk()); sock_a_.ReceivePacket(b.Build()); // ERROR is sent as a reply. Capture it. ASSERT_HAS_VALUE_AND_ASSIGN(SctpPacket reply_packet, SctpPacket::Parse(cb_a_.ConsumeSentPacket())); 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()); EXPECT_THAT(cause.unrecognized_chunk(), ElementsAre(0x49, 0x00, 0x00, 0x04)); } TEST_F(DcSctpSocketTest, ReceivingErrorChunkReportsAsCallback) { ConnectSockets(); // Inject a ERROR chunk SctpPacket::Builder b(sock_a_.verification_tag(), DcSctpOptions()); b.Add( ErrorChunk(Parameters::Builder() .Add(UnrecognizedChunkTypeCause({0x49, 0x00, 0x00, 0x04})) .Build())); EXPECT_CALL(cb_a_, OnError(ErrorKind::kPeerReported, HasSubstr("Unrecognized Chunk Type"))); sock_a_.ReceivePacket(b.Build()); } TEST_F(DcSctpSocketTest, PassingHighWatermarkWillOnlyAcceptCumAckTsn) { // Create a new association, z2 - and don't use z anymore. testing::NiceMock cb_z2("Z2"); DcSctpOptions options = options_; options.max_receiver_window_buffer_size = 100; DcSctpSocket sock_z2("Z2", cb_z2, nullptr, options); EXPECT_CALL(cb_z2, OnClosed).Times(0); EXPECT_CALL(cb_z2, OnAborted).Times(0); sock_a_.Connect(); std::vector init_data = cb_a_.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)); sock_z2.ReceivePacket(init_data); sock_a_.ReceivePacket(cb_z2.ConsumeSentPacket()); sock_z2.ReceivePacket(cb_a_.ConsumeSentPacket()); sock_a_.ReceivePacket(cb_z2.ConsumeSentPacket()); // Fill up Z2 to the high watermark limit. TSN tsn = init_chunk.initial_tsn(); AnyDataChunk::Options opts; opts.is_beginning = Data::IsBeginning(true); sock_z2.ReceivePacket( SctpPacket::Builder(sock_z2.verification_tag(), options) .Add(DataChunk(tsn, StreamID(1), SSN(0), PPID(53), std::vector( 100 * ReassemblyQueue::kHighWatermarkLimit + 1), opts)) .Build()); // First DATA will always trigger a SACK. It's not interesting. EXPECT_THAT(cb_z2.ConsumeSentPacket(), AllOf(HasSackWithCumAckTsn(tsn), HasSackWithNoGapAckBlocks())); // This DATA should be accepted - it's advancing cum ack tsn. sock_z2.ReceivePacket(SctpPacket::Builder(sock_z2.verification_tag(), options) .Add(DataChunk(AddTo(tsn, 1), StreamID(1), SSN(0), PPID(53), std::vector(1), /*options=*/{})) .Build()); // The receiver might have moved into delayed ack mode. cb_z2.AdvanceTime(options.rto_initial); RunTimers(cb_z2, sock_z2); EXPECT_THAT( cb_z2.ConsumeSentPacket(), AllOf(HasSackWithCumAckTsn(AddTo(tsn, 1)), HasSackWithNoGapAckBlocks())); // This DATA will not be accepted - it's not advancing cum ack tsn. sock_z2.ReceivePacket(SctpPacket::Builder(sock_z2.verification_tag(), options) .Add(DataChunk(AddTo(tsn, 3), StreamID(1), SSN(0), PPID(53), std::vector(1), /*options=*/{})) .Build()); // Sack will be sent in IMMEDIATE mode when this is happening. EXPECT_THAT( cb_z2.ConsumeSentPacket(), AllOf(HasSackWithCumAckTsn(AddTo(tsn, 1)), HasSackWithNoGapAckBlocks())); // This DATA will not be accepted either. sock_z2.ReceivePacket(SctpPacket::Builder(sock_z2.verification_tag(), options) .Add(DataChunk(AddTo(tsn, 4), StreamID(1), SSN(0), PPID(53), std::vector(1), /*options=*/{})) .Build()); // Sack will be sent in IMMEDIATE mode when this is happening. EXPECT_THAT( cb_z2.ConsumeSentPacket(), AllOf(HasSackWithCumAckTsn(AddTo(tsn, 1)), HasSackWithNoGapAckBlocks())); // This DATA should be accepted, and it fills the reassembly queue. sock_z2.ReceivePacket( SctpPacket::Builder(sock_z2.verification_tag(), options) .Add(DataChunk(AddTo(tsn, 2), StreamID(1), SSN(0), PPID(53), std::vector(kSmallMessageSize), /*options=*/{})) .Build()); // The receiver might have moved into delayed ack mode. cb_z2.AdvanceTime(options.rto_initial); RunTimers(cb_z2, sock_z2); EXPECT_THAT( cb_z2.ConsumeSentPacket(), AllOf(HasSackWithCumAckTsn(AddTo(tsn, 2)), HasSackWithNoGapAckBlocks())); EXPECT_CALL(cb_z2, OnAborted(ErrorKind::kResourceExhaustion, _)); EXPECT_CALL(cb_z2, OnClosed).Times(0); // This DATA will make the connection close. It's too full now. sock_z2.ReceivePacket( SctpPacket::Builder(sock_z2.verification_tag(), options) .Add(DataChunk(AddTo(tsn, 3), StreamID(1), SSN(0), PPID(53), std::vector(kSmallMessageSize), /*options=*/{})) .Build()); } TEST_F(DcSctpSocketTest, SetMaxMessageSize) { sock_a_.SetMaxMessageSize(42u); EXPECT_EQ(sock_a_.options().max_message_size, 42u); } TEST_F(DcSctpSocketTest, SendsMessagesWithLowLifetime) { ConnectSockets(); // Mock that the time always goes forward. TimeMs now(0); EXPECT_CALL(cb_a_, TimeMillis).WillRepeatedly([&]() { now += DurationMs(3); return now; }); EXPECT_CALL(cb_z_, TimeMillis).WillRepeatedly([&]() { 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 sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2}), send_options); } ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); for (int i = 0; i < kIterations; ++i) { EXPECT_TRUE(cb_z_.ConsumeReceivedMessage().has_value()); } EXPECT_FALSE(cb_z_.ConsumeReceivedMessage().has_value()); // Validate that the sockets really make the time move forward. EXPECT_GE(*now, kIterations * 2); } TEST_F(DcSctpSocketTest, DiscardsMessagesWithLowLifetimeIfMustBuffer) { ConnectSockets(); 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); EXPECT_CALL(cb_a_, TimeMillis).WillRepeatedly([&]() { now += DurationMs(3); return now; }); EXPECT_CALL(cb_z_, TimeMillis).WillRepeatedly([&]() { now += DurationMs(3); return now; }); // Fill up the send buffer with a large message. std::vector payload(kLargeMessageSize); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), payload), kSendOptions); // And queue a few small messages with lifetime=0 or 1 ms - can't be sent. sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), {1, 2, 3}), lifetime_0); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), {4, 5, 6}), lifetime_1); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), {7, 8, 9}), lifetime_0); // Handle all that was sent until congestion window got full. for (;;) { std::vector packet_from_a = cb_a_.ConsumeSentPacket(); if (packet_from_a.empty()) { break; } sock_z_.ReceivePacket(std::move(packet_from_a)); } // Shouldn't be enough to send that large message. EXPECT_FALSE(cb_z_.ConsumeReceivedMessage().has_value()); // Exchange the rest of the messages, with the time ever increasing. ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); // The large message should be delivered. It was sent reliably. ASSERT_HAS_VALUE_AND_ASSIGN(DcSctpMessage m1, cb_z_.ConsumeReceivedMessage()); EXPECT_EQ(m1.stream_id(), StreamID(1)); EXPECT_THAT(m1.payload(), SizeIs(kLargeMessageSize)); // But none of the smaller messages. EXPECT_FALSE(cb_z_.ConsumeReceivedMessage().has_value()); } TEST_F(DcSctpSocketTest, HasReasonableBufferedAmountValues) { ConnectSockets(); EXPECT_EQ(sock_a_.buffered_amount(StreamID(1)), 0u); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), std::vector(kSmallMessageSize)), kSendOptions); // Sending a small message will directly send it as a single packet, so // nothing is left in the queue. EXPECT_EQ(sock_a_.buffered_amount(StreamID(1)), 0u); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), std::vector(kLargeMessageSize)), kSendOptions); // Sending a message will directly start sending a few packets, so the // buffered amount is not the full message size. EXPECT_GT(sock_a_.buffered_amount(StreamID(1)), 0u); EXPECT_LT(sock_a_.buffered_amount(StreamID(1)), kLargeMessageSize); } TEST_F(DcSctpSocketTest, HasDefaultOnBufferedAmountLowValueZero) { EXPECT_EQ(sock_a_.buffered_amount_low_threshold(StreamID(1)), 0u); } TEST_F(DcSctpSocketTest, TriggersOnBufferedAmountLowWithDefaultValueZero) { EXPECT_CALL(cb_a_, OnBufferedAmountLow).Times(0); ConnectSockets(); EXPECT_CALL(cb_a_, OnBufferedAmountLow(StreamID(1))); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), std::vector(kSmallMessageSize)), kSendOptions); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); } TEST_F(DcSctpSocketTest, DoesntTriggerOnBufferedAmountLowIfBelowThreshold) { static constexpr size_t kMessageSize = 1000; static constexpr size_t kBufferedAmountLowThreshold = kMessageSize * 10; sock_a_.SetBufferedAmountLowThreshold(StreamID(1), kBufferedAmountLowThreshold); EXPECT_CALL(cb_a_, OnBufferedAmountLow).Times(0); ConnectSockets(); EXPECT_CALL(cb_a_, OnBufferedAmountLow(StreamID(1))).Times(0); sock_a_.Send( DcSctpMessage(StreamID(1), PPID(53), std::vector(kMessageSize)), kSendOptions); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); sock_a_.Send( DcSctpMessage(StreamID(1), PPID(53), std::vector(kMessageSize)), kSendOptions); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); } TEST_F(DcSctpSocketTest, TriggersOnBufferedAmountMultipleTimes) { static constexpr size_t kMessageSize = 1000; static constexpr size_t kBufferedAmountLowThreshold = kMessageSize / 2; sock_a_.SetBufferedAmountLowThreshold(StreamID(1), kBufferedAmountLowThreshold); EXPECT_CALL(cb_a_, OnBufferedAmountLow).Times(0); ConnectSockets(); EXPECT_CALL(cb_a_, OnBufferedAmountLow(StreamID(1))).Times(3); EXPECT_CALL(cb_a_, OnBufferedAmountLow(StreamID(2))).Times(2); sock_a_.Send( DcSctpMessage(StreamID(1), PPID(53), std::vector(kMessageSize)), kSendOptions); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); sock_a_.Send( DcSctpMessage(StreamID(2), PPID(53), std::vector(kMessageSize)), kSendOptions); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); sock_a_.Send( DcSctpMessage(StreamID(1), PPID(53), std::vector(kMessageSize)), kSendOptions); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); sock_a_.Send( DcSctpMessage(StreamID(2), PPID(53), std::vector(kMessageSize)), kSendOptions); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); sock_a_.Send( DcSctpMessage(StreamID(1), PPID(53), std::vector(kMessageSize)), kSendOptions); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); } TEST_F(DcSctpSocketTest, TriggersOnBufferedAmountLowOnlyWhenCrossingThreshold) { static constexpr size_t kMessageSize = 1000; static constexpr size_t kBufferedAmountLowThreshold = kMessageSize * 1.5; sock_a_.SetBufferedAmountLowThreshold(StreamID(1), kBufferedAmountLowThreshold); EXPECT_CALL(cb_a_, OnBufferedAmountLow).Times(0); ConnectSockets(); EXPECT_CALL(cb_a_, OnBufferedAmountLow).Times(0); // Add a few messages to fill up the congestion window. When that is full, // messages will start to be fully buffered. while (sock_a_.buffered_amount(StreamID(1)) == 0) { sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), std::vector(kMessageSize)), kSendOptions); } size_t initial_buffered = sock_a_.buffered_amount(StreamID(1)); ASSERT_GE(initial_buffered, 0u); ASSERT_LT(initial_buffered, kMessageSize); // Up to kMessageSize (which is below the threshold) sock_a_.Send( DcSctpMessage(StreamID(1), PPID(53), std::vector(kMessageSize - initial_buffered)), kSendOptions); EXPECT_EQ(sock_a_.buffered_amount(StreamID(1)), kMessageSize); // Up to 2*kMessageSize (which is above the threshold) sock_a_.Send( DcSctpMessage(StreamID(1), PPID(53), std::vector(kMessageSize)), kSendOptions); EXPECT_EQ(sock_a_.buffered_amount(StreamID(1)), 2 * kMessageSize); // Start ACKing packets, which will empty the send queue, and trigger the // callback. EXPECT_CALL(cb_a_, OnBufferedAmountLow(StreamID(1))).Times(1); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); } TEST_F(DcSctpSocketTest, DoesntTriggerOnTotalBufferAmountLowWhenBelow) { ConnectSockets(); EXPECT_CALL(cb_a_, OnTotalBufferedAmountLow).Times(0); sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), std::vector(kLargeMessageSize)), kSendOptions); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); } TEST_F(DcSctpSocketTest, TriggersOnTotalBufferAmountLowWhenCrossingThreshold) { ConnectSockets(); EXPECT_CALL(cb_a_, OnTotalBufferedAmountLow).Times(0); // Fill up the send queue completely. for (;;) { if (sock_a_.Send(DcSctpMessage(StreamID(1), PPID(53), std::vector(kLargeMessageSize)), kSendOptions) == SendStatus::kErrorResourceExhaustion) { break; } } EXPECT_CALL(cb_a_, OnTotalBufferedAmountLow).Times(1); ExchangeMessages(sock_a_, cb_a_, sock_z_, cb_z_); } } // namespace } // namespace dcsctp