summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/net/dcsctp/tx/outstanding_data_test.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/net/dcsctp/tx/outstanding_data_test.cc
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/net/dcsctp/tx/outstanding_data_test.cc')
-rw-r--r--third_party/libwebrtc/net/dcsctp/tx/outstanding_data_test.cc661
1 files changed, 661 insertions, 0 deletions
diff --git a/third_party/libwebrtc/net/dcsctp/tx/outstanding_data_test.cc b/third_party/libwebrtc/net/dcsctp/tx/outstanding_data_test.cc
new file mode 100644
index 0000000000..b8c2e593a1
--- /dev/null
+++ b/third_party/libwebrtc/net/dcsctp/tx/outstanding_data_test.cc
@@ -0,0 +1,661 @@
+/*
+ * 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/tx/outstanding_data.h"
+
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "net/dcsctp/common/internal_types.h"
+#include "net/dcsctp/common/math.h"
+#include "net/dcsctp/common/sequence_numbers.h"
+#include "net/dcsctp/packet/chunk/data_chunk.h"
+#include "net/dcsctp/packet/chunk/forward_tsn_chunk.h"
+#include "net/dcsctp/public/dcsctp_socket.h"
+#include "net/dcsctp/public/types.h"
+#include "net/dcsctp/testing/data_generator.h"
+#include "net/dcsctp/testing/testing_macros.h"
+#include "rtc_base/gunit.h"
+#include "test/gmock.h"
+
+namespace dcsctp {
+namespace {
+using ::testing::MockFunction;
+using State = ::dcsctp::OutstandingData::State;
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::Pair;
+using ::testing::Property;
+using ::testing::Return;
+using ::testing::StrictMock;
+using ::testing::UnorderedElementsAre;
+
+constexpr TimeMs kNow(42);
+constexpr OutgoingMessageId kMessageId = OutgoingMessageId(17);
+
+class OutstandingDataTest : public testing::Test {
+ protected:
+ OutstandingDataTest()
+ : gen_(MID(42)),
+ buf_(DataChunk::kHeaderSize,
+ unwrapper_.Unwrap(TSN(10)),
+ unwrapper_.Unwrap(TSN(9)),
+ on_discard_.AsStdFunction()) {}
+
+ UnwrappedTSN::Unwrapper unwrapper_;
+ DataGenerator gen_;
+ StrictMock<MockFunction<bool(StreamID, OutgoingMessageId)>> on_discard_;
+ OutstandingData buf_;
+};
+
+TEST_F(OutstandingDataTest, HasInitialState) {
+ EXPECT_TRUE(buf_.empty());
+ EXPECT_EQ(buf_.outstanding_bytes(), 0u);
+ EXPECT_EQ(buf_.outstanding_items(), 0u);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+ EXPECT_EQ(buf_.last_cumulative_tsn_ack().Wrap(), TSN(9));
+ EXPECT_EQ(buf_.next_tsn().Wrap(), TSN(10));
+ EXPECT_EQ(buf_.highest_outstanding_tsn().Wrap(), TSN(9));
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(9), State::kAcked)));
+ EXPECT_FALSE(buf_.ShouldSendForwardTsn());
+}
+
+TEST_F(OutstandingDataTest, InsertChunk) {
+ ASSERT_HAS_VALUE_AND_ASSIGN(
+ UnwrappedTSN tsn, buf_.Insert(kMessageId, gen_.Ordered({1}, "BE"), kNow));
+
+ EXPECT_EQ(tsn.Wrap(), TSN(10));
+
+ EXPECT_EQ(buf_.outstanding_bytes(), DataChunk::kHeaderSize + RoundUpTo4(1));
+ EXPECT_EQ(buf_.outstanding_items(), 1u);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+ EXPECT_EQ(buf_.last_cumulative_tsn_ack().Wrap(), TSN(9));
+ EXPECT_EQ(buf_.next_tsn().Wrap(), TSN(11));
+ EXPECT_EQ(buf_.highest_outstanding_tsn().Wrap(), TSN(10));
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(9), State::kAcked),
+ Pair(TSN(10), State::kInFlight)));
+}
+
+TEST_F(OutstandingDataTest, AcksSingleChunk) {
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "BE"), kNow);
+ OutstandingData::AckInfo ack =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(10)), {}, false);
+
+ EXPECT_EQ(ack.bytes_acked, DataChunk::kHeaderSize + RoundUpTo4(1));
+ EXPECT_EQ(ack.highest_tsn_acked.Wrap(), TSN(10));
+ EXPECT_FALSE(ack.has_packet_loss);
+
+ EXPECT_EQ(buf_.outstanding_bytes(), 0u);
+ EXPECT_EQ(buf_.outstanding_items(), 0u);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+ EXPECT_EQ(buf_.last_cumulative_tsn_ack().Wrap(), TSN(10));
+ EXPECT_EQ(buf_.next_tsn().Wrap(), TSN(11));
+ EXPECT_EQ(buf_.highest_outstanding_tsn().Wrap(), TSN(10));
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(10), State::kAcked)));
+}
+
+TEST_F(OutstandingDataTest, AcksPreviousChunkDoesntUpdate) {
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "BE"), kNow);
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), {}, false);
+
+ EXPECT_EQ(buf_.outstanding_bytes(), DataChunk::kHeaderSize + RoundUpTo4(1));
+ EXPECT_EQ(buf_.outstanding_items(), 1u);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+ EXPECT_EQ(buf_.last_cumulative_tsn_ack().Wrap(), TSN(9));
+ EXPECT_EQ(buf_.next_tsn().Wrap(), TSN(11));
+ EXPECT_EQ(buf_.highest_outstanding_tsn().Wrap(), TSN(10));
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(9), State::kAcked),
+ Pair(TSN(10), State::kInFlight)));
+}
+
+TEST_F(OutstandingDataTest, AcksAndNacksWithGapAckBlocks) {
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "B"), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "E"), kNow);
+
+ std::vector<SackChunk::GapAckBlock> gab = {SackChunk::GapAckBlock(2, 2)};
+ OutstandingData::AckInfo ack =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab, false);
+ EXPECT_EQ(ack.bytes_acked, DataChunk::kHeaderSize + RoundUpTo4(1));
+ EXPECT_EQ(ack.highest_tsn_acked.Wrap(), TSN(11));
+ EXPECT_FALSE(ack.has_packet_loss);
+
+ EXPECT_EQ(buf_.outstanding_bytes(), 0u);
+ EXPECT_EQ(buf_.outstanding_items(), 0u);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+ EXPECT_EQ(buf_.last_cumulative_tsn_ack().Wrap(), TSN(9));
+ EXPECT_EQ(buf_.next_tsn().Wrap(), TSN(12));
+ EXPECT_EQ(buf_.highest_outstanding_tsn().Wrap(), TSN(11));
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kNacked), //
+ Pair(TSN(11), State::kAcked)));
+}
+
+TEST_F(OutstandingDataTest, NacksThreeTimesWithSameTsnDoesntRetransmit) {
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "B"), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "E"), kNow);
+
+ std::vector<SackChunk::GapAckBlock> gab1 = {SackChunk::GapAckBlock(2, 2)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab1, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab1, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab1, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kNacked), //
+ Pair(TSN(11), State::kAcked)));
+}
+
+TEST_F(OutstandingDataTest, NacksThreeTimesResultsInRetransmission) {
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "B"), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "E"), kNow);
+
+ std::vector<SackChunk::GapAckBlock> gab1 = {SackChunk::GapAckBlock(2, 2)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab1, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab2 = {SackChunk::GapAckBlock(2, 3)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab2, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab3 = {SackChunk::GapAckBlock(2, 4)};
+ OutstandingData::AckInfo ack =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab3, false);
+ EXPECT_EQ(ack.bytes_acked, DataChunk::kHeaderSize + RoundUpTo4(1));
+ EXPECT_EQ(ack.highest_tsn_acked.Wrap(), TSN(13));
+ EXPECT_TRUE(ack.has_packet_loss);
+
+ EXPECT_TRUE(buf_.has_data_to_be_retransmitted());
+
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kToBeRetransmitted), //
+ Pair(TSN(11), State::kAcked), //
+ Pair(TSN(12), State::kAcked), //
+ Pair(TSN(13), State::kAcked)));
+
+ EXPECT_THAT(buf_.GetChunksToBeFastRetransmitted(1000),
+ ElementsAre(Pair(TSN(10), _)));
+ EXPECT_THAT(buf_.GetChunksToBeRetransmitted(1000), IsEmpty());
+}
+
+TEST_F(OutstandingDataTest, NacksThreeTimesResultsInAbandoning) {
+ static constexpr MaxRetransmits kMaxRetransmissions(0);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "B"), kNow, kMaxRetransmissions);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow, kMaxRetransmissions);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow, kMaxRetransmissions);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "E"), kNow, kMaxRetransmissions);
+
+ std::vector<SackChunk::GapAckBlock> gab1 = {SackChunk::GapAckBlock(2, 2)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab1, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab2 = {SackChunk::GapAckBlock(2, 3)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab2, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ EXPECT_CALL(on_discard_, Call(StreamID(1), kMessageId))
+ .WillOnce(Return(false));
+ std::vector<SackChunk::GapAckBlock> gab3 = {SackChunk::GapAckBlock(2, 4)};
+ OutstandingData::AckInfo ack =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab3, false);
+ EXPECT_EQ(ack.bytes_acked, DataChunk::kHeaderSize + RoundUpTo4(1));
+ EXPECT_EQ(ack.highest_tsn_acked.Wrap(), TSN(13));
+ EXPECT_TRUE(ack.has_packet_loss);
+
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+ EXPECT_EQ(buf_.next_tsn().Wrap(), TSN(14));
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kAbandoned), //
+ Pair(TSN(11), State::kAbandoned), //
+ Pair(TSN(12), State::kAbandoned), //
+ Pair(TSN(13), State::kAbandoned)));
+}
+
+TEST_F(OutstandingDataTest, NacksThreeTimesResultsInAbandoningWithPlaceholder) {
+ static constexpr MaxRetransmits kMaxRetransmissions(0);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "B"), kNow, kMaxRetransmissions);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow, kMaxRetransmissions);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow, kMaxRetransmissions);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow, kMaxRetransmissions);
+
+ std::vector<SackChunk::GapAckBlock> gab1 = {SackChunk::GapAckBlock(2, 2)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab1, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab2 = {SackChunk::GapAckBlock(2, 3)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab2, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ EXPECT_CALL(on_discard_, Call(StreamID(1), kMessageId))
+ .WillOnce(Return(true));
+ std::vector<SackChunk::GapAckBlock> gab3 = {SackChunk::GapAckBlock(2, 4)};
+ OutstandingData::AckInfo ack =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab3, false);
+ EXPECT_EQ(ack.bytes_acked, DataChunk::kHeaderSize + RoundUpTo4(1));
+ EXPECT_EQ(ack.highest_tsn_acked.Wrap(), TSN(13));
+ EXPECT_TRUE(ack.has_packet_loss);
+
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+ EXPECT_EQ(buf_.next_tsn().Wrap(), TSN(15));
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kAbandoned), //
+ Pair(TSN(11), State::kAbandoned), //
+ Pair(TSN(12), State::kAbandoned), //
+ Pair(TSN(13), State::kAbandoned), //
+ Pair(TSN(14), State::kAbandoned)));
+}
+
+TEST_F(OutstandingDataTest, ExpiresChunkBeforeItIsInserted) {
+ static constexpr TimeMs kExpiresAt = kNow + DurationMs(1);
+ EXPECT_TRUE(buf_.Insert(kMessageId, gen_.Ordered({1}, "B"), kNow,
+ MaxRetransmits::NoLimit(), kExpiresAt)
+ .has_value());
+ EXPECT_TRUE(buf_.Insert(kMessageId, gen_.Ordered({1}, ""),
+ kNow + DurationMs(0), MaxRetransmits::NoLimit(),
+ kExpiresAt)
+ .has_value());
+
+ EXPECT_CALL(on_discard_, Call(StreamID(1), kMessageId))
+ .WillOnce(Return(false));
+ EXPECT_FALSE(buf_.Insert(kMessageId, gen_.Ordered({1}, "E"),
+ kNow + DurationMs(1), MaxRetransmits::NoLimit(),
+ kExpiresAt)
+ .has_value());
+
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+ EXPECT_EQ(buf_.last_cumulative_tsn_ack().Wrap(), TSN(9));
+ EXPECT_EQ(buf_.next_tsn().Wrap(), TSN(13));
+ EXPECT_EQ(buf_.highest_outstanding_tsn().Wrap(), TSN(12));
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kAbandoned), //
+ Pair(TSN(11), State::kAbandoned),
+ Pair(TSN(12), State::kAbandoned)));
+}
+
+TEST_F(OutstandingDataTest, CanGenerateForwardTsn) {
+ static constexpr MaxRetransmits kMaxRetransmissions(0);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "B"), kNow, kMaxRetransmissions);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow, kMaxRetransmissions);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "E"), kNow, kMaxRetransmissions);
+
+ EXPECT_CALL(on_discard_, Call(StreamID(1), kMessageId))
+ .WillOnce(Return(false));
+ buf_.NackAll();
+
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kAbandoned), //
+ Pair(TSN(11), State::kAbandoned),
+ Pair(TSN(12), State::kAbandoned)));
+
+ EXPECT_TRUE(buf_.ShouldSendForwardTsn());
+ ForwardTsnChunk chunk = buf_.CreateForwardTsn();
+ EXPECT_EQ(chunk.new_cumulative_tsn(), TSN(12));
+}
+
+TEST_F(OutstandingDataTest, AckWithGapBlocksFromRFC4960Section334) {
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "B"), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "E"), kNow);
+
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ testing::ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kInFlight), //
+ Pair(TSN(11), State::kInFlight), //
+ Pair(TSN(12), State::kInFlight), //
+ Pair(TSN(13), State::kInFlight), //
+ Pair(TSN(14), State::kInFlight), //
+ Pair(TSN(15), State::kInFlight), //
+ Pair(TSN(16), State::kInFlight), //
+ Pair(TSN(17), State::kInFlight)));
+
+ std::vector<SackChunk::GapAckBlock> gab = {SackChunk::GapAckBlock(2, 3),
+ SackChunk::GapAckBlock(5, 5)};
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(12)), gab, false);
+
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(12), State::kAcked), //
+ Pair(TSN(13), State::kNacked), //
+ Pair(TSN(14), State::kAcked), //
+ Pair(TSN(15), State::kAcked), //
+ Pair(TSN(16), State::kNacked), //
+ Pair(TSN(17), State::kAcked)));
+}
+
+TEST_F(OutstandingDataTest, MeasureRTT) {
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "BE"), kNow);
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "BE"), kNow + DurationMs(1));
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "BE"), kNow + DurationMs(2));
+
+ static constexpr DurationMs kDuration(123);
+ ASSERT_HAS_VALUE_AND_ASSIGN(
+ DurationMs duration,
+ buf_.MeasureRTT(kNow + kDuration, unwrapper_.Unwrap(TSN(11))));
+
+ EXPECT_EQ(duration, kDuration - DurationMs(1));
+}
+
+TEST_F(OutstandingDataTest, MustRetransmitBeforeGettingNackedAgain) {
+ // This test case verifies that a chunk that has been nacked, and scheduled to
+ // be retransmitted, doesn't get nacked again until it has been actually sent
+ // on the wire.
+
+ static constexpr MaxRetransmits kOneRetransmission(1);
+ for (int tsn = 10; tsn <= 20; ++tsn) {
+ buf_.Insert(kMessageId,
+ gen_.Ordered({1}, tsn == 10 ? "B"
+ : tsn == 20 ? "E"
+ : ""),
+ kNow, kOneRetransmission);
+ }
+
+ std::vector<SackChunk::GapAckBlock> gab1 = {SackChunk::GapAckBlock(2, 2)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab1, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab2 = {SackChunk::GapAckBlock(2, 3)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab2, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab3 = {SackChunk::GapAckBlock(2, 4)};
+ OutstandingData::AckInfo ack =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab3, false);
+ EXPECT_TRUE(ack.has_packet_loss);
+ EXPECT_TRUE(buf_.has_data_to_be_retransmitted());
+
+ // Don't call GetChunksToBeRetransmitted yet - simulate that the congestion
+ // window doesn't allow it to be retransmitted yet. It does however get more
+ // SACKs indicating packet loss.
+
+ std::vector<SackChunk::GapAckBlock> gab4 = {SackChunk::GapAckBlock(2, 5)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab4, false).has_packet_loss);
+ EXPECT_TRUE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab5 = {SackChunk::GapAckBlock(2, 6)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab5, false).has_packet_loss);
+ EXPECT_TRUE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab6 = {SackChunk::GapAckBlock(2, 7)};
+ OutstandingData::AckInfo ack2 =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab6, false);
+
+ EXPECT_FALSE(ack2.has_packet_loss);
+ EXPECT_TRUE(buf_.has_data_to_be_retransmitted());
+
+ // Now it's retransmitted.
+ EXPECT_THAT(buf_.GetChunksToBeFastRetransmitted(1000),
+ ElementsAre(Pair(TSN(10), _)));
+ EXPECT_THAT(buf_.GetChunksToBeRetransmitted(1000), IsEmpty());
+
+ // And obviously lost, as it will get NACKed and abandoned.
+ std::vector<SackChunk::GapAckBlock> gab7 = {SackChunk::GapAckBlock(2, 8)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab7, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab8 = {SackChunk::GapAckBlock(2, 9)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab8, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ EXPECT_CALL(on_discard_, Call(StreamID(1), kMessageId))
+ .WillOnce(Return(false));
+
+ std::vector<SackChunk::GapAckBlock> gab9 = {SackChunk::GapAckBlock(2, 10)};
+ OutstandingData::AckInfo ack3 =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab9, false);
+
+ EXPECT_TRUE(ack3.has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+}
+
+TEST_F(OutstandingDataTest, LifecyleReturnsAckedItemsInAckInfo) {
+ buf_.Insert(OutgoingMessageId(1), gen_.Ordered({1}, "BE"), kNow,
+ MaxRetransmits::NoLimit(), TimeMs::InfiniteFuture(),
+ LifecycleId(42));
+ buf_.Insert(OutgoingMessageId(2), gen_.Ordered({1}, "BE"), kNow,
+ MaxRetransmits::NoLimit(), TimeMs::InfiniteFuture(),
+ LifecycleId(43));
+ buf_.Insert(OutgoingMessageId(3), gen_.Ordered({1}, "BE"), kNow,
+ MaxRetransmits::NoLimit(), TimeMs::InfiniteFuture(),
+ LifecycleId(44));
+
+ OutstandingData::AckInfo ack1 =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(11)), {}, false);
+
+ EXPECT_THAT(ack1.acked_lifecycle_ids,
+ ElementsAre(LifecycleId(42), LifecycleId(43)));
+
+ OutstandingData::AckInfo ack2 =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(12)), {}, false);
+
+ EXPECT_THAT(ack2.acked_lifecycle_ids, ElementsAre(LifecycleId(44)));
+}
+
+TEST_F(OutstandingDataTest, LifecycleReturnsAbandonedNackedThreeTimes) {
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "B"), kNow, MaxRetransmits(0));
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow, MaxRetransmits(0));
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow, MaxRetransmits(0));
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "E"), kNow, MaxRetransmits(0),
+ TimeMs::InfiniteFuture(), LifecycleId(42));
+
+ std::vector<SackChunk::GapAckBlock> gab1 = {SackChunk::GapAckBlock(2, 2)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab1, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab2 = {SackChunk::GapAckBlock(2, 3)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab2, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ std::vector<SackChunk::GapAckBlock> gab3 = {SackChunk::GapAckBlock(2, 4)};
+ EXPECT_CALL(on_discard_, Call(StreamID(1), kMessageId))
+ .WillOnce(Return(false));
+ OutstandingData::AckInfo ack1 =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab3, false);
+ EXPECT_TRUE(ack1.has_packet_loss);
+ EXPECT_THAT(ack1.abandoned_lifecycle_ids, IsEmpty());
+
+ // This will generate a FORWARD-TSN, which is acked
+ EXPECT_TRUE(buf_.ShouldSendForwardTsn());
+ ForwardTsnChunk chunk = buf_.CreateForwardTsn();
+ EXPECT_EQ(chunk.new_cumulative_tsn(), TSN(13));
+
+ OutstandingData::AckInfo ack2 =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(13)), {}, false);
+ EXPECT_FALSE(ack2.has_packet_loss);
+ EXPECT_THAT(ack2.abandoned_lifecycle_ids, ElementsAre(LifecycleId(42)));
+}
+
+TEST_F(OutstandingDataTest, LifecycleReturnsAbandonedAfterT3rtxExpired) {
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "B"), kNow, MaxRetransmits(0));
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow, MaxRetransmits(0));
+ buf_.Insert(kMessageId, gen_.Ordered({1}, ""), kNow, MaxRetransmits(0));
+ buf_.Insert(kMessageId, gen_.Ordered({1}, "E"), kNow, MaxRetransmits(0),
+ TimeMs::InfiniteFuture(), LifecycleId(42));
+
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ testing::ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kInFlight), //
+ Pair(TSN(11), State::kInFlight), //
+ Pair(TSN(12), State::kInFlight), //
+ Pair(TSN(13), State::kInFlight)));
+
+ std::vector<SackChunk::GapAckBlock> gab1 = {SackChunk::GapAckBlock(2, 4)};
+ EXPECT_FALSE(
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(9)), gab1, false).has_packet_loss);
+ EXPECT_FALSE(buf_.has_data_to_be_retransmitted());
+
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ testing::ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kNacked), //
+ Pair(TSN(11), State::kAcked), //
+ Pair(TSN(12), State::kAcked), //
+ Pair(TSN(13), State::kAcked)));
+
+ // T3-rtx triggered.
+ EXPECT_CALL(on_discard_, Call(StreamID(1), kMessageId))
+ .WillOnce(Return(false));
+ buf_.NackAll();
+
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ testing::ElementsAre(Pair(TSN(9), State::kAcked), //
+ Pair(TSN(10), State::kAbandoned), //
+ Pair(TSN(11), State::kAbandoned), //
+ Pair(TSN(12), State::kAbandoned), //
+ Pair(TSN(13), State::kAbandoned)));
+
+ // This will generate a FORWARD-TSN, which is acked
+ EXPECT_TRUE(buf_.ShouldSendForwardTsn());
+ ForwardTsnChunk chunk = buf_.CreateForwardTsn();
+ EXPECT_EQ(chunk.new_cumulative_tsn(), TSN(13));
+
+ OutstandingData::AckInfo ack2 =
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(13)), {}, false);
+ EXPECT_FALSE(ack2.has_packet_loss);
+ EXPECT_THAT(ack2.abandoned_lifecycle_ids, ElementsAre(LifecycleId(42)));
+}
+
+TEST_F(OutstandingDataTest, GeneratesForwardTsnUntilNextStreamResetTsn) {
+ // This test generates:
+ // * Stream 1: TSN 10, 11, 12 <RESET>
+ // * Stream 2: TSN 13, 14 <RESET>
+ // * Stream 3: TSN 15, 16
+ //
+ // Then it expires chunk 12-15, and ensures that the generated FORWARD-TSN
+ // only includes up till TSN 12 until the cum ack TSN has reached 12, and then
+ // 13 and 14 are included, and then after the cum ack TSN has reached 14, then
+ // 15 is included.
+ //
+ // What it shouldn't do, is to generate a FORWARD-TSN directly at the start
+ // with new TSN=15, and setting [(sid=1, ssn=44), (sid=2, ssn=46),
+ // (sid=3, ssn=47)], because that will confuse the receiver at TSN=17,
+ // receiving SID=1, SSN=0 (it's reset!), expecting SSN to be 45.
+ constexpr DataGeneratorOptions kStream1 = {.stream_id = StreamID(1)};
+ constexpr DataGeneratorOptions kStream2 = {.stream_id = StreamID(2)};
+ constexpr DataGeneratorOptions kStream3 = {.stream_id = StreamID(3)};
+ constexpr MaxRetransmits kNoRtx = MaxRetransmits(0);
+ EXPECT_CALL(on_discard_, Call).WillRepeatedly(Return(false));
+
+ // TSN 10-12
+ buf_.Insert(OutgoingMessageId(0), gen_.Ordered({1}, "BE", kStream1), kNow,
+ kNoRtx);
+ buf_.Insert(OutgoingMessageId(1), gen_.Ordered({1}, "BE", kStream1), kNow,
+ kNoRtx);
+ buf_.Insert(OutgoingMessageId(2), gen_.Ordered({1}, "BE", kStream1), kNow,
+ kNoRtx);
+
+ buf_.BeginResetStreams();
+
+ // TSN 13, 14
+ buf_.Insert(OutgoingMessageId(3), gen_.Ordered({1}, "BE", kStream2), kNow,
+ kNoRtx);
+ buf_.Insert(OutgoingMessageId(4), gen_.Ordered({1}, "BE", kStream2), kNow,
+ kNoRtx);
+
+ buf_.BeginResetStreams();
+
+ // TSN 15, 16
+ buf_.Insert(OutgoingMessageId(5), gen_.Ordered({1}, "BE", kStream3), kNow,
+ kNoRtx);
+ buf_.Insert(OutgoingMessageId(6), gen_.Ordered({1}, "BE", kStream3), kNow);
+
+ EXPECT_FALSE(buf_.ShouldSendForwardTsn());
+
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(11)), {}, false);
+ buf_.NackAll();
+ EXPECT_THAT(buf_.GetChunkStatesForTesting(),
+ ElementsAre(Pair(TSN(11), State::kAcked), //
+ Pair(TSN(12), State::kAbandoned), //
+ Pair(TSN(13), State::kAbandoned), //
+ Pair(TSN(14), State::kAbandoned), //
+ Pair(TSN(15), State::kAbandoned), //
+ Pair(TSN(16), State::kToBeRetransmitted)));
+
+ EXPECT_TRUE(buf_.ShouldSendForwardTsn());
+ EXPECT_THAT(
+ buf_.CreateForwardTsn(),
+ AllOf(Property(&ForwardTsnChunk::new_cumulative_tsn, TSN(12)),
+ Property(&ForwardTsnChunk::skipped_streams,
+ UnorderedElementsAre(ForwardTsnChunk::SkippedStream(
+ StreamID(1), SSN(44))))));
+
+ // Ack 12, allowing a FORWARD-TSN that spans to TSN=14 to be created.
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(12)), {}, false);
+ EXPECT_TRUE(buf_.ShouldSendForwardTsn());
+ EXPECT_THAT(
+ buf_.CreateForwardTsn(),
+ AllOf(Property(&ForwardTsnChunk::new_cumulative_tsn, TSN(14)),
+ Property(&ForwardTsnChunk::skipped_streams,
+ UnorderedElementsAre(ForwardTsnChunk::SkippedStream(
+ StreamID(2), SSN(46))))));
+
+ // Ack 13, allowing a FORWARD-TSN that spans to TSN=14 to be created.
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(13)), {}, false);
+ EXPECT_TRUE(buf_.ShouldSendForwardTsn());
+ EXPECT_THAT(
+ buf_.CreateForwardTsn(),
+ AllOf(Property(&ForwardTsnChunk::new_cumulative_tsn, TSN(14)),
+ Property(&ForwardTsnChunk::skipped_streams,
+ UnorderedElementsAre(ForwardTsnChunk::SkippedStream(
+ StreamID(2), SSN(46))))));
+
+ // Ack 14, allowing a FORWARD-TSN that spans to TSN=15 to be created.
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(14)), {}, false);
+ EXPECT_TRUE(buf_.ShouldSendForwardTsn());
+ EXPECT_THAT(
+ buf_.CreateForwardTsn(),
+ AllOf(Property(&ForwardTsnChunk::new_cumulative_tsn, TSN(15)),
+ Property(&ForwardTsnChunk::skipped_streams,
+ UnorderedElementsAre(ForwardTsnChunk::SkippedStream(
+ StreamID(3), SSN(47))))));
+
+ buf_.HandleSack(unwrapper_.Unwrap(TSN(15)), {}, false);
+ EXPECT_FALSE(buf_.ShouldSendForwardTsn());
+}
+
+} // namespace
+} // namespace dcsctp