mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-09 20:42:44 +03:00
Summary: See title Reviewed By: kvtsoy Differential Revision: D73444489 fbshipit-source-id: f83566ce023e8237335d3bb43d89fc471f053afa
3613 lines
138 KiB
C++
3613 lines
138 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#include <folly/portability/GMock.h>
|
|
#include <folly/portability/GTest.h>
|
|
#include <quic/codec/Types.h>
|
|
#include <quic/state/AckEvent.h>
|
|
|
|
#include <folly/io/async/test/MockTimeoutManager.h>
|
|
#include <quic/api/QuicTransportFunctions.h>
|
|
#include <quic/api/test/MockQuicSocket.h>
|
|
#include <quic/api/test/Mocks.h>
|
|
#include <quic/client/state/ClientStateMachine.h>
|
|
#include <quic/codec/DefaultConnectionIdAlgo.h>
|
|
#include <quic/common/events/FollyQuicEventBase.h>
|
|
#include <quic/common/test/TestUtils.h>
|
|
#include <quic/common/testutil/MockAsyncUDPSocket.h>
|
|
#include <quic/dsr/test/Mocks.h>
|
|
#include <quic/fizz/client/handshake/FizzClientQuicHandshakeContext.h>
|
|
#include <quic/fizz/server/handshake/FizzServerQuicHandshakeContext.h>
|
|
#include <quic/logging/test/Mocks.h>
|
|
#include <quic/loss/QuicLossFunctions.h>
|
|
#include <quic/server/state/ServerStateMachine.h>
|
|
#include <quic/state/stream/StreamSendHandlers.h>
|
|
#include <quic/state/test/MockQuicStats.h>
|
|
#include <quic/state/test/Mocks.h>
|
|
|
|
using namespace folly::test;
|
|
using namespace testing;
|
|
using namespace folly;
|
|
|
|
namespace {
|
|
auto getOutstandingPacketMatcher(
|
|
quic::PacketNum packetNum,
|
|
bool lostByReorder,
|
|
bool lostByTimeout) {
|
|
return AllOf(
|
|
testing::Field(
|
|
&quic::OutstandingPacketWrapper::metadata,
|
|
testing::Field(
|
|
&quic::OutstandingPacketMetadata::lossReorderDistance,
|
|
testing::Property(
|
|
&quic::OptionalIntegral<uint16_t>::has_value,
|
|
testing::Eq(lostByReorder)))),
|
|
testing::Field(
|
|
&quic::OutstandingPacketWrapper::metadata,
|
|
testing::Field(
|
|
&quic::OutstandingPacketMetadata::lossTimeoutDividend,
|
|
testing::Property(
|
|
&quic::OptionalIntegral<quic::DurationRep>::has_value,
|
|
testing::Eq(lostByTimeout)))),
|
|
testing::Field(
|
|
&quic::OutstandingPacketWrapper::packet,
|
|
testing::Field(
|
|
&quic::RegularPacket::header,
|
|
testing::Property(
|
|
&quic::PacketHeader::getPacketSequenceNum, packetNum))));
|
|
}
|
|
} // namespace
|
|
|
|
namespace quic::test {
|
|
|
|
class MockLossTimeout {
|
|
public:
|
|
MOCK_METHOD(void, cancelLossTimeout, ());
|
|
MOCK_METHOD(void, scheduleLossTimeout, (std::chrono::milliseconds));
|
|
MOCK_METHOD(bool, isLossTimeoutScheduled, ());
|
|
};
|
|
|
|
enum class PacketType {
|
|
Initial,
|
|
Handshake,
|
|
ZeroRtt,
|
|
OneRtt,
|
|
};
|
|
|
|
class QuicLossFunctionsTest : public TestWithParam<PacketNumberSpace> {
|
|
public:
|
|
void SetUp() override {
|
|
aead = createNoOpAead();
|
|
headerCipher = createNoOpHeaderCipher();
|
|
quicStats_ = std::make_unique<MockQuicStats>();
|
|
connIdAlgo_ = std::make_unique<DefaultConnectionIdAlgo>();
|
|
socket_ = std::make_unique<MockQuicSocket>();
|
|
observerContainer_ =
|
|
std::make_shared<SocketObserverContainer>(socket_.get());
|
|
}
|
|
|
|
PacketNum sendPacket(
|
|
QuicConnectionStateBase& conn,
|
|
TimePoint time,
|
|
Optional<ClonedPacketIdentifier> maybeClonedPacketIdentifier,
|
|
PacketType packetType,
|
|
Optional<uint16_t> forcedSize = none,
|
|
bool isDsr = false);
|
|
|
|
std::unique_ptr<QuicServerConnectionState> createConn() {
|
|
auto conn = std::make_unique<QuicServerConnectionState>(
|
|
FizzServerQuicHandshakeContext::Builder().build());
|
|
conn->clientConnectionId = getTestConnectionId();
|
|
conn->version = QuicVersion::MVFST;
|
|
conn->ackStates.initialAckState->nextPacketNum = 1;
|
|
conn->ackStates.initialAckState->nonDsrPacketSequenceNumber = 1;
|
|
conn->ackStates.handshakeAckState->nextPacketNum = 1;
|
|
conn->ackStates.handshakeAckState->nonDsrPacketSequenceNumber = 1;
|
|
conn->ackStates.appDataAckState.nextPacketNum = 1;
|
|
conn->ackStates.appDataAckState.nonDsrPacketSequenceNumber = 1;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiLocal =
|
|
kDefaultStreamFlowControlWindow;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote =
|
|
kDefaultStreamFlowControlWindow;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetUni =
|
|
kDefaultStreamFlowControlWindow;
|
|
conn->flowControlState.peerAdvertisedMaxOffset =
|
|
kDefaultConnectionFlowControlWindow;
|
|
CHECK(
|
|
!conn->streamManager
|
|
->setMaxLocalBidirectionalStreams(kDefaultMaxStreamsBidirectional)
|
|
.hasError());
|
|
CHECK(!conn->streamManager
|
|
->setMaxLocalUnidirectionalStreams(
|
|
kDefaultMaxStreamsUnidirectional)
|
|
.hasError());
|
|
conn->statsCallback = quicStats_.get();
|
|
// create a serverConnectionId that is different from the client connId
|
|
// with bits for processId and workerId set to 0
|
|
ServerConnectionIdParams params(0, 0, 0);
|
|
conn->connIdAlgo = connIdAlgo_.get();
|
|
conn->serverConnectionId = *connIdAlgo_->encodeConnectionId(params);
|
|
// for canSetLossTimerForAppData()
|
|
conn->oneRttWriteCipher = createNoOpAead();
|
|
conn->observerContainer = observerContainer_;
|
|
return conn;
|
|
}
|
|
|
|
std::unique_ptr<QuicClientConnectionState> createClientConn() {
|
|
auto conn = std::make_unique<QuicClientConnectionState>(
|
|
FizzClientQuicHandshakeContext::Builder().build());
|
|
conn->clientConnectionId = getTestConnectionId();
|
|
conn->version = QuicVersion::MVFST;
|
|
conn->ackStates.initialAckState->nextPacketNum = 1;
|
|
conn->ackStates.handshakeAckState->nextPacketNum = 1;
|
|
conn->ackStates.appDataAckState.nextPacketNum = 1;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiLocal =
|
|
kDefaultStreamFlowControlWindow;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote =
|
|
kDefaultStreamFlowControlWindow;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetUni =
|
|
kDefaultStreamFlowControlWindow;
|
|
conn->flowControlState.peerAdvertisedMaxOffset =
|
|
kDefaultConnectionFlowControlWindow;
|
|
conn->statsCallback = quicStats_.get();
|
|
// create a serverConnectionId that is different from the client connId
|
|
// with bits for processId and workerId set to 0
|
|
ServerConnectionIdParams params(0, 0, 0);
|
|
conn->serverConnectionId = *connIdAlgo_.get()->encodeConnectionId(params);
|
|
return conn;
|
|
}
|
|
|
|
EventBase evb;
|
|
std::unique_ptr<Aead> aead;
|
|
std::unique_ptr<PacketNumberCipher> headerCipher;
|
|
MockLossTimeout timeout;
|
|
std::unique_ptr<MockQuicStats> quicStats_;
|
|
std::unique_ptr<ConnectionIdAlgo> connIdAlgo_;
|
|
std::unique_ptr<MockQuicSocket> socket_;
|
|
std::shared_ptr<SocketObserverContainer> observerContainer_;
|
|
|
|
auto getLossPacketMatcher(
|
|
PacketNum packetNum,
|
|
bool lossByReorder,
|
|
bool lossByTimeout) {
|
|
return MockLegacyObserver::getLossPacketMatcher(
|
|
packetNum, lossByReorder, lossByTimeout);
|
|
}
|
|
|
|
StreamId writeQueueContains(QuicConnectionStateBase& conn, StreamId id) {
|
|
auto oldWriteQueue = conn.streamManager->oldWriteQueue();
|
|
if (oldWriteQueue) {
|
|
return oldWriteQueue->count(id);
|
|
}
|
|
return conn.streamManager->writeQueue().contains(
|
|
PriorityQueue::Identifier::fromStreamID(id));
|
|
}
|
|
};
|
|
|
|
LossVisitor testingLossMarkFunc(std::vector<PacketNum>& lostPackets) {
|
|
return [&lostPackets](auto& /* conn */, auto& packet, bool processed)
|
|
-> folly::Expected<folly::Unit, QuicError> {
|
|
if (!processed) {
|
|
auto packetNum = packet.header.getPacketSequenceNum();
|
|
lostPackets.push_back(packetNum);
|
|
}
|
|
return folly::unit;
|
|
};
|
|
}
|
|
|
|
PacketNum QuicLossFunctionsTest::sendPacket(
|
|
QuicConnectionStateBase& conn,
|
|
TimePoint time,
|
|
Optional<ClonedPacketIdentifier> maybeClonedPacketIdentifier,
|
|
PacketType packetType,
|
|
Optional<uint16_t> forcedSize,
|
|
bool isDsr) {
|
|
Optional<PacketHeader> header;
|
|
switch (packetType) {
|
|
case PacketType::Initial:
|
|
header = LongHeader(
|
|
LongHeader::Types::Initial,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
conn.ackStates.initialAckState->nextPacketNum,
|
|
*conn.version);
|
|
break;
|
|
case PacketType::Handshake:
|
|
header = LongHeader(
|
|
LongHeader::Types::Handshake,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
conn.ackStates.handshakeAckState->nextPacketNum,
|
|
*conn.version);
|
|
break;
|
|
case PacketType::ZeroRtt:
|
|
header = LongHeader(
|
|
LongHeader::Types::ZeroRtt,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
conn.ackStates.appDataAckState.nextPacketNum,
|
|
*conn.version);
|
|
break;
|
|
case PacketType::OneRtt:
|
|
header = ShortHeader(
|
|
ProtectionType::KeyPhaseZero,
|
|
*conn.serverConnectionId,
|
|
conn.ackStates.appDataAckState.nextPacketNum);
|
|
break;
|
|
}
|
|
PacketNumberSpace packetNumberSpace;
|
|
auto shortHeader = header->asShort();
|
|
if (shortHeader) {
|
|
packetNumberSpace = shortHeader->getPacketNumberSpace();
|
|
} else {
|
|
packetNumberSpace = header->asLong()->getPacketNumberSpace();
|
|
}
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen,
|
|
std::move(*header),
|
|
getAckState(conn, packetNumberSpace).largestAckedByPeer.value_or(0));
|
|
CHECK(!builder.encodePacketHeader().hasError());
|
|
EXPECT_TRUE(builder.canBuildPacket());
|
|
auto packet = std::move(builder).buildPacket();
|
|
if (forcedSize) {
|
|
RegularSizeEnforcedPacketBuilder sizeEnforcedBuilder(
|
|
std::move(packet), *forcedSize, aead->getCipherOverhead());
|
|
EXPECT_TRUE(sizeEnforcedBuilder.canBuildPacket());
|
|
packet = std::move(sizeEnforcedBuilder).buildPacket();
|
|
}
|
|
uint32_t encodedSize = 0;
|
|
uint32_t encodedBodySize = 0;
|
|
if (!packet.header.empty()) {
|
|
encodedSize += packet.header.computeChainDataLength();
|
|
}
|
|
if (!packet.body.empty()) {
|
|
encodedSize += packet.body.computeChainDataLength();
|
|
encodedBodySize += packet.body.computeChainDataLength();
|
|
}
|
|
auto outstandingPacket = OutstandingPacketWrapper(
|
|
packet.packet,
|
|
time,
|
|
encodedSize,
|
|
encodedBodySize,
|
|
encodedSize,
|
|
0,
|
|
LossState(),
|
|
0,
|
|
OutstandingPacketMetadata::DetailsPerStream());
|
|
outstandingPacket.maybeClonedPacketIdentifier = maybeClonedPacketIdentifier;
|
|
conn.lossState.lastRetransmittablePacketSentTime = time;
|
|
if (conn.congestionController) {
|
|
conn.congestionController->onPacketSent(outstandingPacket);
|
|
}
|
|
if (maybeClonedPacketIdentifier) {
|
|
conn.outstandings.clonedPacketCount[packetNumberSpace]++;
|
|
// Simulates what the real writer does.
|
|
auto it = std::find_if(
|
|
conn.outstandings.packets.begin(),
|
|
conn.outstandings.packets.end(),
|
|
[&maybeClonedPacketIdentifier](const auto& packet) {
|
|
auto packetNum = packet.packet.header.getPacketSequenceNum();
|
|
auto packetNumSpace = packet.packet.header.getPacketNumberSpace();
|
|
return packetNum == maybeClonedPacketIdentifier->packetNumber &&
|
|
packetNumSpace == maybeClonedPacketIdentifier->packetNumberSpace;
|
|
});
|
|
if (it != conn.outstandings.packets.end()) {
|
|
if (!it->maybeClonedPacketIdentifier) {
|
|
conn.outstandings.clonedPacketIdentifiers.emplace(
|
|
*maybeClonedPacketIdentifier);
|
|
conn.outstandings.clonedPacketCount[packetNumberSpace]++;
|
|
it->maybeClonedPacketIdentifier = *maybeClonedPacketIdentifier;
|
|
}
|
|
}
|
|
} else {
|
|
conn.outstandings.packetCount[packetNumberSpace]++;
|
|
}
|
|
conn.outstandings.packets.emplace_back(std::move(outstandingPacket));
|
|
conn.lossState.largestSent = getNextPacketNum(conn, packetNumberSpace);
|
|
increaseNextPacketNum(conn, packetNumberSpace, isDsr);
|
|
conn.outstandings.packets.back().isDSRPacket = isDsr;
|
|
if (!isDsr) {
|
|
conn.outstandings.packets.back().nonDsrPacketSequenceNumber =
|
|
getAckState(conn, packetNumberSpace).nonDsrPacketSequenceNumber - 1;
|
|
}
|
|
conn.pendingEvents.setLossDetectionAlarm = true;
|
|
return conn.lossState.largestSent.value();
|
|
}
|
|
|
|
RegularQuicWritePacket stripPaddingFrames(RegularQuicWritePacket packet) {
|
|
RegularQuicWritePacket::Vec trimmedFrames{};
|
|
for (auto frame : packet.frames) {
|
|
if (!frame.asPaddingFrame()) {
|
|
trimmedFrames.push_back(frame);
|
|
}
|
|
}
|
|
packet.frames = trimmedFrames;
|
|
return packet;
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, AllPacketsProcessed) {
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*quicStats_, onPTO()).Times(0);
|
|
ClonedPacketIdentifier clonedPacketIdentifier1(
|
|
PacketNumberSpace::AppData,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
sendPacket(*conn, Clock::now(), clonedPacketIdentifier1, PacketType::OneRtt);
|
|
ClonedPacketIdentifier clonedPacketIdentifier2(
|
|
PacketNumberSpace::AppData,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
sendPacket(*conn, Clock::now(), clonedPacketIdentifier2, PacketType::OneRtt);
|
|
ClonedPacketIdentifier clonedPacketIdentifier3(
|
|
PacketNumberSpace::AppData,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
sendPacket(*conn, Clock::now(), clonedPacketIdentifier3, PacketType::OneRtt);
|
|
EXPECT_CALL(timeout, cancelLossTimeout()).Times(1);
|
|
setLossDetectionAlarm(*conn, timeout);
|
|
EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, HasDataToWrite) {
|
|
auto conn = createConn();
|
|
// There needs to be at least one outstanding packet.
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
conn->streamManager->addLoss(1);
|
|
conn->pendingEvents.setLossDetectionAlarm = true;
|
|
EXPECT_CALL(timeout, cancelLossTimeout()).Times(2);
|
|
EXPECT_CALL(timeout, scheduleLossTimeout(_)).Times(1);
|
|
setLossDetectionAlarm(*conn, timeout);
|
|
EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, ClearEarlyRetranTimer) {
|
|
auto conn = createConn();
|
|
// make delayUntilLoss relatively large
|
|
conn->lossState.srtt = 1s;
|
|
conn->lossState.lrtt = 1s;
|
|
auto currentTime = Clock::now();
|
|
auto firstPacketNum =
|
|
sendPacket(*conn, currentTime, none, PacketType::Initial);
|
|
auto secondPacketNum =
|
|
sendPacket(*conn, currentTime, none, PacketType::Initial);
|
|
ASSERT_GT(secondPacketNum, firstPacketNum);
|
|
ASSERT_EQ(2, conn->outstandings.packets.size());
|
|
// detectLossPackets will set lossTime on Initial space.
|
|
auto lossVisitor =
|
|
[](auto&, auto&, bool) -> folly::Expected<folly::Unit, QuicError> {
|
|
EXPECT_FALSE(true) << "Shouldn't call lossVisitor";
|
|
return folly::unit;
|
|
};
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::Initial);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = secondPacketNum;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
lossVisitor,
|
|
Clock::now(),
|
|
PacketNumberSpace::Initial)
|
|
.hasError());
|
|
ASSERT_TRUE(earliestLossTimer(*conn).first.has_value());
|
|
ASSERT_EQ(PacketNumberSpace::Initial, earliestLossTimer(*conn).second);
|
|
conn->pendingEvents.setLossDetectionAlarm = true;
|
|
|
|
// Schedule a loss timer
|
|
EXPECT_CALL(timeout, isLossTimeoutScheduled()).WillRepeatedly(Return(false));
|
|
EXPECT_CALL(timeout, cancelLossTimeout()).Times(1);
|
|
EXPECT_CALL(timeout, scheduleLossTimeout(_)).Times(1);
|
|
setLossDetectionAlarm(*conn, timeout);
|
|
EXPECT_EQ(
|
|
LossState::AlarmMethod::EarlyRetransmitOrReordering,
|
|
conn->lossState.currentAlarmMethod);
|
|
|
|
// Ack the initial packets
|
|
ReadAckFrame ackFrame;
|
|
ackFrame.largestAcked = secondPacketNum;
|
|
ackFrame.ackBlocks.emplace_back(firstPacketNum, secondPacketNum);
|
|
// Ack won't cancel loss timer
|
|
EXPECT_CALL(timeout, cancelLossTimeout()).Times(0);
|
|
ASSERT_FALSE(processAckFrame(
|
|
*conn,
|
|
PacketNumberSpace::Initial,
|
|
ackFrame,
|
|
[](auto&) { return folly::unit; },
|
|
[&](auto&, auto&) { return folly::unit; },
|
|
lossVisitor,
|
|
Clock::now())
|
|
.hasError());
|
|
|
|
// Send out a AppData packet that isn't retransmittable
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
conn->pendingEvents.setLossDetectionAlarm = false;
|
|
|
|
// setLossDetectionAlarm will cancel loss timer, and not schedule another one.
|
|
EXPECT_CALL(timeout, isLossTimeoutScheduled())
|
|
.Times(1)
|
|
.WillOnce(Return(false));
|
|
EXPECT_CALL(timeout, cancelLossTimeout()).Times(1);
|
|
EXPECT_CALL(timeout, scheduleLossTimeout(_)).Times(0);
|
|
setLossDetectionAlarm(*conn, timeout);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestOnLossDetectionAlarm) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
MockClock::mockNow = []() { return TimePoint(123ms); };
|
|
std::vector<PacketNum> lostPacket;
|
|
MockClock::mockNow = []() { return TimePoint(23ms); };
|
|
EXPECT_CALL(*quicStats_, onPTO());
|
|
setLossDetectionAlarm<decltype(timeout), MockClock>(*conn, timeout);
|
|
EXPECT_EQ(LossState::AlarmMethod::PTO, conn->lossState.currentAlarmMethod);
|
|
ASSERT_FALSE(
|
|
onLossDetectionAlarm<MockClock>(*conn, testingLossMarkFunc(lostPacket))
|
|
.hasError());
|
|
EXPECT_EQ(conn->lossState.ptoCount, 1);
|
|
EXPECT_TRUE(conn->pendingEvents.setLossDetectionAlarm);
|
|
// PTO shouldn't mark loss
|
|
EXPECT_TRUE(lostPacket.empty());
|
|
|
|
MockClock::mockNow = []() { return TimePoint(3ms); };
|
|
EXPECT_CALL(*quicStats_, onPTO());
|
|
sendPacket(*conn, TimePoint(), none, PacketType::OneRtt);
|
|
setLossDetectionAlarm<decltype(timeout), MockClock>(*conn, timeout);
|
|
EXPECT_CALL(*rawCongestionController, onPacketAckOrLoss(_, _)).Times(0);
|
|
ASSERT_FALSE(
|
|
onLossDetectionAlarm<MockClock>(*conn, testingLossMarkFunc(lostPacket))
|
|
.hasError());
|
|
EXPECT_EQ(conn->lossState.ptoCount, 2);
|
|
// PTO doesn't take anything out of outstandings.packets
|
|
EXPECT_FALSE(conn->outstandings.packets.empty());
|
|
EXPECT_TRUE(conn->pendingEvents.setLossDetectionAlarm);
|
|
// PTO shouldn't mark loss
|
|
EXPECT_TRUE(lostPacket.empty());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestOnPTOSkipProcessed) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
// By adding an maybeClonedPacketIdentifier that doesn't exist in the
|
|
// outstandings.clonedPacketIdentifiers, they are all processed and will skip
|
|
// lossVisitor
|
|
for (auto i = 0; i < 10; i++) {
|
|
ClonedPacketIdentifier clonedPacketIdentifier(
|
|
PacketNumberSpace::AppData, i);
|
|
sendPacket(*conn, TimePoint(), clonedPacketIdentifier, PacketType::OneRtt);
|
|
}
|
|
EXPECT_EQ(10, conn->outstandings.packets.size());
|
|
std::vector<PacketNum> lostPackets;
|
|
EXPECT_CALL(*rawCongestionController, onRemoveBytesFromInflight(_)).Times(0);
|
|
EXPECT_CALL(*quicStats_, onPTO());
|
|
auto ret = onPTOAlarm(*conn);
|
|
EXPECT_FALSE(ret.hasError());
|
|
EXPECT_EQ(10, conn->outstandings.packets.size());
|
|
EXPECT_TRUE(lostPackets.empty());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLoss) {
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(2);
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream2Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
auto stream2 = conn->streamManager->findStream(stream2Id);
|
|
auto buf = buildRandomInputData(20);
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream2, buf->clone(), true).hasError());
|
|
|
|
auto packetSeqNum = conn->ackStates.handshakeAckState->nextPacketNum;
|
|
LongHeader header(
|
|
LongHeader::Types::Handshake,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
packetSeqNum,
|
|
*conn->version);
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
auto& packet =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet, false).hasError());
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream2->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 1);
|
|
EXPECT_EQ(stream2->lossBuffer.size(), 1);
|
|
EXPECT_EQ(stream1->streamLossCount, 1);
|
|
EXPECT_EQ(stream2->streamLossCount, 1);
|
|
|
|
auto& buffer = stream1->lossBuffer.front();
|
|
EXPECT_EQ(buffer.offset, 0);
|
|
EXPECT_EQ(
|
|
ByteRange(buf->data(), buf->length()), buffer.data.getHead()->getRange());
|
|
}
|
|
|
|
bool areEqual(folly::IOBuf* ptr, ChainedByteRangeHead* rch) {
|
|
if (ptr->computeChainDataLength() != rch->chainLength()) {
|
|
return false;
|
|
}
|
|
|
|
auto* chainedByteRange = rch->getHead();
|
|
|
|
const uint8_t* currentIOBufDataPtr = ptr->data();
|
|
const uint8_t* currentRchDataPtr = chainedByteRange->getRange().data();
|
|
|
|
uint32_t remainingLenIOBuf = ptr->length();
|
|
uint32_t remainingLenRch = chainedByteRange->getRange().size();
|
|
|
|
for (uint32_t i = 0; i < rch->chainLength(); i++) {
|
|
while (remainingLenIOBuf == 0) {
|
|
ptr = ptr->next();
|
|
remainingLenIOBuf = ptr->length();
|
|
currentIOBufDataPtr = ptr->data();
|
|
}
|
|
|
|
while (remainingLenRch == 0) {
|
|
chainedByteRange = chainedByteRange->getNext();
|
|
remainingLenRch = chainedByteRange->getRange().size();
|
|
currentRchDataPtr = chainedByteRange->getRange().data();
|
|
}
|
|
|
|
if (*currentIOBufDataPtr != *currentRchDataPtr) {
|
|
return false;
|
|
}
|
|
|
|
remainingLenIOBuf--;
|
|
remainingLenRch--;
|
|
|
|
currentIOBufDataPtr++;
|
|
currentRchDataPtr++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLossMerge) {
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
|
|
auto buf1 = buildRandomInputData(20);
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream1, buf1->clone(), false).hasError());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
|
|
auto buf2 = buildRandomInputData(20);
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream1, buf2->clone(), false).hasError());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
|
|
auto& packet1 =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet1, false).hasError());
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 1);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 1);
|
|
EXPECT_EQ(stream1->streamLossCount, 1);
|
|
auto& packet2 =
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet2, false).hasError());
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 1);
|
|
EXPECT_EQ(stream1->streamLossCount, 2);
|
|
|
|
auto combined = buf1->clone();
|
|
combined->prependChain(buf2->clone());
|
|
auto& buffer = stream1->lossBuffer.front();
|
|
EXPECT_EQ(buffer.offset, 0);
|
|
EXPECT_TRUE(areEqual(combined.get(), &buffer.data));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLossNoMerge) {
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
|
|
auto buf1 = buildRandomInputData(20);
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream1, buf1->clone(), false).hasError());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
|
|
auto buf2 = buildRandomInputData(20);
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream1, buf2->clone(), false).hasError());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
|
|
auto buf3 = buildRandomInputData(20);
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream1, buf3->clone(), false).hasError());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_EQ(3, conn->outstandings.packets.size());
|
|
|
|
auto& packet1 =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet1, false).hasError());
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 2);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 1);
|
|
EXPECT_EQ(stream1->streamLossCount, 1);
|
|
auto& packet3 =
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet3, false).hasError());
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 1);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 2);
|
|
EXPECT_EQ(stream1->streamLossCount, 2);
|
|
|
|
auto& buffer1 = stream1->lossBuffer[0];
|
|
EXPECT_EQ(buffer1.offset, 0);
|
|
EXPECT_EQ(
|
|
ByteRange(buf1->data(), buf1->length()),
|
|
buffer1.data.getHead()->getRange());
|
|
|
|
auto& buffer3 = stream1->lossBuffer[1];
|
|
EXPECT_EQ(buffer3.offset, 40);
|
|
EXPECT_EQ(
|
|
ByteRange(buf3->data(), buf3->length()),
|
|
buffer3.data.getHead()->getRange());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, RetxBufferSortedAfterLoss) {
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
auto conn = createConn();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf1 = IOBuf::copyBuffer("Worse case scenario");
|
|
auto buf2 = IOBuf::copyBuffer("The hard problem");
|
|
auto buf3 = IOBuf::copyBuffer("And then we had a flash of insight...");
|
|
writeQuicPacket(
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
socket,
|
|
*stream,
|
|
*buf1);
|
|
writeQuicPacket(
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
socket,
|
|
*stream,
|
|
*buf2);
|
|
writeQuicPacket(
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
socket,
|
|
*stream,
|
|
*buf3);
|
|
EXPECT_EQ(3, stream->retransmissionBuffer.size());
|
|
EXPECT_EQ(3, conn->outstandings.packets.size());
|
|
auto& packet = conn->outstandings.packets[folly::Random::rand32() % 3];
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet.packet, false).hasError());
|
|
EXPECT_EQ(1, stream->streamLossCount);
|
|
EXPECT_EQ(2, stream->retransmissionBuffer.size());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLossAfterStreamReset) {
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
auto conn = createConn();
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = buildRandomInputData(20);
|
|
|
|
auto packet = writeQuicPacket(
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
socket,
|
|
*stream1,
|
|
*buf,
|
|
true);
|
|
ASSERT_FALSE(sendRstSMHandler(*stream1, GenericApplicationErrorCode::UNKNOWN)
|
|
.hasError());
|
|
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet, false).hasError());
|
|
|
|
EXPECT_TRUE(stream1->lossBuffer.empty());
|
|
EXPECT_TRUE(stream1->retransmissionBuffer.empty());
|
|
EXPECT_TRUE(stream1->pendingWrites.empty());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestReorderingThreshold) {
|
|
std::vector<PacketNum> lostPacket;
|
|
auto conn = createConn();
|
|
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
auto testingLossMarkFunc = [&lostPacket](auto& /*conn*/, auto& packet, bool) {
|
|
auto packetNum = packet.header.getPacketSequenceNum();
|
|
lostPacket.push_back(packetNum);
|
|
return folly::unit;
|
|
};
|
|
for (int i = 0; i < 6; ++i) {
|
|
sendPacket(*conn, Clock::now(), none, PacketType::Handshake);
|
|
}
|
|
EXPECT_EQ(6, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
// Assume some packets are already acked
|
|
for (auto iter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake) + 2;
|
|
iter <
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake) + 5;
|
|
iter++) {
|
|
conn->outstandings.packetCount[PacketNumberSpace::Handshake]--;
|
|
}
|
|
auto firstHandshakeOpIter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake);
|
|
conn->outstandings.packets.erase(
|
|
firstHandshakeOpIter + 2, firstHandshakeOpIter + 5);
|
|
// Ack for packet 9 arrives
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::Handshake);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = 9;
|
|
auto lossResult = detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
testingLossMarkFunc,
|
|
TimePoint(90ms),
|
|
PacketNumberSpace::Handshake);
|
|
ASSERT_FALSE(lossResult.hasError());
|
|
auto& lossEvent = lossResult.value();
|
|
EXPECT_EQ(2, lossEvent->largestLostPacketNum.value());
|
|
EXPECT_EQ(TimePoint(90ms), lossEvent->lossTime);
|
|
// Packet 1,2 should be marked as loss
|
|
EXPECT_EQ(lostPacket.size(), 2);
|
|
EXPECT_EQ(lostPacket.front(), 1);
|
|
EXPECT_EQ(lostPacket.back(), 2);
|
|
|
|
// Packet 6 is the only thing remaining inflight, it is a handshake pkt
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
|
|
// Packet 6 should remain in packet as the delta is less than threshold
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
EXPECT_EQ(conn->outstandings.packets.size(), 1 + numDeclaredLost);
|
|
auto first = getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto packetNum = first->packet.header.getPacketSequenceNum();
|
|
EXPECT_EQ(packetNum, 6);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestHandleAckForLoss) {
|
|
auto conn = createConn();
|
|
auto mockQLogger = std::make_shared<MockQLogger>(VantagePoint::Server);
|
|
conn->qLogger = mockQLogger;
|
|
conn->lossState.ptoCount = 100;
|
|
conn->lossState.reorderingThreshold = 10;
|
|
|
|
LongHeader longHeader(
|
|
LongHeader::Types::Handshake,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
conn->ackStates.handshakeAckState->nextPacketNum++,
|
|
conn->version.value());
|
|
RegularQuicWritePacket outstandingRegularPacket(std::move(longHeader));
|
|
auto now = Clock::now();
|
|
conn->outstandings.packets.emplace_back(
|
|
outstandingRegularPacket,
|
|
now,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
LossState(),
|
|
0,
|
|
OutstandingPacketMetadata::DetailsPerStream());
|
|
conn->outstandings.packetCount[PacketNumberSpace::Handshake]++;
|
|
|
|
bool testLossMarkFuncCalled = false;
|
|
auto testLossMarkFunc = [&](auto& /* conn */, auto&, bool) {
|
|
testLossMarkFuncCalled = true;
|
|
return folly::unit;
|
|
};
|
|
EXPECT_CALL(*mockQLogger, addPacketsLost(1, 0, 1));
|
|
|
|
auto ackTime = Clock::now();
|
|
auto ackEvent = AckEvent::Builder()
|
|
.setAckTime(ackTime)
|
|
.setAdjustedAckTime(ackTime)
|
|
.setAckDelay(0us)
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(1000)
|
|
.build();
|
|
ackEvent.largestNewlyAckedPacket = 1000;
|
|
ASSERT_FALSE(
|
|
handleAckForLoss(
|
|
*conn, testLossMarkFunc, ackEvent, PacketNumberSpace::Handshake)
|
|
.hasError());
|
|
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
EXPECT_EQ(1, numDeclaredLost);
|
|
EXPECT_EQ(0, conn->lossState.ptoCount);
|
|
EXPECT_EQ(numDeclaredLost, conn->outstandings.packets.size());
|
|
EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm);
|
|
EXPECT_TRUE(testLossMarkFuncCalled);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestHandleAckedPacket) {
|
|
auto conn = createConn();
|
|
auto mockQLogger = std::make_shared<MockQLogger>(VantagePoint::Server);
|
|
conn->qLogger = mockQLogger;
|
|
conn->lossState.ptoCount = 10;
|
|
conn->lossState.reorderingThreshold = 10;
|
|
|
|
sendPacket(*conn, TimePoint(), none, PacketType::OneRtt);
|
|
|
|
ReadAckFrame ackFrame;
|
|
ackFrame.largestAcked = conn->lossState.largestSent.value_or(0);
|
|
ackFrame.ackBlocks.emplace_back(
|
|
conn->lossState.largestSent.value_or(0),
|
|
conn->lossState.largestSent.value_or(0));
|
|
|
|
bool testLossMarkFuncCalled = false;
|
|
auto testLossMarkFunc = [&](auto& /* conn */, auto&, bool) {
|
|
testLossMarkFuncCalled = true;
|
|
return folly::unit;
|
|
};
|
|
|
|
auto ackPacketVisitor = [](auto&) { return folly::unit; };
|
|
auto ackFrameVisitor = [&](auto&, auto&) { return folly::unit; };
|
|
|
|
// process and remove the acked packet.
|
|
ASSERT_FALSE(processAckFrame(
|
|
*conn,
|
|
PacketNumberSpace::AppData,
|
|
ackFrame,
|
|
ackPacketVisitor,
|
|
ackFrameVisitor,
|
|
testLossMarkFunc,
|
|
Clock::now())
|
|
.hasError());
|
|
|
|
EXPECT_EQ(0, conn->lossState.ptoCount);
|
|
EXPECT_TRUE(conn->outstandings.packets.empty());
|
|
EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm);
|
|
EXPECT_FALSE(testLossMarkFuncCalled);
|
|
ASSERT_TRUE(conn->outstandings.packets.empty());
|
|
|
|
setLossDetectionAlarm<decltype(timeout), MockClock>(*conn, timeout);
|
|
EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkRstLoss) {
|
|
auto conn = createConn();
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto currentOffset = stream->currentWriteOffset;
|
|
RstStreamFrame rstFrame(
|
|
stream->id, GenericApplicationErrorCode::UNKNOWN, currentOffset);
|
|
conn->pendingEvents.resets.insert({stream->id, rstFrame});
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
|
|
EXPECT_EQ(conn->outstandings.packets.size(), 1);
|
|
EXPECT_TRUE(conn->pendingEvents.resets.empty());
|
|
auto& packet =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet, false).hasError());
|
|
|
|
EXPECT_EQ(1, conn->pendingEvents.resets.size());
|
|
EXPECT_EQ(1, conn->pendingEvents.resets.count(stream->id));
|
|
auto& retxRstFrame = conn->pendingEvents.resets.at(stream->id);
|
|
EXPECT_EQ(stream->id, retxRstFrame.streamId);
|
|
EXPECT_EQ(GenericApplicationErrorCode::UNKNOWN, retxRstFrame.errorCode);
|
|
EXPECT_EQ(currentOffset, retxRstFrame.finalSize);
|
|
|
|
// write again:
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_TRUE(conn->pendingEvents.resets.empty());
|
|
auto& packet2 =
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
bool rstFound = false;
|
|
for (auto& frame : packet2.frames) {
|
|
auto resetFrame = frame.asRstStreamFrame();
|
|
if (!resetFrame) {
|
|
continue;
|
|
}
|
|
EXPECT_EQ(stream->id, resetFrame->streamId);
|
|
EXPECT_EQ(GenericApplicationErrorCode::UNKNOWN, resetFrame->errorCode);
|
|
EXPECT_EQ(currentOffset, resetFrame->finalSize);
|
|
rstFound = true;
|
|
}
|
|
EXPECT_TRUE(rstFound);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, ReorderingThresholdChecksSamePacketNumberSpace) {
|
|
auto conn = createConn();
|
|
uint16_t lossVisitorCount = 0;
|
|
auto countingLossVisitor =
|
|
[&](auto& /* conn */, auto& /* packet */, bool processed) {
|
|
if (!processed) {
|
|
lossVisitorCount++;
|
|
}
|
|
return folly::unit;
|
|
};
|
|
PacketNum latestSent = 0;
|
|
for (size_t i = 0; i < conn->lossState.reorderingThreshold + 1; i++) {
|
|
latestSent = sendPacket(*conn, Clock::now(), none, PacketType::Handshake);
|
|
}
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = latestSent + 1;
|
|
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
countingLossVisitor,
|
|
Clock::now(),
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
EXPECT_EQ(0, lossVisitorCount);
|
|
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
countingLossVisitor,
|
|
Clock::now(),
|
|
PacketNumberSpace::Handshake)
|
|
.hasError());
|
|
EXPECT_GT(lossVisitorCount, 0);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkWindowUpdateLoss) {
|
|
auto conn = createConn();
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
conn->streamManager->queueWindowUpdate(stream->id);
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_FALSE(conn->streamManager->hasWindowUpdates());
|
|
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
auto& packet =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet, false).hasError());
|
|
EXPECT_TRUE(conn->streamManager->pendingWindowUpdate(stream->id));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestTimeReordering) {
|
|
std::vector<PacketNum> lostPacket;
|
|
auto conn = createConn();
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 100ms), none, PacketType::OneRtt);
|
|
}
|
|
// Some packets are already acked
|
|
conn->lossState.srtt = 400ms;
|
|
conn->lossState.lrtt = 350ms;
|
|
conn->outstandings.packets.erase(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5);
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent;
|
|
auto lossEventResult = detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
testingLossMarkFunc(lostPacket),
|
|
TimePoint(900ms),
|
|
PacketNumberSpace::AppData);
|
|
ASSERT_FALSE(lossEventResult.hasError());
|
|
auto& lossEvent = lossEventResult.value();
|
|
EXPECT_EQ(2, lossEvent->largestLostPacketNum.value());
|
|
EXPECT_EQ(TimePoint(900ms), lossEvent->lossTime);
|
|
// Packet 1,2 should be marked as loss
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
EXPECT_EQ(lostPacket.size(), 2);
|
|
EXPECT_EQ(numDeclaredLost, lostPacket.size());
|
|
EXPECT_EQ(lostPacket.front(), 1);
|
|
EXPECT_EQ(lostPacket.back(), 2);
|
|
|
|
// Packet 6, 7 should remain in outstanding packet list
|
|
EXPECT_EQ(2 + numDeclaredLost, conn->outstandings.packets.size());
|
|
auto packetNum = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
EXPECT_EQ(packetNum, 6);
|
|
EXPECT_TRUE(conn->lossState.lossTimes[PacketNumberSpace::AppData]);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, LossTimePreemptsCryptoTimer) {
|
|
std::vector<PacketNum> lostPackets;
|
|
auto conn = createConn();
|
|
conn->lossState.srtt = 100ms;
|
|
conn->lossState.lrtt = 100ms;
|
|
auto expectedDelayUntilLost =
|
|
500ms / conn->transportSettings.timeReorderingThreshDivisor;
|
|
auto sendTime = Clock::now();
|
|
// Send two:
|
|
sendPacket(*conn, sendTime, none, PacketType::Handshake);
|
|
PacketNum second =
|
|
sendPacket(*conn, sendTime + 1ms, none, PacketType::Handshake);
|
|
auto lossTime = sendTime + 50ms;
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::Handshake);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = second;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
testingLossMarkFunc(lostPackets),
|
|
lossTime,
|
|
PacketNumberSpace::Handshake)
|
|
.hasError());
|
|
EXPECT_TRUE(lostPackets.empty());
|
|
EXPECT_TRUE(
|
|
conn->lossState.lossTimes[PacketNumberSpace::Handshake].has_value());
|
|
EXPECT_EQ(
|
|
expectedDelayUntilLost + sendTime,
|
|
conn->lossState.lossTimes[PacketNumberSpace::Handshake].value());
|
|
|
|
MockClock::mockNow = [=]() { return sendTime; };
|
|
auto alarm = calculateAlarmDuration<MockClock>(*conn);
|
|
EXPECT_EQ(
|
|
folly::chrono::ceil<std::chrono::milliseconds>(expectedDelayUntilLost),
|
|
alarm.first);
|
|
EXPECT_EQ(LossState::AlarmMethod::EarlyRetransmitOrReordering, alarm.second);
|
|
// Manual set lossState. Calling setLossDetectionAlarm requires a Timeout
|
|
conn->lossState.currentAlarmMethod = alarm.second;
|
|
|
|
// Second packet gets acked:
|
|
getAckState(*conn, PacketNumberSpace::Handshake).largestAckedByPeer = second;
|
|
conn->outstandings.packets.pop_back();
|
|
MockClock::mockNow = [=]() { return sendTime + expectedDelayUntilLost + 5s; };
|
|
ASSERT_FALSE(
|
|
onLossDetectionAlarm<MockClock>(*conn, testingLossMarkFunc(lostPackets))
|
|
.hasError());
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
EXPECT_EQ(1, lostPackets.size());
|
|
EXPECT_EQ(numDeclaredLost, lostPackets.size());
|
|
EXPECT_FALSE(
|
|
conn->lossState.lossTimes[PacketNumberSpace::Handshake].has_value());
|
|
EXPECT_EQ(numDeclaredLost, conn->outstandings.packets.size());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PTONoLongerMarksPacketsToBeRetransmitted) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
TimePoint startTime(123ms);
|
|
MockClock::mockNow = [&]() { return startTime; };
|
|
std::vector<PacketNum> lostPackets;
|
|
for (auto i = 0; i < kPacketToSendForPTO + 10; i++) {
|
|
sendPacket(*conn, startTime, none, PacketType::OneRtt);
|
|
setLossDetectionAlarm<decltype(timeout), MockClock>(*conn, timeout);
|
|
startTime += 1ms;
|
|
}
|
|
EXPECT_CALL(*rawCongestionController, onPacketAckOrLoss(_, _)).Times(0);
|
|
EXPECT_CALL(*quicStats_, onPTO());
|
|
ASSERT_FALSE(
|
|
onLossDetectionAlarm<MockClock>(*conn, testingLossMarkFunc(lostPackets))
|
|
.hasError());
|
|
EXPECT_EQ(1, conn->lossState.ptoCount);
|
|
// Hey PTOs are not losses either from now on
|
|
EXPECT_TRUE(lostPackets.empty());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PTOWithHandshakePackets) {
|
|
auto conn = createConn();
|
|
conn->handshakeWriteCipher = createNoOpAead();
|
|
conn->handshakeWriteHeaderCipher = createNoOpHeaderCipher();
|
|
auto mockQLogger = std::make_shared<MockQLogger>(VantagePoint::Server);
|
|
conn->qLogger = mockQLogger;
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
EXPECT_CALL(*mockQLogger, addLossAlarm(_, _, _, _));
|
|
std::vector<PacketNum> lostPackets;
|
|
PacketNum expectedLargestLostNum = 0;
|
|
conn->lossState.currentAlarmMethod = LossState::AlarmMethod::PTO;
|
|
for (auto i = 0; i < 10; i++) {
|
|
// Half are handshakes
|
|
auto sentPacketNum = sendPacket(
|
|
*conn,
|
|
TimePoint(100ms),
|
|
none,
|
|
(i % 2 ? PacketType::OneRtt : PacketType::Handshake));
|
|
expectedLargestLostNum = std::max(
|
|
expectedLargestLostNum, i % 2 ? sentPacketNum : expectedLargestLostNum);
|
|
}
|
|
EXPECT_CALL(*quicStats_, onPTO());
|
|
// Verify packet count doesn't change across PTO.
|
|
auto originalPacketCount = conn->outstandings.packetCount;
|
|
ASSERT_FALSE(
|
|
onLossDetectionAlarm<Clock>(*conn, testingLossMarkFunc(lostPackets))
|
|
.hasError());
|
|
EXPECT_EQ(originalPacketCount, conn->outstandings.packetCount);
|
|
|
|
EXPECT_EQ(0, lostPackets.size());
|
|
EXPECT_EQ(1, conn->lossState.ptoCount);
|
|
EXPECT_EQ(0, conn->lossState.timeoutBasedRtxCount);
|
|
EXPECT_EQ(
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Handshake],
|
|
kPacketToSendForPTO);
|
|
EXPECT_EQ(
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData],
|
|
kPacketToSendForPTO);
|
|
EXPECT_EQ(0, conn->lossState.rtxCount);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PTOWithLostInitialData) {
|
|
// Verify that lost initial data will trigger PTOs even if there are no other
|
|
// outstanding packets.
|
|
// The other packet number spaces have no outstanding data or losses so they
|
|
// will have 0 probe packets.
|
|
|
|
auto conn = createConn();
|
|
|
|
conn->initialWriteCipher = createNoOpAead();
|
|
conn->initialHeaderCipher = createNoOpHeaderCipher();
|
|
conn->handshakeWriteCipher = createNoOpAead();
|
|
conn->handshakeWriteHeaderCipher = createNoOpHeaderCipher();
|
|
conn->oneRttWriteCipher = createNoOpAead();
|
|
conn->oneRttWriteHeaderCipher = createNoOpHeaderCipher();
|
|
|
|
auto buf = buildRandomInputData(20);
|
|
WriteStreamBuffer initialData(ChainedByteRangeHead(buf), 0);
|
|
conn->cryptoState->initialStream.lossBuffer.push_back(std::move(initialData));
|
|
|
|
ASSERT_TRUE(conn->outstandings.packets.empty())
|
|
<< "There should be no outstanding packets";
|
|
auto ret = onPTOAlarm(*conn);
|
|
EXPECT_FALSE(ret.hasError());
|
|
EXPECT_EQ(
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Initial],
|
|
kPacketToSendForPTO);
|
|
EXPECT_EQ(
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Handshake], 0);
|
|
EXPECT_EQ(conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData], 0);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PTOWithLostHandshakeData) {
|
|
// Verify that lost handshake data will trigger PTOs even if there are no
|
|
// other outstanding packets.
|
|
// The other packet number spaces have no outstanding data or losses so they
|
|
// will have 0 probe packets.
|
|
auto conn = createConn();
|
|
|
|
conn->initialWriteCipher = createNoOpAead();
|
|
conn->initialHeaderCipher = createNoOpHeaderCipher();
|
|
conn->handshakeWriteCipher = createNoOpAead();
|
|
conn->handshakeWriteHeaderCipher = createNoOpHeaderCipher();
|
|
conn->oneRttWriteCipher = createNoOpAead();
|
|
conn->oneRttWriteHeaderCipher = createNoOpHeaderCipher();
|
|
|
|
auto buf = buildRandomInputData(20);
|
|
WriteStreamBuffer handshakeData(ChainedByteRangeHead(buf), 0);
|
|
conn->cryptoState->handshakeStream.lossBuffer.push_back(
|
|
std::move(handshakeData));
|
|
|
|
ASSERT_TRUE(conn->outstandings.packets.empty())
|
|
<< "There should be no outstanding packets";
|
|
auto ret = onPTOAlarm(*conn);
|
|
EXPECT_FALSE(ret.hasError());
|
|
|
|
EXPECT_EQ(conn->pendingEvents.numProbePackets[PacketNumberSpace::Initial], 0);
|
|
EXPECT_EQ(
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Handshake],
|
|
kPacketToSendForPTO);
|
|
EXPECT_EQ(conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData], 0);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PTOWithLostAppData) {
|
|
// Verify that lost app data will trigger PTOs even if there are no other
|
|
// outstanding packets.
|
|
// The other packet number spaces have no outstanding data or losses so they
|
|
// will have 0 probe packets.
|
|
auto conn = createConn();
|
|
|
|
conn->initialWriteCipher = createNoOpAead();
|
|
conn->initialHeaderCipher = createNoOpHeaderCipher();
|
|
conn->handshakeWriteCipher = createNoOpAead();
|
|
conn->handshakeWriteHeaderCipher = createNoOpHeaderCipher();
|
|
conn->oneRttWriteCipher = createNoOpAead();
|
|
conn->oneRttWriteHeaderCipher = createNoOpHeaderCipher();
|
|
|
|
auto buf = buildRandomInputData(20);
|
|
WriteStreamBuffer appData(ChainedByteRangeHead(buf), 0);
|
|
conn->cryptoState->oneRttStream.lossBuffer.push_back(std::move(appData));
|
|
|
|
ASSERT_TRUE(conn->outstandings.packets.empty())
|
|
<< "There should be no outstanding packets";
|
|
auto ret = onPTOAlarm(*conn);
|
|
EXPECT_FALSE(ret.hasError());
|
|
|
|
EXPECT_EQ(conn->pendingEvents.numProbePackets[PacketNumberSpace::Initial], 0);
|
|
EXPECT_EQ(
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Handshake], 0);
|
|
EXPECT_EQ(
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData],
|
|
kPacketToSendForPTO);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PTOAvoidPointless) {
|
|
// If there is no lost data and the outstanding data is less than the
|
|
// kPacketToSendForPTO packets, send only the available outstanding count.
|
|
auto conn = createConn();
|
|
|
|
conn->initialWriteCipher = createNoOpAead();
|
|
conn->initialHeaderCipher = createNoOpHeaderCipher();
|
|
|
|
conn->handshakeWriteCipher = createNoOpAead();
|
|
conn->handshakeWriteHeaderCipher = createNoOpHeaderCipher();
|
|
|
|
conn->oneRttWriteCipher = createNoOpAead();
|
|
conn->oneRttWriteHeaderCipher = createNoOpHeaderCipher();
|
|
|
|
conn->outstandings.packetCount[PacketNumberSpace::Initial] = 1;
|
|
conn->outstandings.packetCount[PacketNumberSpace::Handshake] = 1;
|
|
conn->outstandings.packetCount[PacketNumberSpace::AppData] = 1;
|
|
|
|
auto ret = onPTOAlarm(*conn);
|
|
EXPECT_FALSE(ret.hasError());
|
|
|
|
EXPECT_EQ(conn->pendingEvents.numProbePackets[PacketNumberSpace::Initial], 1);
|
|
EXPECT_EQ(
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Handshake], 1);
|
|
EXPECT_EQ(conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData], 1);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, EmptyOutstandingNoTimeout) {
|
|
auto conn = createConn();
|
|
EXPECT_CALL(timeout, cancelLossTimeout()).Times(1);
|
|
setLossDetectionAlarm(*conn, timeout);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, AlarmDurationHasLossTime) {
|
|
auto conn = createConn();
|
|
TimePoint lastPacketSentTime = Clock::now();
|
|
auto thisMoment = lastPacketSentTime;
|
|
MockClock::mockNow = [=]() { return thisMoment; };
|
|
conn->lossState.lossTimes[PacketNumberSpace::AppData] = thisMoment + 100ms;
|
|
conn->lossState.srtt = 200ms;
|
|
conn->lossState.lrtt = 150ms;
|
|
|
|
sendPacket(*conn, lastPacketSentTime, none, PacketType::OneRtt);
|
|
auto duration = calculateAlarmDuration<MockClock>(*conn);
|
|
EXPECT_EQ(100ms, duration.first);
|
|
EXPECT_EQ(
|
|
duration.second, LossState::AlarmMethod::EarlyRetransmitOrReordering);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, AlarmDurationLossTimeIsZero) {
|
|
// The timer could be delayed a bit, so this tests that the alarm will return
|
|
// a timer of 0 if we are in the loss time case.
|
|
auto conn = createConn();
|
|
TimePoint lastPacketSentTime = Clock::now();
|
|
auto thisMoment = lastPacketSentTime + 200ms;
|
|
MockClock::mockNow = [=]() { return thisMoment; };
|
|
conn->lossState.lossTimes[PacketNumberSpace::AppData] =
|
|
lastPacketSentTime + 100ms;
|
|
conn->lossState.srtt = 200ms;
|
|
conn->lossState.lrtt = 150ms;
|
|
|
|
sendPacket(*conn, lastPacketSentTime, none, PacketType::OneRtt);
|
|
auto duration = calculateAlarmDuration<MockClock>(*conn);
|
|
EXPECT_EQ(0ms, duration.first);
|
|
EXPECT_EQ(
|
|
duration.second, LossState::AlarmMethod::EarlyRetransmitOrReordering);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, AlarmDurationNonHandshakeOutstanding) {
|
|
auto conn = createConn();
|
|
conn->lossState.srtt = 4ms;
|
|
conn->lossState.rttvar = 10ms;
|
|
conn->lossState.maxAckDelay = 25ms;
|
|
TimePoint lastPacketSentTime = Clock::now();
|
|
MockClock::mockNow = [=]() { return lastPacketSentTime; };
|
|
sendPacket(*conn, lastPacketSentTime, none, PacketType::OneRtt);
|
|
auto duration = calculateAlarmDuration<MockClock>(*conn);
|
|
EXPECT_EQ(duration.second, LossState::AlarmMethod::PTO);
|
|
setLossDetectionAlarm<decltype(timeout), MockClock>(*conn, timeout);
|
|
EXPECT_EQ(conn->lossState.currentAlarmMethod, LossState::AlarmMethod::PTO);
|
|
|
|
conn->lossState.ptoCount = 2;
|
|
auto newDuration = calculateAlarmDuration<MockClock>(*conn);
|
|
EXPECT_EQ(duration.second, LossState::AlarmMethod::PTO);
|
|
EXPECT_LT(duration.first, newDuration.first);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, NoSkipLossVisitor) {
|
|
auto conn = createConn();
|
|
conn->congestionController.reset();
|
|
// make srtt large so delayUntilLost won't kick in
|
|
conn->lossState.srtt = 1000000000us;
|
|
uint16_t lossVisitorCount = 0;
|
|
auto countingLossVisitor =
|
|
[&](auto& /* conn */, auto& /* packet */, bool processed) {
|
|
if (!processed) {
|
|
lossVisitorCount++;
|
|
}
|
|
return folly::unit;
|
|
};
|
|
// Send 5 packets, so when we ack the last one, we mark the first one loss
|
|
PacketNum lastSent;
|
|
for (size_t i = 0; i < 5; i++) {
|
|
lastSent = sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
}
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = lastSent;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
countingLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
EXPECT_EQ(1, lossVisitorCount);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, SkipLossVisitor) {
|
|
auto conn = createConn();
|
|
conn->congestionController.reset();
|
|
// make srtt large so delayUntilLost won't kick in
|
|
conn->lossState.srtt = 1000000000us;
|
|
uint16_t lossVisitorCount = 0;
|
|
auto countingLossVisitor =
|
|
[&](auto& /* conn */, auto& /* packet */, bool processed) {
|
|
if (!processed) {
|
|
lossVisitorCount++;
|
|
}
|
|
return folly::unit;
|
|
};
|
|
// Send 5 packets, so when we ack the last one, we mark the first one loss
|
|
PacketNum lastSent;
|
|
for (size_t i = 0; i < 5; i++) {
|
|
lastSent = conn->ackStates.appDataAckState.nextPacketNum;
|
|
ClonedPacketIdentifier clonedPacketIdentifier(
|
|
PacketNumberSpace::AppData, lastSent);
|
|
sendPacket(*conn, Clock::now(), clonedPacketIdentifier, PacketType::OneRtt);
|
|
}
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = lastSent;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
countingLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
EXPECT_EQ(0, lossVisitorCount);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, NoDoubleProcess) {
|
|
auto conn = createConn();
|
|
conn->congestionController.reset();
|
|
// make srtt large so delayUntilLost won't kick in
|
|
conn->lossState.srtt = 1000000000us;
|
|
|
|
uint16_t lossVisitorCount = 0;
|
|
auto countingLossVisitor =
|
|
[&](auto& /* conn */, auto& /* packet */, bool processed) {
|
|
if (!processed) {
|
|
lossVisitorCount++;
|
|
}
|
|
return folly::unit;
|
|
};
|
|
// Send 6 packets, so when we ack the last one, we mark the first two loss
|
|
EXPECT_EQ(1, conn->ackStates.appDataAckState.nextPacketNum);
|
|
PacketNum lastSent;
|
|
lastSent = sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
ClonedPacketIdentifier clonedPacketIdentifier(
|
|
PacketNumberSpace::AppData, lastSent);
|
|
for (size_t i = 0; i < 6; i++) {
|
|
lastSent = sendPacket(
|
|
*conn, Clock::now(), clonedPacketIdentifier, PacketType::OneRtt);
|
|
}
|
|
EXPECT_EQ(7, conn->outstandings.packets.size());
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
// Add the ClonedPacketIdentifier to the outstandings.clonedPacketIdentifiers
|
|
// set
|
|
conn->outstandings.clonedPacketIdentifiers.insert(clonedPacketIdentifier);
|
|
|
|
// Ack the last sent packet. Despite three losses, lossVisitor only visit one
|
|
// packet
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = lastSent;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
countingLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
EXPECT_EQ(3, numDeclaredLost);
|
|
EXPECT_EQ(1, lossVisitorCount);
|
|
EXPECT_EQ(4 + numDeclaredLost, conn->outstandings.packets.size());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, DetectPacketLossClonedPacketsCounter) {
|
|
auto conn = createConn();
|
|
ClonedPacketIdentifier clonedPacketIdentifier1(
|
|
PacketNumberSpace::AppData,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
sendPacket(*conn, Clock::now(), clonedPacketIdentifier1, PacketType::OneRtt);
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
auto ackedPacket = sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
auto noopLossMarker = [](auto&, auto&, bool) { return folly::unit; };
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = ackedPacket;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
noopLossMarker,
|
|
Clock::now(),
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
EXPECT_EQ(0, conn->outstandings.numClonedPackets());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLossProcessedPacket) {
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
auto conn = createConn();
|
|
ASSERT_TRUE(conn->outstandings.packets.empty());
|
|
ASSERT_TRUE(conn->outstandings.clonedPacketIdentifiers.empty());
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto buf = folly::IOBuf::copyBuffer("I wrestled by the sea.");
|
|
auto stream2Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
conn->streamManager->queueWindowUpdate(stream2Id);
|
|
conn->pendingEvents.connWindowUpdate = true;
|
|
// writeQuicPacket will call writeQuicDataToSocket which will also take care
|
|
// of sending the MaxStreamDataFrame for stream2
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
auto stream2 = conn->streamManager->findStream(stream2Id);
|
|
auto packet = writeQuicPacket(
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
socket,
|
|
*stream1,
|
|
*buf,
|
|
true);
|
|
EXPECT_FALSE(conn->streamManager->pendingWindowUpdate(stream2->id));
|
|
EXPECT_FALSE(conn->pendingEvents.connWindowUpdate);
|
|
ASSERT_EQ(1, conn->outstandings.packets.size());
|
|
ASSERT_TRUE(conn->outstandings.clonedPacketIdentifiers.empty());
|
|
uint32_t streamDataCounter = 0, streamWindowUpdateCounter = 0,
|
|
connWindowUpdateCounter = 0;
|
|
auto strippedPacket = stripPaddingFrames(
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet);
|
|
for (const auto& frame : strippedPacket.frames) {
|
|
switch (frame.type()) {
|
|
case QuicWriteFrame::Type::WriteStreamFrame:
|
|
streamDataCounter++;
|
|
break;
|
|
case QuicWriteFrame::Type::MaxStreamDataFrame:
|
|
streamWindowUpdateCounter++;
|
|
break;
|
|
case QuicWriteFrame::Type::MaxDataFrame:
|
|
connWindowUpdateCounter++;
|
|
break;
|
|
default:
|
|
CHECK(false) << "unexpected frame=" << (int)frame.type();
|
|
}
|
|
}
|
|
EXPECT_EQ(1, streamDataCounter);
|
|
EXPECT_EQ(1, streamWindowUpdateCounter);
|
|
EXPECT_EQ(1, connWindowUpdateCounter);
|
|
// Force this packet to be a processed clone
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet, true).hasError());
|
|
EXPECT_EQ(1, stream1->retransmissionBuffer.size());
|
|
EXPECT_TRUE(stream1->lossBuffer.empty());
|
|
|
|
// Window update though, will still be marked loss
|
|
EXPECT_TRUE(conn->streamManager->pendingWindowUpdate(stream2->id));
|
|
EXPECT_TRUE(conn->pendingEvents.connWindowUpdate);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestTotalPTOCount) {
|
|
auto conn = createConn();
|
|
auto mockQLogger = std::make_shared<MockQLogger>(VantagePoint::Server);
|
|
conn->qLogger = mockQLogger;
|
|
conn->lossState.totalPTOCount = 100;
|
|
EXPECT_CALL(*mockQLogger, addLossAlarm(0, 1, 0, kPtoAlarm));
|
|
EXPECT_CALL(*quicStats_, onPTO());
|
|
auto ret = onPTOAlarm(*conn);
|
|
EXPECT_FALSE(ret.hasError());
|
|
EXPECT_EQ(101, conn->lossState.totalPTOCount);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestExceedsMaxPTOError) {
|
|
auto conn = createConn();
|
|
auto mockQLogger = std::make_shared<MockQLogger>(VantagePoint::Server);
|
|
conn->qLogger = mockQLogger;
|
|
conn->transportSettings.maxNumPTOs = 3;
|
|
for (int i = 1; i <= 3; i++) {
|
|
EXPECT_CALL(*mockQLogger, addLossAlarm(0, i, 0, kPtoAlarm));
|
|
}
|
|
EXPECT_CALL(*quicStats_, onPTO()).Times(3);
|
|
auto ret = onPTOAlarm(*conn);
|
|
EXPECT_FALSE(ret.hasError());
|
|
ret = onPTOAlarm(*conn);
|
|
EXPECT_FALSE(ret.hasError());
|
|
ret = onPTOAlarm(*conn);
|
|
EXPECT_TRUE(ret.hasError());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TotalLossCount) {
|
|
auto conn = createConn();
|
|
conn->congestionController = nullptr;
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 10; i++) {
|
|
largestSent = sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
}
|
|
EXPECT_EQ(10, conn->outstandings.packets.size());
|
|
uint32_t lostPackets = 0;
|
|
auto countingLossVisitor =
|
|
[&](auto& /* conn */, auto& /* packet */, bool processed) {
|
|
if (!processed) {
|
|
lostPackets++;
|
|
}
|
|
return folly::unit;
|
|
};
|
|
|
|
conn->lossState.rtxCount = 135;
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
countingLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
EXPECT_EQ(135 + lostPackets, conn->lossState.rtxCount);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestZeroRttRejected) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
// By adding an maybeClonedPacketIdentifier that doesn't exist in the
|
|
// outstandings.clonedPacketIdentifiers, they are all processed and will skip
|
|
// lossVisitor
|
|
for (auto i = 0; i < 2; i++) {
|
|
sendPacket(*conn, TimePoint(), none, PacketType::OneRtt);
|
|
sendPacket(*conn, TimePoint(), none, PacketType::ZeroRtt);
|
|
}
|
|
EXPECT_FALSE(conn->outstandings.packets.empty());
|
|
EXPECT_EQ(4, conn->outstandings.packets.size());
|
|
std::vector<bool> lostPackets;
|
|
// onRemoveBytesFromInflight should still happen
|
|
EXPECT_CALL(*rawCongestionController, onRemoveBytesFromInflight(_)).Times(1);
|
|
auto result = markZeroRttPacketsLost(
|
|
*conn, [&lostPackets](auto&, auto&, bool processed) {
|
|
lostPackets.emplace_back(processed);
|
|
return folly::unit;
|
|
});
|
|
ASSERT_FALSE(result.hasError());
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
EXPECT_EQ(lostPackets.size(), 2);
|
|
for (auto lostPacket : lostPackets) {
|
|
EXPECT_FALSE(lostPacket);
|
|
}
|
|
for (size_t i = 0; i < conn->outstandings.packets.size(); ++i) {
|
|
auto longHeader = conn->outstandings.packets[i].packet.header.asLong();
|
|
EXPECT_FALSE(
|
|
longHeader &&
|
|
longHeader->getProtectionType() == ProtectionType::ZeroRtt);
|
|
}
|
|
EXPECT_EQ(2, conn->lossState.rtxCount);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestZeroRttRejectedWithClones) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
// By adding an maybeClonedPacketIdentifier that doesn't exist in the
|
|
// outstandings.clonedPacketIdentifiers, they are all processed and will skip
|
|
// lossVisitor
|
|
std::set<PacketNum> zeroRttPackets;
|
|
Optional<ClonedPacketIdentifier> lastClonedPacketIdentifier;
|
|
for (auto i = 0; i < 2; i++) {
|
|
auto packetNum = sendPacket(
|
|
*conn, TimePoint(), lastClonedPacketIdentifier, PacketType::ZeroRtt);
|
|
lastClonedPacketIdentifier =
|
|
ClonedPacketIdentifier(PacketNumberSpace::AppData, packetNum);
|
|
zeroRttPackets.emplace(packetNum);
|
|
}
|
|
zeroRttPackets.emplace(
|
|
sendPacket(*conn, TimePoint(), none, PacketType::ZeroRtt));
|
|
for (auto zeroRttPacketNum : zeroRttPackets) {
|
|
ClonedPacketIdentifier zeroRttPacketEvent(
|
|
PacketNumberSpace::AppData, zeroRttPacketNum);
|
|
sendPacket(*conn, TimePoint(), zeroRttPacketEvent, PacketType::OneRtt);
|
|
}
|
|
|
|
EXPECT_EQ(6, conn->outstandings.packets.size());
|
|
ASSERT_EQ(conn->outstandings.numClonedPackets(), 6);
|
|
ASSERT_EQ(conn->outstandings.clonedPacketIdentifiers.size(), 2);
|
|
ASSERT_EQ(2, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
|
|
std::vector<bool> lostPackets;
|
|
// onRemoveBytesFromInflight should still happen
|
|
EXPECT_CALL(*rawCongestionController, onRemoveBytesFromInflight(_)).Times(1);
|
|
auto result = markZeroRttPacketsLost(
|
|
*conn, [&lostPackets](auto&, auto&, bool processed) {
|
|
lostPackets.emplace_back(processed);
|
|
return folly::unit;
|
|
});
|
|
ASSERT_FALSE(result.hasError());
|
|
ASSERT_EQ(conn->outstandings.clonedPacketIdentifiers.size(), 0);
|
|
EXPECT_EQ(3, conn->outstandings.packets.size());
|
|
EXPECT_EQ(lostPackets.size(), 3);
|
|
ASSERT_EQ(conn->outstandings.numClonedPackets(), 3);
|
|
size_t numProcessed = 0;
|
|
for (auto lostPacket : lostPackets) {
|
|
numProcessed += lostPacket;
|
|
}
|
|
EXPECT_EQ(numProcessed, 1);
|
|
for (size_t i = 0; i < conn->outstandings.packets.size(); ++i) {
|
|
auto longHeader = conn->outstandings.packets[i].packet.header.asLong();
|
|
EXPECT_FALSE(
|
|
longHeader &&
|
|
longHeader->getProtectionType() == ProtectionType::ZeroRtt);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PTOLargerThanMaxDelay) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Client);
|
|
conn.lossState.srtt = 1ms;
|
|
conn.lossState.maxAckDelay = 20s;
|
|
EXPECT_GE(calculatePTO(conn), 20s);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, InitialPTOs) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Client);
|
|
conn.transportSettings.initialRtt = 20ms;
|
|
EXPECT_EQ(40ms, calculatePTO(conn));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TimeThreshold) {
|
|
auto conn = createConn();
|
|
conn->lossState.srtt = 10ms;
|
|
auto referenceTime = Clock::now();
|
|
auto packet1 =
|
|
sendPacket(*conn, referenceTime - 10ms, none, PacketType::OneRtt);
|
|
auto packet2 = sendPacket(
|
|
*conn,
|
|
referenceTime + conn->lossState.srtt / 2,
|
|
none,
|
|
PacketType::OneRtt);
|
|
auto lossVisitor = [&](const auto& /*conn*/, const auto& packet, bool) {
|
|
EXPECT_EQ(packet1, packet.header.getPacketSequenceNum());
|
|
return folly::unit;
|
|
};
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = packet2;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
lossVisitor,
|
|
referenceTime + conn->lossState.srtt * 9 / 8 + 5ms,
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, OutstandingInitialCounting) {
|
|
auto conn = createConn();
|
|
// Simplify the test by never triggering timer threshold
|
|
conn->lossState.srtt = 100s;
|
|
PacketNum largestSent = 0;
|
|
while (largestSent < 10) {
|
|
largestSent = sendPacket(*conn, Clock::now(), none, PacketType::Initial);
|
|
}
|
|
EXPECT_EQ(10, conn->outstandings.packetCount[PacketNumberSpace::Initial]);
|
|
auto noopLossVisitor =
|
|
[&](auto& /* conn */, auto& /* packet */, bool /* processed */
|
|
) { return folly::unit; };
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::Initial);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
noopLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::Initial)
|
|
.hasError());
|
|
// [1, 6] are removed, [7, 10] are still in OP list
|
|
EXPECT_EQ(4, conn->outstandings.packetCount[PacketNumberSpace::Initial]);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, OutstandingHandshakeCounting) {
|
|
auto conn = createConn();
|
|
// Simplify the test by never triggering timer threshold
|
|
conn->lossState.srtt = 100s;
|
|
PacketNum largestSent = 0;
|
|
while (largestSent < 10) {
|
|
largestSent = sendPacket(*conn, Clock::now(), none, PacketType::Handshake);
|
|
}
|
|
EXPECT_EQ(10, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
auto noopLossVisitor =
|
|
[&](auto& /* conn */, auto& /* packet */, bool /* processed */
|
|
) { return folly::unit; };
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::Handshake);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
noopLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::Handshake)
|
|
.hasError());
|
|
// [1, 6] are removed, [7, 10] are still in OP list
|
|
EXPECT_EQ(4, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
}
|
|
|
|
TEST_P(QuicLossFunctionsTest, CappedShiftNoCrash) {
|
|
auto conn = createConn();
|
|
conn->outstandings.reset();
|
|
conn->lossState.ptoCount =
|
|
std::numeric_limits<decltype(conn->lossState.ptoCount)>::max();
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
calculateAlarmDuration(*conn);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PersistentCongestion) {
|
|
// Test cases copied over from PersistentCongestion above.
|
|
auto conn = createConn();
|
|
auto currentTime = Clock::now();
|
|
conn->lossState.srtt = 1s;
|
|
|
|
auto ackTime = Clock::now();
|
|
auto ack = AckEvent::Builder()
|
|
.setAckTime(ackTime)
|
|
.setAdjustedAckTime(ackTime)
|
|
.setAckDelay(0us)
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(1)
|
|
.build();
|
|
|
|
EXPECT_TRUE(isPersistentCongestion(
|
|
calculatePTO(*conn), currentTime - 10s, currentTime, ack));
|
|
EXPECT_TRUE(isPersistentCongestion(
|
|
calculatePTO(*conn), currentTime - 3s, currentTime, ack));
|
|
EXPECT_TRUE(isPersistentCongestion(
|
|
calculatePTO(*conn),
|
|
currentTime - (1s * kPersistentCongestionThreshold),
|
|
currentTime,
|
|
ack));
|
|
EXPECT_FALSE(isPersistentCongestion(
|
|
calculatePTO(*conn),
|
|
currentTime - (1s * kPersistentCongestionThreshold) + 1us,
|
|
currentTime,
|
|
ack));
|
|
EXPECT_FALSE(isPersistentCongestion(
|
|
calculatePTO(*conn), currentTime - 2s, currentTime, ack));
|
|
EXPECT_FALSE(isPersistentCongestion(
|
|
calculatePTO(*conn), currentTime - 100ms, currentTime, ack));
|
|
|
|
conn->lossState.rttvar = 2s;
|
|
conn->lossState.maxAckDelay = 5s;
|
|
EXPECT_TRUE(isPersistentCongestion(
|
|
calculatePTO(*conn), currentTime - 42s, currentTime, ack));
|
|
EXPECT_TRUE(isPersistentCongestion(
|
|
calculatePTO(*conn), currentTime - 43s, currentTime, ack));
|
|
EXPECT_FALSE(isPersistentCongestion(
|
|
calculatePTO(*conn), currentTime - 42s + 1ms, currentTime, ack));
|
|
EXPECT_FALSE(isPersistentCongestion(
|
|
calculatePTO(*conn), currentTime - 100us, currentTime, ack));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PersistentCongestionAckOutsideWindow) {
|
|
auto conn = createConn();
|
|
auto currentTime = Clock::now();
|
|
conn->lossState.srtt = 1s;
|
|
|
|
const auto now = TimePoint::clock::now();
|
|
auto ack = AckEvent::Builder()
|
|
.setAckTime(now)
|
|
.setAdjustedAckTime(now)
|
|
.setAckDelay(0us)
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(1)
|
|
.build();
|
|
OutstandingPacketMetadata opm(
|
|
currentTime + 12s /* sentTime */,
|
|
0 /* encodedSize */,
|
|
0 /* encodedBodySize */,
|
|
0 /* totalBytesSent */,
|
|
0 /* inflightBytes */,
|
|
LossState() /* lossState */,
|
|
0 /* writeCount */,
|
|
OutstandingPacketMetadata::DetailsPerStream());
|
|
ack.ackedPackets.push_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(1)
|
|
.setNonDsrPacketSequenceNumber(1)
|
|
.setOutstandingPacketMetadata(opm)
|
|
.setDetailsPerStream(AckEvent::AckPacket::DetailsPerStream())
|
|
.build());
|
|
|
|
EXPECT_TRUE(isPersistentCongestion(
|
|
calculatePTO(*conn), currentTime + 1s, currentTime + 8s, ack));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PersistentCongestionAckInsideWindow) {
|
|
auto conn = createConn();
|
|
auto currentTime = Clock::now();
|
|
conn->lossState.srtt = 1s;
|
|
|
|
const auto now = TimePoint::clock::now();
|
|
auto ack = AckEvent::Builder()
|
|
.setAckTime(now)
|
|
.setAdjustedAckTime(now)
|
|
.setAckDelay(0us)
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(1)
|
|
.build();
|
|
OutstandingPacketMetadata opm(
|
|
currentTime + 4s /* sentTime */,
|
|
0 /* encodedSize */,
|
|
0 /* encodedBodySize */,
|
|
0 /* totalBytesSent */,
|
|
0 /* inflightBytes */,
|
|
LossState() /* lossState */,
|
|
0 /* writeCount */,
|
|
OutstandingPacketMetadata::DetailsPerStream());
|
|
ack.ackedPackets.push_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(1)
|
|
.setNonDsrPacketSequenceNumber(1)
|
|
.setOutstandingPacketMetadata(opm)
|
|
.setDetailsPerStream(AckEvent::AckPacket::DetailsPerStream())
|
|
.build());
|
|
|
|
EXPECT_FALSE(isPersistentCongestion(
|
|
calculatePTO(*conn), currentTime + 1s, currentTime + 8s, ack));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PersistentCongestionNoPTO) {
|
|
auto conn = createConn();
|
|
auto currentTime = Clock::now();
|
|
|
|
const auto now = TimePoint::clock::now();
|
|
auto ack = AckEvent::Builder()
|
|
.setAckTime(now)
|
|
.setAdjustedAckTime(now)
|
|
.setAckDelay(0us)
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(1)
|
|
.build();
|
|
OutstandingPacketMetadata opm(
|
|
currentTime + 12s /* sentTime */,
|
|
0 /* encodedSize */,
|
|
0 /* encodedBodySize */,
|
|
0 /* totalBytesSent */,
|
|
0 /* inflightBytes */,
|
|
LossState() /* lossState */,
|
|
0 /* writeCount */,
|
|
OutstandingPacketMetadata::DetailsPerStream());
|
|
ack.ackedPackets.push_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(1)
|
|
.setNonDsrPacketSequenceNumber(1)
|
|
.setOutstandingPacketMetadata(opm)
|
|
.setDetailsPerStream(AckEvent::AckPacket::DetailsPerStream())
|
|
.build());
|
|
|
|
EXPECT_FALSE(isPersistentCongestion(
|
|
std::nullopt, currentTime + 1s, currentTime + 8s, ack));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, ObserverLossEventReorder) {
|
|
auto conn = createConn();
|
|
|
|
LegacyObserver::EventSet eventSet;
|
|
eventSet.enable(SocketObserverInterface::Events::lossEvents);
|
|
auto obs1 = std::make_unique<NiceMock<MockLegacyObserver>>(eventSet);
|
|
CHECK_NOTNULL(conn->getSocketObserverContainer())->addObserver(obs1.get());
|
|
|
|
// send 7 packets
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), none, PacketType::OneRtt);
|
|
}
|
|
// Some packets are already acked
|
|
conn->outstandings.packets.erase(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5);
|
|
|
|
// setting a very low reordering threshold to force loss by reorder
|
|
conn->lossState.reorderingThreshold = 1;
|
|
// setting time out parameters higher than the time at which detectLossPackets
|
|
// is called to make sure there are no losses by timeout
|
|
conn->lossState.srtt = 400ms;
|
|
conn->lossState.lrtt = 350ms;
|
|
conn->transportSettings.timeReorderingThreshDividend = 1.0;
|
|
conn->transportSettings.timeReorderingThreshDivisor = 1.0;
|
|
TimePoint checkTime = TimePoint(200ms);
|
|
|
|
// Out of 1, 2, 3, 4, 5, 6, 7 -- we deleted (acked) 3,4,5.
|
|
// 1, 2 and 6 are "lost" due to reodering. None lost due to timeout
|
|
EXPECT_CALL(
|
|
*obs1,
|
|
packetLossDetected(
|
|
socket_.get(),
|
|
Field(
|
|
&SocketObserverInterface::LossEvent::lostPackets,
|
|
UnorderedElementsAre(
|
|
getLossPacketMatcher(
|
|
1 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
false /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
2 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
false /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
6 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
false /* lossByTimeout */)))))
|
|
.Times(1);
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent + 1;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
[](auto&, auto&, bool) { return folly::unit; },
|
|
checkTime,
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
EXPECT_THAT(
|
|
conn->outstandings.packets,
|
|
UnorderedElementsAre(
|
|
getOutstandingPacketMatcher(
|
|
1 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
false /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
2 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
false /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
6 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
false /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
7 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
false /* lossByTimeout */)));
|
|
|
|
CHECK_NOTNULL(conn->getSocketObserverContainer())->removeObserver(obs1.get());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, ObserverLossEventTimeout) {
|
|
auto conn = createConn();
|
|
|
|
LegacyObserver::EventSet eventSet;
|
|
eventSet.enable(SocketObserverInterface::Events::lossEvents);
|
|
auto obs1 = std::make_unique<NiceMock<MockLegacyObserver>>(eventSet);
|
|
CHECK_NOTNULL(conn->getSocketObserverContainer())->addObserver(obs1.get());
|
|
|
|
// send 7 packets
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), none, PacketType::OneRtt);
|
|
}
|
|
|
|
// setting a very high reordering threshold to force loss by timeout only
|
|
conn->lossState.reorderingThreshold = 100;
|
|
// setting time out parameters lower than the time at which detectLossPackets
|
|
// is called to make sure all packets timeout
|
|
conn->lossState.srtt = 400ms;
|
|
conn->lossState.lrtt = 350ms;
|
|
conn->transportSettings.timeReorderingThreshDividend = 1.0;
|
|
conn->transportSettings.timeReorderingThreshDivisor = 1.0;
|
|
TimePoint checkTime = TimePoint(500ms);
|
|
|
|
// expect all packets to be lost due to timeout
|
|
EXPECT_CALL(
|
|
*obs1,
|
|
packetLossDetected(
|
|
socket_.get(),
|
|
Field(
|
|
&SocketObserverInterface::LossEvent::lostPackets,
|
|
UnorderedElementsAre(
|
|
getLossPacketMatcher(
|
|
1 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
2 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
3 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
4 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
5 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
6 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
7 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */)))))
|
|
.Times(1);
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent + 1;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
[](auto&, auto&, bool) { return folly::unit; },
|
|
checkTime,
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
EXPECT_THAT(
|
|
conn->outstandings.packets,
|
|
UnorderedElementsAre(
|
|
getOutstandingPacketMatcher(
|
|
1 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
2 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
3 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
4 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
5 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
6 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
7 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */)));
|
|
|
|
CHECK_NOTNULL(conn->getSocketObserverContainer())->removeObserver(obs1.get());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, ObserverLossEventTimeoutAndReorder) {
|
|
auto conn = createConn();
|
|
|
|
LegacyObserver::EventSet eventSet;
|
|
eventSet.enable(SocketObserverInterface::Events::lossEvents);
|
|
auto obs1 = std::make_unique<NiceMock<MockLegacyObserver>>(eventSet);
|
|
CHECK_NOTNULL(conn->getSocketObserverContainer())->addObserver(obs1.get());
|
|
|
|
// send 7 packets
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), none, PacketType::OneRtt);
|
|
}
|
|
|
|
// Some packets are already acked
|
|
conn->outstandings.packets.erase(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5);
|
|
|
|
// setting a low reorder threshold
|
|
conn->lossState.reorderingThreshold = 1;
|
|
|
|
// setting time out parameters lower than the time at which detectLossPackets
|
|
// is called to make sure all packets timeout
|
|
conn->lossState.srtt = 400ms;
|
|
conn->lossState.lrtt = 350ms;
|
|
conn->transportSettings.timeReorderingThreshDividend = 1.0;
|
|
conn->transportSettings.timeReorderingThreshDivisor = 1.0;
|
|
TimePoint checkTime = TimePoint(500ms);
|
|
|
|
// Out of 1, 2, 3, 4, 5, 6, 7 -- we deleted (acked) 3,4,5.
|
|
// 1, 2, 6 are lost due to reodering and timeout.
|
|
// 7 just timed out
|
|
EXPECT_CALL(
|
|
*obs1,
|
|
packetLossDetected(
|
|
socket_.get(),
|
|
Field(
|
|
&SocketObserverInterface::LossEvent::lostPackets,
|
|
UnorderedElementsAre(
|
|
getLossPacketMatcher(
|
|
1 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
2 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
6 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getLossPacketMatcher(
|
|
7 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */)))))
|
|
.Times(1);
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent + 1;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
[](auto&, auto&, bool) { return folly::unit; },
|
|
checkTime,
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
EXPECT_THAT(
|
|
conn->outstandings.packets,
|
|
UnorderedElementsAre(
|
|
getOutstandingPacketMatcher(
|
|
1 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
2 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
6 /* packetNum */,
|
|
true /* lossByReorder */,
|
|
true /* lossByTimeout */),
|
|
getOutstandingPacketMatcher(
|
|
7 /* packetNum */,
|
|
false /* lossByReorder */,
|
|
true /* lossByTimeout */)));
|
|
|
|
CHECK_NOTNULL(conn->getSocketObserverContainer())->removeObserver(obs1.get());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TotalPacketsMarkedLostByReordering) {
|
|
auto conn = createConn();
|
|
auto noopLossVisitor = [](auto&, auto&, bool) { return folly::unit; };
|
|
|
|
// send 7 packets
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), none, PacketType::OneRtt);
|
|
}
|
|
|
|
// Some packets are already acked
|
|
conn->outstandings.packets.erase(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5);
|
|
|
|
// setting a very low reordering threshold to force loss by reorder
|
|
conn->lossState.reorderingThreshold = 1;
|
|
// setting time out parameters higher than the time at which detectLossPackets
|
|
// is called to make sure there are no losses by timeout
|
|
conn->lossState.srtt = 400ms;
|
|
conn->lossState.lrtt = 350ms;
|
|
conn->transportSettings.timeReorderingThreshDividend = 1.0;
|
|
conn->transportSettings.timeReorderingThreshDivisor = 1.0;
|
|
TimePoint checkTime = TimePoint(200ms);
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent + 1;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
noopLossVisitor,
|
|
checkTime,
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
|
|
// Sent 7 packets, out of 0, 1, 2, 3, 4, 5, 6 -- we deleted (acked) 2,3,4
|
|
// 0, 1, and 5 should be marked lost due to reordering, none due to timeout
|
|
// 6 is outstanding / on the wire still (no determination made)
|
|
EXPECT_EQ(3, conn->lossState.totalPacketsMarkedLost);
|
|
EXPECT_EQ(0, conn->lossState.totalPacketsMarkedLostByTimeout);
|
|
EXPECT_EQ(3, conn->lossState.totalPacketsMarkedLostByReorderingThreshold);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TotalPacketsMarkedLostByTimeout) {
|
|
auto conn = createConn();
|
|
auto noopLossVisitor = [](auto&, auto&, bool) { return folly::unit; };
|
|
|
|
// send 7 packets
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), none, PacketType::OneRtt);
|
|
}
|
|
|
|
// setting a very high reordering threshold to force loss by timeout only
|
|
conn->lossState.reorderingThreshold = 100;
|
|
// setting time out parameters lower than the time at which detectLossPackets
|
|
// is called to make sure all packets timeout
|
|
conn->lossState.srtt = 400ms;
|
|
conn->lossState.lrtt = 350ms;
|
|
conn->transportSettings.timeReorderingThreshDividend = 1.0;
|
|
conn->transportSettings.timeReorderingThreshDivisor = 1.0;
|
|
TimePoint checkTime = TimePoint(500ms);
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent + 1;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
noopLossVisitor,
|
|
checkTime,
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
|
|
// All 7 packets should be marked as lost by PTO
|
|
EXPECT_EQ(7, conn->lossState.totalPacketsMarkedLost);
|
|
EXPECT_EQ(7, conn->lossState.totalPacketsMarkedLostByTimeout);
|
|
EXPECT_EQ(0, conn->lossState.totalPacketsMarkedLostByReorderingThreshold);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TotalPacketsMarkedLostByTimeoutPartial) {
|
|
auto conn = createConn();
|
|
auto noopLossVisitor = [](auto&, auto&, bool) { return folly::unit; };
|
|
|
|
// send 7 packets
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), none, PacketType::OneRtt);
|
|
}
|
|
|
|
// Some packets are already acked
|
|
conn->outstandings.packets.erase(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5);
|
|
|
|
// setting a very high reordering threshold to force loss by timeout only
|
|
conn->lossState.reorderingThreshold = 100;
|
|
// setting time out parameters lower than the time at which detectLossPackets
|
|
// is called to make sure all packets timeout
|
|
conn->lossState.srtt = 400ms;
|
|
conn->lossState.lrtt = 350ms;
|
|
conn->transportSettings.timeReorderingThreshDividend = 1.0;
|
|
conn->transportSettings.timeReorderingThreshDivisor = 1.0;
|
|
TimePoint checkTime = TimePoint(500ms);
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent + 1;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
noopLossVisitor,
|
|
checkTime,
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
|
|
// Sent 7 packets, out of 0, 1, 2, 3, 4, 5, 6 -- we deleted (acked) 2,3,4
|
|
// 0, 1, 5, and 6 should be marked lost due to timeout, none due to reordering
|
|
EXPECT_EQ(4, conn->lossState.totalPacketsMarkedLost);
|
|
EXPECT_EQ(4, conn->lossState.totalPacketsMarkedLostByTimeout);
|
|
EXPECT_EQ(0, conn->lossState.totalPacketsMarkedLostByReorderingThreshold);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TotalPacketsMarkedLostByTimeoutAndReordering) {
|
|
auto conn = createConn();
|
|
auto noopLossVisitor = [](auto&, auto&, bool) { return folly::unit; };
|
|
|
|
// send 7 packets
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), none, PacketType::OneRtt);
|
|
}
|
|
|
|
// Some packets are already acked
|
|
conn->outstandings.packets.erase(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5);
|
|
|
|
// setting a low reorder threshold
|
|
conn->lossState.reorderingThreshold = 1;
|
|
|
|
// setting time out parameters lower than the time at which detectLossPackets
|
|
// is called to make sure all packets timeout
|
|
conn->lossState.srtt = 400ms;
|
|
conn->lossState.lrtt = 350ms;
|
|
conn->transportSettings.timeReorderingThreshDividend = 1.0;
|
|
conn->transportSettings.timeReorderingThreshDivisor = 1.0;
|
|
TimePoint checkTime = TimePoint(500ms);
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = largestSent + 1;
|
|
ASSERT_FALSE(detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
noopLossVisitor,
|
|
checkTime,
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
|
|
// Sent 7 packets, out of 0, 1, 2, 3, 4, 5, 6 -- we deleted (acked) 2,3,4
|
|
// 0, 1, and 5 should be marked lost due to reordering AND timeout
|
|
// 6 should be marked as lost due to timeout only
|
|
EXPECT_EQ(4, conn->lossState.totalPacketsMarkedLost);
|
|
EXPECT_EQ(4, conn->lossState.totalPacketsMarkedLostByTimeout);
|
|
EXPECT_EQ(3, conn->lossState.totalPacketsMarkedLostByReorderingThreshold);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, LossVisitorDSRTest) {
|
|
auto conn = createConn();
|
|
auto* stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
stream->dsrSender = std::make_unique<MockDSRPacketizationRequestSender>();
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream, folly::IOBuf::copyBuffer("grape"), false)
|
|
.hasError());
|
|
ASSERT_FALSE(
|
|
writeBufMetaToQuicStream(*stream, BufferMeta(1000), true).hasError());
|
|
auto bufMetaStartingOffset = stream->writeBufMeta.offset;
|
|
ASSERT_EQ(1000, stream->writeBufMeta.length);
|
|
ASSERT_TRUE(stream->writeBufMeta.eof);
|
|
ASSERT_EQ(bufMetaStartingOffset + 1000, *stream->finalWriteOffset);
|
|
// Send real data
|
|
ASSERT_FALSE(handleStreamWritten(
|
|
*conn,
|
|
*stream,
|
|
0,
|
|
bufMetaStartingOffset,
|
|
false,
|
|
0 /* PacketNum */,
|
|
PacketNumberSpace::AppData)
|
|
.hasError());
|
|
ASSERT_EQ(0, stream->pendingWrites.chainLength());
|
|
auto retxIter = stream->retransmissionBuffer.find(0);
|
|
ASSERT_NE(stream->retransmissionBuffer.end(), retxIter);
|
|
ASSERT_EQ(0, retxIter->second->offset);
|
|
ASSERT_FALSE(retxIter->second->eof);
|
|
auto expectedBuf = folly::IOBuf::copyBuffer("grape");
|
|
ASSERT_EQ(
|
|
ByteRange(expectedBuf->buffer(), expectedBuf->length()),
|
|
retxIter->second->data.getHead()->getRange());
|
|
ASSERT_EQ(stream->currentWriteOffset, bufMetaStartingOffset);
|
|
|
|
// Send BufMeta in 3 chunks:
|
|
handleStreamBufMetaWritten(
|
|
*conn,
|
|
*stream,
|
|
bufMetaStartingOffset,
|
|
200,
|
|
false,
|
|
1 /* PacketNum */,
|
|
PacketNumberSpace::AppData);
|
|
ASSERT_EQ(800, stream->writeBufMeta.length);
|
|
ASSERT_TRUE(stream->writeBufMeta.eof);
|
|
ASSERT_EQ(bufMetaStartingOffset + 200, stream->writeBufMeta.offset);
|
|
auto retxBufMetaIter =
|
|
stream->retransmissionBufMetas.find(bufMetaStartingOffset);
|
|
ASSERT_NE(stream->retransmissionBufMetas.end(), retxBufMetaIter);
|
|
ASSERT_EQ(bufMetaStartingOffset, retxBufMetaIter->second.offset);
|
|
ASSERT_EQ(200, retxBufMetaIter->second.length);
|
|
ASSERT_FALSE(retxBufMetaIter->second.eof);
|
|
conn->streamManager->updateWritableStreams(*stream);
|
|
EXPECT_FALSE(conn->streamManager->hasLoss());
|
|
EXPECT_FALSE(conn->streamManager->writableDSRStreams().empty());
|
|
|
|
handleStreamBufMetaWritten(
|
|
*conn,
|
|
*stream,
|
|
bufMetaStartingOffset + 200,
|
|
400,
|
|
false,
|
|
2 /* PacketNum */,
|
|
PacketNumberSpace::AppData);
|
|
ASSERT_EQ(400, stream->writeBufMeta.length);
|
|
ASSERT_TRUE(stream->writeBufMeta.eof);
|
|
ASSERT_EQ(bufMetaStartingOffset + 600, stream->writeBufMeta.offset);
|
|
retxBufMetaIter =
|
|
stream->retransmissionBufMetas.find(bufMetaStartingOffset + 200);
|
|
ASSERT_NE(stream->retransmissionBufMetas.end(), retxBufMetaIter);
|
|
ASSERT_EQ(bufMetaStartingOffset + 200, retxBufMetaIter->second.offset);
|
|
ASSERT_EQ(400, retxBufMetaIter->second.length);
|
|
ASSERT_FALSE(retxBufMetaIter->second.eof);
|
|
conn->streamManager->updateWritableStreams(*stream);
|
|
EXPECT_FALSE(conn->streamManager->hasLoss());
|
|
EXPECT_FALSE(conn->streamManager->writableDSRStreams().empty());
|
|
|
|
handleStreamBufMetaWritten(
|
|
*conn,
|
|
*stream,
|
|
bufMetaStartingOffset + 600,
|
|
400,
|
|
true,
|
|
3 /* PacketNum */,
|
|
PacketNumberSpace::AppData);
|
|
ASSERT_EQ(0, stream->writeBufMeta.length);
|
|
ASSERT_TRUE(stream->writeBufMeta.eof);
|
|
ASSERT_EQ(bufMetaStartingOffset + 1000 + 1, stream->writeBufMeta.offset);
|
|
retxBufMetaIter =
|
|
stream->retransmissionBufMetas.find(bufMetaStartingOffset + 600);
|
|
ASSERT_NE(stream->retransmissionBufMetas.end(), retxBufMetaIter);
|
|
ASSERT_EQ(bufMetaStartingOffset + 600, retxBufMetaIter->second.offset);
|
|
ASSERT_EQ(400, retxBufMetaIter->second.length);
|
|
ASSERT_TRUE(retxBufMetaIter->second.eof);
|
|
conn->streamManager->updateWritableStreams(*stream);
|
|
EXPECT_FALSE(conn->streamManager->hasLoss());
|
|
EXPECT_TRUE(conn->streamManager->writableDSRStreams().empty());
|
|
|
|
// Lose the 1st dsr packet:
|
|
RegularQuicWritePacket packet1(PacketHeader(ShortHeader(
|
|
ProtectionType::KeyPhaseZero, conn->serverConnectionId.value(), 1)));
|
|
WriteStreamFrame frame1(stream->id, bufMetaStartingOffset, 200, false);
|
|
frame1.fromBufMeta = true;
|
|
packet1.frames.push_back(frame1);
|
|
ASSERT_FALSE(
|
|
markPacketLoss(*conn, packet1, false /* processed */).hasError());
|
|
EXPECT_EQ(1, stream->lossBufMetas.size());
|
|
const auto& lostBufMeta1 = stream->lossBufMetas.front();
|
|
EXPECT_EQ(bufMetaStartingOffset, lostBufMeta1.offset);
|
|
EXPECT_EQ(200, lostBufMeta1.length);
|
|
EXPECT_FALSE(lostBufMeta1.eof);
|
|
EXPECT_EQ(2, stream->retransmissionBufMetas.size());
|
|
EXPECT_TRUE(conn->streamManager->hasLoss());
|
|
ASSERT_EQ(stream->streamLossCount, 1);
|
|
EXPECT_FALSE(stream->hasWritableBufMeta());
|
|
EXPECT_FALSE(conn->streamManager->writableDSRStreams().contains(stream->id));
|
|
EXPECT_TRUE(writeQueueContains(*conn, stream->id));
|
|
|
|
// Lose the 3rd dsr packet:
|
|
RegularQuicWritePacket packet3(PacketHeader(ShortHeader(
|
|
ProtectionType::KeyPhaseZero, conn->serverConnectionId.value(), 3)));
|
|
WriteStreamFrame frame3(stream->id, bufMetaStartingOffset + 600, 400, true);
|
|
frame3.fromBufMeta = true;
|
|
packet3.frames.push_back(frame3);
|
|
ASSERT_FALSE(
|
|
markPacketLoss(*conn, packet3, false /* processed */).hasError());
|
|
EXPECT_EQ(2, stream->lossBufMetas.size());
|
|
const auto& lostBufMeta2 = stream->lossBufMetas.back();
|
|
EXPECT_EQ(bufMetaStartingOffset + 600, lostBufMeta2.offset);
|
|
EXPECT_EQ(400, lostBufMeta2.length);
|
|
EXPECT_TRUE(lostBufMeta2.eof);
|
|
EXPECT_EQ(1, stream->retransmissionBufMetas.size());
|
|
ASSERT_EQ(stream->streamLossCount, 2);
|
|
EXPECT_TRUE(conn->streamManager->hasLoss());
|
|
EXPECT_FALSE(stream->hasWritableBufMeta());
|
|
EXPECT_FALSE(conn->streamManager->writableDSRStreams().contains(stream->id));
|
|
EXPECT_TRUE(writeQueueContains(*conn, stream->id));
|
|
|
|
// Lose the 3rd dsr packet, it should be merged together with the first
|
|
// element in the lossBufMetas:
|
|
RegularQuicWritePacket packet2(PacketHeader(ShortHeader(
|
|
ProtectionType::KeyPhaseZero, conn->serverConnectionId.value(), 2)));
|
|
WriteStreamFrame frame2(stream->id, bufMetaStartingOffset + 200, 400, false);
|
|
frame2.fromBufMeta = true;
|
|
packet2.frames.push_back(frame2);
|
|
ASSERT_FALSE(
|
|
markPacketLoss(*conn, packet2, false /* processed */).hasError());
|
|
EXPECT_EQ(2, stream->lossBufMetas.size());
|
|
const auto& lostBufMeta3 = stream->lossBufMetas.front();
|
|
EXPECT_EQ(bufMetaStartingOffset, lostBufMeta3.offset);
|
|
EXPECT_EQ(600, lostBufMeta3.length);
|
|
EXPECT_FALSE(lostBufMeta3.eof);
|
|
EXPECT_EQ(0, stream->retransmissionBufMetas.size());
|
|
ASSERT_EQ(stream->streamLossCount, 3);
|
|
EXPECT_TRUE(conn->streamManager->hasLoss());
|
|
EXPECT_FALSE(stream->hasWritableBufMeta());
|
|
EXPECT_FALSE(conn->streamManager->writableDSRStreams().contains(stream->id));
|
|
EXPECT_TRUE(writeQueueContains(*conn, stream->id));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestReorderingThresholdDSRNormal) {
|
|
std::vector<PacketNum> lostPacket;
|
|
auto conn = createConn();
|
|
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
auto testingLossMarkFunc = [&lostPacket](auto& /*conn*/, auto& packet, bool) {
|
|
auto packetNum = packet.header.getPacketSequenceNum();
|
|
lostPacket.push_back(packetNum);
|
|
return folly::unit;
|
|
};
|
|
for (int i = 0; i < 6; ++i) {
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt, none, true);
|
|
}
|
|
// Add some DSR frames
|
|
for (auto& op : conn->outstandings.packets) {
|
|
// This matches the stream packet index to the one used by the packet.
|
|
op.packet.frames.emplace_back(WriteStreamFrame{
|
|
0,
|
|
10,
|
|
100,
|
|
false,
|
|
true,
|
|
std::nullopt,
|
|
op.packet.header.getPacketSequenceNum()});
|
|
op.isDSRPacket = true;
|
|
conn->outstandings.dsrCount++;
|
|
}
|
|
EXPECT_EQ(6, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
// Assume some packets are already acked
|
|
for (auto iter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2;
|
|
iter < getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5;
|
|
iter++) {
|
|
conn->outstandings.packetCount[PacketNumberSpace::AppData]--;
|
|
}
|
|
auto firstAppDataOpIter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
conn->outstandings.packets.erase(
|
|
firstAppDataOpIter + 2, firstAppDataOpIter + 5);
|
|
// Ack for packet 9 arrives
|
|
auto ack = AckEvent::Builder()
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(9)
|
|
.setIsImplicitAck(false)
|
|
.setAckTime(Clock::now())
|
|
.setAdjustedAckTime(Clock::now())
|
|
.setAckDelay(0us)
|
|
.build();
|
|
AckEvent::AckPacket::DetailsPerStream detailsPerStream;
|
|
detailsPerStream.recordFrameDelivered(
|
|
WriteStreamFrame{0, 10, 100, false, true, std::nullopt, 9});
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(9)
|
|
.setNonDsrPacketSequenceNumber(0)
|
|
.setDetailsPerStream(std::move(detailsPerStream))
|
|
.setOutstandingPacketMetadata(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata)
|
|
.build());
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = 9;
|
|
auto lossEventResult = detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
testingLossMarkFunc,
|
|
TimePoint(90ms),
|
|
PacketNumberSpace::AppData,
|
|
&ack);
|
|
ASSERT_FALSE(lossEventResult.hasError());
|
|
auto& lossEvent = lossEventResult.value();
|
|
ASSERT_TRUE(lossEvent->largestLostPacketNum.has_value());
|
|
EXPECT_EQ(2, lossEvent->largestLostPacketNum.value());
|
|
EXPECT_EQ(TimePoint(90ms), lossEvent->lossTime);
|
|
// Packet 1,2 should be marked as loss
|
|
EXPECT_EQ(lostPacket.size(), 2);
|
|
EXPECT_EQ(lostPacket.front(), 1);
|
|
EXPECT_EQ(lostPacket.back(), 2);
|
|
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
|
|
// Packet 6 should remain in packet as the delta is less than threshold
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
EXPECT_EQ(conn->outstandings.packets.size(), 1 + numDeclaredLost);
|
|
auto first = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
auto packetNum = first->packet.header.getPacketSequenceNum();
|
|
EXPECT_EQ(packetNum, 6);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestReorderingThresholdDSRNormalOverflow) {
|
|
std::vector<PacketNum> lostPacket;
|
|
auto conn = createConn();
|
|
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
auto testingLossMarkFunc = [&lostPacket](auto& /*conn*/, auto& packet, bool) {
|
|
auto packetNum = packet.header.getPacketSequenceNum();
|
|
lostPacket.push_back(packetNum);
|
|
return folly::unit;
|
|
};
|
|
for (int i = 0; i < 6; ++i) {
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt, none, true);
|
|
}
|
|
// Add some DSR frames
|
|
for (auto& op : conn->outstandings.packets) {
|
|
op.packet.frames.emplace_back(WriteStreamFrame{0, 10, 100, false, true});
|
|
op.isDSRPacket = true;
|
|
conn->outstandings.dsrCount++;
|
|
}
|
|
EXPECT_EQ(6, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
// Assume some packets are already acked
|
|
for (auto iter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2;
|
|
iter < getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5;
|
|
iter++) {
|
|
conn->outstandings.packetCount[PacketNumberSpace::AppData]--;
|
|
}
|
|
auto firstAppDataOpIter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
conn->outstandings.packets.erase(
|
|
firstAppDataOpIter + 2, firstAppDataOpIter + 5);
|
|
// Ack for packet 9 arrives
|
|
auto ack = AckEvent::Builder()
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(9)
|
|
.setIsImplicitAck(false)
|
|
.setAckTime(Clock::now())
|
|
.setAdjustedAckTime(Clock::now())
|
|
.setAckDelay(0us)
|
|
.build();
|
|
AckEvent::AckPacket::DetailsPerStream detailsPerStream;
|
|
detailsPerStream.recordFrameDelivered(
|
|
WriteStreamFrame{0, 10, 100, false, true});
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(0)
|
|
.setNonDsrPacketSequenceNumber(0)
|
|
.setDetailsPerStream(std::move(detailsPerStream))
|
|
.setOutstandingPacketMetadata(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata)
|
|
.build());
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = 9;
|
|
auto lossEventResult = detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
testingLossMarkFunc,
|
|
TimePoint(90ms),
|
|
PacketNumberSpace::AppData,
|
|
&ack);
|
|
EXPECT_FALSE(lossEventResult.hasError());
|
|
auto& lossEvent = lossEventResult.value();
|
|
EXPECT_FALSE(lossEvent.has_value());
|
|
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
EXPECT_EQ(numDeclaredLost, 0);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestReorderingThresholdDSRIgnoreReorder) {
|
|
std::vector<PacketNum> lostPacket;
|
|
auto conn = createConn();
|
|
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
auto testingLossMarkFunc = [&lostPacket](auto& /*conn*/, auto& packet, bool) {
|
|
auto packetNum = packet.header.getPacketSequenceNum();
|
|
lostPacket.push_back(packetNum);
|
|
return folly::unit;
|
|
};
|
|
for (int i = 0; i < 6; ++i) {
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt, none, true);
|
|
}
|
|
// Add some DSR frames
|
|
for (auto& op : conn->outstandings.packets) {
|
|
op.packet.frames.emplace_back(WriteStreamFrame{
|
|
0,
|
|
10,
|
|
100,
|
|
false,
|
|
true,
|
|
std::nullopt,
|
|
op.packet.header.getPacketSequenceNum()});
|
|
op.isDSRPacket = true;
|
|
conn->outstandings.dsrCount++;
|
|
}
|
|
// Add another non-DSR after
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
EXPECT_EQ(7, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
// Assume some packets are already acked
|
|
for (auto iter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2;
|
|
iter < getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5;
|
|
iter++) {
|
|
conn->outstandings.packetCount[PacketNumberSpace::AppData]--;
|
|
}
|
|
auto firstAppDataOpIter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
conn->outstandings.packets.erase(
|
|
firstAppDataOpIter + 2, firstAppDataOpIter + 5);
|
|
// Ack for packet 20 arrives
|
|
auto ack = AckEvent::Builder()
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(20)
|
|
.setIsImplicitAck(false)
|
|
.setAckTime(Clock::now())
|
|
.setAdjustedAckTime(Clock::now())
|
|
.setAckDelay(0us)
|
|
.build();
|
|
AckEvent::AckPacket::DetailsPerStream detailsPerStream;
|
|
// Ack a different stream.
|
|
detailsPerStream.recordFrameDelivered(
|
|
WriteStreamFrame{4, 10, 100, false, true});
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(20)
|
|
.setNonDsrPacketSequenceNumber(0)
|
|
.setDetailsPerStream(std::move(detailsPerStream))
|
|
.setOutstandingPacketMetadata(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata)
|
|
.build());
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(19)
|
|
.setNonDsrPacketSequenceNumber(19 - 6)
|
|
.setDetailsPerStream(AckEvent::AckPacket::DetailsPerStream())
|
|
.setOutstandingPacketMetadata(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata)
|
|
.build());
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = 20;
|
|
auto lossEventResult = detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
testingLossMarkFunc,
|
|
TimePoint(90ms),
|
|
PacketNumberSpace::AppData,
|
|
&ack);
|
|
EXPECT_FALSE(lossEventResult.hasError());
|
|
auto& lossEvent = lossEventResult.value();
|
|
ASSERT_TRUE(lossEvent.has_value());
|
|
|
|
EXPECT_EQ(3, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
// We should declare the non DSR packet lost.
|
|
EXPECT_EQ(1, numDeclaredLost);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestReorderingThresholdNonDSRIgnoreReorder) {
|
|
std::vector<PacketNum> lostPacket;
|
|
auto conn = createConn();
|
|
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
auto testingLossMarkFunc = [&lostPacket](auto& /*conn*/, auto& packet, bool) {
|
|
auto packetNum = packet.header.getPacketSequenceNum();
|
|
lostPacket.push_back(packetNum);
|
|
return folly::unit;
|
|
};
|
|
for (int i = 0; i < 6; ++i) {
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
}
|
|
EXPECT_EQ(6, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
// Assume some packets are already acked
|
|
for (auto iter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2;
|
|
iter < getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5;
|
|
iter++) {
|
|
conn->outstandings.packetCount[PacketNumberSpace::AppData]--;
|
|
}
|
|
auto firstAppDataOpIter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
conn->outstandings.packets.erase(
|
|
firstAppDataOpIter + 2, firstAppDataOpIter + 5);
|
|
// Ack for packet 9 arrives
|
|
auto ack = AckEvent::Builder()
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(9)
|
|
.setIsImplicitAck(false)
|
|
.setAckTime(Clock::now())
|
|
.setAdjustedAckTime(Clock::now())
|
|
.setAckDelay(0us)
|
|
.build();
|
|
AckEvent::AckPacket::DetailsPerStream detailsPerStream;
|
|
// Ack a different stream.
|
|
detailsPerStream.recordFrameDelivered(
|
|
WriteStreamFrame{4, 10, 100, false, true});
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(9)
|
|
.setNonDsrPacketSequenceNumber(9)
|
|
.setDetailsPerStream(std::move(detailsPerStream))
|
|
.setOutstandingPacketMetadata(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata)
|
|
.build());
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = 9;
|
|
auto lossEventResult = detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
testingLossMarkFunc,
|
|
TimePoint(90ms),
|
|
PacketNumberSpace::AppData,
|
|
&ack);
|
|
EXPECT_FALSE(lossEventResult.hasError());
|
|
auto& lossEvent = lossEventResult.value();
|
|
EXPECT_FALSE(lossEvent.has_value());
|
|
|
|
EXPECT_EQ(3, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
EXPECT_EQ(0, numDeclaredLost);
|
|
EXPECT_EQ(conn->outstandings.packets.size(), 3);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicLossFunctionsTest,
|
|
TestReorderingThresholdNonDSRIgnoreReorderOverflow) {
|
|
std::vector<PacketNum> lostPacket;
|
|
auto conn = createConn();
|
|
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
auto testingLossMarkFunc = [&lostPacket](auto& /*conn*/, auto& packet, bool) {
|
|
auto packetNum = packet.header.getPacketSequenceNum();
|
|
lostPacket.push_back(packetNum);
|
|
return folly::unit;
|
|
};
|
|
for (int i = 0; i < 6; ++i) {
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
}
|
|
EXPECT_EQ(6, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
// Assume some packets are already acked
|
|
for (auto iter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2;
|
|
iter < getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5;
|
|
iter++) {
|
|
conn->outstandings.packetCount[PacketNumberSpace::AppData]--;
|
|
}
|
|
auto firstAppDataOpIter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
conn->outstandings.packets.erase(
|
|
firstAppDataOpIter + 2, firstAppDataOpIter + 5);
|
|
// Ack for packet 0 arrives
|
|
auto ack = AckEvent::Builder()
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(9)
|
|
.setIsImplicitAck(false)
|
|
.setAckTime(Clock::now())
|
|
.setAdjustedAckTime(Clock::now())
|
|
.setAckDelay(0us)
|
|
.build();
|
|
AckEvent::AckPacket::DetailsPerStream detailsPerStream;
|
|
detailsPerStream.recordFrameDelivered(
|
|
WriteStreamFrame{4, 10, 100, false, true});
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(9)
|
|
.setNonDsrPacketSequenceNumber(9)
|
|
.setDetailsPerStream(std::move(detailsPerStream))
|
|
.setOutstandingPacketMetadata(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata)
|
|
.build());
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(0)
|
|
.setNonDsrPacketSequenceNumber(0)
|
|
.setOutstandingPacketMetadata(
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata)
|
|
.setDetailsPerStream(AckEvent::AckPacket::DetailsPerStream())
|
|
.build());
|
|
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = 9;
|
|
auto lossEventResult = detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
testingLossMarkFunc,
|
|
TimePoint(90ms),
|
|
PacketNumberSpace::AppData,
|
|
&ack);
|
|
EXPECT_FALSE(lossEventResult.hasError());
|
|
auto& lossEvent = lossEventResult.value();
|
|
EXPECT_FALSE(lossEvent.has_value());
|
|
|
|
EXPECT_EQ(3, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
EXPECT_EQ(0, numDeclaredLost);
|
|
EXPECT_EQ(conn->outstandings.packets.size(), 3);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestReorderingThresholdDSRIgnoreReorderBurst) {
|
|
std::vector<PacketNum> lostPacket;
|
|
auto conn = createConn();
|
|
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
auto testingLossMarkFunc = [&lostPacket](auto& /*conn*/, auto& packet, bool) {
|
|
auto packetNum = packet.header.getPacketSequenceNum();
|
|
lostPacket.push_back(packetNum);
|
|
return folly::unit;
|
|
};
|
|
for (int i = 0; i < 4; ++i) {
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt, none, true);
|
|
}
|
|
// Add some DSR frames and build the ACK
|
|
auto ack = AckEvent::Builder()
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(9)
|
|
.setIsImplicitAck(false)
|
|
.setAckTime(Clock::now())
|
|
.setAdjustedAckTime(Clock::now())
|
|
.setAckDelay(0us)
|
|
.build();
|
|
for (auto& op : conn->outstandings.packets) {
|
|
WriteStreamFrame f{
|
|
0,
|
|
10,
|
|
100,
|
|
false,
|
|
true,
|
|
std::nullopt,
|
|
op.packet.header.getPacketSequenceNum()};
|
|
AckEvent::AckPacket::DetailsPerStream detailsPerStream;
|
|
if (op.packet.header.getPacketSequenceNum() != 4) {
|
|
detailsPerStream.recordFrameDelivered(f);
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(op.packet.header.getPacketSequenceNum())
|
|
.setNonDsrPacketSequenceNumber(0)
|
|
.setDetailsPerStream(std::move(detailsPerStream))
|
|
.setOutstandingPacketMetadata(op.metadata)
|
|
.build());
|
|
}
|
|
op.packet.frames.emplace_back(f);
|
|
op.isDSRPacket = true;
|
|
conn->outstandings.dsrCount++;
|
|
}
|
|
// Add another non-DSR burst and ACK all of them
|
|
for (int i = 0; i < 4; ++i) {
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt, none, false);
|
|
auto& op = *getLastOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(op.packet.header.getPacketSequenceNum())
|
|
.setNonDsrPacketSequenceNumber(
|
|
op.nonDsrPacketSequenceNumber.value())
|
|
.setDetailsPerStream({})
|
|
.setOutstandingPacketMetadata(op.metadata)
|
|
.build());
|
|
}
|
|
// Add one more DSR packet from the same stream, ACKed
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt, none, true);
|
|
auto& op = *getLastOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame f{0, 10, 100, false, true, std::nullopt, 5};
|
|
AckEvent::AckPacket::DetailsPerStream detailsPerStream;
|
|
detailsPerStream.recordFrameDelivered(f);
|
|
op.packet.frames.emplace_back(f);
|
|
op.isDSRPacket = true;
|
|
conn->outstandings.dsrCount++;
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(op.packet.header.getPacketSequenceNum())
|
|
.setNonDsrPacketSequenceNumber(0)
|
|
.setDetailsPerStream(std::move(detailsPerStream))
|
|
.setOutstandingPacketMetadata(op.metadata)
|
|
.build());
|
|
|
|
ASSERT_EQ(9, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
// ACK everything that isn't packet 4.
|
|
auto itr = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
while (itr != conn->outstandings.packets.end()) {
|
|
if (itr->packet.header.getPacketSequenceNum() != 4) {
|
|
itr = conn->outstandings.packets.erase(itr);
|
|
conn->outstandings.packetCount[PacketNumberSpace::AppData]--;
|
|
} else {
|
|
itr++;
|
|
}
|
|
}
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = 10;
|
|
auto lossEventResult = detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
testingLossMarkFunc,
|
|
TimePoint(90ms),
|
|
PacketNumberSpace::AppData,
|
|
&ack);
|
|
EXPECT_FALSE(lossEventResult.hasError());
|
|
auto& lossEvent = lossEventResult.value();
|
|
EXPECT_FALSE(lossEvent.has_value());
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
// The DSR packet before the burst shouldn't be lost.
|
|
EXPECT_EQ(0, numDeclaredLost);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestReorderingThresholdNonDSRIgnoreReorderBurst) {
|
|
std::vector<PacketNum> lostPacket;
|
|
auto conn = createConn();
|
|
|
|
auto mockCongestionController = std::make_unique<MockCongestionController>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.WillRepeatedly(Return());
|
|
|
|
auto testingLossMarkFunc = [&lostPacket](auto& /*conn*/, auto& packet, bool) {
|
|
auto packetNum = packet.header.getPacketSequenceNum();
|
|
lostPacket.push_back(packetNum);
|
|
return folly::unit;
|
|
};
|
|
for (int i = 0; i < 4; ++i) {
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt, none, false);
|
|
}
|
|
// Add some non-DSR frames and build the ACK
|
|
auto ack = AckEvent::Builder()
|
|
.setPacketNumberSpace(PacketNumberSpace::AppData)
|
|
.setLargestAckedPacket(9)
|
|
.setIsImplicitAck(false)
|
|
.setAckTime(Clock::now())
|
|
.setAdjustedAckTime(Clock::now())
|
|
.setAckDelay(0us)
|
|
.build();
|
|
for (auto& op : conn->outstandings.packets) {
|
|
WriteStreamFrame f{0, 10, 100, false, false, std::nullopt, 0};
|
|
AckEvent::AckPacket::DetailsPerStream detailsPerStream;
|
|
if (op.packet.header.getPacketSequenceNum() != 4) {
|
|
detailsPerStream.recordFrameDelivered(f);
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(op.packet.header.getPacketSequenceNum())
|
|
.setNonDsrPacketSequenceNumber(
|
|
op.nonDsrPacketSequenceNumber.value())
|
|
.setDetailsPerStream(std::move(detailsPerStream))
|
|
.setOutstandingPacketMetadata(op.metadata)
|
|
.build());
|
|
}
|
|
op.packet.frames.emplace_back(f);
|
|
}
|
|
// Add a DSR burst and ACK all of them
|
|
for (int i = 0; i < 4; ++i) {
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt, none, true);
|
|
auto& op = *getLastOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame f{
|
|
4, 10, 100, false, true, std::nullopt, conn->outstandings.dsrCount++};
|
|
AckEvent::AckPacket::DetailsPerStream detailsPerStream;
|
|
detailsPerStream.recordFrameDelivered(f);
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(op.packet.header.getPacketSequenceNum())
|
|
.setNonDsrPacketSequenceNumber(0)
|
|
.setDetailsPerStream(std::move(detailsPerStream))
|
|
.setOutstandingPacketMetadata(op.metadata)
|
|
.build());
|
|
op.packet.frames.emplace_back(f);
|
|
op.isDSRPacket = true;
|
|
}
|
|
// Add one more non-DSR packet from the same stream, ACKed
|
|
sendPacket(*conn, Clock::now(), none, PacketType::OneRtt);
|
|
auto& op = *getLastOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame f{0, 10, 100, false, false, std::nullopt, 0};
|
|
AckEvent::AckPacket::DetailsPerStream detailsPerStream;
|
|
detailsPerStream.recordFrameDelivered(f);
|
|
op.packet.frames.emplace_back(f);
|
|
ack.ackedPackets.emplace_back(
|
|
CongestionController::AckEvent::AckPacket::Builder()
|
|
.setPacketNum(op.packet.header.getPacketSequenceNum())
|
|
.setNonDsrPacketSequenceNumber(op.nonDsrPacketSequenceNumber.value())
|
|
.setDetailsPerStream(std::move(detailsPerStream))
|
|
.setOutstandingPacketMetadata(op.metadata)
|
|
.build());
|
|
|
|
ASSERT_EQ(9, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
// ACK everything that isn't packet 4.
|
|
auto itr = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData);
|
|
while (itr != conn->outstandings.packets.end()) {
|
|
if (itr->packet.header.getPacketSequenceNum() != 4) {
|
|
itr = conn->outstandings.packets.erase(itr);
|
|
conn->outstandings.packetCount[PacketNumberSpace::AppData]--;
|
|
} else {
|
|
itr++;
|
|
}
|
|
}
|
|
auto& ackState = getAckState(*conn, PacketNumberSpace::AppData);
|
|
ackState.largestAckedByPeer =
|
|
ackState.largestNonDsrSequenceNumberAckedByPeer = 10;
|
|
auto lossEventResult = detectLossPackets(
|
|
*conn,
|
|
ackState,
|
|
testingLossMarkFunc,
|
|
TimePoint(90ms),
|
|
PacketNumberSpace::AppData,
|
|
&ack);
|
|
EXPECT_FALSE(lossEventResult.hasError());
|
|
auto& lossEvent = lossEventResult.value();
|
|
EXPECT_FALSE(lossEvent.has_value());
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::AppData]);
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
// The non-DSR packet before the burst shouldn't be lost.
|
|
EXPECT_EQ(0, numDeclaredLost);
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
QuicLossFunctionsTests,
|
|
QuicLossFunctionsTest,
|
|
Values(
|
|
PacketNumberSpace::Initial,
|
|
PacketNumberSpace::Handshake,
|
|
PacketNumberSpace::AppData));
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLossRetransmissionDisabled) {
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
auto conn = createConn();
|
|
|
|
conn->transportSettings.advertisedMaxStreamGroups = 16;
|
|
|
|
const auto groupId =
|
|
conn->streamManager->createNextBidirectionalStreamGroup();
|
|
QuicStreamGroupRetransmissionPolicy policy;
|
|
policy.disableRetransmission = true;
|
|
conn->retransmissionPolicies.emplace(*groupId, policy);
|
|
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(2);
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream(*groupId).value()->id;
|
|
auto stream2Id =
|
|
conn->streamManager->createNextBidirectionalStream(*groupId).value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
auto stream2 = conn->streamManager->findStream(stream2Id);
|
|
auto buf = buildRandomInputData(20);
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream2, buf->clone(), true).hasError());
|
|
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
|
|
// One packet in outstandings.
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
|
|
// Lose the packet.
|
|
auto& packet =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet, false).hasError());
|
|
|
|
// The lost packet data should not be transferred to the loss buffer of either
|
|
// stream.
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 0);
|
|
EXPECT_EQ(stream2->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream2->lossBuffer.size(), 0);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicLossFunctionsTest,
|
|
TestMarkPacketLossRetransmissionPolicyPresentButNotDisabled) {
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
auto conn = createConn();
|
|
|
|
conn->transportSettings.advertisedMaxStreamGroups = 16;
|
|
|
|
const auto groupId =
|
|
conn->streamManager->createNextBidirectionalStreamGroup();
|
|
QuicStreamGroupRetransmissionPolicy policy;
|
|
policy.disableRetransmission = false;
|
|
conn->retransmissionPolicies.emplace(*groupId, policy);
|
|
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(2);
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream(*groupId).value()->id;
|
|
auto stream2Id =
|
|
conn->streamManager->createNextBidirectionalStream(*groupId).value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
auto stream2 = conn->streamManager->findStream(stream2Id);
|
|
auto buf = buildRandomInputData(20);
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream2, buf->clone(), true).hasError());
|
|
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
|
|
// One packet in outstandings.
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
|
|
// Lose the packet.
|
|
auto& packet =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet, false).hasError());
|
|
|
|
// The data from the lost packet should be transferred to the loss buffer of
|
|
// both streams because disableRetransmission = false on the policy.
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 1);
|
|
EXPECT_EQ(stream2->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream2->lossBuffer.size(), 1);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLossRetransmissionPolicyTwoGroups) {
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
auto conn = createConn();
|
|
|
|
conn->transportSettings.advertisedMaxStreamGroups = 16;
|
|
|
|
const auto groupId1 =
|
|
conn->streamManager->createNextBidirectionalStreamGroup();
|
|
const auto groupId2 =
|
|
conn->streamManager->createNextBidirectionalStreamGroup();
|
|
|
|
QuicStreamGroupRetransmissionPolicy policy1;
|
|
policy1.disableRetransmission = true;
|
|
conn->retransmissionPolicies.emplace(*groupId1, policy1);
|
|
|
|
QuicStreamGroupRetransmissionPolicy policy2;
|
|
policy2.disableRetransmission = false;
|
|
conn->retransmissionPolicies.emplace(*groupId2, policy2);
|
|
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(2);
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream(*groupId1).value()->id;
|
|
auto stream2Id =
|
|
conn->streamManager->createNextBidirectionalStream(*groupId2).value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
auto stream2 = conn->streamManager->findStream(stream2Id);
|
|
auto buf = buildRandomInputData(20);
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream2, buf->clone(), true).hasError());
|
|
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
|
|
// One packet in outstandings.
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
|
|
// Lose the packet.
|
|
auto& packet =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet, false).hasError());
|
|
|
|
// The data from the lost packet should not be transferred to the loss buffer
|
|
// of stream1, but should be transferred to the loss buffer of stream2.
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 0);
|
|
EXPECT_EQ(stream2->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream2->lossBuffer.size(), 1);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicLossFunctionsTest,
|
|
TestMarkPacketLossRetransmissionPolicyTwoGroupsTwoPackets) {
|
|
folly::EventBase evb;
|
|
auto qEvb = std::make_shared<FollyQuicEventBase>(&evb);
|
|
MockAsyncUDPSocket socket(qEvb);
|
|
ON_CALL(socket, getGSO).WillByDefault(testing::Return(0));
|
|
auto conn = createConn();
|
|
|
|
conn->transportSettings.advertisedMaxStreamGroups = 16;
|
|
|
|
const auto groupId1 =
|
|
conn->streamManager->createNextBidirectionalStreamGroup();
|
|
const auto groupId2 =
|
|
conn->streamManager->createNextBidirectionalStreamGroup();
|
|
|
|
QuicStreamGroupRetransmissionPolicy policy1;
|
|
policy1.disableRetransmission = true;
|
|
conn->retransmissionPolicies.emplace(*groupId1, policy1);
|
|
|
|
QuicStreamGroupRetransmissionPolicy policy2;
|
|
policy2.disableRetransmission = false;
|
|
conn->retransmissionPolicies.emplace(*groupId2, policy2);
|
|
|
|
// Generate packet 1.
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream(*groupId1).value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
auto buf = buildRandomInputData(20);
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
|
|
// Generate packet 2.
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
auto stream2Id =
|
|
conn->streamManager->createNextBidirectionalStream(*groupId2).value()->id;
|
|
auto stream2 = conn->streamManager->findStream(stream2Id);
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream2, buf->clone(), true).hasError());
|
|
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
|
|
// Two packets in outstandings.
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
|
|
// Lose the first packet.
|
|
auto& packet =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet, false).hasError());
|
|
// Lose the second packet.
|
|
auto& packet2 =
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
ASSERT_FALSE(markPacketLoss(*conn, packet2, false).hasError());
|
|
|
|
// The data from the lost packet should not be transferred to the loss buffer
|
|
// of stream1, but should be transferred to the loss buffer of stream2.
|
|
stream1 = conn->streamManager->findStream(stream1Id);
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 0);
|
|
stream2 = conn->streamManager->findStream(stream2Id);
|
|
EXPECT_EQ(stream2->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream2->lossBuffer.size(), 1);
|
|
}
|
|
|
|
} // namespace quic::test
|