1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE file in the root directory of this source tree.
6  *
7  */
8 
9 #include <gmock/gmock.h>
10 #include <gtest/gtest.h>
11 
12 #include <quic/api/test/Mocks.h>
13 #include <quic/common/test/TestUtils.h>
14 #include <quic/d6d/QuicD6DStateFunctions.h>
15 #include <quic/d6d/test/Mocks.h>
16 #include <quic/state/StateData.h>
17 
18 using namespace testing;
19 
20 namespace quic {
21 namespace test {
22 
23 // timeLastNonSearchState can only increase
24 enum class TimeLastNonSearchStateEnd : uint8_t { EQ, GE };
25 
26 struct D6DProbeLostTestFixture {
27   D6DMachineState stateBegin;
28   D6DMachineState stateEnd;
29   bool sendProbeBegin;
30   folly::Optional<std::chrono::milliseconds> sendProbeDelayEnd;
31   // probe loss doesn't change outstanding probes, so a begin value
32   // is enough
33   uint64_t outstandingProbes;
34   uint32_t currentProbeSizeBegin;
35   uint32_t currentProbeSizeEnd;
36   D6DProbePacket lastProbe;
37   D6DMachineState lastNonSearchStateBegin;
38   D6DMachineState lastNonSearchStateEnd;
39   TimePoint timeLastNonSearchStateBegin;
40   TimeLastNonSearchStateEnd timeLastNonSearchStateEndE;
41 };
42 
makeTestShortPacket()43 RegularQuicWritePacket makeTestShortPacket() {
44   ShortHeader header(
45       ProtectionType::KeyPhaseZero, getTestConnectionId(), 2 /* packetNum */);
46   RegularQuicWritePacket packet(std::move(header));
47   return packet;
48 }
49 
50 class QuicD6DStateFunctionsTest : public Test {
51  public:
runD6DProbeLostTest(QuicConnectionStateBase & conn,D6DProbeLostTestFixture fixture)52   void runD6DProbeLostTest(
53       QuicConnectionStateBase& conn,
54       D6DProbeLostTestFixture fixture) {
55     conn.d6d.state = fixture.stateBegin;
56     conn.d6d.outstandingProbes = fixture.outstandingProbes;
57     conn.d6d.currentProbeSize = fixture.currentProbeSizeBegin;
58     conn.d6d.lastProbe = fixture.lastProbe;
59     conn.pendingEvents.d6d.sendProbePacket = fixture.sendProbeBegin;
60     conn.d6d.meta.lastNonSearchState = fixture.lastNonSearchStateBegin;
61     conn.d6d.meta.timeLastNonSearchState = fixture.timeLastNonSearchStateBegin;
62     onD6DLastProbeLost(conn);
63     EXPECT_EQ(conn.d6d.state, fixture.stateEnd);
64     EXPECT_EQ(conn.d6d.currentProbeSize, fixture.currentProbeSizeEnd);
65     if (fixture.sendProbeDelayEnd.hasValue()) {
66       ASSERT_TRUE(conn.pendingEvents.d6d.sendProbeDelay.hasValue());
67       EXPECT_EQ(
68           *conn.pendingEvents.d6d.sendProbeDelay, *fixture.sendProbeDelayEnd);
69     } else {
70       ASSERT_FALSE(conn.pendingEvents.d6d.sendProbeDelay.hasValue());
71     }
72     EXPECT_EQ(conn.d6d.meta.lastNonSearchState, fixture.lastNonSearchStateEnd);
73     switch (fixture.timeLastNonSearchStateEndE) {
74       case TimeLastNonSearchStateEnd::EQ:
75         EXPECT_EQ(
76             conn.d6d.meta.timeLastNonSearchState,
77             fixture.timeLastNonSearchStateBegin);
78         break;
79       default:
80         EXPECT_GE(
81             conn.d6d.meta.timeLastNonSearchState,
82             fixture.timeLastNonSearchStateBegin);
83     }
84   }
85 };
86 
TEST_F(QuicD6DStateFunctionsTest,D6DProbeTimeoutExpiredOneInBase)87 TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInBase) {
88   QuicConnectionStateBase conn(QuicNodeType::Server);
89   auto now = Clock::now();
90   // One probe lost in BASE state
91   D6DProbeLostTestFixture oneProbeLostInBase = {
92       D6DMachineState::BASE, // stateBegin
93       D6DMachineState::BASE, // stateEnd
94       false, // sendProbeBegin
95       kDefaultD6DProbeDelayWhenLost, // sendProbeEnd
96       1, // outstandingProbes
97       conn.d6d.basePMTU, // currentProbeSizeBegin
98       conn.d6d.basePMTU, // currentProbeSizeEnd
99       D6DProbePacket(0, conn.d6d.basePMTU + 10),
100       D6DMachineState::DISABLED, // lastNonSearchStateBegin
101       D6DMachineState::DISABLED, // lastNonSearchStateEnd
102       now, // timeLastNonSearchStateBegin
103       TimeLastNonSearchStateEnd::EQ // timeLastNonSearchStateEndE
104   };
105   runD6DProbeLostTest(conn, oneProbeLostInBase);
106 }
107 
TEST_F(QuicD6DStateFunctionsTest,D6DProbeTimeoutExpiredMaxInBase)108 TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredMaxInBase) {
109   QuicConnectionStateBase conn(QuicNodeType::Server);
110   auto now = Clock::now();
111   // max number of probes lost in BASE state
112   D6DProbeLostTestFixture maxNumProbesLostInBase = {
113       D6DMachineState::BASE,
114       D6DMachineState::ERROR,
115       false,
116       kDefaultD6DProbeDelayWhenLost,
117       kDefaultD6DMaxOutstandingProbes,
118       conn.d6d.basePMTU,
119       kMinMaxUDPPayload,
120       D6DProbePacket(0, conn.d6d.basePMTU + 10),
121       D6DMachineState::DISABLED,
122       D6DMachineState::BASE,
123       now,
124       TimeLastNonSearchStateEnd::GE};
125   runD6DProbeLostTest(conn, maxNumProbesLostInBase);
126 }
127 
TEST_F(QuicD6DStateFunctionsTest,D6DProbeTimeoutExpiredOneInSearching)128 TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInSearching) {
129   QuicConnectionStateBase conn(QuicNodeType::Server);
130   auto now = Clock::now();
131   // One probe lots in SEARCHING state
132   D6DProbeLostTestFixture oneProbeLostInSearching = {
133       D6DMachineState::SEARCHING,
134       D6DMachineState::SEARCHING,
135       false,
136       kDefaultD6DProbeDelayWhenLost,
137       1,
138       static_cast<uint32_t>(conn.d6d.basePMTU + 10),
139       static_cast<uint32_t>(conn.d6d.basePMTU + 10),
140       D6DProbePacket(0, conn.d6d.basePMTU + 10),
141       D6DMachineState::BASE,
142       D6DMachineState::BASE,
143       now,
144       TimeLastNonSearchStateEnd::EQ};
145   runD6DProbeLostTest(conn, oneProbeLostInSearching);
146 }
147 
TEST_F(QuicD6DStateFunctionsTest,D6DProbeTimeoutExpiredMaxInSearching)148 TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredMaxInSearching) {
149   QuicConnectionStateBase conn(QuicNodeType::Server);
150   auto now = Clock::now();
151   // Max number of probes lost in SEARCHING state
152   D6DProbeLostTestFixture maxProbesLostInSearching = {
153       D6DMachineState::SEARCHING,
154       D6DMachineState::SEARCH_COMPLETE,
155       false,
156       folly::none,
157       kDefaultD6DMaxOutstandingProbes,
158       static_cast<uint32_t>(conn.d6d.basePMTU + 10),
159       static_cast<uint32_t>(conn.d6d.basePMTU + 10),
160       D6DProbePacket(0, conn.d6d.basePMTU + 10),
161       D6DMachineState::BASE,
162       D6DMachineState::BASE,
163       now,
164       TimeLastNonSearchStateEnd::EQ};
165   runD6DProbeLostTest(conn, maxProbesLostInSearching);
166 }
167 
TEST_F(QuicD6DStateFunctionsTest,D6DProbeTimeoutExpiredOneInError)168 TEST_F(QuicD6DStateFunctionsTest, D6DProbeTimeoutExpiredOneInError) {
169   QuicConnectionStateBase conn(QuicNodeType::Server);
170   auto now = Clock::now();
171   // Probe lost in ERROR state
172   D6DProbeLostTestFixture probeLostInError = {
173       D6DMachineState::ERROR,
174       D6DMachineState::ERROR,
175       false,
176       kDefaultD6DProbeDelayWhenLost,
177       kDefaultD6DMaxOutstandingProbes + 1,
178       kMinMaxUDPPayload,
179       kMinMaxUDPPayload,
180       D6DProbePacket(0, conn.d6d.basePMTU + 10),
181       D6DMachineState::BASE,
182       D6DMachineState::BASE,
183       now,
184       TimeLastNonSearchStateEnd::EQ};
185   runD6DProbeLostTest(conn, probeLostInError);
186 }
187 
TEST_F(QuicD6DStateFunctionsTest,D6DProbeAckedInBase)188 TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInBase) {
189   QuicConnectionStateBase conn(QuicNodeType::Server);
190   const uint16_t expectPMTU = 1400;
191   auto& d6d = conn.d6d;
192   auto now = Clock::now();
193   Observer::Config config = {};
194   config.pmtuEvents = true;
195   auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
196   auto observers = std::make_shared<ObserverVec>();
197   observers->emplace_back(mockObserver.get());
198   conn.observers = observers;
199   d6d.state = D6DMachineState::BASE;
200   d6d.outstandingProbes = 1;
201   d6d.currentProbeSize = d6d.basePMTU;
202   d6d.meta.lastNonSearchState = D6DMachineState::DISABLED;
203   d6d.meta.timeLastNonSearchState = now;
204   auto pkt = OutstandingPacket(
205       makeTestShortPacket(),
206       Clock::now(),
207       d6d.currentProbeSize,
208       0,
209       false,
210       true,
211       d6d.currentProbeSize,
212       d6d.currentProbeSize,
213       0,
214       LossState(),
215       0);
216   d6d.lastProbe = D6DProbePacket(
217       pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
218   d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
219   auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
220   EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
221       .Times(1)
222       .WillOnce(Return(expectPMTU));
223   EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _)).Times(0);
224   onD6DLastProbeAcked(conn);
225   for (auto& callback : conn.pendingCallbacks) {
226     callback(nullptr);
227   }
228   EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
229   EXPECT_EQ(d6d.currentProbeSize, expectPMTU);
230   EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
231   EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
232   EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
233 }
234 
TEST_F(QuicD6DStateFunctionsTest,D6DProbeAckedInSearchingOne)235 TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInSearchingOne) {
236   QuicConnectionStateBase conn(QuicNodeType::Server);
237   const uint16_t expectPMTU = 1400;
238   auto& d6d = conn.d6d;
239   auto now = Clock::now();
240   Observer::Config config = {};
241   config.pmtuEvents = true;
242   auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
243   auto observers = std::make_shared<ObserverVec>();
244   observers->emplace_back(mockObserver.get());
245   conn.observers = observers;
246   d6d.state = D6DMachineState::SEARCHING;
247   d6d.outstandingProbes = 1;
248   conn.udpSendPacketLen = 1250;
249   d6d.currentProbeSize = 1300;
250   d6d.meta.lastNonSearchState = D6DMachineState::BASE;
251   d6d.meta.timeLastNonSearchState = now;
252   auto pkt = OutstandingPacket(
253       makeTestShortPacket(),
254       Clock::now(),
255       d6d.currentProbeSize,
256       0,
257       false,
258       true,
259       d6d.currentProbeSize,
260       d6d.currentProbeSize,
261       0,
262       LossState(),
263       0);
264   d6d.lastProbe = D6DProbePacket(
265       pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
266   d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
267   auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
268   EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
269       .Times(1)
270       .WillOnce(Return(expectPMTU));
271   EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _)).Times(0);
272   onD6DLastProbeAcked(conn);
273   for (auto& callback : conn.pendingCallbacks) {
274     callback(nullptr);
275   }
276   EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
277   EXPECT_EQ(d6d.currentProbeSize, expectPMTU);
278   EXPECT_EQ(conn.udpSendPacketLen, 1300);
279   EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
280   EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
281 }
282 
TEST_F(QuicD6DStateFunctionsTest,D6DProbeAckedInSearchingMax)283 TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInSearchingMax) {
284   QuicConnectionStateBase conn(QuicNodeType::Server);
285   const uint16_t oversize = 1500;
286   auto& d6d = conn.d6d;
287   auto now = Clock::now();
288   Observer::Config config = {};
289   config.pmtuEvents = true;
290   auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
291   auto observers = std::make_shared<ObserverVec>();
292   observers->emplace_back(mockObserver.get());
293   conn.observers = observers;
294   d6d.state = D6DMachineState::SEARCHING;
295   d6d.outstandingProbes = 3;
296   conn.udpSendPacketLen = 1400;
297   d6d.currentProbeSize = 1450;
298   d6d.meta.lastNonSearchState = D6DMachineState::BASE;
299   d6d.meta.timeLastNonSearchState = now;
300   d6d.meta.totalTxedProbes = 10;
301   auto pkt = OutstandingPacket(
302       makeTestShortPacket(),
303       Clock::now(),
304       d6d.currentProbeSize,
305       0,
306       false,
307       true,
308       d6d.currentProbeSize,
309       d6d.currentProbeSize,
310       0,
311       LossState(),
312       0);
313   d6d.lastProbe = D6DProbePacket(
314       pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
315   d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
316   auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
317   EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
318       .Times(1)
319       .WillOnce(Return(oversize));
320   EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _))
321       .Times(1)
322       .WillOnce(Invoke([&](QuicSocket* /* qSocket */,
323                            const Observer::PMTUUpperBoundEvent& event) {
324         EXPECT_LT(now, event.upperBoundTime);
325         EXPECT_LT(0us, event.timeSinceLastNonSearchState);
326         EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
327         EXPECT_EQ(1450, event.upperBoundPMTU);
328         EXPECT_EQ(10, event.cumulativeProbesSent);
329         EXPECT_EQ(ProbeSizeRaiserType::ConstantStep, event.probeSizeRaiserType);
330       }));
331   onD6DLastProbeAcked(conn);
332   for (auto& callback : conn.pendingCallbacks) {
333     callback(nullptr);
334   }
335   EXPECT_EQ(d6d.state, D6DMachineState::SEARCH_COMPLETE);
336   EXPECT_EQ(d6d.currentProbeSize, 1450);
337   EXPECT_EQ(conn.udpSendPacketLen, 1450);
338   EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
339   EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
340 }
341 
TEST_F(QuicD6DStateFunctionsTest,D6DProbeAckedInError)342 TEST_F(QuicD6DStateFunctionsTest, D6DProbeAckedInError) {
343   QuicConnectionStateBase conn(QuicNodeType::Server);
344   auto& d6d = conn.d6d;
345   auto now = Clock::now();
346   Observer::Config config = {};
347   config.pmtuEvents = true;
348   auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
349   auto observers = std::make_shared<ObserverVec>();
350   observers->emplace_back(mockObserver.get());
351   conn.observers = observers;
352   d6d.state = D6DMachineState::ERROR;
353   d6d.outstandingProbes = 3;
354   conn.udpSendPacketLen = d6d.basePMTU;
355   d6d.currentProbeSize = d6d.basePMTU - 20;
356   d6d.meta.lastNonSearchState = D6DMachineState::BASE;
357   d6d.meta.timeLastNonSearchState = now;
358   auto pkt = OutstandingPacket(
359       makeTestShortPacket(),
360       Clock::now(),
361       d6d.currentProbeSize,
362       0,
363       false,
364       true,
365       d6d.currentProbeSize,
366       d6d.currentProbeSize,
367       0,
368       LossState(),
369       0);
370   d6d.lastProbe = D6DProbePacket(
371       pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
372   d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
373   auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
374   EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
375       .Times(1)
376       .WillOnce(Return(1300)); // Won't be used
377   EXPECT_CALL(*mockObserver, pmtuUpperBoundDetected(_, _)).Times(0);
378   onD6DLastProbeAcked(conn);
379   for (auto& callback : conn.pendingCallbacks) {
380     callback(nullptr);
381   }
382   EXPECT_EQ(d6d.state, D6DMachineState::BASE);
383   EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
384   EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
385   EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::ERROR);
386   EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
387 }
388 
TEST_F(QuicD6DStateFunctionsTest,BlackholeInSearching)389 TEST_F(QuicD6DStateFunctionsTest, BlackholeInSearching) {
390   QuicConnectionStateBase conn(QuicNodeType::Server);
391   auto& d6d = conn.d6d;
392   auto now = Clock::now();
393   Observer::Config config = {};
394   config.pmtuEvents = true;
395   auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
396   auto observers = std::make_shared<ObserverVec>();
397   observers->emplace_back(mockObserver.get());
398   conn.observers = observers;
399   d6d.state = D6DMachineState::SEARCHING;
400   d6d.outstandingProbes = 2;
401   conn.udpSendPacketLen = d6d.basePMTU + 20;
402   d6d.currentProbeSize = d6d.basePMTU + 30;
403   d6d.meta.lastNonSearchState = D6DMachineState::BASE;
404   d6d.meta.timeLastNonSearchState = now;
405   auto pkt = OutstandingPacket(
406       makeTestShortPacket(),
407       now + 10s,
408       d6d.currentProbeSize,
409       0,
410       false,
411       true,
412       d6d.currentProbeSize,
413       d6d.currentProbeSize,
414       0,
415       LossState(),
416       0);
417   d6d.lastProbe = D6DProbePacket(
418       pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
419 
420   auto lostPacket = OutstandingPacket(
421       makeTestShortPacket(),
422       now + 8s,
423       conn.udpSendPacketLen,
424       0,
425       false,
426       conn.udpSendPacketLen + d6d.currentProbeSize,
427       0,
428       conn.udpSendPacketLen + d6d.currentProbeSize,
429       0,
430       LossState(),
431       0);
432 
433   d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
434       std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
435       1); // Threshold of 1 will cause window to be set to 0
436 
437   EXPECT_CALL(*mockObserver, pmtuBlackholeDetected(_, _))
438       .Times(1)
439       .WillOnce(Invoke([&](QuicSocket* /* qSocket */,
440                            const Observer::PMTUBlackholeEvent& event) {
441         EXPECT_LE(d6d.meta.timeLastNonSearchState, event.blackholeTime);
442         EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
443         EXPECT_EQ(D6DMachineState::SEARCHING, event.currentState);
444         EXPECT_EQ(d6d.basePMTU + 20, event.udpSendPacketLen);
445         EXPECT_EQ(d6d.basePMTU + 30, event.lastProbeSize);
446         EXPECT_EQ(0, event.blackholeDetectionWindow);
447         EXPECT_EQ(1, event.blackholeDetectionThreshold);
448         EXPECT_EQ(
449             d6d.basePMTU + 20, event.triggeringPacketMetadata.encodedSize);
450       }));
451 
452   detectPMTUBlackhole(conn, lostPacket);
453   for (auto& callback : conn.pendingCallbacks) {
454     callback(nullptr);
455   }
456 
457   EXPECT_EQ(d6d.state, D6DMachineState::BASE);
458   EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
459   EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
460   EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
461   EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
462 }
463 
TEST_F(QuicD6DStateFunctionsTest,BlackholeInSearchComplete)464 TEST_F(QuicD6DStateFunctionsTest, BlackholeInSearchComplete) {
465   QuicConnectionStateBase conn(QuicNodeType::Server);
466   auto& d6d = conn.d6d;
467   auto now = Clock::now();
468   Observer::Config config = {};
469   config.pmtuEvents = true;
470   auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
471   auto observers = std::make_shared<ObserverVec>();
472   observers->emplace_back(mockObserver.get());
473   conn.observers = observers;
474   d6d.state = D6DMachineState::SEARCH_COMPLETE;
475   conn.udpSendPacketLen = d6d.basePMTU + 20;
476   d6d.currentProbeSize = d6d.basePMTU + 20;
477   d6d.meta.lastNonSearchState = D6DMachineState::BASE;
478   d6d.meta.timeLastNonSearchState = now;
479   auto pkt = OutstandingPacket(
480       makeTestShortPacket(),
481       now + 10s,
482       d6d.currentProbeSize,
483       0,
484       false,
485       true,
486       d6d.currentProbeSize,
487       d6d.currentProbeSize,
488       0,
489       LossState(),
490       0);
491   d6d.lastProbe = D6DProbePacket(
492       pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
493 
494   auto lostPacket = OutstandingPacket(
495       makeTestShortPacket(),
496       now + 12s,
497       conn.udpSendPacketLen,
498       0,
499       false,
500       conn.udpSendPacketLen + d6d.currentProbeSize,
501       0,
502       conn.udpSendPacketLen + d6d.currentProbeSize,
503       0,
504       LossState(),
505       0);
506 
507   d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
508       std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
509       1); // Threshold of 1 will cause window to be set to 0
510 
511   EXPECT_CALL(*mockObserver, pmtuBlackholeDetected(_, _))
512       .Times(1)
513       .WillOnce(Invoke([&](QuicSocket* /* qSocket */,
514                            const Observer::PMTUBlackholeEvent& event) {
515         EXPECT_EQ(d6d.meta.timeLastNonSearchState, event.blackholeTime);
516         EXPECT_EQ(D6DMachineState::BASE, event.lastNonSearchState);
517         EXPECT_EQ(D6DMachineState::SEARCH_COMPLETE, event.currentState);
518         EXPECT_EQ(d6d.basePMTU + 20, event.udpSendPacketLen);
519         EXPECT_EQ(d6d.basePMTU + 20, event.lastProbeSize);
520         EXPECT_EQ(0, event.blackholeDetectionWindow);
521         EXPECT_EQ(1, event.blackholeDetectionThreshold);
522         EXPECT_EQ(
523             d6d.basePMTU + 20, event.triggeringPacketMetadata.encodedSize);
524       }));
525 
526   detectPMTUBlackhole(conn, lostPacket);
527   for (auto& callback : conn.pendingCallbacks) {
528     callback(nullptr);
529   }
530 
531   EXPECT_EQ(d6d.state, D6DMachineState::BASE);
532   EXPECT_EQ(d6d.currentProbeSize, d6d.basePMTU);
533   EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
534   EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::SEARCH_COMPLETE);
535   EXPECT_GE(d6d.meta.timeLastNonSearchState, now);
536 }
537 
TEST_F(QuicD6DStateFunctionsTest,ReachMaxPMTU)538 TEST_F(QuicD6DStateFunctionsTest, ReachMaxPMTU) {
539   QuicConnectionStateBase conn(QuicNodeType::Server);
540   auto& d6d = conn.d6d;
541   auto now = Clock::now();
542   Observer::Config config = {};
543   config.pmtuEvents = true;
544   auto mockObserver = std::make_unique<StrictMock<MockObserver>>(config);
545   auto observers = std::make_shared<ObserverVec>();
546   observers->emplace_back(mockObserver.get());
547   conn.observers = observers;
548   d6d.state = D6DMachineState::SEARCHING;
549   d6d.maxPMTU = 1452;
550   d6d.outstandingProbes = 1;
551   conn.udpSendPacketLen = 1400;
552   d6d.currentProbeSize = 1442;
553   d6d.meta.lastNonSearchState = D6DMachineState::BASE;
554   d6d.meta.timeLastNonSearchState = now;
555   d6d.meta.totalTxedProbes = 10;
556   auto pkt = OutstandingPacket(
557       makeTestShortPacket(),
558       Clock::now(),
559       d6d.currentProbeSize,
560       0,
561       false,
562       true,
563       d6d.currentProbeSize,
564       d6d.currentProbeSize,
565       0,
566       LossState(),
567       0);
568   d6d.lastProbe = D6DProbePacket(
569       pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
570   d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
571   auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
572   EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
573       .Times(1)
574       .WillOnce(Return(1452));
575   onD6DLastProbeAcked(conn);
576   EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
577   EXPECT_EQ(d6d.currentProbeSize, 1452);
578   EXPECT_EQ(conn.udpSendPacketLen, 1442);
579   EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
580   EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
581 }
582 
TEST_F(QuicD6DStateFunctionsTest,MaintainStateWhenFalsePositiveBlackholeDetected)583 TEST_F(
584     QuicD6DStateFunctionsTest,
585     MaintainStateWhenFalsePositiveBlackholeDetected) {
586   QuicConnectionStateBase conn(QuicNodeType::Server);
587   auto& d6d = conn.d6d;
588   auto now = Clock::now();
589   d6d.state = D6DMachineState::SEARCHING;
590   d6d.maxPMTU = 1452;
591   d6d.outstandingProbes = 1;
592   conn.udpSendPacketLen = 1400;
593   d6d.currentProbeSize = 1442;
594   d6d.meta.lastNonSearchState = D6DMachineState::BASE;
595   d6d.meta.timeLastNonSearchState = now;
596   d6d.meta.totalTxedProbes = 10;
597   auto pkt = OutstandingPacket(
598       makeTestShortPacket(),
599       Clock::now(),
600       d6d.currentProbeSize,
601       0,
602       false,
603       true,
604       d6d.currentProbeSize,
605       d6d.currentProbeSize,
606       0,
607       LossState(),
608       0);
609   d6d.lastProbe = D6DProbePacket(
610       pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
611   d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
612   auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
613   EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
614       .Times(1)
615       .WillOnce(Return(1452));
616   d6d.thresholdCounter = std::make_unique<WindowedCounter<uint64_t, uint64_t>>(
617       std::chrono::microseconds(kDefaultD6DBlackholeDetectionWindow).count(),
618       1); // Threshold of 1 will cause window to be set to 0
619 
620   auto lostPacket = OutstandingPacket(
621       makeTestShortPacket(),
622       Clock::now(),
623       d6d.currentProbeSize,
624       0,
625       false,
626       false,
627       d6d.currentProbeSize,
628       d6d.currentProbeSize,
629       0,
630       LossState(),
631       0);
632   // Generate a false positive blackhole signal
633   detectPMTUBlackhole(conn, lostPacket);
634   EXPECT_EQ(d6d.state, D6DMachineState::BASE);
635   EXPECT_EQ(conn.udpSendPacketLen, d6d.basePMTU);
636 
637   // The ack of a non-stale probe should bring us back to SEARCHING state and
638   // correct probe size
639   onD6DLastProbeAcked(conn);
640   EXPECT_EQ(d6d.state, D6DMachineState::SEARCHING);
641   EXPECT_EQ(d6d.currentProbeSize, 1452);
642   EXPECT_EQ(conn.udpSendPacketLen, 1442);
643   EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
644   EXPECT_EQ(d6d.meta.timeLastNonSearchState, now);
645 }
646 
TEST_F(QuicD6DStateFunctionsTest,UpperboundIsBase)647 TEST_F(QuicD6DStateFunctionsTest, UpperboundIsBase) {
648   QuicConnectionStateBase conn(QuicNodeType::Server);
649   auto& d6d = conn.d6d;
650   auto now = Clock::now();
651   d6d.state = D6DMachineState::BASE;
652   d6d.basePMTU = 1400;
653   d6d.maxPMTU = 1400;
654   d6d.outstandingProbes = 1;
655   conn.udpSendPacketLen = 1400;
656   d6d.currentProbeSize = 1400;
657   d6d.meta.lastNonSearchState = D6DMachineState::DISABLED;
658   d6d.meta.timeLastNonSearchState = now;
659   d6d.meta.totalTxedProbes = 10;
660   auto pkt = OutstandingPacket(
661       makeTestShortPacket(),
662       Clock::now(),
663       d6d.currentProbeSize,
664       0,
665       false,
666       true,
667       d6d.currentProbeSize,
668       d6d.currentProbeSize,
669       0,
670       LossState(),
671       0);
672   d6d.lastProbe = D6DProbePacket(
673       pkt.packet.header.getPacketSequenceNum(), pkt.metadata.encodedSize);
674   d6d.raiser = std::make_unique<MockProbeSizeRaiser>();
675   auto mockRaiser = dynamic_cast<MockProbeSizeRaiser*>(d6d.raiser.get());
676   EXPECT_CALL(*mockRaiser, raiseProbeSize(d6d.currentProbeSize))
677       .Times(1)
678       .WillOnce(Return(1452));
679 
680   // The ack of a non-stale probe should bring us back to SEARCHING state and
681   // correct probe size
682   onD6DLastProbeAcked(conn);
683   EXPECT_EQ(d6d.state, D6DMachineState::SEARCH_COMPLETE);
684   EXPECT_EQ(d6d.currentProbeSize, 1400);
685   EXPECT_EQ(conn.udpSendPacketLen, 1400);
686   EXPECT_EQ(d6d.meta.lastNonSearchState, D6DMachineState::BASE);
687   EXPECT_GT(d6d.meta.timeLastNonSearchState, now);
688 }
689 
690 } // namespace test
691 } // namespace quic
692