webrtc_m130/net/dcsctp/tx/outstanding_data_test.cc
Victor Boivie bcf588da8f dcsctp: Estimate rwnd by payload bytes
The dcSCTP receiver was advertising available window space (arwnd) based
solely on payload bytes, while the sender's rwnd estimation included
packet headers. This mismatch caused the sender to underestimate the
receiver's available buffer, potentially leading to reduced throughput.

This commit resolves the issue by ensuring both sender and receiver use
payload bytes, as headers have been removed on the receiver side while
in the reassembly queue.

Bug: webrtc:396373001
Change-Id: I508419efb09cabf2fb011f952f5f4a06586a4019
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/377122
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43899}
2025-02-17 02:36:07 -08:00

678 lines
29 KiB
C++

/*
* 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 <optional>
#include <vector>
#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;
using ::webrtc::TimeDelta;
using ::webrtc::Timestamp;
constexpr Timestamp kNow = Timestamp::Millis(42);
constexpr OutgoingMessageId kMessageId = OutgoingMessageId(17);
class OutstandingDataTest : public testing::Test {
protected:
OutstandingDataTest()
: gen_(MID(42)),
buf_(DataChunk::kHeaderSize,
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_.unacked_payload_bytes(), 0u);
EXPECT_EQ(buf_.unacked_packet_bytes(), 0u);
EXPECT_EQ(buf_.unacked_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_.unacked_payload_bytes(), 1u);
EXPECT_EQ(buf_.unacked_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_.unacked_payload_bytes(), 0u);
EXPECT_EQ(buf_.unacked_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_.unacked_payload_bytes(), 1u);
EXPECT_EQ(buf_.unacked_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_.unacked_payload_bytes(), 0u);
EXPECT_EQ(buf_.unacked_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 Timestamp kExpiresAt = kNow + TimeDelta::Millis(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 + TimeDelta::Millis(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 + TimeDelta::Millis(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 + TimeDelta::Millis(1));
buf_.Insert(kMessageId, gen_.Ordered({1}, "BE"), kNow + TimeDelta::Millis(2));
static constexpr TimeDelta kDuration = TimeDelta::Millis(123);
TimeDelta duration =
buf_.MeasureRTT(kNow + kDuration, unwrapper_.Unwrap(TSN(11)));
EXPECT_EQ(duration, kDuration - TimeDelta::Millis(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(), Timestamp::PlusInfinity(),
LifecycleId(42));
buf_.Insert(OutgoingMessageId(2), gen_.Ordered({1}, "BE"), kNow,
MaxRetransmits::NoLimit(), Timestamp::PlusInfinity(),
LifecycleId(43));
buf_.Insert(OutgoingMessageId(3), gen_.Ordered({1}, "BE"), kNow,
MaxRetransmits::NoLimit(), Timestamp::PlusInfinity(),
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),
Timestamp::PlusInfinity(), 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),
Timestamp::PlusInfinity(), 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());
}
TEST_F(OutstandingDataTest, TreatsUnackedPayloadBytesDifferentFromPacketBytes) {
buf_.Insert(kMessageId, gen_.Ordered({1}, "BE"), kNow);
EXPECT_EQ(buf_.unacked_payload_bytes(), 1u);
EXPECT_EQ(buf_.unacked_packet_bytes(),
DataChunk::kHeaderSize + RoundUpTo4(1));
EXPECT_EQ(buf_.unacked_items(), 1u);
buf_.Insert(kMessageId, gen_.Ordered({1}, "BE"), kNow);
EXPECT_EQ(buf_.unacked_payload_bytes(), 2u);
EXPECT_EQ(buf_.unacked_packet_bytes(),
2 * (DataChunk::kHeaderSize + RoundUpTo4(1)));
EXPECT_EQ(buf_.unacked_items(), 2u);
}
} // namespace
} // namespace dcsctp