1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-08-09 20:42:44 +03:00
Files
mvfst/quic/api/test/QuicPacketSchedulerTest.cpp
Matt Joras b8053a74e7 Fix middle starting iterator initialization
Summary: Previously this would set `wrappedAround_` to `true` if the result of `lower_bound` was the container end. This is not actually a useful behavior and results in no streams being written if e.g. there is only one stream in the writable collection. What we actually want is for the iterator to start over from the start of the collection in this case.

Reviewed By: siyengar

Differential Revision: D17856193

fbshipit-source-id: d4a285879f16bb6827446c35dcaf6dc48ac03876
2019-10-10 12:56:11 -07:00

821 lines
33 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
#include <quic/api/QuicPacketScheduler.h>
#include <folly/portability/GTest.h>
#include <quic/api/test/Mocks.h>
#include <quic/client/state/ClientStateMachine.h>
#include <quic/codec/QuicPacketBuilder.h>
#include <quic/codec/test/Mocks.h>
#include <quic/common/test/TestUtils.h>
#include <quic/server/state/ServerStateMachine.h>
#include <quic/state/QuicStreamFunctions.h>
using namespace quic;
using namespace testing;
namespace {
PacketNum addInitialOutstandingPacket(QuicConnectionStateBase& conn) {
PacketNum nextPacketNum =
getNextPacketNum(conn, PacketNumberSpace::Handshake);
std::vector<uint8_t> zeroConnIdData(quic::kDefaultConnectionIdSize, 0);
ConnectionId srcConnId(zeroConnIdData);
LongHeader header(
LongHeader::Types::Initial,
srcConnId,
conn.clientConnectionId.value_or(quic::test::getTestConnectionId()),
nextPacketNum,
QuicVersion::QUIC_DRAFT);
RegularQuicWritePacket packet(std::move(header));
conn.outstandingPackets.emplace_back(packet, Clock::now(), 0, true, false, 0);
conn.outstandingHandshakePacketsCount++;
increaseNextPacketNum(conn, PacketNumberSpace::Handshake);
return nextPacketNum;
}
PacketNum addHandshakeOutstandingPacket(QuicConnectionStateBase& conn) {
PacketNum nextPacketNum =
getNextPacketNum(conn, PacketNumberSpace::Handshake);
std::vector<uint8_t> zeroConnIdData(quic::kDefaultConnectionIdSize, 0);
ConnectionId srcConnId(zeroConnIdData);
LongHeader header(
LongHeader::Types::Handshake,
srcConnId,
conn.clientConnectionId.value_or(quic::test::getTestConnectionId()),
nextPacketNum,
QuicVersion::QUIC_DRAFT);
RegularQuicWritePacket packet(std::move(header));
conn.outstandingPackets.emplace_back(packet, Clock::now(), 0, true, false, 0);
conn.outstandingHandshakePacketsCount++;
increaseNextPacketNum(conn, PacketNumberSpace::Handshake);
return nextPacketNum;
}
PacketNum addPureAckOutstandingPacket(QuicConnectionStateBase& conn) {
PacketNum nextPacketNum = getNextPacketNum(conn, PacketNumberSpace::AppData);
ShortHeader header(
ProtectionType::KeyPhaseOne,
conn.clientConnectionId.value_or(quic::test::getTestConnectionId()),
nextPacketNum);
RegularQuicWritePacket packet(std::move(header));
conn.outstandingPackets.emplace_back(packet, Clock::now(), 0, false, true, 0);
increaseNextPacketNum(conn, PacketNumberSpace::AppData);
return nextPacketNum;
}
PacketNum addOutstandingPacket(QuicConnectionStateBase& conn) {
PacketNum nextPacketNum = getNextPacketNum(conn, PacketNumberSpace::AppData);
ShortHeader header(
ProtectionType::KeyPhaseOne,
conn.clientConnectionId.value_or(quic::test::getTestConnectionId()),
nextPacketNum);
RegularQuicWritePacket packet(std::move(header));
conn.outstandingPackets.emplace_back(
packet, Clock::now(), 0, false, false, 0);
increaseNextPacketNum(conn, PacketNumberSpace::AppData);
return nextPacketNum;
}
} // namespace
namespace quic {
namespace test {
class QuicPacketSchedulerTest : public Test {
public:
QuicVersion version{QuicVersion::MVFST};
};
TEST_F(QuicPacketSchedulerTest, NoopScheduler) {
QuicConnectionStateBase conn(QuicNodeType::Client);
FrameScheduler scheduler("frame");
EXPECT_FALSE(scheduler.hasData());
LongHeader header(
LongHeader::Types::Initial,
getTestConnectionId(1),
getTestConnectionId(),
0x1356,
version);
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(header),
conn.ackStates.initialAckState.largestAckedByPeer);
auto builtPacket = std::move(builder).buildPacket();
EXPECT_TRUE(builtPacket.packet.frames.empty());
}
TEST_F(QuicPacketSchedulerTest, CryptoPaddingInitialPacket) {
QuicClientConnectionState conn;
auto connId = getTestConnectionId();
LongHeader longHeader1(
LongHeader::Types::Initial,
getTestConnectionId(1),
connId,
getNextPacketNum(conn, PacketNumberSpace::Initial),
QuicVersion::MVFST);
increaseNextPacketNum(conn, PacketNumberSpace::Initial);
RegularQuicPacketBuilder builder1(
conn.udpSendPacketLen,
std::move(longHeader1),
conn.ackStates.initialAckState.largestAckedByPeer);
CryptoStreamScheduler scheduler(
conn, *getCryptoStream(*conn.cryptoState, EncryptionLevel::Initial));
writeDataToQuicStream(
conn.cryptoState->initialStream, folly::IOBuf::copyBuffer("chlo"));
scheduler.writeCryptoData(builder1);
EXPECT_EQ(builder1.remainingSpaceInPkt(), 0);
LongHeader longHeader2(
LongHeader::Types::Handshake,
connId,
connId,
getNextPacketNum(conn, PacketNumberSpace::Handshake),
QuicVersion::MVFST);
RegularQuicPacketBuilder builder2(
conn.udpSendPacketLen,
std::move(longHeader2),
conn.ackStates.handshakeAckState.largestAckedByPeer);
writeDataToQuicStream(
conn.cryptoState->initialStream, folly::IOBuf::copyBuffer("finished"));
scheduler.writeCryptoData(builder2);
EXPECT_GT(builder2.remainingSpaceInPkt(), 0);
}
TEST_F(QuicPacketSchedulerTest, CryptoServerInitialNotPadded) {
QuicServerConnectionState conn;
auto connId = getTestConnectionId();
PacketNum nextPacketNum = getNextPacketNum(conn, PacketNumberSpace::Initial);
LongHeader longHeader1(
LongHeader::Types::Initial,
getTestConnectionId(1),
connId,
nextPacketNum,
QuicVersion::MVFST);
RegularQuicPacketBuilder builder1(
conn.udpSendPacketLen,
std::move(longHeader1),
conn.ackStates.initialAckState.largestAckedByPeer);
CryptoStreamScheduler scheduler(
conn, *getCryptoStream(*conn.cryptoState, EncryptionLevel::Initial));
writeDataToQuicStream(
conn.cryptoState->initialStream, folly::IOBuf::copyBuffer("shlo"));
scheduler.writeCryptoData(builder1);
EXPECT_GT(builder1.remainingSpaceInPkt(), 0);
}
TEST_F(QuicPacketSchedulerTest, CryptoPaddingRetransmissionClientInitial) {
QuicClientConnectionState conn;
auto connId = getTestConnectionId();
LongHeader longHeader(
LongHeader::Types::Initial,
getTestConnectionId(1),
connId,
getNextPacketNum(conn, PacketNumberSpace::Initial),
QuicVersion::MVFST);
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(longHeader),
conn.ackStates.initialAckState.largestAckedByPeer);
CryptoStreamScheduler scheduler(
conn, *getCryptoStream(*conn.cryptoState, EncryptionLevel::Initial));
conn.cryptoState->initialStream.lossBuffer.push_back(
StreamBuffer{folly::IOBuf::copyBuffer("chlo"), 0, false});
scheduler.writeCryptoData(builder);
EXPECT_EQ(builder.remainingSpaceInPkt(), 0);
}
TEST_F(QuicPacketSchedulerTest, CryptoSchedulerOnlySingleLossFits) {
QuicServerConnectionState conn;
auto connId = getTestConnectionId();
LongHeader longHeader(
LongHeader::Types::Handshake,
connId,
connId,
getNextPacketNum(conn, PacketNumberSpace::Handshake),
QuicVersion::MVFST);
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(longHeader),
conn.ackStates.handshakeAckState.largestAckedByPeer);
PacketBuilderWrapper builderWrapper(builder, 13);
CryptoStreamScheduler scheduler(
conn, *getCryptoStream(*conn.cryptoState, EncryptionLevel::Handshake));
conn.cryptoState->handshakeStream.lossBuffer.push_back(
StreamBuffer{folly::IOBuf::copyBuffer("shlo"), 0, false});
conn.cryptoState->handshakeStream.lossBuffer.push_back(StreamBuffer{
folly::IOBuf::copyBuffer(
"certificatethatisverylongseriouslythisisextremelylongandcannotfitintoapacket"),
7,
false});
EXPECT_TRUE(scheduler.writeCryptoData(builderWrapper));
}
TEST_F(QuicPacketSchedulerTest, CryptoWritePartialLossBuffer) {
QuicClientConnectionState conn;
auto connId = getTestConnectionId();
LongHeader longHeader(
LongHeader::Types::Initial,
ConnectionId(std::vector<uint8_t>()),
connId,
getNextPacketNum(conn, PacketNumberSpace::Initial),
QuicVersion::MVFST);
RegularQuicPacketBuilder builder(
25,
std::move(longHeader),
conn.ackStates.initialAckState.largestAckedByPeer);
CryptoStreamScheduler scheduler(
conn, *getCryptoStream(*conn.cryptoState, EncryptionLevel::Initial));
conn.cryptoState->initialStream.lossBuffer.push_back(StreamBuffer{
folly::IOBuf::copyBuffer("return the special duration value max"),
0,
false});
EXPECT_TRUE(scheduler.writeCryptoData(builder));
EXPECT_EQ(builder.remainingSpaceInPkt(), 0);
EXPECT_FALSE(conn.cryptoState->initialStream.lossBuffer.empty());
}
TEST_F(QuicPacketSchedulerTest, StreamFrameSchedulerExists) {
QuicServerConnectionState conn;
conn.streamManager->setMaxLocalBidirectionalStreams(10);
auto connId = getTestConnectionId();
auto stream = conn.streamManager->createNextBidirectionalStream().value();
WindowUpdateScheduler scheduler(conn);
ShortHeader shortHeader(
ProtectionType::KeyPhaseZero,
connId,
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(shortHeader),
conn.ackStates.appDataAckState.largestAckedByPeer);
auto originalSpace = builder.remainingSpaceInPkt();
conn.streamManager->queueWindowUpdate(stream->id);
scheduler.writeWindowUpdates(builder);
EXPECT_LT(builder.remainingSpaceInPkt(), originalSpace);
}
TEST_F(QuicPacketSchedulerTest, StreamFrameNoSpace) {
QuicServerConnectionState conn;
conn.streamManager->setMaxLocalBidirectionalStreams(10);
auto connId = getTestConnectionId();
auto stream = conn.streamManager->createNextBidirectionalStream().value();
WindowUpdateScheduler scheduler(conn);
ShortHeader shortHeader(
ProtectionType::KeyPhaseZero,
connId,
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(shortHeader),
conn.ackStates.appDataAckState.largestAckedByPeer);
PacketBuilderWrapper builderWrapper(builder, 2);
auto originalSpace = builder.remainingSpaceInPkt();
conn.streamManager->queueWindowUpdate(stream->id);
scheduler.writeWindowUpdates(builderWrapper);
EXPECT_EQ(builder.remainingSpaceInPkt(), originalSpace);
}
TEST_F(QuicPacketSchedulerTest, StreamFrameSchedulerStreamNotExists) {
QuicServerConnectionState conn;
auto connId = getTestConnectionId();
StreamId nonExistentStream = 11;
WindowUpdateScheduler scheduler(conn);
ShortHeader shortHeader(
ProtectionType::KeyPhaseZero,
connId,
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(shortHeader),
conn.ackStates.appDataAckState.largestAckedByPeer);
auto originalSpace = builder.remainingSpaceInPkt();
conn.streamManager->queueWindowUpdate(nonExistentStream);
scheduler.writeWindowUpdates(builder);
EXPECT_EQ(builder.remainingSpaceInPkt(), originalSpace);
}
TEST_F(QuicPacketSchedulerTest, CloningSchedulerTest) {
QuicClientConnectionState conn;
FrameScheduler noopScheduler("frame");
ASSERT_FALSE(noopScheduler.hasData());
CloningScheduler cloningScheduler(noopScheduler, conn, "CopyCat", 0);
EXPECT_FALSE(cloningScheduler.hasData());
auto packetNum = addOutstandingPacket(conn);
// There needs to have retransmittable frame for the rebuilder to work
conn.outstandingPackets.back().packet.frames.push_back(
MaxDataFrame(conn.flowControlState.advertisedMaxOffset));
EXPECT_TRUE(cloningScheduler.hasData());
ASSERT_FALSE(noopScheduler.hasData());
ShortHeader header(
ProtectionType::KeyPhaseOne,
conn.clientConnectionId.value_or(getTestConnectionId()),
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(header),
conn.ackStates.appDataAckState.largestAckedByPeer);
auto result = cloningScheduler.scheduleFramesForPacket(
std::move(builder), kDefaultUDPSendPacketLen);
EXPECT_TRUE(result.first.hasValue() && result.second.hasValue());
EXPECT_EQ(packetNum, *result.first);
}
TEST_F(QuicPacketSchedulerTest, WriteOnlyOutstandingPacketsTest) {
QuicClientConnectionState conn;
FrameScheduler noopScheduler("frame");
ASSERT_FALSE(noopScheduler.hasData());
CloningScheduler cloningScheduler(noopScheduler, conn, "CopyCat", 0);
EXPECT_FALSE(cloningScheduler.hasData());
auto packetNum = addOutstandingPacket(conn);
// There needs to have retransmittable frame for the rebuilder to work
conn.outstandingPackets.back().packet.frames.push_back(
MaxDataFrame(conn.flowControlState.advertisedMaxOffset));
EXPECT_TRUE(cloningScheduler.hasData());
ASSERT_FALSE(noopScheduler.hasData());
ShortHeader header(
ProtectionType::KeyPhaseOne,
conn.clientConnectionId.value_or(getTestConnectionId()),
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder regularBuilder(
conn.udpSendPacketLen,
std::move(header),
conn.ackStates.appDataAckState.largestAckedByPeer);
// Create few frames
ConnectionCloseFrame connCloseFrame(
TransportErrorCode::FRAME_ENCODING_ERROR, "The sun is in the sky.");
MaxStreamsFrame maxStreamFrame(999, true);
PingFrame pingFrame;
IntervalSet<PacketNum> ackBlocks;
ackBlocks.insert(10, 100);
ackBlocks.insert(200, 1000);
AckFrameMetaData ackMeta(ackBlocks, 0us, kDefaultAckDelayExponent);
// Write those framses with a regular builder
writeFrame(connCloseFrame, regularBuilder);
writeFrame(QuicSimpleFrame(maxStreamFrame), regularBuilder);
writeFrame(pingFrame, regularBuilder);
writeAckFrame(ackMeta, regularBuilder);
auto result = cloningScheduler.scheduleFramesForPacket(
std::move(regularBuilder), kDefaultUDPSendPacketLen);
EXPECT_TRUE(result.first.hasValue() && result.second.hasValue());
EXPECT_EQ(packetNum, *result.first);
// written packet (result.second) should not have any frame in the builder
auto& writtenPacket = *result.second;
auto shortHeader = writtenPacket.packet.header.asShort();
CHECK(shortHeader);
EXPECT_EQ(ProtectionType::KeyPhaseOne, shortHeader->getProtectionType());
EXPECT_EQ(
conn.ackStates.appDataAckState.nextPacketNum,
shortHeader->getPacketSequenceNum());
// Test that the only frame that's written is maxdataframe
EXPECT_GE(writtenPacket.packet.frames.size(), 1);
auto& writtenFrame = writtenPacket.packet.frames.at(0);
auto maxDataFrame = writtenFrame.asMaxDataFrame();
CHECK(maxDataFrame);
for (auto& frame : writtenPacket.packet.frames) {
bool present = false;
/* the next four frames should not be written */
present |= frame.asConnectionCloseFrame() ? true : false;
present |= frame.asQuicSimpleFrame() ? true : false;
present |= frame.asPingFrame() ? true : false;
present |= frame.asWriteAckFrame() ? true : false;
ASSERT_FALSE(present);
}
}
TEST_F(QuicPacketSchedulerTest, DoNotCloneProcessedClonedPacket) {
QuicClientConnectionState conn;
FrameScheduler noopScheduler("frame");
CloningScheduler cloningScheduler(noopScheduler, conn, "CopyCat", 0);
// Add two outstanding packets, but then mark the second one processed by
// adding a PacketEvent that's missing from the outstandingPacketEvents set
PacketNum expected = addOutstandingPacket(conn);
// There needs to have retransmittable frame for the rebuilder to work
conn.outstandingPackets.back().packet.frames.push_back(
MaxDataFrame(conn.flowControlState.advertisedMaxOffset));
addOutstandingPacket(conn);
conn.outstandingPackets.back().associatedEvent = 1;
// There needs to have retransmittable frame for the rebuilder to work
conn.outstandingPackets.back().packet.frames.push_back(
MaxDataFrame(conn.flowControlState.advertisedMaxOffset));
ShortHeader header(
ProtectionType::KeyPhaseOne,
conn.clientConnectionId.value_or(getTestConnectionId()),
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(header),
conn.ackStates.initialAckState.largestAckedByPeer);
auto result = cloningScheduler.scheduleFramesForPacket(
std::move(builder), kDefaultUDPSendPacketLen);
EXPECT_TRUE(result.first.hasValue() && result.second.hasValue());
EXPECT_EQ(expected, *result.first);
}
TEST_F(QuicPacketSchedulerTest, DoNotClonePureAck) {
QuicClientConnectionState conn;
FrameScheduler noopScheduler("frame");
CloningScheduler cloningScheduler(noopScheduler, conn, "CopyCat", 0);
// Add two outstanding packets, with second one being pureAck
auto expected = addOutstandingPacket(conn);
// There needs to have retransmittable frame for the rebuilder to work
conn.outstandingPackets.back().packet.frames.push_back(
MaxDataFrame(conn.flowControlState.advertisedMaxOffset));
addPureAckOutstandingPacket(conn);
conn.outstandingPackets.back().packet.frames.push_back(WriteAckFrame());
ShortHeader header(
ProtectionType::KeyPhaseOne,
conn.clientConnectionId.value_or(getTestConnectionId()),
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(header),
conn.ackStates.appDataAckState.largestAckedByPeer);
auto result = cloningScheduler.scheduleFramesForPacket(
std::move(builder), kDefaultUDPSendPacketLen);
EXPECT_TRUE(result.first.hasValue() && result.second.hasValue());
EXPECT_EQ(expected, *result.first);
}
TEST_F(QuicPacketSchedulerTest, CloneSchedulerHasDataIgnoresNonAppData) {
QuicClientConnectionState conn;
FrameScheduler noopScheduler("frame");
CloningScheduler cloningScheduler(noopScheduler, conn, "CopyCat", 0);
EXPECT_FALSE(cloningScheduler.hasData());
addHandshakeOutstandingPacket(conn);
EXPECT_FALSE(cloningScheduler.hasData());
addInitialOutstandingPacket(conn);
EXPECT_FALSE(cloningScheduler.hasData());
addOutstandingPacket(conn);
EXPECT_TRUE(cloningScheduler.hasData());
}
TEST_F(QuicPacketSchedulerTest, DoNotCloneHandshake) {
QuicClientConnectionState conn;
FrameScheduler noopScheduler("frame");
CloningScheduler cloningScheduler(noopScheduler, conn, "CopyCat", 0);
// Add two outstanding packets, with second one being handshake
auto expected = addOutstandingPacket(conn);
// There needs to have retransmittable frame for the rebuilder to work
conn.outstandingPackets.back().packet.frames.push_back(
MaxDataFrame(conn.flowControlState.advertisedMaxOffset));
addHandshakeOutstandingPacket(conn);
conn.outstandingPackets.back().packet.frames.push_back(
MaxDataFrame(conn.flowControlState.advertisedMaxOffset));
ShortHeader header(
ProtectionType::KeyPhaseOne,
conn.clientConnectionId.value_or(getTestConnectionId()),
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(header),
conn.ackStates.appDataAckState.largestAckedByPeer);
auto result = cloningScheduler.scheduleFramesForPacket(
std::move(builder), kDefaultUDPSendPacketLen);
EXPECT_TRUE(result.first.hasValue() && result.second.hasValue());
EXPECT_EQ(expected, *result.first);
}
TEST_F(QuicPacketSchedulerTest, CloneSchedulerUseNormalSchedulerFirst) {
QuicClientConnectionState conn;
MockFrameScheduler mockScheduler;
CloningScheduler cloningScheduler(mockScheduler, conn, "Mocker", 0);
ShortHeader header(
ProtectionType::KeyPhaseOne,
conn.clientConnectionId.value_or(getTestConnectionId()),
getNextPacketNum(conn, PacketNumberSpace::AppData));
EXPECT_CALL(mockScheduler, hasData()).Times(1).WillOnce(Return(true));
EXPECT_CALL(mockScheduler, _scheduleFramesForPacket(_, _))
.Times(1)
.WillOnce(Invoke(
[&, headerCopy = header](
std::unique_ptr<RegularQuicPacketBuilder>&, uint32_t) mutable {
RegularQuicWritePacket packet(std::move(headerCopy));
packet.frames.push_back(MaxDataFrame(2832));
RegularQuicPacketBuilder::Packet builtPacket(
std::move(packet),
folly::IOBuf::copyBuffer("if you are the dealer"),
folly::IOBuf::copyBuffer("I'm out of the game"));
return std::make_pair(folly::none, std::move(builtPacket));
}));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(header),
conn.ackStates.appDataAckState.largestAckedByPeer);
auto result = cloningScheduler.scheduleFramesForPacket(
std::move(builder), kDefaultUDPSendPacketLen);
EXPECT_EQ(folly::none, result.first);
EXPECT_EQ(result.second->packet.header.getHeaderForm(), HeaderForm::Short);
ShortHeader& shortHeader = *result.second->packet.header.asShort();
EXPECT_EQ(ProtectionType::KeyPhaseOne, shortHeader.getProtectionType());
EXPECT_EQ(
conn.ackStates.appDataAckState.nextPacketNum,
shortHeader.getPacketSequenceNum());
EXPECT_EQ(1, result.second->packet.frames.size());
MaxDataFrame* maxDataFrame =
result.second->packet.frames.front().asMaxDataFrame();
ASSERT_NE(maxDataFrame, nullptr);
EXPECT_EQ(2832, maxDataFrame->maximumData);
EXPECT_TRUE(folly::IOBufEqualTo{}(
*folly::IOBuf::copyBuffer("if you are the dealer"),
*result.second->header));
EXPECT_TRUE(folly::IOBufEqualTo{}(
*folly::IOBuf::copyBuffer("I'm out of the game"), *result.second->body));
}
TEST_F(QuicPacketSchedulerTest, CloneWillGenerateNewWindowUpdate) {
QuicClientConnectionState conn;
conn.streamManager->setMaxLocalBidirectionalStreams(10);
auto stream = conn.streamManager->createNextBidirectionalStream().value();
FrameScheduler noopScheduler("frame");
CloningScheduler cloningScheduler(noopScheduler, conn, "GiantsShoulder", 0);
auto expectedPacketEvent = addOutstandingPacket(conn);
ASSERT_EQ(1, conn.outstandingPackets.size());
conn.outstandingPackets.back().packet.frames.push_back(MaxDataFrame(1000));
conn.outstandingPackets.back().packet.frames.push_back(
MaxStreamDataFrame(stream->id, 1000));
conn.flowControlState.advertisedMaxOffset = 1000;
stream->flowControlState.advertisedMaxOffset = 1000;
conn.flowControlState.sumCurReadOffset = 300;
conn.flowControlState.windowSize = 3000;
stream->currentReadOffset = 200;
stream->flowControlState.windowSize = 1500;
ShortHeader header(
ProtectionType::KeyPhaseOne,
conn.clientConnectionId.value_or(getTestConnectionId()),
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(header),
conn.ackStates.appDataAckState.largestAckedByPeer);
auto packetResult = cloningScheduler.scheduleFramesForPacket(
std::move(builder), conn.udpSendPacketLen);
EXPECT_EQ(expectedPacketEvent, *packetResult.first);
int32_t verifyConnWindowUpdate = 1, verifyStreamWindowUpdate = 1;
for (const auto& frame : packetResult.second->packet.frames) {
switch (frame.type()) {
case QuicWriteFrame::Type::MaxStreamDataFrame_E: {
const MaxStreamDataFrame& maxStreamDataFrame =
*frame.asMaxStreamDataFrame();
EXPECT_EQ(stream->id, maxStreamDataFrame.streamId);
verifyStreamWindowUpdate--;
break;
}
case QuicWriteFrame::Type::MaxDataFrame_E: {
verifyConnWindowUpdate--;
break;
}
case QuicWriteFrame::Type::PaddingFrame_E: {
break;
}
default:
// should never happen
EXPECT_TRUE(false);
}
}
EXPECT_EQ(0, verifyStreamWindowUpdate);
EXPECT_EQ(0, verifyConnWindowUpdate);
// Verify the built out packet has refreshed window update values
EXPECT_GE(packetResult.second->packet.frames.size(), 2);
uint32_t streamWindowUpdateCounter = 0;
uint32_t connWindowUpdateCounter = 0;
for (auto& frame : packetResult.second->packet.frames) {
auto streamFlowControl = frame.asMaxStreamDataFrame();
if (!streamFlowControl) {
continue;
}
streamWindowUpdateCounter++;
EXPECT_EQ(1700, streamFlowControl->maximumData);
}
for (auto& frame : packetResult.second->packet.frames) {
auto connFlowControl = frame.asMaxDataFrame();
if (!connFlowControl) {
continue;
}
connWindowUpdateCounter++;
EXPECT_EQ(3300, connFlowControl->maximumData);
}
EXPECT_EQ(1, connWindowUpdateCounter);
EXPECT_EQ(1, streamWindowUpdateCounter);
}
class AckSchedulingTest : public TestWithParam<PacketNumberSpace> {};
TEST_F(QuicPacketSchedulerTest, AckStateHasAcksToSchedule) {
QuicClientConnectionState conn;
EXPECT_FALSE(hasAcksToSchedule(conn.ackStates.initialAckState));
EXPECT_FALSE(hasAcksToSchedule(conn.ackStates.handshakeAckState));
EXPECT_FALSE(hasAcksToSchedule(conn.ackStates.appDataAckState));
conn.ackStates.initialAckState.acks.insert(0, 100);
EXPECT_TRUE(hasAcksToSchedule(conn.ackStates.initialAckState));
conn.ackStates.handshakeAckState.acks.insert(0, 100);
conn.ackStates.handshakeAckState.largestAckScheduled = 200;
EXPECT_FALSE(hasAcksToSchedule(conn.ackStates.handshakeAckState));
conn.ackStates.handshakeAckState.largestAckScheduled = folly::none;
EXPECT_TRUE(hasAcksToSchedule(conn.ackStates.handshakeAckState));
}
TEST_F(QuicPacketSchedulerTest, AckSchedulerHasAcksToSchedule) {
QuicClientConnectionState conn;
AckScheduler initialAckScheduler(
conn, getAckState(conn, PacketNumberSpace::Initial));
AckScheduler handshakeAckScheduler(
conn, getAckState(conn, PacketNumberSpace::Handshake));
AckScheduler appDataAckScheduler(
conn, getAckState(conn, PacketNumberSpace::AppData));
EXPECT_FALSE(initialAckScheduler.hasPendingAcks());
EXPECT_FALSE(handshakeAckScheduler.hasPendingAcks());
EXPECT_FALSE(appDataAckScheduler.hasPendingAcks());
conn.ackStates.initialAckState.acks.insert(0, 100);
EXPECT_TRUE(initialAckScheduler.hasPendingAcks());
conn.ackStates.handshakeAckState.acks.insert(0, 100);
conn.ackStates.handshakeAckState.largestAckScheduled = 200;
EXPECT_FALSE(handshakeAckScheduler.hasPendingAcks());
conn.ackStates.handshakeAckState.largestAckScheduled = folly::none;
EXPECT_TRUE(handshakeAckScheduler.hasPendingAcks());
}
TEST_F(QuicPacketSchedulerTest, LargestAckToSend) {
QuicClientConnectionState conn;
EXPECT_EQ(folly::none, largestAckToSend(conn.ackStates.initialAckState));
EXPECT_EQ(folly::none, largestAckToSend(conn.ackStates.handshakeAckState));
EXPECT_EQ(folly::none, largestAckToSend(conn.ackStates.appDataAckState));
conn.ackStates.initialAckState.acks.insert(0, 50);
conn.ackStates.handshakeAckState.acks.insert(0, 50);
conn.ackStates.handshakeAckState.acks.insert(75, 150);
EXPECT_EQ(50, *largestAckToSend(conn.ackStates.initialAckState));
EXPECT_EQ(150, *largestAckToSend(conn.ackStates.handshakeAckState));
EXPECT_EQ(folly::none, largestAckToSend(conn.ackStates.appDataAckState));
}
TEST_F(QuicPacketSchedulerTest, StreamFrameSchedulerAllFit) {
QuicClientConnectionState conn;
conn.streamManager->setMaxLocalBidirectionalStreams(10);
conn.flowControlState.peerAdvertisedMaxOffset = 100000;
conn.flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote = 100000;
auto connId = getTestConnectionId();
StreamFrameScheduler scheduler(conn);
ShortHeader shortHeader(
ProtectionType::KeyPhaseZero,
connId,
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(shortHeader),
conn.ackStates.appDataAckState.largestAckedByPeer);
auto stream1 = conn.streamManager->createNextBidirectionalStream().value();
auto stream2 = conn.streamManager->createNextBidirectionalStream().value();
auto stream3 = conn.streamManager->createNextBidirectionalStream().value();
writeDataToQuicStream(*stream1, folly::IOBuf::copyBuffer("some data"), false);
writeDataToQuicStream(*stream2, folly::IOBuf::copyBuffer("some data"), false);
writeDataToQuicStream(*stream3, folly::IOBuf::copyBuffer("some data"), false);
scheduler.writeStreams(builder);
EXPECT_EQ(conn.schedulingState.nextScheduledStream, 0);
}
TEST_F(QuicPacketSchedulerTest, StreamFrameSchedulerRoundRobin) {
QuicClientConnectionState conn;
conn.streamManager->setMaxLocalBidirectionalStreams(10);
conn.flowControlState.peerAdvertisedMaxOffset = 100000;
conn.flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote = 100000;
auto connId = getTestConnectionId();
StreamFrameScheduler scheduler(conn);
ShortHeader shortHeader1(
ProtectionType::KeyPhaseZero,
connId,
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder1(
conn.udpSendPacketLen,
std::move(shortHeader1),
conn.ackStates.appDataAckState.largestAckedByPeer);
auto stream1 = conn.streamManager->createNextBidirectionalStream().value();
auto stream2 = conn.streamManager->createNextBidirectionalStream().value();
auto stream3 = conn.streamManager->createNextBidirectionalStream().value();
auto largeBuf = folly::IOBuf::createChain(conn.udpSendPacketLen * 2, 4096);
auto curBuf = largeBuf.get();
do {
curBuf->append(curBuf->capacity());
curBuf = curBuf->next();
} while (curBuf != largeBuf.get());
auto chainLen = largeBuf->computeChainDataLength();
writeDataToQuicStream(*stream1, std::move(largeBuf), false);
writeDataToQuicStream(*stream2, folly::IOBuf::copyBuffer("some data"), false);
writeDataToQuicStream(*stream3, folly::IOBuf::copyBuffer("some data"), false);
// Force the wraparound initially.
conn.schedulingState.nextScheduledStream = stream3->id + 8;
scheduler.writeStreams(builder1);
EXPECT_EQ(conn.schedulingState.nextScheduledStream, 4);
// Should write frames for stream2, stream3, followed by stream1 again.
MockQuicPacketBuilder builder2;
EXPECT_CALL(builder2, remainingSpaceInPkt()).WillRepeatedly(Return(4096));
EXPECT_CALL(builder2, appendFrame(_)).WillRepeatedly(Invoke([&](auto f) {
builder2.frames_.push_back(f);
}));
scheduler.writeStreams(builder2);
auto& frames = builder2.frames_;
ASSERT_EQ(frames.size(), 3);
WriteStreamFrame f1(stream2->id, 0, 9, false);
WriteStreamFrame f2(stream3->id, 0, 9, false);
WriteStreamFrame f3(stream1->id, 0, chainLen, false);
ASSERT_TRUE(frames[0].asWriteStreamFrame());
EXPECT_EQ(*frames[0].asWriteStreamFrame(), f1);
ASSERT_TRUE(frames[1].asWriteStreamFrame());
EXPECT_EQ(*frames[1].asWriteStreamFrame(), f2);
ASSERT_TRUE(frames[2].asWriteStreamFrame());
EXPECT_EQ(*frames[2].asWriteStreamFrame(), f3);
}
TEST_F(QuicPacketSchedulerTest, StreamFrameSchedulerOneStream) {
QuicClientConnectionState conn;
conn.streamManager->setMaxLocalBidirectionalStreams(10);
conn.flowControlState.peerAdvertisedMaxOffset = 100000;
conn.flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote = 100000;
auto connId = getTestConnectionId();
StreamFrameScheduler scheduler(conn);
ShortHeader shortHeader(
ProtectionType::KeyPhaseZero,
connId,
getNextPacketNum(conn, PacketNumberSpace::AppData));
RegularQuicPacketBuilder builder(
conn.udpSendPacketLen,
std::move(shortHeader),
conn.ackStates.appDataAckState.largestAckedByPeer);
auto stream1 = conn.streamManager->createNextBidirectionalStream().value();
writeDataToQuicStream(*stream1, folly::IOBuf::copyBuffer("some data"), false);
scheduler.writeStreams(builder);
EXPECT_EQ(conn.schedulingState.nextScheduledStream, 0);
}
TEST_F(QuicPacketSchedulerTest, StreamFrameSchedulerRemoveOne) {
QuicClientConnectionState conn;
conn.streamManager->setMaxLocalBidirectionalStreams(10);
conn.flowControlState.peerAdvertisedMaxOffset = 100000;
conn.flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote = 100000;
StreamFrameScheduler scheduler(conn);
MockQuicPacketBuilder builder;
auto stream1 = conn.streamManager->createNextBidirectionalStream().value();
auto stream2 = conn.streamManager->createNextBidirectionalStream().value();
writeDataToQuicStream(*stream1, folly::IOBuf::copyBuffer("some data"), false);
writeDataToQuicStream(*stream2, folly::IOBuf::copyBuffer("some data"), false);
EXPECT_CALL(builder, remainingSpaceInPkt()).WillRepeatedly(Return(4096));
EXPECT_CALL(builder, appendFrame(_)).WillRepeatedly(Invoke([&](auto f) {
builder.frames_.push_back(f);
}));
scheduler.writeStreams(builder);
WriteStreamFrame f1(stream1->id, 0, 9, false);
WriteStreamFrame f2(stream2->id, 0, 9, false);
ASSERT_TRUE(builder.frames_[0].asWriteStreamFrame());
EXPECT_EQ(*builder.frames_[0].asWriteStreamFrame(), f1);
ASSERT_TRUE(builder.frames_[1].asWriteStreamFrame());
EXPECT_EQ(*builder.frames_[1].asWriteStreamFrame(), f2);
// Manually remove a stream and set the next scheduled to that stream.
builder.frames_.clear();
conn.streamManager->removeWritable(stream2->id);
conn.schedulingState.nextScheduledStream = stream2->id;
scheduler.writeStreams(builder);
ASSERT_EQ(builder.frames_.size(), 1);
ASSERT_TRUE(builder.frames_[0].asWriteStreamFrame());
EXPECT_EQ(*builder.frames_[0].asWriteStreamFrame(), f1);
}
} // namespace test
} // namespace quic