#include "common.hpp"
#include "RTC/SCTP/packet/UserData.hpp"
#include "RTC/SCTP/packet/chunks/DataChunk.hpp"
#include "RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp"
#include "RTC/SCTP/packet/chunks/SackChunk.hpp"
#include "RTC/SCTP/public/SctpTypes.hpp"
#include "RTC/SCTP/tx/OutstandingData.hpp"
#include "Utils.hpp"
#include "test/include/RTC/SCTP/sctpCommon.hpp"
#include <catch2/catch_test_macros.hpp>
#include <stdexcept>
#include <vector>
SCENARIO("SCTP OutstandingData", "[sctp][outstandingdata]")
{
sctpCommon::ResetBuffers();
class DiscardFromSendQueueTester
{
public:
void Prepare(bool returnValue)
{
if (this->prepared)
{
throw std::runtime_error("already prepared");
}
this->prepared = true;
this->returnValue = returnValue;
}
bool Called(uint16_t streamId, uint32_t outgoingMessageId)
{
if (!this->prepared)
{
return false;
}
this->callCount++;
this->lastStreamId = streamId;
this->lastOutgoingMessageId = outgoingMessageId;
return this->returnValue;
}
void Test(
size_t expectedCallCount, uint16_t expectedLastStreamId, uint32_t expectedLastOutgoingMessageId)
{
if (!this->prepared)
{
throw std::runtime_error("not prepared");
}
REQUIRE(this->callCount == expectedCallCount);
REQUIRE(this->lastStreamId == expectedLastStreamId);
REQUIRE(this->lastOutgoingMessageId == expectedLastOutgoingMessageId);
this->prepared = false;
this->callCount = 0;
this->lastStreamId = 0;
this->lastOutgoingMessageId = 0;
this->returnValue = false;
}
private:
bool prepared{ false };
size_t callCount{ 0 };
uint16_t lastStreamId{ 0 };
uint32_t lastOutgoingMessageId{ 0 };
bool returnValue{ false };
};
constexpr uint64_t NowMs{ 42 };
constexpr uint32_t OutgoingMessageId{ 17 };
RTC::SCTP::Types::UnwrappedTsn::Unwrapper unwrapper;
DiscardFromSendQueueTester discardFromSendQueueTester;
auto discardFromSendQueue =
[&discardFromSendQueueTester](uint16_t streamId, uint32_t outgoingMessageId)
{
return discardFromSendQueueTester.Called(streamId, outgoingMessageId);
};
RTC::SCTP::OutstandingData buffer(
RTC::SCTP::DataChunk::DataChunkHeaderLength,
unwrapper.Unwrap(9),
discardFromSendQueue);
SECTION("has initial state")
{
REQUIRE(buffer.IsEmpty() == true);
REQUIRE(buffer.GetUnackedPayloadBytes() == 0);
REQUIRE(buffer.GetUnackedPacketBytes() == 0);
REQUIRE(buffer.GetUnackedItems() == 0);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9);
REQUIRE(buffer.GetNextTsn().Wrap() == 10);
REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 9);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
});
REQUIRE(buffer.ShouldSendForwardTsn() == false);
}
SECTION("insert chunk")
{
const auto tsn = buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);
REQUIRE(tsn.has_value());
REQUIRE(tsn->Wrap() == 10);
REQUIRE(buffer.IsEmpty() == false);
REQUIRE(buffer.GetUnackedPayloadBytes() == 1);
REQUIRE(buffer.GetUnackedItems() == 1);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9);
REQUIRE(buffer.GetNextTsn().Wrap() == 11);
REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 10);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
});
}
SECTION("acks single chunk")
{
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);
const auto ackInfo = buffer.HandleSack(unwrapper.Unwrap(10), {}, false);
REQUIRE(
ackInfo.bytesAcked ==
RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));
REQUIRE(ackInfo.highestTsnAcked.Wrap() == 10);
REQUIRE(ackInfo.hasPacketLoss == false);
REQUIRE(buffer.IsEmpty() == true);
REQUIRE(buffer.GetUnackedPayloadBytes() == 0);
REQUIRE(buffer.GetUnackedPacketBytes() == 0);
REQUIRE(buffer.GetUnackedItems() == 0);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 10);
REQUIRE(buffer.GetNextTsn().Wrap() == 11);
REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 10);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 10, RTC::SCTP::OutstandingData::State::ACKED },
});
}
SECTION("acks previous chunk doesn't update")
{
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);
REQUIRE(buffer.IsEmpty() == false);
REQUIRE(buffer.GetUnackedPayloadBytes() == 1);
REQUIRE(buffer.GetUnackedItems() == 1);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9);
REQUIRE(buffer.GetNextTsn().Wrap() == 11);
REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 10);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
});
}
SECTION("acks and nacks with gap-ack-blocks")
{
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs);
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs);
const auto ackInfo = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 2 }
},
false);
REQUIRE(
ackInfo.bytesAcked ==
RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));
REQUIRE(ackInfo.highestTsnAcked.Wrap() == 11);
REQUIRE(ackInfo.hasPacketLoss == false);
REQUIRE(buffer.GetUnackedPayloadBytes() == 1);
REQUIRE(buffer.GetUnackedItems() == 1);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9);
REQUIRE(buffer.GetNextTsn().Wrap() == 12);
REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 11);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::NACKED },
{ 11, RTC::SCTP::OutstandingData::State::ACKED },
});
}
SECTION("nacks three times with same TSN doesn't retransmit")
{
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs);
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs);
const std::vector<RTC::SCTP::SackChunk::GapAckBlock> gab1 = {
{ 2, 2 }
};
REQUIRE(buffer.HandleSack(unwrapper.Unwrap(9), gab1, false).hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(buffer.HandleSack(unwrapper.Unwrap(9), gab1, false).hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(buffer.HandleSack(unwrapper.Unwrap(9), gab1, false).hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::NACKED },
{ 11, RTC::SCTP::OutstandingData::State::ACKED },
});
}
SECTION("nacks three times results in retransmission")
{
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs);
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs);
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs);
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 2 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 3 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
const auto ackInfo = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 4 }
},
false);
REQUIRE(
ackInfo.bytesAcked ==
RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));
REQUIRE(ackInfo.highestTsnAcked.Wrap() == 13);
REQUIRE(ackInfo.hasPacketLoss == true);
REQUIRE(buffer.HasDataToBeRetransmitted() == true);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },
{ 11, RTC::SCTP::OutstandingData::State::ACKED },
{ 12, RTC::SCTP::OutstandingData::State::ACKED },
{ 13, RTC::SCTP::OutstandingData::State::ACKED },
});
std::vector<std::pair<uint32_t, RTC::SCTP::UserData>> expectedChunksToBeFastRetransmitted;
expectedChunksToBeFastRetransmitted.emplace_back(
10, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false));
REQUIRE(buffer.GetChunksToBeFastRetransmitted(1000) == expectedChunksToBeFastRetransmitted);
REQUIRE(buffer.GetChunksToBeRetransmitted(1000).empty() == true);
}
SECTION("nacks three times results in abandoning")
{
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false),
NowMs,
0);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 2 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 3 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
discardFromSendQueueTester.Prepare( false);
const auto ackInfo = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 4 }
},
false);
discardFromSendQueueTester.Test(
1,
1,
OutgoingMessageId);
REQUIRE(
ackInfo.bytesAcked ==
RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));
REQUIRE(ackInfo.highestTsnAcked.Wrap() == 13);
REQUIRE(ackInfo.hasPacketLoss == true);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(buffer.GetNextTsn().Wrap() == 14);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 11, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 12, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 13, RTC::SCTP::OutstandingData::State::ABANDONED },
});
}
SECTION("nacks three times results in abandoning with placeholder")
{
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
0);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 2 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 3 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
discardFromSendQueueTester.Prepare( true);
const auto ackInfo = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 4 }
},
false);
discardFromSendQueueTester.Test(
1,
1,
OutgoingMessageId);
REQUIRE(
ackInfo.bytesAcked ==
RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));
REQUIRE(ackInfo.highestTsnAcked.Wrap() == 13);
REQUIRE(ackInfo.hasPacketLoss == true);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(buffer.GetNextTsn().Wrap() == 15);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 11, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 12, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 13, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 14, RTC::SCTP::OutstandingData::State::ABANDONED },
});
}
SECTION("expires chunk before it is inserted")
{
constexpr uint64_t ExpiresAtMs = NowMs + 1;
auto tsn = buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),
NowMs,
RTC::SCTP::Types::MaxRetransmitsNoLimit,
ExpiresAtMs);
REQUIRE(tsn.has_value());
tsn = buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
RTC::SCTP::Types::MaxRetransmitsNoLimit,
ExpiresAtMs);
REQUIRE(tsn.has_value());
discardFromSendQueueTester.Prepare( false);
tsn = buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false),
NowMs + 1,
RTC::SCTP::Types::MaxRetransmitsNoLimit,
ExpiresAtMs);
REQUIRE(!tsn.has_value());
discardFromSendQueueTester.Test(
1,
1,
OutgoingMessageId);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9);
REQUIRE(buffer.GetNextTsn().Wrap() == 13);
REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 12);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 11, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 12, RTC::SCTP::OutstandingData::State::ABANDONED },
});
}
SECTION("can generate Forward-TSN")
{
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false),
NowMs,
0);
discardFromSendQueueTester.Prepare( false);
buffer.NackAll();
discardFromSendQueueTester.Test(
1,
1,
OutgoingMessageId);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 11, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 12, RTC::SCTP::OutstandingData::State::ABANDONED },
});
REQUIRE(buffer.ShouldSendForwardTsn() == true);
std::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(
sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) };
const auto* forwardTsnChunk = buffer.AddForwardTsn(packet.get());
REQUIRE(forwardTsnChunk);
REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 12);
}
SECTION("ack with gap blocks from RFC 9260 section 3.3.4")
{
for (int i{ 0 }; i < 8; ++i)
{
const bool isBeginning = (i == 0);
const bool isEnd = (i == 7);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, isBeginning, isEnd, false),
NowMs);
}
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
{ 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
{ 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
{ 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
{ 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
{ 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
{ 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
{ 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
});
buffer.HandleSack(
unwrapper.Unwrap(12),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 3 },
{ 5, 5 }
},
false);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 12, RTC::SCTP::OutstandingData::State::ACKED },
{ 13, RTC::SCTP::OutstandingData::State::NACKED },
{ 14, RTC::SCTP::OutstandingData::State::ACKED },
{ 15, RTC::SCTP::OutstandingData::State::ACKED },
{ 16, RTC::SCTP::OutstandingData::State::NACKED },
{ 17, RTC::SCTP::OutstandingData::State::ACKED },
});
}
SECTION("MeasureRtt()")
{
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs + 1);
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs + 2);
constexpr uint64_t Duration{ 123 };
const auto duration = buffer.MeasureRtt(NowMs + Duration, unwrapper.Unwrap(11));
REQUIRE(duration.has_value());
REQUIRE(duration.value() == Duration - 1);
}
SECTION("must retransmit before getting nacked again")
{
for (int i{ 0 }; i <= 10; ++i)
{
const bool isBeginning = (i == 0);
const bool isEnd = (i == 10);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, isBeginning, isEnd, false),
NowMs,
1);
}
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 2 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 3 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
const auto ackInfo = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 4 }
},
false);
REQUIRE(ackInfo.hasPacketLoss == true);
REQUIRE(buffer.HasDataToBeRetransmitted() == true);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 5 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == true);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 6 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == true);
const auto ackInfo2 = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 7 }
},
false);
REQUIRE(ackInfo2.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == true);
const auto fastRetransmit = buffer.GetChunksToBeFastRetransmitted(1000);
REQUIRE(fastRetransmit.size() == 1);
REQUIRE(fastRetransmit[0].first == 10);
REQUIRE(buffer.GetChunksToBeRetransmitted(1000).empty() == true);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 8 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(
buffer
.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 9 }
},
false)
.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
const auto ackInfo3 = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 10 }
},
false);
REQUIRE(ackInfo3.hasPacketLoss == true);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
}
SECTION("lifecyle returns acked items in ack-info")
{
buffer.Insert(
1,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false),
NowMs,
RTC::SCTP::Types::MaxRetransmitsNoLimit,
RTC::SCTP::Types::ExpiresAtMsInfinite,
42);
buffer.Insert(
2,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false),
NowMs,
RTC::SCTP::Types::MaxRetransmitsNoLimit,
RTC::SCTP::Types::ExpiresAtMsInfinite,
43);
buffer.Insert(
3,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false),
NowMs,
RTC::SCTP::Types::MaxRetransmitsNoLimit,
RTC::SCTP::Types::ExpiresAtMsInfinite,
44);
const auto ackInfo1 = buffer.HandleSack(unwrapper.Unwrap(11), {}, false);
std::vector<uint64_t> expectedAckedLifecycleIds = { 42, 43 };
REQUIRE(ackInfo1.ackedLifecycleIds == expectedAckedLifecycleIds);
const auto ackInfo2 = buffer.HandleSack(unwrapper.Unwrap(12), {}, false);
expectedAckedLifecycleIds = { 44 };
REQUIRE(ackInfo2.ackedLifecycleIds == expectedAckedLifecycleIds);
}
SECTION("lifecyle returns abandoned nacked three times")
{
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false),
NowMs,
0);
const auto ackInfo1 = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 2 }
},
false);
REQUIRE(ackInfo1.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
const auto ackInfo2 = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 3 }
},
false);
REQUIRE(ackInfo2.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
discardFromSendQueueTester.Prepare( false);
const auto ackInfo3 = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 4 }
},
false);
discardFromSendQueueTester.Test(
1,
1,
OutgoingMessageId);
REQUIRE(ackInfo3.hasPacketLoss == true);
REQUIRE(ackInfo3.abandonedLifecycleIds.empty() == true);
}
SECTION("lifecyle returns abandoned after T3 RTX timer expired")
{
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),
NowMs,
0);
buffer.Insert(
OutgoingMessageId,
RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false),
NowMs,
0,
RTC::SCTP::Types::ExpiresAtMsInfinite,
42);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
{ 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
{ 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
{ 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },
});
const auto ackInfo1 = buffer.HandleSack(
unwrapper.Unwrap(9),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 4 }
},
false);
REQUIRE(ackInfo1.hasPacketLoss == false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::NACKED },
{ 11, RTC::SCTP::OutstandingData::State::ACKED },
{ 12, RTC::SCTP::OutstandingData::State::ACKED },
{ 13, RTC::SCTP::OutstandingData::State::ACKED },
});
discardFromSendQueueTester.Prepare( false);
buffer.NackAll();
discardFromSendQueueTester.Test(
1,
1,
OutgoingMessageId);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 9, RTC::SCTP::OutstandingData::State::ACKED },
{ 10, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 11, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 12, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 13, RTC::SCTP::OutstandingData::State::ABANDONED },
});
;
REQUIRE(buffer.ShouldSendForwardTsn() == true);
std::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(
sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) };
const auto* forwardTsnChunk = buffer.AddForwardTsn(packet.get());
REQUIRE(forwardTsnChunk);
REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 13);
const auto ackInfo2 = buffer.HandleSack(unwrapper.Unwrap(13), {}, false);
REQUIRE(ackInfo2.hasPacketLoss == false);
REQUIRE(ackInfo2.abandonedLifecycleIds == std::vector<uint64_t>{ 42 });
}
SECTION("generates Forward-TSN until next stream reset TSN")
{
buffer.Insert(
0,
RTC::SCTP::UserData(1, 42, 0, 0, 53, { 0x00 }, true, true, false),
NowMs,
0);
buffer.Insert(
1,
RTC::SCTP::UserData(1, 43, 0, 0, 53, { 0x00 }, true, true, false),
NowMs,
0);
buffer.Insert(
2,
RTC::SCTP::UserData(1, 44, 0, 0, 53, { 0x00 }, true, true, false),
NowMs,
0);
buffer.BeginResetStreams();
buffer.Insert(
3,
RTC::SCTP::UserData(2, 45, 0, 0, 53, { 0x00 }, true, true, false),
NowMs,
0);
buffer.Insert(
4,
RTC::SCTP::UserData(2, 46, 0, 0, 53, { 0x00 }, true, true, false),
NowMs,
0);
buffer.BeginResetStreams();
buffer.Insert(
5,
RTC::SCTP::UserData(3, 47, 0, 0, 53, { 0x00 }, true, true, false),
NowMs,
0);
buffer.Insert(6, RTC::SCTP::UserData(3, 48, 0, 0, 53, { 0x00 }, true, true, false), NowMs);
REQUIRE(buffer.ShouldSendForwardTsn() == false);
buffer.HandleSack(unwrapper.Unwrap(11), {}, false);
buffer.NackAll();
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 11, RTC::SCTP::OutstandingData::State::ACKED },
{ 12, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 13, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 14, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 15, RTC::SCTP::OutstandingData::State::ABANDONED },
{ 16, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },
});
REQUIRE(buffer.ShouldSendForwardTsn() == true);
std::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(
sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) };
const auto* forwardTsnChunk = buffer.AddForwardTsn(packet.get());
REQUIRE(forwardTsnChunk);
REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 12);
REQUIRE(
forwardTsnChunk->GetSkippedStreams() == std::vector<RTC::SCTP::ForwardTsnChunk::SkippedStream>{
{ 1, 44 },
});
buffer.HandleSack(unwrapper.Unwrap(12), {}, false);
REQUIRE(buffer.ShouldSendForwardTsn() == true);
packet.reset(
RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)));
forwardTsnChunk = buffer.AddForwardTsn(packet.get());
REQUIRE(forwardTsnChunk);
REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 14);
REQUIRE(
forwardTsnChunk->GetSkippedStreams() == std::vector<RTC::SCTP::ForwardTsnChunk::SkippedStream>{
{ 2, 46 },
});
buffer.HandleSack(unwrapper.Unwrap(13), {}, false);
REQUIRE(buffer.ShouldSendForwardTsn() == true);
packet.reset(
RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)));
forwardTsnChunk = buffer.AddForwardTsn(packet.get());
REQUIRE(forwardTsnChunk);
REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 14);
REQUIRE(
forwardTsnChunk->GetSkippedStreams() == std::vector<RTC::SCTP::ForwardTsnChunk::SkippedStream>{
{ 2, 46 },
});
buffer.HandleSack(unwrapper.Unwrap(14), {}, false);
REQUIRE(buffer.ShouldSendForwardTsn() == true);
packet.reset(
RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)));
forwardTsnChunk = buffer.AddForwardTsn(packet.get());
REQUIRE(forwardTsnChunk);
REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 15);
REQUIRE(
forwardTsnChunk->GetSkippedStreams() == std::vector<RTC::SCTP::ForwardTsnChunk::SkippedStream>{
{ 3, 47 },
});
buffer.HandleSack(unwrapper.Unwrap(15), {}, false);
REQUIRE(buffer.ShouldSendForwardTsn() == false);
}
SECTION("treats unacked payload bytes different from packet bytes")
{
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);
REQUIRE(buffer.GetUnackedPayloadBytes() == 1);
REQUIRE(
buffer.GetUnackedPacketBytes() ==
RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));
REQUIRE(buffer.GetUnackedItems() == 1);
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);
REQUIRE(buffer.GetUnackedPayloadBytes() == 2);
REQUIRE(
buffer.GetUnackedPacketBytes() ==
2 * (RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1)));
REQUIRE(buffer.GetUnackedItems() == 2);
}
SECTION("fast recovery increments nack count when cumulative TSN advances")
{
for (int i{ 10 }; i <= 16; ++i)
{
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs);
}
buffer.HandleSack(
unwrapper.Unwrap(10),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 2 }, { 4, 4 }, { 6, 6 } },
false);
buffer.HandleSack(
unwrapper.Unwrap(11),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 1, 1 }, { 3, 3 }, { 5, 5 } },
true);
buffer.HandleSack(
unwrapper.Unwrap(12),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 2 }, { 4, 4 } },
true);
REQUIRE(
buffer.GetChunkStatesForTesting() ==
std::vector<std::pair<uint32_t , RTC::SCTP::OutstandingData::State>>{
{ 12, RTC::SCTP::OutstandingData::State::ACKED },
{ 13, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },
{ 14, RTC::SCTP::OutstandingData::State::ACKED },
{ 15, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },
{ 16, RTC::SCTP::OutstandingData::State::ACKED },
});
}
SECTION("nack between ack blocks does not access out of bounds")
{
for (int i{ 0 }; i < 5; ++i)
{
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs);
}
std::vector<RTC::SCTP::SackChunk::GapAckBlock> malformedBlocks = {
{ 1, 40000 },
};
buffer.HandleSack(unwrapper.Unwrap(10), malformedBlocks, false);
REQUIRE(buffer.HasDataToBeRetransmitted() == false);
}
SECTION("handles SACKs with out of bounds TSNs")
{
for (int i{ 0 }; i < 7; ++i)
{
buffer.Insert(
OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs);
}
buffer.HandleSack(
unwrapper.Unwrap(10),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 2, 2 }, { 4, 4 }, { 6, 6 } },
false);
REQUIRE(buffer.GetUnackedItems() == 3);
buffer.HandleSack(
unwrapper.Unwrap(11),
std::vector<RTC::SCTP::SackChunk::GapAckBlock>{
{ 1, 1 }, { 1000, 60000 } },
true);
REQUIRE(buffer.GetUnackedItems() == 2);
}
}