/* * 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 #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->statsCallback = transportInfoCb_.get(); // create a serverConnectionId that is different from the client connId // with bits for processId and workerId set to 0 ServerConnectionIdParams params(0, 0, 0); conn->connIdAlgo = connIdAlgo_.get(); conn->serverConnectionId = *connIdAlgo_->encodeConnectionId(params); // for canSetLossTimerForAppData() conn->oneRttWriteCipher = createNoOpAead(); return conn; } std::unique_ptr 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->statsCallback = transportInfoCb_.get(); // create a serverConnectionId that is different from the client connId // with bits for processId and workerId set to 0 ServerConnectionIdParams params(0, 0, 0); conn->serverConnectionId = *connIdAlgo_.get()->encodeConnectionId(params); return conn; } 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.value_or(0)); builder.encodePacketHeader(); 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.outstandings.handshakePacketsCount++; conn.lossState.lastHandshakePacketSentTime = time; } conn.lossState.lastRetransmittablePacketSentTime = time; if (conn.congestionController) { conn.congestionController->onPacketSent(outstandingPacket); } if (associatedEvent) { conn.outstandings.clonedPacketsCount++; // Simulates what the real writer does. auto it = std::find_if( conn.outstandings.packets.begin(), conn.outstandings.packets.end(), [&associatedEvent](const auto& packet) { auto packetNum = packet.packet.header.getPacketSequenceNum(); return packetNum == *associatedEvent; }); if (it != conn.outstandings.packets.end()) { if (!it->associatedEvent) { conn.outstandings.packetEvents.emplace(*associatedEvent); conn.outstandings.clonedPacketsCount++; it->associatedEvent = *associatedEvent; } } } conn.outstandings.packets.emplace_back(std::move(outstandingPacket)); conn.lossState.largestSent = getNextPacketNum(conn, packetNumberSpace); increaseNextPacketNum(conn, packetNumberSpace); conn.pendingEvents.setLossDetectionAlarm = true; return conn.lossState.largestSent.value(); } 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 outstandings.packets EXPECT_FALSE(conn->outstandings.packets.empty()); EXPECT_TRUE(conn->pendingEvents.setLossDetectionAlarm); // PTO shouldn't mark loss EXPECT_TRUE(lostPacket.empty()); } TEST_F(QuicLossFunctionsTest, TestOnPTOSkipProcessed) { auto conn = createConn(); auto mockCongestionController = std::make_unique(); auto rawCongestionController = mockCongestionController.get(); conn->congestionController = std::move(mockCongestionController); EXPECT_CALL(*rawCongestionController, onPacketSent(_)) .WillRepeatedly(Return()); // By adding an associatedEvent that doesn't exist in the // outstandings.packetEvents, they are all processed and will skip lossVisitor for (auto i = 0; i < 10; i++) { sendPacket(*conn, TimePoint(), i, PacketType::OneRtt); } EXPECT_EQ(10, conn->outstandings.packets.size()); std::vector lostPackets; EXPECT_CALL(*rawCongestionController, onRemoveBytesFromInflight(_)).Times(0); EXPECT_CALL(*transportInfoCb_, onPTO()); onPTOAlarm(*conn); EXPECT_EQ(10, conn->outstandings.packets.size()); EXPECT_TRUE(lostPackets.empty()); } TEST_F(QuicLossFunctionsTest, TestMarkPacketLoss) { folly::EventBase evb; MockAsyncUDPSocket socket(&evb); auto conn = createConn(); EXPECT_CALL(*transportInfoCb_, onNewQuicStream()).Times(2); auto stream1Id = conn->streamManager->createNextBidirectionalStream().value()->id; auto stream2Id = conn->streamManager->createNextBidirectionalStream().value()->id; auto stream1 = conn->streamManager->findStream(stream1Id); auto stream2 = conn->streamManager->findStream(stream2Id); auto buf = buildRandomInputData(20); writeDataToQuicStream(*stream1, buf->clone(), true); writeDataToQuicStream(*stream2, buf->clone(), true); auto packetSeqNum = conn->ackStates.handshakeAckState.nextPacketNum; LongHeader header( LongHeader::Types::Handshake, *conn->clientConnectionId, *conn->serverConnectionId, packetSeqNum, *conn->version); writeQuicDataToSocket( socket, *conn, *conn->clientConnectionId, *conn->serverConnectionId, *aead, *headerCipher, *conn->version, conn->transportSettings.writeConnectionDataPacketsLimit); EXPECT_EQ(1, conn->outstandings.packets.size()); auto& packet = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet; 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, TestMarkPacketLossMerge) { folly::EventBase evb; MockAsyncUDPSocket socket(&evb); auto conn = createConn(); EXPECT_CALL(*transportInfoCb_, onNewQuicStream()).Times(1); auto stream1Id = conn->streamManager->createNextBidirectionalStream().value()->id; auto stream1 = conn->streamManager->findStream(stream1Id); auto buf1 = buildRandomInputData(20); writeDataToQuicStream(*stream1, buf1->clone(), false); writeQuicDataToSocket( socket, *conn, *conn->clientConnectionId, *conn->serverConnectionId, *aead, *headerCipher, *conn->version, conn->transportSettings.writeConnectionDataPacketsLimit); EXPECT_EQ(1, conn->outstandings.packets.size()); auto buf2 = buildRandomInputData(20); writeDataToQuicStream(*stream1, buf2->clone(), false); writeQuicDataToSocket( socket, *conn, *conn->clientConnectionId, *conn->serverConnectionId, *aead, *headerCipher, *conn->version, conn->transportSettings.writeConnectionDataPacketsLimit); EXPECT_EQ(2, conn->outstandings.packets.size()); auto& packet1 = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet; auto packetNum = packet1.header.getPacketSequenceNum(); markPacketLoss(*conn, packet1, false, packetNum); EXPECT_EQ(stream1->retransmissionBuffer.size(), 1); EXPECT_EQ(stream1->lossBuffer.size(), 1); auto& packet2 = getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet; packetNum = packet2.header.getPacketSequenceNum(); markPacketLoss(*conn, packet2, false, packetNum); EXPECT_EQ(stream1->retransmissionBuffer.size(), 0); EXPECT_EQ(stream1->lossBuffer.size(), 1); auto combined = buf1->clone(); combined->prependChain(buf2->clone()); auto& buffer = stream1->lossBuffer.front(); EXPECT_EQ(buffer.offset, 0); IOBufEqualTo eq; EXPECT_TRUE(eq(combined, buffer.data.move())); } TEST_F(QuicLossFunctionsTest, TestMarkPacketLossNoMerge) { folly::EventBase evb; MockAsyncUDPSocket socket(&evb); auto conn = createConn(); EXPECT_CALL(*transportInfoCb_, onNewQuicStream()).Times(1); auto stream1Id = conn->streamManager->createNextBidirectionalStream().value()->id; auto stream1 = conn->streamManager->findStream(stream1Id); auto buf1 = buildRandomInputData(20); writeDataToQuicStream(*stream1, buf1->clone(), false); writeQuicDataToSocket( socket, *conn, *conn->clientConnectionId, *conn->serverConnectionId, *aead, *headerCipher, *conn->version, conn->transportSettings.writeConnectionDataPacketsLimit); EXPECT_EQ(1, conn->outstandings.packets.size()); auto buf2 = buildRandomInputData(20); writeDataToQuicStream(*stream1, buf2->clone(), false); writeQuicDataToSocket( socket, *conn, *conn->clientConnectionId, *conn->serverConnectionId, *aead, *headerCipher, *conn->version, conn->transportSettings.writeConnectionDataPacketsLimit); EXPECT_EQ(2, conn->outstandings.packets.size()); auto buf3 = buildRandomInputData(20); writeDataToQuicStream(*stream1, buf3->clone(), false); writeQuicDataToSocket( socket, *conn, *conn->clientConnectionId, *conn->serverConnectionId, *aead, *headerCipher, *conn->version, conn->transportSettings.writeConnectionDataPacketsLimit); EXPECT_EQ(3, conn->outstandings.packets.size()); auto& packet1 = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet; auto packetNum = packet1.header.getPacketSequenceNum(); markPacketLoss(*conn, packet1, false, packetNum); EXPECT_EQ(stream1->retransmissionBuffer.size(), 2); EXPECT_EQ(stream1->lossBuffer.size(), 1); auto& packet3 = getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet; packetNum = packet3.header.getPacketSequenceNum(); markPacketLoss(*conn, packet3, false, packetNum); EXPECT_EQ(stream1->retransmissionBuffer.size(), 1); EXPECT_EQ(stream1->lossBuffer.size(), 2); auto& buffer1 = stream1->lossBuffer[0]; EXPECT_EQ(buffer1.offset, 0); IOBufEqualTo eq; EXPECT_TRUE(eq(buf1, buffer1.data.move())); auto& buffer3 = stream1->lossBuffer[1]; EXPECT_EQ(buffer3.offset, 40); EXPECT_TRUE(eq(buf3, buffer3.data.move())); } TEST_F(QuicLossFunctionsTest, RetxBufferSortedAfterLoss) { folly::EventBase evb; MockAsyncUDPSocket socket(&evb); auto conn = createConn(); auto stream = conn->streamManager->createNextBidirectionalStream().value(); auto buf1 = IOBuf::copyBuffer("Worse case scenario"); auto buf2 = IOBuf::copyBuffer("The hard problem"); auto buf3 = IOBuf::copyBuffer("And then we had a flash of insight..."); writeQuicPacket( *conn, *conn->clientConnectionId, *conn->serverConnectionId, socket, *stream, *buf1); writeQuicPacket( *conn, *conn->clientConnectionId, *conn->serverConnectionId, socket, *stream, *buf2); writeQuicPacket( *conn, *conn->clientConnectionId, *conn->serverConnectionId, socket, *stream, *buf3); EXPECT_EQ(3, stream->retransmissionBuffer.size()); EXPECT_EQ(3, conn->outstandings.packets.size()); auto packet = conn->outstandings.packets[folly::Random::rand32() % 3]; markPacketLoss( *conn, packet.packet, false, 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->outstandings.packets.size(), 1); EXPECT_GT(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0); auto& packet = conn->outstandings.packets.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->outstandings.packets.size(), 1); EXPECT_GT(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0); auto& packet = conn->outstandings.packets.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->outstandings.handshakePacketsCount); // Assume some packets are already acked for (auto iter = getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake) + 2; iter < getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake) + 5; iter++) { if (iter->isHandshake) { conn->outstandings.handshakePacketsCount--; } } auto firstHandshakeOpIter = getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake); conn->outstandings.packets.erase( firstHandshakeOpIter + 2, firstHandshakeOpIter + 5); // Ack for packet 9 arrives auto lossEvent = detectLossPackets( *conn, 9, testingLossMarkFunc, TimePoint(90ms), PacketNumberSpace::Handshake); EXPECT_EQ(2, lossEvent->largestLostPacketNum.value()); EXPECT_EQ(TimePoint(90ms), lossEvent->lossTime); // Packet 1,2 should be marked as loss EXPECT_EQ(lostPacket.size(), 2); EXPECT_EQ(lostPacket.front(), 1); EXPECT_EQ(lostPacket.back(), 2); // Packet 6 is the only thing remaining inflight, it is a handshake pkt EXPECT_EQ(1, conn->outstandings.handshakePacketsCount); // Packet 6 should remain in packet as the delta is less than threshold EXPECT_EQ(conn->outstandings.packets.size(), 1); auto packetNum = conn->outstandings.packets.front().packet.header.getPacketSequenceNum(); EXPECT_EQ(packetNum, 6); } TEST_F(QuicLossFunctionsTest, TestHandleAckForLoss) { auto conn = createConn(); auto mockQLogger = std::make_shared(VantagePoint::Server); conn->qLogger = mockQLogger; conn->lossState.ptoCount = 100; conn->lossState.reorderingThreshold = 10; LongHeader longHeader( LongHeader::Types::Handshake, *conn->clientConnectionId, *conn->serverConnectionId, conn->ackStates.handshakeAckState.nextPacketNum++, conn->version.value()); RegularQuicWritePacket outstandingRegularPacket(std::move(longHeader)); auto now = Clock::now(); conn->outstandings.packets.emplace_back( OutstandingPacket(outstandingRegularPacket, now, 0, false, 0)); bool testLossMarkFuncCalled = false; auto testLossMarkFunc = [&](auto& /* conn */, auto&, bool, PacketNum) { testLossMarkFuncCalled = true; }; EXPECT_CALL(*mockQLogger, addPacketsLost(1, 0, 1)); CongestionController::AckEvent ackEvent; ackEvent.ackTime = now; ackEvent.largestAckedPacket = 1000; handleAckForLoss( *conn, testLossMarkFunc, ackEvent, PacketNumberSpace::Handshake); EXPECT_EQ(0, conn->lossState.ptoCount); EXPECT_TRUE(conn->outstandings.packets.empty()); EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm); EXPECT_TRUE(testLossMarkFuncCalled); } TEST_F(QuicLossFunctionsTest, TestHandleAckedPacket) { auto conn = createConn(); auto mockQLogger = std::make_shared(VantagePoint::Server); conn->qLogger = mockQLogger; 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.value_or(0); ackFrame.ackBlocks.emplace_back( conn->lossState.largestSent.value_or(0), conn->lossState.largestSent.value_or(0)); bool testLossMarkFuncCalled = false; auto testLossMarkFunc = [&](auto& /* conn */, auto&, bool, 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->outstandings.packets.empty()); EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm); EXPECT_FALSE(testLossMarkFuncCalled); ASSERT_TRUE(conn->outstandings.packets.empty()); setLossDetectionAlarm(*conn, timeout); EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm); } TEST_F(QuicLossFunctionsTest, TestMarkRstLoss) { auto conn = createConn(); folly::EventBase evb; MockAsyncUDPSocket socket(&evb); auto stream = conn->streamManager->createNextBidirectionalStream().value(); auto currentOffset = stream->currentWriteOffset; RstStreamFrame rstFrame( stream->id, GenericApplicationErrorCode::UNKNOWN, currentOffset); conn->pendingEvents.resets.insert({stream->id, rstFrame}); writeQuicDataToSocket( socket, *conn, *conn->clientConnectionId, *conn->serverConnectionId, *aead, *headerCipher, *conn->version, conn->transportSettings.writeConnectionDataPacketsLimit); EXPECT_EQ(conn->outstandings.packets.size(), 1); EXPECT_TRUE(conn->pendingEvents.resets.empty()); auto& packet = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)->packet; markPacketLoss(*conn, packet, false, 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->outstandings.packets.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->outstandings.packets.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->outstandings.packets.size()); auto packetNum = getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData) ->packet.header.getPacketSequenceNum(); EXPECT_EQ(packetNum, 6); EXPECT_TRUE(conn->lossState.lossTimes[PacketNumberSpace::AppData]); } TEST_F(QuicLossFunctionsTest, LossTimePreemptsCryptoTimer) { std::vector lostPackets; auto conn = createConn(); conn->lossState.srtt = 100ms; conn->lossState.lrtt = 100ms; auto expectedDelayUntilLost = 500ms / conn->transportSettings.timeReorderingThreshDivisor; auto sendTime = Clock::now(); // Send two: sendPacket(*conn, sendTime, folly::none, PacketType::Handshake); PacketNum second = sendPacket(*conn, sendTime + 1ms, folly::none, PacketType::Handshake); auto lossTime = sendTime + 50ms; detectLossPackets( *conn, second, testingLossMarkFunc(lostPackets), lossTime, PacketNumberSpace::Handshake); EXPECT_TRUE(lostPackets.empty()); EXPECT_TRUE( conn->lossState.lossTimes[PacketNumberSpace::Handshake].has_value()); EXPECT_EQ( expectedDelayUntilLost + sendTime, conn->lossState.lossTimes[PacketNumberSpace::Handshake].value()); MockClock::mockNow = [=]() { return sendTime; }; auto alarm = calculateAlarmDuration(*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->outstandings.packets.pop_back(); MockClock::mockNow = [=]() { return sendTime + expectedDelayUntilLost + 5s; }; onLossDetectionAlarm( *conn, testingLossMarkFunc(lostPackets)); EXPECT_EQ(1, lostPackets.size()); EXPECT_FALSE( conn->lossState.lossTimes[PacketNumberSpace::Handshake].has_value()); EXPECT_TRUE(conn->outstandings.packets.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 mockQLogger = std::make_shared(VantagePoint::Server); conn->qLogger = mockQLogger; auto mockCongestionController = std::make_unique(); auto rawCongestionController = mockCongestionController.get(); conn->congestionController = std::move(mockCongestionController); EXPECT_CALL(*rawCongestionController, onPacketSent(_)) .WillRepeatedly(Return()); EXPECT_CALL(*mockQLogger, addLossAlarm(5, 1, 10, kHandshakeAlarm)); 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->outstandings.packets.begin(), conn->outstandings.packets.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); } TEST_F(QuicLossFunctionsTest, HandshakeAlarmWithOneRttCipher) { auto conn = createClientConn(); auto mockQLogger = std::make_shared(VantagePoint::Client); conn->qLogger = mockQLogger; conn->oneRttWriteCipher = createNoOpAead(); conn->lossState.currentAlarmMethod = LossState::AlarmMethod::Handshake; std::vector lostPackets; EXPECT_CALL(*mockQLogger, addLossAlarm(1, 1, 1, kHandshakeAlarm)); 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); } 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.lossTimes[PacketNumberSpace::AppData] = 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.lossTimes[PacketNumberSpace::AppData] = 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->outstandings.packets.size()); // Add the PacketEvent to the outstandings.packetEvents set conn->outstandings.packetEvents.insert(event); // Ack the last sent packet. Despite two losses, lossVisitor only visit one // packet detectLossPackets( *conn, lastSent, countingLossVisitor, TimePoint(100ms), PacketNumberSpace::AppData); EXPECT_EQ(1, lossVisitorCount); EXPECT_EQ(4, conn->outstandings.packets.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->outstandings.clonedPacketsCount); } TEST_F(QuicLossFunctionsTest, TestMarkPacketLossProcessedPacket) { MockAsyncUDPSocket socket(&evb); auto conn = createConn(); ASSERT_TRUE(conn->outstandings.packets.empty()); ASSERT_TRUE(conn->outstandings.packetEvents.empty()); auto stream1Id = conn->streamManager->createNextBidirectionalStream().value()->id; auto buf = folly::IOBuf::copyBuffer("I wrestled by the sea."); auto stream2Id = conn->streamManager->createNextBidirectionalStream().value()->id; conn->streamManager->queueWindowUpdate(stream2Id); conn->pendingEvents.connWindowUpdate = true; 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->outstandings.packets.size()); ASSERT_TRUE(conn->outstandings.packetEvents.empty()); uint32_t streamDataCounter = 0, streamWindowUpdateCounter = 0, connWindowUpdateCounter = 0; for (const auto& frame : getLastOutstandingPacket(*conn, PacketNumberSpace::AppData) ->packet.frames) { switch (frame.type()) { case QuicWriteFrame::Type::WriteStreamFrame_E: streamDataCounter++; break; case QuicWriteFrame::Type::MaxStreamDataFrame_E: streamWindowUpdateCounter++; break; case QuicWriteFrame::Type::MaxDataFrame_E: connWindowUpdateCounter++; break; default: CHECK(false) << "unexpected frame=" << (int)frame.type(); } } EXPECT_EQ(1, streamDataCounter); EXPECT_EQ(1, streamWindowUpdateCounter); EXPECT_EQ(1, connWindowUpdateCounter); // Force this packet to be a processed clone markPacketLoss(*conn, packet, true, 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 mockQLogger = std::make_shared(VantagePoint::Server); conn->qLogger = mockQLogger; conn->lossState.totalPTOCount = 100; EXPECT_CALL(*mockQLogger, addLossAlarm(0, 1, 0, kPtoAlarm)); EXPECT_CALL(*transportInfoCb_, onPTO()); onPTOAlarm(*conn); EXPECT_EQ(101, conn->lossState.totalPTOCount); } TEST_F(QuicLossFunctionsTest, HandshakeAlarmPTOCountingAndCallbacks) { auto conn = createConn(); auto mockQLogger = std::make_shared(VantagePoint::Server); conn->qLogger = mockQLogger; conn->lossState.ptoCount = 22; conn->lossState.totalPTOCount = 100; conn->lossState.handshakeAlarmCount = 3; EXPECT_CALL(*mockQLogger, addLossAlarm(0, 4, 0, kHandshakeAlarm)); EXPECT_CALL(*transportInfoCb_, onPTO()); onHandshakeAlarm(*conn, [](const auto&, auto, bool, PacketNum) {}); EXPECT_EQ(101, conn->lossState.totalPTOCount); EXPECT_EQ(23, conn->lossState.ptoCount); EXPECT_EQ(4, conn->lossState.handshakeAlarmCount); } TEST_F(QuicLossFunctionsTest, TestExceedsMaxPTOThrows) { auto conn = createConn(); auto mockQLogger = std::make_shared(VantagePoint::Server); conn->qLogger = mockQLogger; conn->transportSettings.maxNumPTOs = 3; for (int i = 1; i <= 3; i++) { EXPECT_CALL(*mockQLogger, addLossAlarm(0, i, 0, kPtoAlarm)); } EXPECT_CALL(*transportInfoCb_, onPTO()).Times(3); onPTOAlarm(*conn); onPTOAlarm(*conn); EXPECT_THROW(onPTOAlarm(*conn), QuicInternalException); } TEST_F(QuicLossFunctionsTest, TotalLossCount) { auto conn = createConn(); conn->congestionController = nullptr; PacketNum largestSent = 0; for (int i = 0; i < 10; i++) { largestSent = sendPacket(*conn, Clock::now(), folly::none, PacketType::OneRtt); } EXPECT_EQ(10, conn->outstandings.packets.size()); uint32_t lostPackets = 0; auto countingLossVisitor = [&](auto& /* conn */, auto& /* packet */, bool processed, 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 // outstandings.packetEvents, they are all processed and will skip lossVisitor for (auto i = 0; i < 2; i++) { sendPacket(*conn, TimePoint(), folly::none, PacketType::OneRtt); sendPacket(*conn, TimePoint(), folly::none, PacketType::ZeroRtt); } EXPECT_FALSE(conn->outstandings.packets.empty()); EXPECT_EQ(4, conn->outstandings.packets.size()); std::vector> 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->outstandings.packets.size()); EXPECT_EQ(lostPackets.size(), 2); for (auto lostPacket : lostPackets) { EXPECT_FALSE(lostPacket.second); } for (size_t i = 0; i < conn->outstandings.packets.size(); ++i) { auto longHeader = conn->outstandings.packets[i].packet.header.asLong(); EXPECT_FALSE( longHeader && longHeader->getProtectionType() == ProtectionType::ZeroRtt); } EXPECT_EQ(2, conn->lossState.rtxCount); } TEST_F(QuicLossFunctionsTest, TestZeroRttRejectedWithClones) { auto conn = createConn(); auto mockCongestionController = std::make_unique(); auto rawCongestionController = mockCongestionController.get(); conn->congestionController = std::move(mockCongestionController); EXPECT_CALL(*rawCongestionController, onPacketSent(_)) .WillRepeatedly(Return()); // By adding an associatedEvent that doesn't exist in the // outstandings.packetEvents, they are all processed and will skip lossVisitor std::set 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->outstandings.packets.size()); ASSERT_EQ(conn->outstandings.clonedPacketsCount, 6); ASSERT_EQ(conn->outstandings.packetEvents.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->outstandings.packetEvents.size(), 0); EXPECT_EQ(3, conn->outstandings.packets.size()); EXPECT_EQ(lostPackets.size(), 3); ASSERT_EQ(conn->outstandings.clonedPacketsCount, 3); size_t numProcessed = 0; for (auto lostPacket : lostPackets) { numProcessed += lostPacket.second; } EXPECT_EQ(numProcessed, 1); for (size_t i = 0; i < conn->outstandings.packets.size(); ++i) { auto longHeader = conn->outstandings.packets[i].packet.header.asLong(); EXPECT_FALSE( longHeader && longHeader->getProtectionType() == ProtectionType::ZeroRtt); } } TEST_F(QuicLossFunctionsTest, PTOLargerThanMaxDelay) { QuicConnectionStateBase conn(QuicNodeType::Client); conn.lossState.srtt = 1ms; conn.lossState.maxAckDelay = 20s; EXPECT_GE(calculatePTO(conn), 20s); } TEST_F(QuicLossFunctionsTest, InitialPTOs) { QuicConnectionStateBase conn(QuicNodeType::Client); conn.transportSettings.initialRtt = 20ms; EXPECT_EQ(40ms, calculatePTO(conn)); } TEST_F(QuicLossFunctionsTest, TimeThreshold) { auto conn = createConn(); conn->lossState.srtt = 10ms; auto referenceTime = Clock::now(); auto packet1 = sendPacket(*conn, referenceTime - 10ms, folly::none, PacketType::OneRtt); auto packet2 = sendPacket( *conn, referenceTime + conn->lossState.srtt / 2, folly::none, PacketType::OneRtt); auto lossVisitor = [&](const auto& /*conn*/, const auto& /*packet*/, bool, 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->outstandings.handshakePacketsCount, 0); calculateAlarmDuration(*conn); conn->lossState.handshakeAlarmCount = 0; conn->outstandings.handshakePacketsCount = 0; conn->outstandings.packets.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