/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace folly::test; using namespace testing; using namespace folly; namespace quic { namespace test { class MockLossTimeout { public: MOCK_METHOD0(cancelLossTimeout, void()); MOCK_METHOD1(scheduleLossTimeout, void(std::chrono::milliseconds)); MOCK_METHOD0(isLossTimeoutScheduled, bool()); }; enum class PacketType { Handshake, ZeroRtt, OneRtt, }; class QuicLossFunctionsTest : public TestWithParam { public: void SetUp() override { aead = createNoOpAead(); headerCipher = createNoOpHeaderCipher(); transportInfoCb_ = std::make_unique(); connIdAlgo_ = std::make_unique(); } PacketNum sendPacket( QuicConnectionStateBase& conn, TimePoint time, folly::Optional associatedEvent, PacketType packetType); std::unique_ptr createConn() { auto conn = std::make_unique(); 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->infoCallback = 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); return conn; } std::unique_ptr createClientConn() { auto conn = std::make_unique( 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->infoCallback = 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; } EventBase evb; std::unique_ptr aead; std::unique_ptr headerCipher; MockLossTimeout timeout; std::unique_ptr transportInfoCb_; std::unique_ptr connIdAlgo_; }; auto testingLossMarkFunc(std::vector& lostPackets) { return [&lostPackets]( auto& /* conn */, auto& packet, bool processed, PacketNum) { if (!processed) { auto packetNum = packet.header.getPacketSequenceNum(); lostPackets.push_back(packetNum); } }; } PacketNum QuicLossFunctionsTest::sendPacket( QuicConnectionStateBase& conn, TimePoint time, folly::Optional associatedEvent, PacketType packetType) { folly::Optional header; bool isHandshake = false; switch (packetType) { case PacketType::Handshake: header = LongHeader( LongHeader::Types::Handshake, *conn.clientConnectionId, *conn.serverConnectionId, conn.ackStates.handshakeAckState.nextPacketNum, *conn.version); 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); EXPECT_TRUE(builder.canBuildPacket()); auto packet = std::move(builder).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, encodedSize); outstandingPacket.associatedEvent = associatedEvent; if (isHandshake) { conn.outstandingHandshakePacketsCount++; conn.lossState.lastHandshakePacketSentTime = time; } conn.lossState.lastRetransmittablePacketSentTime = time; if (conn.congestionController) { conn.congestionController->onPacketSent(outstandingPacket); } if (associatedEvent) { conn.outstandingClonedPacketsCount++; // Simulates what the real writer does. auto it = std::find_if( conn.outstandingPackets.begin(), conn.outstandingPackets.end(), [&associatedEvent](const auto& packet) { auto packetNum = packet.packet.header.getPacketSequenceNum(); return packetNum == *associatedEvent; }); if (it != conn.outstandingPackets.end()) { if (!it->associatedEvent) { conn.outstandingPacketEvents.emplace(*associatedEvent); conn.outstandingClonedPacketsCount++; it->associatedEvent = *associatedEvent; } } } conn.outstandingPackets.emplace_back(std::move(outstandingPacket)); conn.lossState.largestSent = getNextPacketNum(conn, packetNumberSpace); increaseNextPacketNum(conn, packetNumberSpace); conn.pendingEvents.setLossDetectionAlarm = true; return conn.lossState.largestSent; } TEST_F(QuicLossFunctionsTest, AllPacketsProcessed) { auto conn = createConn(); EXPECT_CALL(*transportInfoCb_, onPTO()).Times(0); auto pkt1 = conn->ackStates.appDataAckState.nextPacketNum; sendPacket(*conn, Clock::now(), pkt1, PacketType::OneRtt); auto pkt2 = conn->ackStates.appDataAckState.nextPacketNum; sendPacket(*conn, Clock::now(), pkt2, PacketType::OneRtt); auto pkt3 = conn->ackStates.appDataAckState.nextPacketNum; sendPacket(*conn, Clock::now(), pkt3, 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(1); EXPECT_CALL(timeout, scheduleLossTimeout(_)).Times(1); setLossDetectionAlarm(*conn, timeout); EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm); } TEST_F(QuicLossFunctionsTest, TestOnLossDetectionAlarm) { auto conn = createConn(); auto mockCongestionController = std::make_unique(); 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 lostPacket; MockClock::mockNow = []() { return TimePoint(23ms); }; EXPECT_CALL(*transportInfoCb_, onPTO()); setLossDetectionAlarm(*conn, timeout); EXPECT_EQ(LossState::AlarmMethod::PTO, conn->lossState.currentAlarmMethod); onLossDetectionAlarm( *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(*conn, timeout); EXPECT_CALL(*rawCongestionController, onPacketAckOrLoss(_, _)).Times(0); onLossDetectionAlarm( *conn, testingLossMarkFunc(lostPacket)); EXPECT_EQ(conn->lossState.ptoCount, 2); // PTO doesn't take anything out of outstandingPackets EXPECT_FALSE(conn->outstandingPackets.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(); 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 // outstandingPacketEvents, they are all processed and will skip lossVisitor for (auto i = 0; i < 10; i++) { sendPacket(*conn, TimePoint(), i, PacketType::OneRtt); } EXPECT_EQ(10, conn->outstandingPackets.size()); std::vector lostPackets; EXPECT_CALL(*rawCongestionController, onRemoveBytesFromInflight(_)).Times(0); EXPECT_CALL(*transportInfoCb_, onPTO()); onPTOAlarm(*conn); EXPECT_EQ(10, conn->outstandingPackets.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->outstandingPackets.size()); auto& packet = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet; auto packetNum = packet.header.getPacketSequenceNum(); markPacketLoss(*conn, packet, false, packetNum); 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, 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->outstandingPackets.size()); auto packet = conn->outstandingPackets[folly::Random::rand32() % 3]; markPacketLoss( *conn, packet.packet, false, packet.packet.header.getPacketSequenceNum()); 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->outstandingPackets.size(), 1); EXPECT_GT(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0); auto& packet = conn->outstandingPackets.front().packet; auto packetNum = packet.header.getPacketSequenceNum(); cancelHandshakeCryptoStreamRetransmissions(*conn->cryptoState); markPacketLoss(*conn, packet, false, packetNum); 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->outstandingPackets.size(), 1); EXPECT_GT(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0); auto& packet = conn->outstandingPackets.front().packet; auto packetNum = packet.header.getPacketSequenceNum(); markPacketLoss(*conn, packet, false, packetNum); 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, packet.header.getPacketSequenceNum()); EXPECT_TRUE(stream1->lossBuffer.empty()); EXPECT_TRUE(stream1->retransmissionBuffer.empty()); EXPECT_TRUE(stream1->writeBuffer.empty()); } TEST_F(QuicLossFunctionsTest, TestReorderingThreshold) { std::vector lostPacket; auto conn = createConn(); auto mockCongestionController = std::make_unique(); auto rawCongestionController = mockCongestionController.get(); conn->congestionController = std::move(mockCongestionController); EXPECT_CALL(*rawCongestionController, onPacketSent(_)) .WillRepeatedly(Return()); auto testingLossMarkFunc = [&lostPacket](auto& /*conn*/, auto& packet, bool, PacketNum) { 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->outstandingHandshakePacketsCount); // Assume some packets are already acked for (auto iter = getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake) + 2; iter < getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake) + 5; iter++) { if (iter->isHandshake) { conn->outstandingHandshakePacketsCount--; } } auto firstHandshakeOpIter = getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake); conn->outstandingPackets.erase( firstHandshakeOpIter + 2, firstHandshakeOpIter + 5); // Ack for packet 9 arrives auto lossEvent = detectLossPackets( *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->outstandingHandshakePacketsCount); // Packet 6 should remain in packet as the delta is less than threshold EXPECT_EQ(conn->outstandingPackets.size(), 1); auto packetNum = conn->outstandingPackets.front().packet.header.getPacketSequenceNum(); EXPECT_EQ(packetNum, 6); } TEST_F(QuicLossFunctionsTest, TestHandleAckForLoss) { auto conn = createConn(); auto qLogger = std::make_shared(VantagePoint::SERVER); conn->qLogger = qLogger; 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->outstandingPackets.emplace_back( OutstandingPacket(outstandingRegularPacket, now, 0, false, 0)); bool testLossMarkFuncCalled = false; auto testLossMarkFunc = [&](auto& /* conn */, auto&, bool, PacketNum) { testLossMarkFuncCalled = true; }; CongestionController::AckEvent ackEvent; ackEvent.ackTime = now; ackEvent.largestAckedPacket = 1000; handleAckForLoss( *conn, testLossMarkFunc, ackEvent, PacketNumberSpace::Handshake); EXPECT_EQ(0, conn->lossState.ptoCount); EXPECT_TRUE(conn->outstandingPackets.empty()); EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm); EXPECT_TRUE(testLossMarkFuncCalled); std::vector indices = getQLogEventIndices(QLogEventType::PacketsLost, qLogger); EXPECT_EQ(indices.size(), 1); auto tmp = std::move(qLogger->logs[indices[0]]); auto event = dynamic_cast(tmp.get()); EXPECT_EQ(event->largestLostPacketNum, 1); EXPECT_EQ(event->lostBytes, 0); EXPECT_EQ(event->lostPackets, 1); } TEST_F(QuicLossFunctionsTest, TestHandleAckedPacket) { auto conn = createConn(); auto qLogger = std::make_shared(VantagePoint::SERVER); conn->qLogger = qLogger; conn->lossState.ptoCount = 10; conn->lossState.handshakeAlarmCount = 5; conn->lossState.reorderingThreshold = 10; sendPacket(*conn, TimePoint(), folly::none, PacketType::OneRtt); ReadAckFrame ackFrame; ackFrame.largestAcked = conn->lossState.largestSent; ackFrame.ackBlocks.emplace_back( conn->lossState.largestSent, conn->lossState.largestSent); bool testLossMarkFuncCalled = false; auto testLossMarkFunc = [&](auto& /* conn */, auto&, bool, PacketNum) { 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_EQ(0, conn->lossState.handshakeAlarmCount); EXPECT_TRUE(conn->outstandingPackets.empty()); EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm); EXPECT_FALSE(testLossMarkFuncCalled); ASSERT_TRUE(conn->outstandingPackets.empty()); setLossDetectionAlarm(*conn, timeout); EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm); std::vector indices = getQLogEventIndices(QLogEventType::PacketAck, qLogger); EXPECT_EQ(indices.size(), 1); auto tmp = std::move(qLogger->logs[indices[0]]); auto event = dynamic_cast(tmp.get()); EXPECT_EQ(event->packetNumSpace, PacketNumberSpace::AppData); EXPECT_EQ(event->packetNum, 1); } 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->outstandingPackets.size(), 1); EXPECT_TRUE(conn->pendingEvents.resets.empty()); auto& packet = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet; markPacketLoss(*conn, packet, false, packet.header.getPacketSequenceNum()); 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, PacketNum /* currentPacketNum */) { 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->outstandingPackets.size()); auto& packet = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet; auto packetNum = packet.header.getPacketSequenceNum(); markPacketLoss(*conn, packet, false, packetNum); EXPECT_TRUE(conn->streamManager->pendingWindowUpdate(stream->id)); } TEST_F(QuicLossFunctionsTest, TestTimeReordering) { std::vector lostPacket; auto conn = createConn(); auto mockCongestionController = std::make_unique(); 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->outstandingPackets.erase( getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 2, getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) + 5); auto lossEvent = detectLossPackets( *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 EXPECT_EQ(lostPacket.size(), 2); EXPECT_EQ(lostPacket.front(), 1); EXPECT_EQ(lostPacket.back(), 2); // Packet 6, 7 should remain in outstanding packet list EXPECT_EQ(2, conn->outstandingPackets.size()); auto packetNum = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) ->packet.header.getPacketSequenceNum(); EXPECT_EQ(packetNum, 6); EXPECT_TRUE(conn->lossState.appDataLossTime); } TEST_F(QuicLossFunctionsTest, LossTimePreemptsCryptoTimer) { std::vector lostPackets; auto conn = createConn(); conn->lossState.srtt = 100ms; conn->lossState.lrtt = 100ms; auto expectedDelayUntilLost = 900000us / 8; 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( *conn, second, testingLossMarkFunc(lostPackets), lossTime, PacketNumberSpace::Handshake); EXPECT_TRUE(lostPackets.empty()); EXPECT_TRUE(conn->lossState.handshakeLossTime.hasValue()); EXPECT_EQ( expectedDelayUntilLost + sendTime, conn->lossState.handshakeLossTime.value()); MockClock::mockNow = [=]() { return sendTime; }; auto alarm = calculateAlarmDuration(*conn); EXPECT_EQ( std::chrono::duration_cast( 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->outstandingPackets.pop_back(); MockClock::mockNow = [=]() { return sendTime + expectedDelayUntilLost + 5s; }; onLossDetectionAlarm( *conn, testingLossMarkFunc(lostPackets)); EXPECT_EQ(1, lostPackets.size()); EXPECT_FALSE(conn->lossState.handshakeLossTime.hasValue()); EXPECT_TRUE(conn->outstandingPackets.empty()); } TEST_F(QuicLossFunctionsTest, PTONoLongerMarksPacketsToBeRetransmitted) { auto conn = createConn(); auto mockCongestionController = std::make_unique(); auto rawCongestionController = mockCongestionController.get(); conn->congestionController = std::move(mockCongestionController); EXPECT_CALL(*rawCongestionController, onPacketSent(_)) .WillRepeatedly(Return()); TimePoint startTime(123ms); MockClock::mockNow = [&]() { return startTime; }; std::vector lostPackets; for (auto i = 0; i < kPacketToSendForPTO + 10; i++) { sendPacket(*conn, startTime, folly::none, PacketType::OneRtt); setLossDetectionAlarm(*conn, timeout); startTime += 1ms; } EXPECT_CALL(*rawCongestionController, onPacketAckOrLoss(_, _)).Times(0); EXPECT_CALL(*transportInfoCb_, onPTO()); onLossDetectionAlarm( *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, WhenHandshakeOutstandingAlarmMarksAllHandshakeAsLoss) { auto conn = createConn(); auto qLogger = std::make_shared(VantagePoint::SERVER); conn->qLogger = qLogger; auto mockCongestionController = std::make_unique(); auto rawCongestionController = mockCongestionController.get(); conn->congestionController = std::move(mockCongestionController); EXPECT_CALL(*rawCongestionController, onPacketSent(_)) .WillRepeatedly(Return()); std::vector lostPackets; PacketNum expectedLargestLostNum = 0; conn->lossState.currentAlarmMethod = LossState::AlarmMethod::Handshake; 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); } uint64_t expectedLostBytes = std::accumulate( conn->outstandingPackets.begin(), conn->outstandingPackets.end(), 0, [](uint64_t num, const OutstandingPacket& packet) { return packet.isHandshake ? num + packet.encodedSize : num; }); EXPECT_CALL( *rawCongestionController, onRemoveBytesFromInflight(expectedLostBytes)) .Times(1); onLossDetectionAlarm( *conn, testingLossMarkFunc(lostPackets)); // Half are lost EXPECT_EQ(5, lostPackets.size()); EXPECT_EQ(1, conn->lossState.handshakeAlarmCount); EXPECT_EQ(5, conn->lossState.timeoutBasedRtxCount); EXPECT_EQ(conn->pendingEvents.numProbePackets, 0); EXPECT_EQ(5, conn->lossState.rtxCount); std::vector indices = getQLogEventIndices(QLogEventType::LossAlarm, qLogger); EXPECT_EQ(indices.size(), 1); auto tmp = std::move(qLogger->logs[indices[0]]); auto event = dynamic_cast(tmp.get()); EXPECT_EQ(event->largestSent, 5); EXPECT_EQ(event->alarmCount, 0); EXPECT_EQ(event->outstandingPackets, 10); EXPECT_EQ(event->type, kHandshakeAlarm); } TEST_F(QuicLossFunctionsTest, HandshakeAlarmWithOneRttCipher) { auto conn = createClientConn(); auto qLogger = std::make_shared(VantagePoint::CLIENT); conn->qLogger = qLogger; conn->oneRttWriteCipher = createNoOpAead(); conn->lossState.currentAlarmMethod = LossState::AlarmMethod::Handshake; std::vector lostPackets; sendPacket(*conn, TimePoint(100ms), folly::none, PacketType::Handshake); onLossDetectionAlarm( *conn, testingLossMarkFunc(lostPackets)); // Half should be marked as loss EXPECT_EQ(lostPackets.size(), 1); EXPECT_EQ(conn->lossState.handshakeAlarmCount, 1); EXPECT_EQ(conn->pendingEvents.numProbePackets, kPacketToSendForPTO); std::vector indices = getQLogEventIndices(QLogEventType::LossAlarm, qLogger); EXPECT_EQ(indices.size(), 1); auto tmp = std::move(qLogger->logs[indices[0]]); auto event = dynamic_cast(tmp.get()); EXPECT_EQ(event->largestSent, 1); EXPECT_EQ(event->alarmCount, 0); EXPECT_EQ(event->outstandingPackets, 1); EXPECT_EQ(event->type, kHandshakeAlarm); } TEST_F(QuicLossFunctionsTest, EmptyOutstandingNoTimeout) { auto conn = createConn(); EXPECT_CALL(timeout, cancelLossTimeout()).Times(1); setLossDetectionAlarm(*conn, timeout); } TEST_F(QuicLossFunctionsTest, AlarmDurationHandshakeOutstanding) { auto conn = createConn(); conn->lossState.maxAckDelay = 25ms; TimePoint lastPacketSentTime = Clock::now(); std::chrono::milliseconds packetSentDelay = 10ms; auto thisMoment = lastPacketSentTime + packetSentDelay; MockClock::mockNow = [=]() { return thisMoment; }; sendPacket(*conn, lastPacketSentTime, folly::none, PacketType::Handshake); MockClock::mockNow = [=]() { return thisMoment; }; auto duration = calculateAlarmDuration(*conn); EXPECT_EQ( conn->transportSettings.initialRtt * 2 - packetSentDelay + 25ms, duration.first); EXPECT_EQ(duration.second, LossState::AlarmMethod::Handshake); conn->lossState.srtt = 100ms; duration = calculateAlarmDuration(*conn); EXPECT_EQ( std::chrono::duration_cast(225ms) - packetSentDelay, duration.first); conn->lossState.maxAckDelay = 45ms; conn->lossState.handshakeAlarmCount = 2; duration = calculateAlarmDuration(*conn); EXPECT_EQ( std::chrono::duration_cast(980ms) - packetSentDelay, duration.first); } TEST_F(QuicLossFunctionsTest, AlarmDurationHasLossTime) { auto conn = createConn(); TimePoint lastPacketSentTime = Clock::now(); auto thisMoment = lastPacketSentTime; MockClock::mockNow = [=]() { return thisMoment; }; conn->lossState.appDataLossTime = thisMoment + 100ms; conn->lossState.srtt = 200ms; conn->lossState.lrtt = 150ms; sendPacket(*conn, lastPacketSentTime, folly::none, PacketType::OneRtt); auto duration = calculateAlarmDuration(*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.appDataLossTime = lastPacketSentTime + 100ms; conn->lossState.srtt = 200ms; conn->lossState.lrtt = 150ms; sendPacket(*conn, lastPacketSentTime, folly::none, PacketType::OneRtt); auto duration = calculateAlarmDuration(*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(*conn); EXPECT_EQ(duration.second, LossState::AlarmMethod::PTO); setLossDetectionAlarm(*conn, timeout); EXPECT_EQ(conn->lossState.currentAlarmMethod, LossState::AlarmMethod::PTO); conn->lossState.ptoCount = 2; auto newDuration = calculateAlarmDuration(*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, PacketNum /* currentPacketNum */) { 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, PacketNum /* currentPacketNum */) { 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; sendPacket(*conn, Clock::now(), lastSent, 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, PacketNum /* currentPacketNum */) { if (!processed) { lossVisitorCount++; } }; // Send 6 packets, so when we ack the last one, we mark the first two loss PacketNum lastSent; PacketEvent event = 0; for (size_t i = 0; i < 6; i++) { lastSent = sendPacket(*conn, Clock::now(), event, PacketType::OneRtt); } EXPECT_EQ(6, conn->outstandingPackets.size()); // Add the PacketEvent to the outstandingPacketEvents set conn->outstandingPacketEvents.insert(event); // Ack the last sent packet. Despite two losses, lossVisitor only visit one // packet detectLossPackets( *conn, lastSent, countingLossVisitor, TimePoint(100ms), PacketNumberSpace::AppData); EXPECT_EQ(1, lossVisitorCount); EXPECT_EQ(4, conn->outstandingPackets.size()); } TEST_F(QuicLossFunctionsTest, DetectPacketLossClonedPacketsCounter) { auto conn = createConn(); auto packet1 = conn->ackStates.appDataAckState.nextPacketNum; sendPacket(*conn, Clock::now(), packet1, 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, PacketNum) {}; detectLossPackets( *conn, ackedPacket, noopLossMarker, Clock::now(), PacketNumberSpace::AppData); EXPECT_EQ(0, conn->outstandingClonedPacketsCount); } TEST_F(QuicLossFunctionsTest, TestMarkPacketLossProcessedPacket) { MockAsyncUDPSocket socket(&evb); auto conn = createConn(); ASSERT_TRUE(conn->outstandingPackets.empty()); ASSERT_TRUE(conn->outstandingPacketEvents.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; auto nextPacketNum = conn->ackStates.appDataAckState.nextPacketNum; // 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->outstandingPackets.size()); ASSERT_TRUE(conn->outstandingPacketEvents.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, nextPacketNum); 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 qLogger = std::make_shared(VantagePoint::SERVER); conn->qLogger = qLogger; conn->lossState.totalPTOCount = 100; EXPECT_CALL(*transportInfoCb_, onPTO()); onPTOAlarm(*conn); EXPECT_EQ(101, conn->lossState.totalPTOCount); std::vector indices = getQLogEventIndices(QLogEventType::LossAlarm, qLogger); EXPECT_EQ(indices.size(), 1); auto tmp = std::move(qLogger->logs[indices[0]]); auto event = dynamic_cast(tmp.get()); EXPECT_EQ(event->largestSent, 0); EXPECT_EQ(event->alarmCount, 1); EXPECT_EQ(event->outstandingPackets, 0); EXPECT_EQ(event->type, kPtoAlarm); } TEST_F(QuicLossFunctionsTest, TestExceedsMaxPTOThrows) { auto conn = createConn(); auto qLogger = std::make_shared(VantagePoint::SERVER); conn->qLogger = qLogger; conn->transportSettings.maxNumPTOs = 3; EXPECT_CALL(*transportInfoCb_, onPTO()).Times(3); onPTOAlarm(*conn); onPTOAlarm(*conn); EXPECT_THROW(onPTOAlarm(*conn), QuicInternalException); std::vector indices = getQLogEventIndices(QLogEventType::LossAlarm, qLogger); EXPECT_EQ(indices.size(), 3); for (int i = 0; i < 3; ++i) { auto tmp = std::move(qLogger->logs[indices[i]]); auto event = dynamic_cast(tmp.get()); EXPECT_EQ(event->largestSent, 0); EXPECT_EQ(event->alarmCount, i + 1); EXPECT_EQ(event->outstandingPackets, 0); EXPECT_EQ(event->type, kPtoAlarm); } } 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->outstandingPackets.size()); uint32_t lostPackets = 0; auto countingLossVisitor = [&](auto& /* conn */, auto& /* packet */, bool processed, PacketNum /* currentPacketNum */) { 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(); 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 // outstandingPacketEvents, 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->outstandingPackets.empty()); EXPECT_EQ(4, conn->outstandingPackets.size()); std::vector> lostPackets; // onRemoveBytesFromInflight should still happen EXPECT_CALL(*rawCongestionController, onRemoveBytesFromInflight(_)).Times(1); markZeroRttPacketsLost( *conn, [&lostPackets](auto&, auto&, bool processed, PacketNum packetNum) { lostPackets.emplace_back(packetNum, processed); }); EXPECT_EQ(2, conn->outstandingPackets.size()); EXPECT_EQ(lostPackets.size(), 2); for (auto lostPacket : lostPackets) { EXPECT_FALSE(lostPacket.second); } for (size_t i = 0; i < conn->outstandingPackets.size(); ++i) { auto longHeader = conn->outstandingPackets[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(); 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 // outstandingPacketEvents, they are all processed and will skip lossVisitor std::set zeroRttPackets; folly::Optional lastPacket; for (auto i = 0; i < 2; i++) { lastPacket = sendPacket(*conn, TimePoint(), lastPacket, PacketType::ZeroRtt); zeroRttPackets.emplace(*lastPacket); } zeroRttPackets.emplace( sendPacket(*conn, TimePoint(), folly::none, PacketType::ZeroRtt)); for (auto zeroRttPacketNum : zeroRttPackets) { lastPacket = sendPacket(*conn, TimePoint(), zeroRttPacketNum, PacketType::OneRtt); } EXPECT_EQ(6, conn->outstandingPackets.size()); ASSERT_EQ(conn->outstandingClonedPacketsCount, 6); ASSERT_EQ(conn->outstandingPacketEvents.size(), 2); std::vector> lostPackets; // onRemoveBytesFromInflight should still happen EXPECT_CALL(*rawCongestionController, onRemoveBytesFromInflight(_)).Times(1); markZeroRttPacketsLost( *conn, [&lostPackets](auto&, auto&, bool processed, PacketNum packetNum) { lostPackets.emplace_back(packetNum, processed); }); ASSERT_EQ(conn->outstandingPacketEvents.size(), 0); EXPECT_EQ(3, conn->outstandingPackets.size()); EXPECT_EQ(lostPackets.size(), 3); ASSERT_EQ(conn->outstandingClonedPacketsCount, 3); size_t numProcessed = 0; for (auto lostPacket : lostPackets) { numProcessed += lostPacket.second; } EXPECT_EQ(numProcessed, 1); for (size_t i = 0; i < conn->outstandingPackets.size(); ++i) { auto longHeader = conn->outstandingPackets[i].packet.header.asLong(); EXPECT_FALSE( longHeader && longHeader->getProtectionType() == ProtectionType::ZeroRtt); } } TEST_F(QuicLossFunctionsTest, PTOLargerThanMaxDelay) { QuicConnectionStateBase conn(QuicNodeType::Client); conn.lossState.maxAckDelay = 20s; EXPECT_GE(calculatePTO(conn), 20s); } 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, PacketNum packetNum) { EXPECT_EQ(packet1, packetNum); }; detectLossPackets( *conn, packet2, lossVisitor, referenceTime + conn->lossState.srtt * 9 / 8 + 5ms, PacketNumberSpace::AppData); } TEST_P(QuicLossFunctionsTest, CappedShiftNoCrash) { auto conn = createConn(); conn->lossState.handshakeAlarmCount = std::numeric_limitslossState.handshakeAlarmCount)>::max(); sendPacket(*conn, Clock::now(), folly::none, PacketType::Handshake); ASSERT_GT(conn->outstandingHandshakePacketsCount, 0); calculateAlarmDuration(*conn); conn->lossState.handshakeAlarmCount = 0; conn->outstandingHandshakePacketsCount = 0; conn->outstandingPackets.clear(); conn->lossState.ptoCount = std::numeric_limitslossState.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)); } INSTANTIATE_TEST_CASE_P( QuicLossFunctionsTests, QuicLossFunctionsTest, Values( PacketNumberSpace::Initial, PacketNumberSpace::Handshake, PacketNumberSpace::AppData)); } // namespace test } // namespace quic