mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-09 20:42:44 +03:00
Summary: Previously, we maintained state and counters to count both, header and body bytes together. This commit introduces additional counters and state to keep track of just the body bytes that were sent and acked etc. Body bytes received will be implemented later. Reviewed By: bschlinker Differential Revision: D27312049 fbshipit-source-id: 33f169c9168dfda625e86de45df7c00d1897ba7e
681 lines
24 KiB
C++
681 lines
24 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 <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <quic/api/test/Mocks.h>
|
|
#include <quic/common/test/TestUtils.h>
|
|
#include <quic/d6d/QuicD6DStateFunctions.h>
|
|
#include <quic/d6d/test/Mocks.h>
|
|
#include <quic/state/StateData.h>
|
|
|
|
using namespace testing;
|
|
using namespace std::literals::chrono_literals;
|
|
|
|
namespace quic {
|
|
namespace test {
|
|
|
|
// timeLastNonSearchState can only increase
|
|
enum class TimeLastNonSearchStateEnd : uint8_t { EQ, GE };
|
|
|
|
struct D6DProbeLostTestFixture {
|
|
D6DMachineState stateBegin;
|
|
D6DMachineState stateEnd;
|
|
bool sendProbeBegin;
|
|
folly::Optional<std::chrono::milliseconds> sendProbeDelayEnd;
|
|
// probe loss doesn't change outstanding probes, so a begin value
|
|
// is enough
|
|
uint64_t outstandingProbes;
|
|
uint32_t currentProbeSizeBegin;
|
|
uint32_t currentProbeSizeEnd;
|
|
D6DProbePacket lastProbe;
|
|
D6DMachineState lastNonSearchStateBegin;
|
|
D6DMachineState lastNonSearchStateEnd;
|
|
TimePoint timeLastNonSearchStateBegin;
|
|
TimeLastNonSearchStateEnd timeLastNonSearchStateEndE;
|
|
};
|
|
|
|
RegularQuicWritePacket makeTestShortPacket() {
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 2 /* packetNum */);
|
|
RegularQuicWritePacket packet(std::move(header));
|
|
return packet;
|
|
}
|
|
|
|
class QuicD6DStateFunctionsTest : public Test {
|
|
public:
|
|
void runD6DProbeLostTest(
|
|
QuicConnectionStateBase& conn,
|
|
D6DProbeLostTestFixture fixture) {
|
|
conn.d6d.state = fixture.stateBegin;
|
|
conn.d6d.outstandingProbes = fixture.outstandingProbes;
|
|
conn.d6d.currentProbeSize = fixture.currentProbeSizeBegin;
|
|
conn.d6d.lastProbe = fixture.lastProbe;
|
|
conn.pendingEvents.d6d.sendProbePacket = fixture.sendProbeBegin;
|
|
conn.d6d.meta.lastNonSearchState = fixture.lastNonSearchStateBegin;
|
|
conn.d6d.meta.timeLastNonSearchState = fixture.timeLastNonSearchStateBegin;
|
|
onD6DLastProbeLost(conn);
|
|
EXPECT_EQ(conn.d6d.state, fixture.stateEnd);
|
|
EXPECT_EQ(conn.d6d.currentProbeSize, fixture.currentProbeSizeEnd);
|
|
if (fixture.sendProbeDelayEnd.hasValue()) {
|
|
ASSERT_TRUE(conn.pendingEvents.d6d.sendProbeDelay.hasValue());
|
|
EXPECT_EQ(
|
|
*conn.pendingEvents.d6d.sendProbeDelay, *fixture.sendProbeDelayEnd);
|
|
} else {
|
|
ASSERT_FALSE(conn.pendingEvents.d6d.sendProbeDelay.hasValue());
|
|
}
|
|
EXPECT_EQ(conn.d6d.meta.lastNonSearchState, fixture.lastNonSearchStateEnd);
|
|
switch (fixture.timeLastNonSearchStateEndE) {
|
|
case TimeLastNonSearchStateEnd::EQ:
|
|
EXPECT_EQ(
|
|
conn.d6d.meta.timeLastNonSearchState,
|
|
fixture.timeLastNonSearchStateBegin);
|
|
break;
|
|
default:
|
|
EXPECT_GE(
|
|
conn.d6d.meta.timeLastNonSearchState,
|
|
fixture.timeLastNonSearchStateBegin);
|
|
}
|
|
}
|
|
};
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInBase) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto now = Clock::now();
|
|
// One probe lost in BASE state
|
|
D6DProbeLostTestFixture oneProbeLostInBase = {
|
|
D6DMachineState::BASE, // stateBegin
|
|
D6DMachineState::BASE, // stateEnd
|
|
false, // sendProbeBegin
|
|
kDefaultD6DProbeDelayWhenLost, // sendProbeEnd
|
|
1, // outstandingProbes
|
|
conn.d6d.basePMTU, // currentProbeSizeBegin
|
|
conn.d6d.basePMTU, // currentProbeSizeEnd
|
|
D6DProbePacket(0, conn.d6d.basePMTU + 10),
|
|
D6DMachineState::DISABLED, // lastNonSearchStateBegin
|
|
D6DMachineState::DISABLED, // lastNonSearchStateEnd
|
|
now, // timeLastNonSearchStateBegin
|
|
TimeLastNonSearchStateEnd::EQ // timeLastNonSearchStateEndE
|
|
};
|
|
runD6DProbeLostTest(conn, oneProbeLostInBase);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredMaxInBase) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto now = Clock::now();
|
|
// max number of probes lost in BASE state
|
|
D6DProbeLostTestFixture maxNumProbesLostInBase = {
|
|
D6DMachineState::BASE,
|
|
D6DMachineState::ERROR,
|
|
false,
|
|
kDefaultD6DProbeDelayWhenLost,
|
|
kDefaultD6DMaxOutstandingProbes,
|
|
conn.d6d.basePMTU,
|
|
kMinMaxUDPPayload,
|
|
D6DProbePacket(0, conn.d6d.basePMTU + 10),
|
|
D6DMachineState::DISABLED,
|
|
D6DMachineState::BASE,
|
|
now,
|
|
TimeLastNonSearchStateEnd::GE};
|
|
runD6DProbeLostTest(conn, maxNumProbesLostInBase);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInSearching) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto now = Clock::now();
|
|
// One probe lots in SEARCHING state
|
|
D6DProbeLostTestFixture oneProbeLostInSearching = {
|
|
D6DMachineState::SEARCHING,
|
|
D6DMachineState::SEARCHING,
|
|
false,
|
|
kDefaultD6DProbeDelayWhenLost,
|
|
1,
|
|
static_cast<uint32_t>(conn.d6d.basePMTU + 10),
|
|
static_cast<uint32_t>(conn.d6d.basePMTU + 10),
|
|
D6DProbePacket(0, conn.d6d.basePMTU + 10),
|
|
D6DMachineState::BASE,
|
|
D6DMachineState::BASE,
|
|
now,
|
|
TimeLastNonSearchStateEnd::EQ};
|
|
runD6DProbeLostTest(conn, oneProbeLostInSearching);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredMaxInSearching) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto now = Clock::now();
|
|
// Max number of probes lost in SEARCHING state
|
|
D6DProbeLostTestFixture maxProbesLostInSearching = {
|
|
D6DMachineState::SEARCHING,
|
|
D6DMachineState::SEARCH_COMPLETE,
|
|
false,
|
|
folly::none,
|
|
kDefaultD6DMaxOutstandingProbes,
|
|
static_cast<uint32_t>(conn.d6d.basePMTU + 10),
|
|
static_cast<uint32_t>(conn.d6d.basePMTU + 10),
|
|
D6DProbePacket(0, conn.d6d.basePMTU + 10),
|
|
D6DMachineState::BASE,
|
|
D6DMachineState::BASE,
|
|
now,
|
|
TimeLastNonSearchStateEnd::EQ};
|
|
runD6DProbeLostTest(conn, maxProbesLostInSearching);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInError) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto now = Clock::now();
|
|
// Probe lost in ERROR state
|
|
D6DProbeLostTestFixture probeLostInError = {
|
|
D6DMachineState::ERROR,
|
|
D6DMachineState::ERROR,
|
|
false,
|
|
kDefaultD6DProbeDelayWhenLost,
|
|
kDefaultD6DMaxOutstandingProbes + 1,
|
|
kMinMaxUDPPayload,
|
|
kMinMaxUDPPayload,
|
|
D6DProbePacket(0, conn.d6d.basePMTU + 10),
|
|
D6DMachineState::BASE,
|
|
D6DMachineState::BASE,
|
|
now,
|
|
TimeLastNonSearchStateEnd::EQ};
|
|
runD6DProbeLostTest(conn, probeLostInError);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInBase) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
const uint16_t expectPMTU = 1400;
|
|
auto& d6d = conn.d6d;
|
|
auto now = Clock::now();
|
|
Observer::Config config = {};
|
|
config.pmtuEvents = true;
|
|
auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
|
|
auto observers = std::make_shared<ObserverVec>();
|
|
observers->emplace_back(mockObserver.get());
|
|
conn.observers = observers;
|
|
d6d.state = D6DMachineState::BASE;
|
|
d6d.outstandingProbes = 1;
|
|
d6d.currentProbeSize = d6d.basePMTU;
|
|
d6d.meta.lastNonSearchState = D6DMachineState::DISABLED;
|
|
d6d.meta.timeLastNonSearchState = now;
|
|
auto pkt = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
Clock::now(),
|
|
d6d.currentProbeSize,
|
|
0,
|
|
false,
|
|
true,
|
|
d6d.currentProbeSize,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
d6d.lastProbe = D6DProbePacket(
|
|
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
|
|
d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
|
|
auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
|
|
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
|
|
.Times(1)
|
|
.WillOnce(Return(expectPMTU));
|
|
EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _)).Times(0);
|
|
onD6DLastProbeAcked(conn);
|
|
for (auto& callback : conn.pendingCallbacks) {
|
|
callback(nullptr);
|
|
}
|
|
EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
|
|
EXPECT_EQ(d6d.currentProbeSize, expectPMTU);
|
|
EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
|
|
EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
|
|
EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInSearchingOne) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
const uint16_t expectPMTU = 1400;
|
|
auto& d6d = conn.d6d;
|
|
auto now = Clock::now();
|
|
Observer::Config config = {};
|
|
config.pmtuEvents = true;
|
|
auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
|
|
auto observers = std::make_shared<ObserverVec>();
|
|
observers->emplace_back(mockObserver.get());
|
|
conn.observers = observers;
|
|
d6d.state = D6DMachineState::SEARCHING;
|
|
d6d.outstandingProbes = 1;
|
|
conn.udpSendPacketLen = 1250;
|
|
d6d.currentProbeSize = 1300;
|
|
d6d.meta.lastNonSearchState = D6DMachineState::BASE;
|
|
d6d.meta.timeLastNonSearchState = now;
|
|
auto pkt = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
Clock::now(),
|
|
d6d.currentProbeSize,
|
|
0,
|
|
false,
|
|
true,
|
|
d6d.currentProbeSize,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
d6d.lastProbe = D6DProbePacket(
|
|
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
|
|
d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
|
|
auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
|
|
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
|
|
.Times(1)
|
|
.WillOnce(Return(expectPMTU));
|
|
EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _)).Times(0);
|
|
onD6DLastProbeAcked(conn);
|
|
for (auto& callback : conn.pendingCallbacks) {
|
|
callback(nullptr);
|
|
}
|
|
EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
|
|
EXPECT_EQ(d6d.currentProbeSize, expectPMTU);
|
|
EXPECT_EQ(conn.udpSendPacketLen, 1300);
|
|
EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
|
|
EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInSearchingMax) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
const uint16_t oversize = 1500;
|
|
auto& d6d = conn.d6d;
|
|
auto now = Clock::now();
|
|
Observer::Config config = {};
|
|
config.pmtuEvents = true;
|
|
auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
|
|
auto observers = std::make_shared<ObserverVec>();
|
|
observers->emplace_back(mockObserver.get());
|
|
conn.observers = observers;
|
|
d6d.state = D6DMachineState::SEARCHING;
|
|
d6d.outstandingProbes = 3;
|
|
conn.udpSendPacketLen = 1400;
|
|
d6d.currentProbeSize = 1450;
|
|
d6d.meta.lastNonSearchState = D6DMachineState::BASE;
|
|
d6d.meta.timeLastNonSearchState = now;
|
|
d6d.meta.totalTxedProbes = 10;
|
|
auto pkt = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
Clock::now(),
|
|
d6d.currentProbeSize,
|
|
0,
|
|
false,
|
|
true,
|
|
d6d.currentProbeSize,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
d6d.lastProbe = D6DProbePacket(
|
|
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
|
|
d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
|
|
auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
|
|
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
|
|
.Times(1)
|
|
.WillOnce(Return(oversize));
|
|
EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](QuicSocket* /* qSocket */,
|
|
const Observer::PMTUUpperBoundEvent& event) {
|
|
EXPECT_LT(now, event.upperBoundTime);
|
|
EXPECT_LT(0us, event.timeSinceLastNonSearchState);
|
|
EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
|
|
EXPECT_EQ(1450, event.upperBoundPMTU);
|
|
EXPECT_EQ(10, event.cumulativeProbesSent);
|
|
EXPECT_EQ(ProbeSizeRaiserType::ConstantStep, event.probeSizeRaiserType);
|
|
}));
|
|
onD6DLastProbeAcked(conn);
|
|
for (auto& callback : conn.pendingCallbacks) {
|
|
callback(nullptr);
|
|
}
|
|
EXPECT_EQ(d6d.state, D6DMachineState::SEARCH_COMPLETE);
|
|
EXPECT_EQ(d6d.currentProbeSize, 1450);
|
|
EXPECT_EQ(conn.udpSendPacketLen, 1450);
|
|
EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
|
|
EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInError) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto& d6d = conn.d6d;
|
|
auto now = Clock::now();
|
|
Observer::Config config = {};
|
|
config.pmtuEvents = true;
|
|
auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
|
|
auto observers = std::make_shared<ObserverVec>();
|
|
observers->emplace_back(mockObserver.get());
|
|
conn.observers = observers;
|
|
d6d.state = D6DMachineState::ERROR;
|
|
d6d.outstandingProbes = 3;
|
|
conn.udpSendPacketLen = d6d.basePMTU;
|
|
d6d.currentProbeSize = d6d.basePMTU - 20;
|
|
d6d.meta.lastNonSearchState = D6DMachineState::BASE;
|
|
d6d.meta.timeLastNonSearchState = now;
|
|
auto pkt = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
Clock::now(),
|
|
d6d.currentProbeSize,
|
|
0,
|
|
false,
|
|
true,
|
|
d6d.currentProbeSize,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
d6d.lastProbe = D6DProbePacket(
|
|
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
|
|
d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
|
|
auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
|
|
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
|
|
.Times(1)
|
|
.WillOnce(Return(1300)); // Won't be used
|
|
EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _)).Times(0);
|
|
onD6DLastProbeAcked(conn);
|
|
for (auto& callback : conn.pendingCallbacks) {
|
|
callback(nullptr);
|
|
}
|
|
EXPECT_EQ(d6d.state, D6DMachineState::BASE);
|
|
EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
|
|
EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
|
|
EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::ERROR);
|
|
EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, BlackholeInSearching) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto& d6d = conn.d6d;
|
|
auto now = Clock::now();
|
|
Observer::Config config = {};
|
|
config.pmtuEvents = true;
|
|
auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
|
|
auto observers = std::make_shared<ObserverVec>();
|
|
observers->emplace_back(mockObserver.get());
|
|
conn.observers = observers;
|
|
d6d.state = D6DMachineState::SEARCHING;
|
|
d6d.outstandingProbes = 2;
|
|
conn.udpSendPacketLen = d6d.basePMTU + 20;
|
|
d6d.currentProbeSize = d6d.basePMTU + 30;
|
|
d6d.meta.lastNonSearchState = D6DMachineState::BASE;
|
|
d6d.meta.timeLastNonSearchState = now;
|
|
auto pkt = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
now + 10s,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
false,
|
|
true,
|
|
d6d.currentProbeSize,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
d6d.lastProbe = D6DProbePacket(
|
|
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
|
|
|
|
auto lostPacket = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
now + 8s,
|
|
conn.udpSendPacketLen,
|
|
0,
|
|
false,
|
|
conn.udpSendPacketLen + d6d.currentProbeSize,
|
|
0,
|
|
conn.udpSendPacketLen + d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
|
|
d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
|
|
std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
|
|
1); // Threshold of 1 will cause window to be set to 0
|
|
|
|
EXPECT_CALL(*mockObserver, pmtuBlackholeDetected(_, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](QuicSocket* /* qSocket */,
|
|
const Observer::PMTUBlackholeEvent& event) {
|
|
EXPECT_LE(d6d.meta.timeLastNonSearchState, event.blackholeTime);
|
|
EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
|
|
EXPECT_EQ(D6DMachineState::SEARCHING, event.currentState);
|
|
EXPECT_EQ(d6d.basePMTU + 20, event.udpSendPacketLen);
|
|
EXPECT_EQ(d6d.basePMTU + 30, event.lastProbeSize);
|
|
EXPECT_EQ(0, event.blackholeDetectionWindow);
|
|
EXPECT_EQ(1, event.blackholeDetectionThreshold);
|
|
EXPECT_EQ(
|
|
d6d.basePMTU + 20, event.triggeringPacketMetadata.encodedSize);
|
|
}));
|
|
|
|
detectPMTUBlackhole(conn, lostPacket);
|
|
for (auto& callback : conn.pendingCallbacks) {
|
|
callback(nullptr);
|
|
}
|
|
|
|
EXPECT_EQ(d6d.state, D6DMachineState::BASE);
|
|
EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
|
|
EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
|
|
EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
|
|
EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, BlackholeInSearchComplete) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto& d6d = conn.d6d;
|
|
auto now = Clock::now();
|
|
Observer::Config config = {};
|
|
config.pmtuEvents = true;
|
|
auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
|
|
auto observers = std::make_shared<ObserverVec>();
|
|
observers->emplace_back(mockObserver.get());
|
|
conn.observers = observers;
|
|
d6d.state = D6DMachineState::SEARCH_COMPLETE;
|
|
conn.udpSendPacketLen = d6d.basePMTU + 20;
|
|
d6d.currentProbeSize = d6d.basePMTU + 20;
|
|
d6d.meta.lastNonSearchState = D6DMachineState::BASE;
|
|
d6d.meta.timeLastNonSearchState = now;
|
|
auto pkt = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
now + 10s,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
false,
|
|
true,
|
|
d6d.currentProbeSize,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
d6d.lastProbe = D6DProbePacket(
|
|
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
|
|
|
|
auto lostPacket = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
now + 12s,
|
|
conn.udpSendPacketLen,
|
|
0,
|
|
false,
|
|
conn.udpSendPacketLen + d6d.currentProbeSize,
|
|
0,
|
|
conn.udpSendPacketLen + d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
|
|
d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
|
|
std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
|
|
1); // Threshold of 1 will cause window to be set to 0
|
|
|
|
EXPECT_CALL(*mockObserver, pmtuBlackholeDetected(_, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](QuicSocket* /* qSocket */,
|
|
const Observer::PMTUBlackholeEvent& event) {
|
|
EXPECT_EQ(d6d.meta.timeLastNonSearchState, event.blackholeTime);
|
|
EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
|
|
EXPECT_EQ(D6DMachineState::SEARCH_COMPLETE, event.currentState);
|
|
EXPECT_EQ(d6d.basePMTU + 20, event.udpSendPacketLen);
|
|
EXPECT_EQ(d6d.basePMTU + 20, event.lastProbeSize);
|
|
EXPECT_EQ(0, event.blackholeDetectionWindow);
|
|
EXPECT_EQ(1, event.blackholeDetectionThreshold);
|
|
EXPECT_EQ(
|
|
d6d.basePMTU + 20, event.triggeringPacketMetadata.encodedSize);
|
|
}));
|
|
|
|
detectPMTUBlackhole(conn, lostPacket);
|
|
for (auto& callback : conn.pendingCallbacks) {
|
|
callback(nullptr);
|
|
}
|
|
|
|
EXPECT_EQ(d6d.state, D6DMachineState::BASE);
|
|
EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
|
|
EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
|
|
EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::SEARCH_COMPLETE);
|
|
EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, ReachMaxPMTU) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto& d6d = conn.d6d;
|
|
auto now = Clock::now();
|
|
Observer::Config config = {};
|
|
config.pmtuEvents = true;
|
|
auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
|
|
auto observers = std::make_shared<ObserverVec>();
|
|
observers->emplace_back(mockObserver.get());
|
|
conn.observers = observers;
|
|
d6d.state = D6DMachineState::SEARCHING;
|
|
d6d.maxPMTU = 1452;
|
|
d6d.outstandingProbes = 1;
|
|
conn.udpSendPacketLen = 1400;
|
|
d6d.currentProbeSize = 1442;
|
|
d6d.meta.lastNonSearchState = D6DMachineState::BASE;
|
|
d6d.meta.timeLastNonSearchState = now;
|
|
d6d.meta.totalTxedProbes = 10;
|
|
auto pkt = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
Clock::now(),
|
|
d6d.currentProbeSize,
|
|
0,
|
|
false,
|
|
true,
|
|
d6d.currentProbeSize,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
d6d.lastProbe = D6DProbePacket(
|
|
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
|
|
d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
|
|
auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
|
|
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
|
|
.Times(1)
|
|
.WillOnce(Return(1452));
|
|
onD6DLastProbeAcked(conn);
|
|
EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
|
|
EXPECT_EQ(d6d.currentProbeSize, 1452);
|
|
EXPECT_EQ(conn.udpSendPacketLen, 1442);
|
|
EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
|
|
EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicD6DStateFunctionsTest,
|
|
MaintainStateWhenFalsePositiveBlackholeDetected) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto& d6d = conn.d6d;
|
|
auto now = Clock::now();
|
|
d6d.state = D6DMachineState::SEARCHING;
|
|
d6d.maxPMTU = 1452;
|
|
d6d.outstandingProbes = 1;
|
|
conn.udpSendPacketLen = 1400;
|
|
d6d.currentProbeSize = 1442;
|
|
d6d.meta.lastNonSearchState = D6DMachineState::BASE;
|
|
d6d.meta.timeLastNonSearchState = now;
|
|
d6d.meta.totalTxedProbes = 10;
|
|
auto pkt = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
Clock::now(),
|
|
d6d.currentProbeSize,
|
|
0,
|
|
false,
|
|
true,
|
|
d6d.currentProbeSize,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
d6d.lastProbe = D6DProbePacket(
|
|
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
|
|
d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
|
|
auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
|
|
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
|
|
.Times(1)
|
|
.WillOnce(Return(1452));
|
|
d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
|
|
std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
|
|
1); // Threshold of 1 will cause window to be set to 0
|
|
|
|
auto lostPacket = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
Clock::now(),
|
|
d6d.currentProbeSize,
|
|
0,
|
|
false,
|
|
false,
|
|
d6d.currentProbeSize,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
// Generate a false positive blackhole signal
|
|
detectPMTUBlackhole(conn, lostPacket);
|
|
EXPECT_EQ(d6d.state, D6DMachineState::BASE);
|
|
EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
|
|
|
|
// The ack of a non-stale probe should bring us back to SEARCHING state and
|
|
// correct probe size
|
|
onD6DLastProbeAcked(conn);
|
|
EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
|
|
EXPECT_EQ(d6d.currentProbeSize, 1452);
|
|
EXPECT_EQ(conn.udpSendPacketLen, 1442);
|
|
EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
|
|
EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
|
|
}
|
|
|
|
TEST_F(QuicD6DStateFunctionsTest, UpperboundIsBase) {
|
|
QuicConnectionStateBase conn(QuicNodeType::Server);
|
|
auto& d6d = conn.d6d;
|
|
auto now = Clock::now();
|
|
d6d.state = D6DMachineState::BASE;
|
|
d6d.basePMTU = 1400;
|
|
d6d.maxPMTU = 1400;
|
|
d6d.outstandingProbes = 1;
|
|
conn.udpSendPacketLen = 1400;
|
|
d6d.currentProbeSize = 1400;
|
|
d6d.meta.lastNonSearchState = D6DMachineState::DISABLED;
|
|
d6d.meta.timeLastNonSearchState = now;
|
|
d6d.meta.totalTxedProbes = 10;
|
|
auto pkt = OutstandingPacket(
|
|
makeTestShortPacket(),
|
|
Clock::now(),
|
|
d6d.currentProbeSize,
|
|
0,
|
|
false,
|
|
true,
|
|
d6d.currentProbeSize,
|
|
d6d.currentProbeSize,
|
|
0,
|
|
LossState());
|
|
d6d.lastProbe = D6DProbePacket(
|
|
pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
|
|
d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
|
|
auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
|
|
EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
|
|
.Times(1)
|
|
.WillOnce(Return(1452));
|
|
|
|
// The ack of a non-stale probe should bring us back to SEARCHING state and
|
|
// correct probe size
|
|
onD6DLastProbeAcked(conn);
|
|
EXPECT_EQ(d6d.state, D6DMachineState::SEARCH_COMPLETE);
|
|
EXPECT_EQ(d6d.currentProbeSize, 1400);
|
|
EXPECT_EQ(conn.udpSendPacketLen, 1400);
|
|
EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
|
|
EXPECT_GT(d6d.meta.timeLastNonSearchState, now);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace quic
|