mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-09 20:42:44 +03:00
Summary: Giving more access to the current state of the connection by exposing the socket. Pull Request resolved: https://github.com/facebookincubator/mvfst/pull/185 Reviewed By: yangchi Differential Revision: D23924861 Pulled By: bschlinker fbshipit-source-id: 16866a93e58b6544e25d474a51ca17fab9ffdc55
2104 lines
76 KiB
C++
2104 lines
76 KiB
C++
/*
|
|
* Copyright (c) Facebook, Inc. and its 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 <folly/io/async/test/MockAsyncUDPSocket.h>
|
|
#include <folly/io/async/test/MockTimeoutManager.h>
|
|
#include <quic/api/QuicTransportFunctions.h>
|
|
#include <quic/api/test/Mocks.h>
|
|
#include <quic/client/state/ClientStateMachine.h>
|
|
#include <quic/codec/DefaultConnectionIdAlgo.h>
|
|
#include <quic/common/test/TestUtils.h>
|
|
#include <quic/d6d/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 quic {
|
|
namespace test {
|
|
|
|
namespace {
|
|
using D6DMachineState = QuicConnectionStateBase::D6DMachineState;
|
|
} // namespace
|
|
|
|
class MockLossTimeout {
|
|
public:
|
|
MOCK_METHOD0(cancelLossTimeout, void());
|
|
MOCK_METHOD1(scheduleLossTimeout, void(std::chrono::milliseconds));
|
|
MOCK_METHOD0(isLossTimeoutScheduled, bool());
|
|
};
|
|
|
|
enum class PacketType {
|
|
Initial,
|
|
Handshake,
|
|
ZeroRtt,
|
|
OneRtt,
|
|
};
|
|
|
|
struct PMTUBlackholeDetectionTestFixture; // Forward declaration
|
|
|
|
class QuicLossFunctionsTest : public TestWithParam<PacketNumberSpace> {
|
|
public:
|
|
void SetUp() override {
|
|
aead = createNoOpAead();
|
|
headerCipher = createNoOpHeaderCipher();
|
|
transportInfoCb_ = std::make_unique<MockQuicStats>();
|
|
connIdAlgo_ = std::make_unique<DefaultConnectionIdAlgo>();
|
|
}
|
|
|
|
PacketNum sendPacket(
|
|
QuicConnectionStateBase& conn,
|
|
TimePoint time,
|
|
folly::Optional<PacketEvent> associatedEvent,
|
|
PacketType packetType,
|
|
bool isD6DProbe = false,
|
|
folly::Optional<uint16_t> forcedSize = folly::none);
|
|
|
|
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.handshakeAckState.nextPacketNum = 1;
|
|
conn->ackStates.appDataAckState.nextPacketNum = 1;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiLocal =
|
|
kDefaultStreamWindowSize;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote =
|
|
kDefaultStreamWindowSize;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetUni =
|
|
kDefaultStreamWindowSize;
|
|
conn->flowControlState.peerAdvertisedMaxOffset =
|
|
kDefaultConnectionWindowSize;
|
|
conn->streamManager->setMaxLocalBidirectionalStreams(
|
|
kDefaultMaxStreamsBidirectional);
|
|
conn->streamManager->setMaxLocalUnidirectionalStreams(
|
|
kDefaultMaxStreamsUnidirectional);
|
|
conn->statsCallback = transportInfoCb_.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();
|
|
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 =
|
|
kDefaultStreamWindowSize;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote =
|
|
kDefaultStreamWindowSize;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetUni =
|
|
kDefaultStreamWindowSize;
|
|
conn->flowControlState.peerAdvertisedMaxOffset =
|
|
kDefaultConnectionWindowSize;
|
|
conn->statsCallback = transportInfoCb_.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;
|
|
}
|
|
|
|
void runDetectPMTUBlackholeTest(
|
|
QuicConnectionStateBase* conn,
|
|
PMTUBlackholeDetectionTestFixture fixture);
|
|
|
|
EventBase evb;
|
|
std::unique_ptr<Aead> aead;
|
|
std::unique_ptr<PacketNumberCipher> headerCipher;
|
|
MockLossTimeout timeout;
|
|
std::unique_ptr<MockQuicStats> transportInfoCb_;
|
|
std::unique_ptr<ConnectionIdAlgo> connIdAlgo_;
|
|
|
|
auto getLossPacketMatcher(bool lossByReorder, bool lossByTimeout) {
|
|
return MockInstrumentationObserver::getLossPacketMatcher(
|
|
lossByReorder, lossByTimeout);
|
|
}
|
|
};
|
|
|
|
auto testingLossMarkFunc(std::vector<PacketNum>& lostPackets) {
|
|
return [&lostPackets](auto& /* conn */, auto& packet, bool processed) {
|
|
if (!processed) {
|
|
auto packetNum = packet.header.getPacketSequenceNum();
|
|
lostPackets.push_back(packetNum);
|
|
}
|
|
};
|
|
}
|
|
|
|
PacketNum QuicLossFunctionsTest::sendPacket(
|
|
QuicConnectionStateBase& conn,
|
|
TimePoint time,
|
|
folly::Optional<PacketEvent> associatedEvent,
|
|
PacketType packetType,
|
|
bool isD6DProbe,
|
|
folly::Optional<uint16_t> forcedSize) {
|
|
folly::Optional<PacketHeader> header;
|
|
bool isHandshake = false;
|
|
switch (packetType) {
|
|
case PacketType::Initial:
|
|
header = LongHeader(
|
|
LongHeader::Types::Initial,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
conn.ackStates.initialAckState.nextPacketNum,
|
|
*conn.version);
|
|
conn.outstandings.initialPacketsCount++;
|
|
isHandshake = true;
|
|
break;
|
|
case PacketType::Handshake:
|
|
header = LongHeader(
|
|
LongHeader::Types::Handshake,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
conn.ackStates.handshakeAckState.nextPacketNum,
|
|
*conn.version);
|
|
conn.outstandings.handshakePacketsCount++;
|
|
isHandshake = true;
|
|
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));
|
|
builder.encodePacketHeader();
|
|
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;
|
|
if (packet.header) {
|
|
encodedSize += packet.header->computeChainDataLength();
|
|
}
|
|
if (packet.body) {
|
|
encodedSize += packet.body->computeChainDataLength();
|
|
}
|
|
auto outstandingPacket = OutstandingPacket(
|
|
packet.packet,
|
|
time,
|
|
encodedSize,
|
|
isHandshake,
|
|
isD6DProbe,
|
|
encodedSize,
|
|
0);
|
|
outstandingPacket.associatedEvent = associatedEvent;
|
|
if (isHandshake) {
|
|
conn.lossState.lastHandshakePacketSentTime = time;
|
|
}
|
|
conn.lossState.lastRetransmittablePacketSentTime = time;
|
|
if (conn.congestionController) {
|
|
conn.congestionController->onPacketSent(outstandingPacket);
|
|
}
|
|
if (associatedEvent) {
|
|
conn.outstandings.clonedPacketsCount++;
|
|
// Simulates what the real writer does.
|
|
auto it = std::find_if(
|
|
conn.outstandings.packets.begin(),
|
|
conn.outstandings.packets.end(),
|
|
[&associatedEvent](const auto& packet) {
|
|
auto packetNum = packet.packet.header.getPacketSequenceNum();
|
|
auto packetNumSpace = packet.packet.header.getPacketNumberSpace();
|
|
return packetNum == associatedEvent->packetNumber &&
|
|
packetNumSpace == associatedEvent->packetNumberSpace;
|
|
});
|
|
if (it != conn.outstandings.packets.end()) {
|
|
if (!it->associatedEvent) {
|
|
conn.outstandings.packetEvents.emplace(*associatedEvent);
|
|
conn.outstandings.clonedPacketsCount++;
|
|
it->associatedEvent = *associatedEvent;
|
|
}
|
|
}
|
|
}
|
|
conn.outstandings.packets.emplace_back(std::move(outstandingPacket));
|
|
conn.lossState.largestSent = getNextPacketNum(conn, packetNumberSpace);
|
|
increaseNextPacketNum(conn, packetNumberSpace);
|
|
conn.pendingEvents.setLossDetectionAlarm = true;
|
|
if (isD6DProbe) {
|
|
++conn.d6d.outstandingProbes;
|
|
conn.d6d.lastProbe = QuicConnectionStateBase::D6DProbePacket(
|
|
encodedSize, conn.lossState.largestSent.value());
|
|
}
|
|
return conn.lossState.largestSent.value();
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, AllPacketsProcessed) {
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*transportInfoCb_, onPTO()).Times(0);
|
|
PacketEvent packetEvent1(
|
|
PacketNumberSpace::AppData,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
sendPacket(*conn, Clock::now(), packetEvent1, PacketType::OneRtt);
|
|
PacketEvent packetEvent2(
|
|
PacketNumberSpace::AppData,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
sendPacket(*conn, Clock::now(), packetEvent2, PacketType::OneRtt);
|
|
PacketEvent packetEvent3(
|
|
PacketNumberSpace::AppData,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
sendPacket(*conn, Clock::now(), packetEvent3, 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(), folly::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, D6DProbeDoesNotSetLossDetectionAlarms) {
|
|
auto conn = createConn();
|
|
sendPacket(*conn, Clock::now(), folly::none, PacketType::OneRtt, true);
|
|
conn->pendingEvents.setLossDetectionAlarm = true;
|
|
EXPECT_CALL(timeout, cancelLossTimeout()).Times(1);
|
|
EXPECT_CALL(timeout, scheduleLossTimeout(_)).Times(0);
|
|
setLossDetectionAlarm(*conn, timeout);
|
|
EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, D6DProbeExcludedInLossEvent) {
|
|
auto conn = createConn();
|
|
// 0 rtt means all packets are automatically lost upon sending
|
|
conn->lossState.srtt = 0s;
|
|
conn->lossState.lrtt = 0s;
|
|
auto currentTime = Clock::now();
|
|
auto firstPacketNum =
|
|
sendPacket(*conn, currentTime, folly::none, PacketType::OneRtt, true);
|
|
auto secondPacketNum =
|
|
sendPacket(*conn, currentTime, folly::none, PacketType::OneRtt, true);
|
|
ASSERT_GT(secondPacketNum, firstPacketNum);
|
|
ASSERT_EQ(2, conn->outstandings.packets.size());
|
|
auto lossVisitor = [](auto&, auto&, bool) { ASSERT_FALSE(true); };
|
|
auto lossEvent = detectLossPackets(
|
|
*conn,
|
|
secondPacketNum,
|
|
lossVisitor,
|
|
Clock::now(),
|
|
PacketNumberSpace::AppData);
|
|
// We should not set loss timer for un-acked d6d probes
|
|
ASSERT_FALSE(earliestLossTimer(*conn).first.has_value());
|
|
ASSERT_FALSE(lossEvent);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, NonExclusiveD6DProbeLossIncludedInLossEvent) {
|
|
auto conn = createConn();
|
|
// 0 rtt means all packets are automatically lost upon sending
|
|
conn->lossState.srtt = 0s;
|
|
conn->lossState.lrtt = 0s;
|
|
conn->d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
|
|
auto currentTime = Clock::now();
|
|
auto firstPacketNum =
|
|
sendPacket(*conn, currentTime, folly::none, PacketType::OneRtt, true);
|
|
auto secondPacketNum =
|
|
sendPacket(*conn, currentTime, folly::none, PacketType::OneRtt, false);
|
|
ASSERT_GT(secondPacketNum, firstPacketNum);
|
|
ASSERT_EQ(2, conn->outstandings.packets.size());
|
|
auto lossVisitor = [&](auto&, auto& packet, bool) {
|
|
EXPECT_EQ(packet.header.getPacketSequenceNum(), secondPacketNum);
|
|
};
|
|
auto lossEvent = detectLossPackets(
|
|
*conn,
|
|
secondPacketNum + 1,
|
|
lossVisitor,
|
|
Clock::now(),
|
|
PacketNumberSpace::AppData);
|
|
// We should not set loss timer for un-acked d6d probes
|
|
ASSERT_TRUE(lossEvent.hasValue());
|
|
ASSERT_FALSE(earliestLossTimer(*conn).first.has_value());
|
|
ASSERT_EQ(lossEvent->lostPackets, 1);
|
|
}
|
|
|
|
struct PMTUBlackholeDetectionTestFixture {
|
|
uint32_t beginPMTU;
|
|
uint32_t endPMTU;
|
|
size_t threshold;
|
|
bool hasLossEvent;
|
|
uint64_t lostBytes;
|
|
uint32_t lostPackets;
|
|
std::chrono::seconds window;
|
|
PacketNum largestAckedPacketNum;
|
|
std::chrono::microseconds detectLossTimeDelta;
|
|
std::chrono::microseconds largestLostTimeDelta;
|
|
std::chrono::microseconds smallestLostTimeDelta;
|
|
QuicConnectionStateBase::D6DMachineState beginState;
|
|
QuicConnectionStateBase::D6DMachineState endState;
|
|
|
|
struct PacketSpec {
|
|
std::chrono::microseconds timeDelta;
|
|
bool isD6DProbe;
|
|
uint32_t encodedSize;
|
|
};
|
|
std::vector<PacketSpec> packetSpecs;
|
|
};
|
|
|
|
void QuicLossFunctionsTest::runDetectPMTUBlackholeTest(
|
|
QuicConnectionStateBase* conn,
|
|
PMTUBlackholeDetectionTestFixture fixture) {
|
|
// 0 rtt means all packets are automatically lost upon sending
|
|
conn->lossState.srtt = 0s;
|
|
conn->lossState.lrtt = 0s;
|
|
conn->d6d.state = QuicConnectionStateBase::D6DMachineState::BASE;
|
|
conn->d6d.currentProbeSize = fixture.beginPMTU;
|
|
conn->udpSendPacketLen = fixture.beginPMTU;
|
|
conn->d6d.thresholdCounter =
|
|
std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
|
|
std::chrono::microseconds(fixture.window).count(), fixture.threshold);
|
|
conn->d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
|
|
conn->d6d.state = fixture.beginState;
|
|
auto currentTime = Clock::now();
|
|
for (auto& spec : fixture.packetSpecs) {
|
|
sendPacket(
|
|
*conn,
|
|
currentTime + spec.timeDelta,
|
|
folly::none,
|
|
PacketType::OneRtt,
|
|
spec.isD6DProbe,
|
|
spec.encodedSize);
|
|
}
|
|
auto lossVisitor = [](auto&, auto&, bool) {};
|
|
ASSERT_EQ(conn->outstandings.packets.size(), fixture.packetSpecs.size());
|
|
auto lossEvent = detectLossPackets(
|
|
*conn,
|
|
fixture.largestAckedPacketNum,
|
|
lossVisitor,
|
|
currentTime + fixture.detectLossTimeDelta,
|
|
PacketNumberSpace::AppData);
|
|
EXPECT_EQ(lossEvent.has_value(), fixture.hasLossEvent);
|
|
if (fixture.hasLossEvent) {
|
|
EXPECT_EQ(lossEvent->lostPackets, fixture.lostPackets);
|
|
EXPECT_EQ(lossEvent->lostBytes, fixture.lostBytes);
|
|
EXPECT_EQ(
|
|
lossEvent->largestLostSentTime.value(),
|
|
currentTime + fixture.largestLostTimeDelta);
|
|
EXPECT_EQ(
|
|
lossEvent->smallestLostSentTime.value(),
|
|
currentTime + fixture.smallestLostTimeDelta);
|
|
EXPECT_EQ(conn->d6d.state, fixture.endState);
|
|
EXPECT_EQ(conn->d6d.currentProbeSize, fixture.endPMTU);
|
|
EXPECT_EQ(conn->udpSendPacketLen, fixture.endPMTU);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, DetectPMTUBlackholeAllNonProbes) {
|
|
// clang-format off
|
|
PMTUBlackholeDetectionTestFixture fixture {
|
|
1400, // beginPMTU
|
|
kDefaultUDPSendPacketLen, // endPMTU
|
|
3, // threshold
|
|
true, // hasLossEvent
|
|
1400 * 3, // lostBytes
|
|
3, // lostPackets
|
|
10s, // window
|
|
3 + kReorderingThreshold, // largestAckedPacketNum
|
|
11s, // detectLossTimeDelta
|
|
10s, // largestLostTimeDelta
|
|
0s, // smallestLostTimeDelta
|
|
D6DMachineState::SEARCH_COMPLETE, // beginState
|
|
D6DMachineState::BASE, // beginState
|
|
{ // packetSpecs
|
|
{0s, false, 1400},
|
|
{1s, false, 1400},
|
|
{10s, false, 1400},
|
|
}
|
|
};
|
|
// clang-format on
|
|
auto conn = createConn();
|
|
runDetectPMTUBlackholeTest(conn.get(), fixture);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, DetectPMTUBlackholeAllProbes) {
|
|
// clang-format off
|
|
PMTUBlackholeDetectionTestFixture fixture {
|
|
1400, // beginPMTU
|
|
1400, // endPMTU
|
|
3, // threshold
|
|
false, // hasLossEvent
|
|
0, // lostBytes
|
|
0, // lostPackets
|
|
10s, // window
|
|
3 + kReorderingThreshold, // largestAckedPacketNum
|
|
11s, // detectLossTimeDelta
|
|
10s, // largestLostTimeDelta
|
|
0s, // smallestLostTimeDelta
|
|
D6DMachineState::SEARCH_COMPLETE, // beginState
|
|
D6DMachineState::SEARCH_COMPLETE, // beginState
|
|
{ // packetSpecs
|
|
{0s, true, 1400},
|
|
{1s, true, 1400},
|
|
{10s, true, 1400},
|
|
}
|
|
};
|
|
// clang-format on
|
|
auto conn = createConn();
|
|
runDetectPMTUBlackholeTest(conn.get(), fixture);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, DetectPMTUBlackholeSparseProbes) {
|
|
// clang-format off
|
|
PMTUBlackholeDetectionTestFixture fixture {
|
|
1400, // beginPMTU
|
|
1400, // endPMTU
|
|
3, // threshold
|
|
true, // hasLossEvent
|
|
1400 * 5 + 200, // lostBytes
|
|
6, // lostPackets
|
|
10s, // window
|
|
6 + kReorderingThreshold, // largestAckedPacketNum
|
|
23s, // detectLossTimeDelta
|
|
22s, // largestLostTimeDelta
|
|
0s, // smallestLostTimeDelta
|
|
D6DMachineState::SEARCH_COMPLETE, // beginState
|
|
D6DMachineState::SEARCH_COMPLETE, // beginState
|
|
{ // packetSpecs
|
|
{0s, false, 1400},
|
|
{1s, false, 1400},
|
|
{11s, false, 1400},
|
|
{10s, false, 200},
|
|
{12s, false, 1400},
|
|
{22s, false, 1400}
|
|
}
|
|
};
|
|
// clang-format on
|
|
auto conn = createConn();
|
|
runDetectPMTUBlackholeTest(conn.get(), fixture);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, DetectPMTUBlackholeRandomData) {
|
|
// clang-format off
|
|
PMTUBlackholeDetectionTestFixture fixture {
|
|
1400, // beginPMTU
|
|
1400, // endPMTU
|
|
3, // threshold
|
|
true, // hasLossEvent
|
|
876 + 249 + 1291 + 1400 * 4 + 1109, // lostBytes
|
|
8, // lostPackets
|
|
10s, // window
|
|
9 + kReorderingThreshold, // largestAckedPacketNum
|
|
90s, // detectLossTimeDelta
|
|
83s, // largestLostTimeDelta
|
|
5s, // smallestLostTimeDelta
|
|
D6DMachineState::SEARCHING, // beginState
|
|
D6DMachineState::SEARCHING, // beginState
|
|
{ // packetSpecs
|
|
{ 5s, false, 876 },
|
|
{ 15s, false, 249 },
|
|
{ 19s, false, 1291 },
|
|
{ 31s, false, 1400 },
|
|
{ 40s, true, 1400 },
|
|
{ 51s, false, 1400 },
|
|
{ 61s, false, 1400 },
|
|
{ 73s, false, 1400 },
|
|
{ 83s, false, 1109 },
|
|
{ 85s, true, 1400 }
|
|
}
|
|
};
|
|
// clang-format on
|
|
auto conn = createConn();
|
|
runDetectPMTUBlackholeTest(conn.get(), fixture);
|
|
}
|
|
|
|
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, folly::none, PacketType::Initial);
|
|
auto secondPacketNum =
|
|
sendPacket(*conn, currentTime, folly::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) { ASSERT_FALSE(true); };
|
|
detectLossPackets(
|
|
*conn,
|
|
secondPacketNum,
|
|
lossVisitor,
|
|
Clock::now(),
|
|
PacketNumberSpace::Initial);
|
|
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);
|
|
processAckFrame(
|
|
*conn,
|
|
PacketNumberSpace::Initial,
|
|
ackFrame,
|
|
[&](auto&, auto&, auto&) {},
|
|
lossVisitor,
|
|
Clock::now());
|
|
|
|
// Send out a AppData packet that isn't retransmittable
|
|
sendPacket(*conn, Clock::now(), folly::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(), folly::none, PacketType::OneRtt);
|
|
MockClock::mockNow = []() { return TimePoint(123ms); };
|
|
std::vector<PacketNum> lostPacket;
|
|
MockClock::mockNow = []() { return TimePoint(23ms); };
|
|
EXPECT_CALL(*transportInfoCb_, onPTO());
|
|
setLossDetectionAlarm<decltype(timeout), MockClock>(*conn, timeout);
|
|
EXPECT_EQ(LossState::AlarmMethod::PTO, conn->lossState.currentAlarmMethod);
|
|
onLossDetectionAlarm<decltype(testingLossMarkFunc(lostPacket)), MockClock>(
|
|
*conn, testingLossMarkFunc(lostPacket));
|
|
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(*transportInfoCb_, onPTO());
|
|
sendPacket(*conn, TimePoint(), folly::none, PacketType::OneRtt);
|
|
setLossDetectionAlarm<decltype(timeout), MockClock>(*conn, timeout);
|
|
EXPECT_CALL(*rawCongestionController, onPacketAckOrLoss(_, _)).Times(0);
|
|
onLossDetectionAlarm<decltype(testingLossMarkFunc(lostPacket)), MockClock>(
|
|
*conn, testingLossMarkFunc(lostPacket));
|
|
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 associatedEvent that doesn't exist in the
|
|
// outstandings.packetEvents, they are all processed and will skip lossVisitor
|
|
for (auto i = 0; i < 10; i++) {
|
|
PacketEvent packetEvent(PacketNumberSpace::AppData, i);
|
|
sendPacket(*conn, TimePoint(), packetEvent, PacketType::OneRtt);
|
|
}
|
|
EXPECT_EQ(10, conn->outstandings.packets.size());
|
|
std::vector<PacketNum> lostPackets;
|
|
EXPECT_CALL(*rawCongestionController, onRemoveBytesFromInflight(_)).Times(0);
|
|
EXPECT_CALL(*transportInfoCb_, onPTO());
|
|
onPTOAlarm(*conn);
|
|
EXPECT_EQ(10, conn->outstandings.packets.size());
|
|
EXPECT_TRUE(lostPackets.empty());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLoss) {
|
|
folly::EventBase evb;
|
|
MockAsyncUDPSocket socket(&evb);
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*transportInfoCb_, 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);
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
writeDataToQuicStream(*stream2, buf->clone(), true);
|
|
|
|
auto packetSeqNum = conn->ackStates.handshakeAckState.nextPacketNum;
|
|
LongHeader header(
|
|
LongHeader::Types::Handshake,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
packetSeqNum,
|
|
*conn->version);
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
auto& packet =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
markPacketLoss(*conn, packet, false);
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream2->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 1);
|
|
EXPECT_EQ(stream2->lossBuffer.size(), 1);
|
|
|
|
auto& buffer = stream1->lossBuffer.front();
|
|
EXPECT_EQ(buffer.offset, 0);
|
|
IOBufEqualTo eq;
|
|
EXPECT_TRUE(eq(buf, buffer.data.move()));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLossMerge) {
|
|
folly::EventBase evb;
|
|
MockAsyncUDPSocket socket(&evb);
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*transportInfoCb_, onNewQuicStream()).Times(1);
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
|
|
auto buf1 = buildRandomInputData(20);
|
|
writeDataToQuicStream(*stream1, buf1->clone(), false);
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
|
|
auto buf2 = buildRandomInputData(20);
|
|
writeDataToQuicStream(*stream1, buf2->clone(), false);
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
|
|
auto& packet1 =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
auto packetNum = packet1.header.getPacketSequenceNum();
|
|
markPacketLoss(*conn, packet1, false);
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 1);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 1);
|
|
auto& packet2 =
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
packetNum = packet2.header.getPacketSequenceNum();
|
|
markPacketLoss(*conn, packet2, false);
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 1);
|
|
|
|
auto combined = buf1->clone();
|
|
combined->prependChain(buf2->clone());
|
|
auto& buffer = stream1->lossBuffer.front();
|
|
EXPECT_EQ(buffer.offset, 0);
|
|
IOBufEqualTo eq;
|
|
EXPECT_TRUE(eq(combined, buffer.data.move()));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLossNoMerge) {
|
|
folly::EventBase evb;
|
|
MockAsyncUDPSocket socket(&evb);
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*transportInfoCb_, onNewQuicStream()).Times(1);
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
|
|
auto buf1 = buildRandomInputData(20);
|
|
writeDataToQuicStream(*stream1, buf1->clone(), false);
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
|
|
auto buf2 = buildRandomInputData(20);
|
|
writeDataToQuicStream(*stream1, buf2->clone(), false);
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
|
|
auto buf3 = buildRandomInputData(20);
|
|
writeDataToQuicStream(*stream1, buf3->clone(), false);
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(3, conn->outstandings.packets.size());
|
|
|
|
auto& packet1 =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
auto packetNum = packet1.header.getPacketSequenceNum();
|
|
markPacketLoss(*conn, packet1, false);
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 2);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 1);
|
|
auto& packet3 =
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
packetNum = packet3.header.getPacketSequenceNum();
|
|
markPacketLoss(*conn, packet3, false);
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 1);
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 2);
|
|
|
|
auto& buffer1 = stream1->lossBuffer[0];
|
|
EXPECT_EQ(buffer1.offset, 0);
|
|
IOBufEqualTo eq;
|
|
EXPECT_TRUE(eq(buf1, buffer1.data.move()));
|
|
|
|
auto& buffer3 = stream1->lossBuffer[1];
|
|
EXPECT_EQ(buffer3.offset, 40);
|
|
EXPECT_TRUE(eq(buf3, buffer3.data.move()));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, RetxBufferSortedAfterLoss) {
|
|
folly::EventBase evb;
|
|
MockAsyncUDPSocket socket(&evb);
|
|
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];
|
|
markPacketLoss(*conn, packet.packet, false);
|
|
EXPECT_EQ(2, stream->retransmissionBuffer.size());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkCryptoLostAfterCancelRetransmission) {
|
|
folly::EventBase evb;
|
|
MockAsyncUDPSocket socket(&evb);
|
|
auto conn = createConn();
|
|
|
|
auto packetSeqNum = conn->ackStates.handshakeAckState.nextPacketNum;
|
|
LongHeader header(
|
|
LongHeader::Types::Handshake,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
packetSeqNum,
|
|
*conn->version);
|
|
writeDataToQuicStream(
|
|
conn->cryptoState->handshakeStream, folly::IOBuf::copyBuffer("CFIN"));
|
|
writeCryptoAndAckDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
LongHeader::Types::Handshake,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
ASSERT_EQ(conn->outstandings.packets.size(), 1);
|
|
EXPECT_GT(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
|
auto& packet = conn->outstandings.packets.front().packet;
|
|
cancelHandshakeCryptoStreamRetransmissions(*conn->cryptoState);
|
|
markPacketLoss(*conn, packet, false);
|
|
EXPECT_EQ(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(conn->cryptoState->handshakeStream.lossBuffer.size(), 0);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkCryptoLostCancel) {
|
|
folly::EventBase evb;
|
|
MockAsyncUDPSocket socket(&evb);
|
|
auto conn = createConn();
|
|
|
|
auto packetSeqNum = conn->ackStates.handshakeAckState.nextPacketNum;
|
|
LongHeader header(
|
|
LongHeader::Types::Handshake,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
packetSeqNum,
|
|
*conn->version);
|
|
writeDataToQuicStream(
|
|
conn->cryptoState->handshakeStream, folly::IOBuf::copyBuffer("CFIN"));
|
|
writeCryptoAndAckDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
LongHeader::Types::Handshake,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
ASSERT_EQ(conn->outstandings.packets.size(), 1);
|
|
EXPECT_GT(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
|
auto& packet = conn->outstandings.packets.front().packet;
|
|
markPacketLoss(*conn, packet, false);
|
|
EXPECT_EQ(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(conn->cryptoState->handshakeStream.lossBuffer.size(), 1);
|
|
cancelHandshakeCryptoStreamRetransmissions(*conn->cryptoState);
|
|
EXPECT_EQ(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(conn->cryptoState->handshakeStream.lossBuffer.size(), 0);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLossAfterStreamReset) {
|
|
folly::EventBase evb;
|
|
MockAsyncUDPSocket socket(&evb);
|
|
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);
|
|
sendRstSMHandler(*stream1, GenericApplicationErrorCode::UNKNOWN);
|
|
|
|
markPacketLoss(*conn, packet, false);
|
|
|
|
EXPECT_TRUE(stream1->lossBuffer.empty());
|
|
EXPECT_TRUE(stream1->retransmissionBuffer.empty());
|
|
EXPECT_TRUE(stream1->writeBuffer.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);
|
|
};
|
|
for (int i = 0; i < 6; ++i) {
|
|
sendPacket(*conn, Clock::now(), folly::none, PacketType::Handshake);
|
|
}
|
|
EXPECT_EQ(6, conn->outstandings.handshakePacketsCount);
|
|
// Assume some packets are already acked
|
|
for (auto iter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake) + 2;
|
|
iter <
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake) + 5;
|
|
iter++) {
|
|
if (iter->metadata.isHandshake) {
|
|
conn->outstandings.handshakePacketsCount--;
|
|
}
|
|
}
|
|
auto firstHandshakeOpIter =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake);
|
|
conn->outstandings.packets.erase(
|
|
firstHandshakeOpIter + 2, firstHandshakeOpIter + 5);
|
|
// Ack for packet 9 arrives
|
|
auto lossEvent = detectLossPackets<decltype(testingLossMarkFunc)>(
|
|
*conn,
|
|
9,
|
|
testingLossMarkFunc,
|
|
TimePoint(90ms),
|
|
PacketNumberSpace::Handshake);
|
|
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.handshakePacketsCount);
|
|
|
|
// 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(
|
|
OutstandingPacket(outstandingRegularPacket, now, 0, false, 0, 0));
|
|
|
|
bool testLossMarkFuncCalled = false;
|
|
auto testLossMarkFunc = [&](auto& /* conn */, auto&, bool) {
|
|
testLossMarkFuncCalled = true;
|
|
};
|
|
EXPECT_CALL(*mockQLogger, addPacketsLost(1, 0, 1));
|
|
|
|
CongestionController::AckEvent ackEvent;
|
|
ackEvent.ackTime = now;
|
|
ackEvent.largestAckedPacket = 1000;
|
|
handleAckForLoss(
|
|
*conn, testLossMarkFunc, ackEvent, PacketNumberSpace::Handshake);
|
|
|
|
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(), folly::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;
|
|
};
|
|
|
|
auto ackVisitor = [&](auto&, auto&, auto&) {};
|
|
|
|
// process and remove the acked packet.
|
|
processAckFrame(
|
|
*conn,
|
|
PacketNumberSpace::AppData,
|
|
ackFrame,
|
|
ackVisitor,
|
|
testLossMarkFunc,
|
|
Clock::now());
|
|
|
|
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;
|
|
MockAsyncUDPSocket socket(&evb);
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto currentOffset = stream->currentWriteOffset;
|
|
RstStreamFrame rstFrame(
|
|
stream->id, GenericApplicationErrorCode::UNKNOWN, currentOffset);
|
|
conn->pendingEvents.resets.insert({stream->id, rstFrame});
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
|
|
EXPECT_EQ(conn->outstandings.packets.size(), 1);
|
|
EXPECT_TRUE(conn->pendingEvents.resets.empty());
|
|
auto& packet =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
markPacketLoss(*conn, packet, false);
|
|
|
|
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.offset);
|
|
|
|
// write again:
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
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->offset);
|
|
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++;
|
|
}
|
|
};
|
|
PacketNum latestSent = 0;
|
|
for (size_t i = 0; i < conn->lossState.reorderingThreshold + 1; i++) {
|
|
latestSent =
|
|
sendPacket(*conn, Clock::now(), folly::none, PacketType::Handshake);
|
|
}
|
|
|
|
detectLossPackets(
|
|
*conn,
|
|
latestSent + 1,
|
|
countingLossVisitor,
|
|
Clock::now(),
|
|
PacketNumberSpace::AppData);
|
|
EXPECT_EQ(0, lossVisitorCount);
|
|
|
|
detectLossPackets(
|
|
*conn,
|
|
latestSent + 1,
|
|
countingLossVisitor,
|
|
Clock::now(),
|
|
PacketNumberSpace::Handshake);
|
|
EXPECT_GT(lossVisitorCount, 0);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkWindowUpdateLoss) {
|
|
auto conn = createConn();
|
|
folly::EventBase evb;
|
|
MockAsyncUDPSocket socket(&evb);
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
conn->streamManager->queueWindowUpdate(stream->id);
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
*conn->version,
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_FALSE(conn->streamManager->hasWindowUpdates());
|
|
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
auto& packet =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet;
|
|
|
|
markPacketLoss(*conn, packet, false);
|
|
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), folly::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 lossEvent = detectLossPackets<decltype(testingLossMarkFunc(lostPacket))>(
|
|
*conn,
|
|
largestSent,
|
|
testingLossMarkFunc(lostPacket),
|
|
TimePoint(900ms),
|
|
PacketNumberSpace::AppData);
|
|
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, folly::none, PacketType::Handshake);
|
|
PacketNum second =
|
|
sendPacket(*conn, sendTime + 1ms, folly::none, PacketType::Handshake);
|
|
auto lossTime = sendTime + 50ms;
|
|
detectLossPackets<decltype(testingLossMarkFunc(lostPackets))>(
|
|
*conn,
|
|
second,
|
|
testingLossMarkFunc(lostPackets),
|
|
lossTime,
|
|
PacketNumberSpace::Handshake);
|
|
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 requries 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; };
|
|
onLossDetectionAlarm<decltype(testingLossMarkFunc(lostPackets)), MockClock>(
|
|
*conn, testingLossMarkFunc(lostPackets));
|
|
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, folly::none, PacketType::OneRtt);
|
|
setLossDetectionAlarm<decltype(timeout), MockClock>(*conn, timeout);
|
|
startTime += 1ms;
|
|
}
|
|
EXPECT_CALL(*rawCongestionController, onPacketAckOrLoss(_, _)).Times(0);
|
|
EXPECT_CALL(*transportInfoCb_, onPTO());
|
|
onLossDetectionAlarm<decltype(testingLossMarkFunc(lostPackets)), MockClock>(
|
|
*conn, testingLossMarkFunc(lostPackets));
|
|
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();
|
|
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),
|
|
folly::none,
|
|
(i % 2 ? PacketType::OneRtt : PacketType::Handshake));
|
|
expectedLargestLostNum = std::max(
|
|
expectedLargestLostNum, i % 2 ? sentPacketNum : expectedLargestLostNum);
|
|
}
|
|
EXPECT_CALL(*transportInfoCb_, onPTO());
|
|
onLossDetectionAlarm<decltype(testingLossMarkFunc(lostPackets)), Clock>(
|
|
*conn, testingLossMarkFunc(lostPackets));
|
|
|
|
EXPECT_EQ(0, lostPackets.size());
|
|
EXPECT_EQ(1, conn->lossState.ptoCount);
|
|
EXPECT_EQ(0, conn->lossState.timeoutBasedRtxCount);
|
|
EXPECT_EQ(conn->pendingEvents.numProbePackets, kPacketToSendForPTO);
|
|
EXPECT_EQ(0, conn->lossState.rtxCount);
|
|
}
|
|
|
|
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, folly::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, folly::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, folly::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++;
|
|
}
|
|
};
|
|
// 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(), folly::none, PacketType::OneRtt);
|
|
}
|
|
detectLossPackets(
|
|
*conn,
|
|
lastSent,
|
|
countingLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::AppData);
|
|
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++;
|
|
}
|
|
};
|
|
// 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;
|
|
PacketEvent packetEvent(PacketNumberSpace::AppData, lastSent);
|
|
sendPacket(*conn, Clock::now(), packetEvent, PacketType::OneRtt);
|
|
}
|
|
detectLossPackets(
|
|
*conn,
|
|
lastSent,
|
|
countingLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::AppData);
|
|
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++;
|
|
}
|
|
};
|
|
// Send 6 packets, so when we ack the last one, we mark the first two loss
|
|
PacketNum lastSent;
|
|
PacketEvent event(PacketNumberSpace::AppData, 0);
|
|
for (size_t i = 0; i < 6; i++) {
|
|
lastSent = sendPacket(*conn, Clock::now(), event, PacketType::OneRtt);
|
|
}
|
|
EXPECT_EQ(6, conn->outstandings.packets.size());
|
|
// Add the PacketEvent to the outstandings.packetEvents set
|
|
conn->outstandings.packetEvents.insert(event);
|
|
|
|
// Ack the last sent packet. Despite two losses, lossVisitor only visit one
|
|
// packet
|
|
detectLossPackets(
|
|
*conn,
|
|
lastSent,
|
|
countingLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::AppData);
|
|
auto numDeclaredLost = std::count_if(
|
|
conn->outstandings.packets.begin(),
|
|
conn->outstandings.packets.end(),
|
|
[](auto& op) { return op.declaredLost; });
|
|
EXPECT_EQ(2, numDeclaredLost);
|
|
EXPECT_EQ(1, lossVisitorCount);
|
|
EXPECT_EQ(4 + numDeclaredLost, conn->outstandings.packets.size());
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, DetectPacketLossClonedPacketsCounter) {
|
|
auto conn = createConn();
|
|
PacketEvent packetEvent1(
|
|
PacketNumberSpace::AppData,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
sendPacket(*conn, Clock::now(), packetEvent1, PacketType::OneRtt);
|
|
sendPacket(*conn, Clock::now(), folly::none, PacketType::OneRtt);
|
|
sendPacket(*conn, Clock::now(), folly::none, PacketType::OneRtt);
|
|
sendPacket(*conn, Clock::now(), folly::none, PacketType::OneRtt);
|
|
auto ackedPacket =
|
|
sendPacket(*conn, Clock::now(), folly::none, PacketType::OneRtt);
|
|
auto noopLossMarker = [](auto&, auto&, bool) {};
|
|
detectLossPackets<decltype(noopLossMarker)>(
|
|
*conn,
|
|
ackedPacket,
|
|
noopLossMarker,
|
|
Clock::now(),
|
|
PacketNumberSpace::AppData);
|
|
EXPECT_EQ(0, conn->outstandings.clonedPacketsCount);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestMarkPacketLossProcessedPacket) {
|
|
MockAsyncUDPSocket socket(&evb);
|
|
auto conn = createConn();
|
|
ASSERT_TRUE(conn->outstandings.packets.empty());
|
|
ASSERT_TRUE(conn->outstandings.packetEvents.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.packetEvents.empty());
|
|
uint32_t streamDataCounter = 0, streamWindowUpdateCounter = 0,
|
|
connWindowUpdateCounter = 0;
|
|
for (const auto& frame :
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->packet.frames) {
|
|
switch (frame.type()) {
|
|
case QuicWriteFrame::Type::WriteStreamFrame_E:
|
|
streamDataCounter++;
|
|
break;
|
|
case QuicWriteFrame::Type::MaxStreamDataFrame_E:
|
|
streamWindowUpdateCounter++;
|
|
break;
|
|
case QuicWriteFrame::Type::MaxDataFrame_E:
|
|
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
|
|
markPacketLoss(*conn, packet, true);
|
|
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(*transportInfoCb_, onPTO());
|
|
onPTOAlarm(*conn);
|
|
EXPECT_EQ(101, conn->lossState.totalPTOCount);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestExceedsMaxPTOThrows) {
|
|
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(*transportInfoCb_, onPTO()).Times(3);
|
|
onPTOAlarm(*conn);
|
|
onPTOAlarm(*conn);
|
|
EXPECT_THROW(onPTOAlarm(*conn), QuicInternalException);
|
|
}
|
|
|
|
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(), folly::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++;
|
|
}
|
|
};
|
|
|
|
conn->lossState.rtxCount = 135;
|
|
detectLossPackets(
|
|
*conn,
|
|
largestSent,
|
|
countingLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::AppData);
|
|
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 associatedEvent that doesn't exist in the
|
|
// outstandings.packetEvents, they are all processed and will skip lossVisitor
|
|
for (auto i = 0; i < 2; i++) {
|
|
sendPacket(*conn, TimePoint(), folly::none, PacketType::OneRtt);
|
|
sendPacket(*conn, TimePoint(), folly::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);
|
|
markZeroRttPacketsLost(*conn, [&lostPackets](auto&, auto&, bool processed) {
|
|
lostPackets.emplace_back(processed);
|
|
});
|
|
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 associatedEvent that doesn't exist in the
|
|
// outstandings.packetEvents, they are all processed and will skip lossVisitor
|
|
std::set<PacketNum> zeroRttPackets;
|
|
folly::Optional<PacketEvent> lastPacketEvent;
|
|
for (auto i = 0; i < 2; i++) {
|
|
auto packetNum =
|
|
sendPacket(*conn, TimePoint(), lastPacketEvent, PacketType::ZeroRtt);
|
|
lastPacketEvent = PacketEvent(PacketNumberSpace::AppData, packetNum);
|
|
zeroRttPackets.emplace(packetNum);
|
|
}
|
|
zeroRttPackets.emplace(
|
|
sendPacket(*conn, TimePoint(), folly::none, PacketType::ZeroRtt));
|
|
for (auto zeroRttPacketNum : zeroRttPackets) {
|
|
PacketEvent zeroRttPacketEvent(
|
|
PacketNumberSpace::AppData, zeroRttPacketNum);
|
|
sendPacket(*conn, TimePoint(), zeroRttPacketEvent, PacketType::OneRtt);
|
|
}
|
|
|
|
EXPECT_EQ(6, conn->outstandings.packets.size());
|
|
ASSERT_EQ(conn->outstandings.clonedPacketsCount, 6);
|
|
ASSERT_EQ(conn->outstandings.packetEvents.size(), 2);
|
|
|
|
std::vector<bool> lostPackets;
|
|
// onRemoveBytesFromInflight should still happen
|
|
EXPECT_CALL(*rawCongestionController, onRemoveBytesFromInflight(_)).Times(1);
|
|
markZeroRttPacketsLost(*conn, [&lostPackets](auto&, auto&, bool processed) {
|
|
lostPackets.emplace_back(processed);
|
|
});
|
|
ASSERT_EQ(conn->outstandings.packetEvents.size(), 0);
|
|
EXPECT_EQ(3, conn->outstandings.packets.size());
|
|
EXPECT_EQ(lostPackets.size(), 3);
|
|
ASSERT_EQ(conn->outstandings.clonedPacketsCount, 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, folly::none, PacketType::OneRtt);
|
|
auto packet2 = sendPacket(
|
|
*conn,
|
|
referenceTime + conn->lossState.srtt / 2,
|
|
folly::none,
|
|
PacketType::OneRtt);
|
|
auto lossVisitor = [&](const auto& /*conn*/, const auto& packet, bool) {
|
|
EXPECT_EQ(packet1, packet.header.getPacketSequenceNum());
|
|
};
|
|
detectLossPackets<decltype(lossVisitor)>(
|
|
*conn,
|
|
packet2,
|
|
lossVisitor,
|
|
referenceTime + conn->lossState.srtt * 9 / 8 + 5ms,
|
|
PacketNumberSpace::AppData);
|
|
}
|
|
|
|
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(), folly::none, PacketType::Initial);
|
|
}
|
|
EXPECT_EQ(10, conn->outstandings.initialPacketsCount);
|
|
auto noopLossVisitor =
|
|
[&](auto& /* conn */, auto& /* packet */, bool /* processed */
|
|
) {};
|
|
detectLossPackets(
|
|
*conn,
|
|
largestSent,
|
|
noopLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::Initial);
|
|
// [1, 6] are removed, [7, 10] are still in OP list
|
|
EXPECT_EQ(4, conn->outstandings.initialPacketsCount);
|
|
}
|
|
|
|
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(), folly::none, PacketType::Handshake);
|
|
}
|
|
EXPECT_EQ(10, conn->outstandings.handshakePacketsCount);
|
|
auto noopLossVisitor =
|
|
[&](auto& /* conn */, auto& /* packet */, bool /* processed */
|
|
) {};
|
|
detectLossPackets(
|
|
*conn,
|
|
largestSent,
|
|
noopLossVisitor,
|
|
TimePoint(100ms),
|
|
PacketNumberSpace::Handshake);
|
|
// [1, 6] are removed, [7, 10] are still in OP list
|
|
EXPECT_EQ(4, conn->outstandings.handshakePacketsCount);
|
|
}
|
|
|
|
TEST_P(QuicLossFunctionsTest, CappedShiftNoCrash) {
|
|
auto conn = createConn();
|
|
conn->outstandings.handshakePacketsCount = 0;
|
|
conn->outstandings.packets.clear();
|
|
conn->lossState.ptoCount =
|
|
std::numeric_limits<decltype(conn->lossState.ptoCount)>::max();
|
|
sendPacket(*conn, Clock::now(), folly::none, PacketType::OneRtt);
|
|
calculateAlarmDuration(*conn);
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, PersistentCongestion) {
|
|
auto conn = createConn();
|
|
auto currentTime = Clock::now();
|
|
conn->lossState.srtt = 1s;
|
|
EXPECT_TRUE(isPersistentCongestion(*conn, currentTime - 10s, currentTime));
|
|
EXPECT_TRUE(isPersistentCongestion(*conn, currentTime - 3s, currentTime));
|
|
EXPECT_TRUE(isPersistentCongestion(
|
|
*conn, currentTime - (1s * kPersistentCongestionThreshold), currentTime));
|
|
EXPECT_FALSE(isPersistentCongestion(
|
|
*conn,
|
|
currentTime - (1s * kPersistentCongestionThreshold) + 1us,
|
|
currentTime));
|
|
EXPECT_FALSE(isPersistentCongestion(*conn, currentTime - 2s, currentTime));
|
|
EXPECT_FALSE(isPersistentCongestion(*conn, currentTime - 100ms, currentTime));
|
|
|
|
conn->lossState.rttvar = 2s;
|
|
conn->lossState.maxAckDelay = 5s;
|
|
EXPECT_TRUE(isPersistentCongestion(*conn, currentTime - 42s, currentTime));
|
|
EXPECT_TRUE(isPersistentCongestion(*conn, currentTime - 43s, currentTime));
|
|
EXPECT_FALSE(
|
|
isPersistentCongestion(*conn, currentTime - 42s + 1ms, currentTime));
|
|
EXPECT_FALSE(isPersistentCongestion(*conn, currentTime - 100us, currentTime));
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestReorderLossObserverCallback) {
|
|
auto ib = MockInstrumentationObserver();
|
|
auto conn = createConn();
|
|
// Register 1 instrumentation observer
|
|
conn->instrumentationObservers_.emplace_back(&ib);
|
|
auto noopLossVisitor = [](auto&, auto&, bool) {};
|
|
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), folly::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);
|
|
|
|
detectLossPackets(
|
|
*conn,
|
|
largestSent + 1,
|
|
noopLossVisitor,
|
|
checkTime,
|
|
PacketNumberSpace::AppData);
|
|
|
|
// expecting 1 callback to be stacked
|
|
EXPECT_EQ(1, size(conn->pendingCallbacks));
|
|
|
|
// 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(
|
|
ib,
|
|
packetLossDetected(
|
|
nullptr,
|
|
Field(
|
|
&InstrumentationObserver::ObserverLossEvent::lostPackets,
|
|
UnorderedElementsAre(
|
|
getLossPacketMatcher(true, false),
|
|
getLossPacketMatcher(true, false),
|
|
getLossPacketMatcher(true, false)))))
|
|
.Times(1);
|
|
|
|
for (auto& callback : conn->pendingCallbacks) {
|
|
callback(nullptr);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestTimeoutLossObserverCallback) {
|
|
auto ib = MockInstrumentationObserver();
|
|
auto conn = createConn();
|
|
// Register 1 instrumentation observer
|
|
conn->instrumentationObservers_.emplace_back(&ib);
|
|
auto noopLossVisitor = [](auto&, auto&, bool) {};
|
|
|
|
PacketNum largestSent = 0;
|
|
|
|
// send 7 packets
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), folly::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);
|
|
|
|
detectLossPackets(
|
|
*conn,
|
|
largestSent + 1,
|
|
noopLossVisitor,
|
|
checkTime,
|
|
PacketNumberSpace::AppData);
|
|
|
|
// expecting 1 callback to be stacked
|
|
EXPECT_EQ(1, size(conn->pendingCallbacks));
|
|
|
|
// expecting all packets to be lost due to timeout
|
|
EXPECT_CALL(
|
|
ib,
|
|
packetLossDetected(
|
|
nullptr,
|
|
Field(
|
|
&InstrumentationObserver::ObserverLossEvent::lostPackets,
|
|
UnorderedElementsAre(
|
|
getLossPacketMatcher(false, true),
|
|
getLossPacketMatcher(false, true),
|
|
getLossPacketMatcher(false, true),
|
|
getLossPacketMatcher(false, true),
|
|
getLossPacketMatcher(false, true),
|
|
getLossPacketMatcher(false, true),
|
|
getLossPacketMatcher(false, true)))))
|
|
.Times(1);
|
|
|
|
for (auto& callback : conn->pendingCallbacks) {
|
|
callback(nullptr);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestTimeoutAndReorderLossObserverCallback) {
|
|
auto ib = MockInstrumentationObserver();
|
|
auto conn = createConn();
|
|
// Register 1 instrumentation observer
|
|
conn->instrumentationObservers_.emplace_back(&ib);
|
|
auto noopLossVisitor = [](auto&, auto&, bool) {};
|
|
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), folly::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);
|
|
|
|
detectLossPackets(
|
|
*conn,
|
|
largestSent + 1,
|
|
noopLossVisitor,
|
|
checkTime,
|
|
PacketNumberSpace::AppData);
|
|
|
|
// expecting 1 callback to be stacked
|
|
EXPECT_EQ(1, size(conn->pendingCallbacks));
|
|
|
|
// 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(
|
|
ib,
|
|
packetLossDetected(
|
|
nullptr,
|
|
Field(
|
|
&InstrumentationObserver::ObserverLossEvent::lostPackets,
|
|
UnorderedElementsAre(
|
|
getLossPacketMatcher(true, true),
|
|
getLossPacketMatcher(true, true),
|
|
getLossPacketMatcher(true, true),
|
|
getLossPacketMatcher(false, true)))))
|
|
.Times(1);
|
|
|
|
for (auto& callback : conn->pendingCallbacks) {
|
|
callback(nullptr);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicLossFunctionsTest, TestNoInstrumentationObserverCallback) {
|
|
auto conn = createConn();
|
|
auto noopLossVisitor = [](auto&, auto&, bool) {};
|
|
|
|
PacketNum largestSent = 0;
|
|
for (int i = 0; i < 7; ++i) {
|
|
largestSent =
|
|
sendPacket(*conn, TimePoint(i * 10ms), folly::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);
|
|
|
|
detectLossPackets(
|
|
*conn,
|
|
largestSent + 1,
|
|
noopLossVisitor,
|
|
checkTime,
|
|
PacketNumberSpace::AppData);
|
|
|
|
// expecting 0 callbacks to be queued
|
|
EXPECT_EQ(0, size(conn->pendingCallbacks));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
QuicLossFunctionsTests,
|
|
QuicLossFunctionsTest,
|
|
Values(
|
|
PacketNumberSpace::Initial,
|
|
PacketNumberSpace::Handshake,
|
|
PacketNumberSpace::AppData));
|
|
|
|
} // namespace test
|
|
} // namespace quic
|