mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-04-18 17:24:03 +03:00
Summary: - as title Reviewed By: lnicco Differential Revision: D33513410 fbshipit-source-id: 282b6f512cf83b9abb7990402661135b658f7bd1
4461 lines
170 KiB
C++
4461 lines
170 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and 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/QuicTransportFunctions.h>
|
|
|
|
#include <folly/io/async/test/MockAsyncUDPSocket.h>
|
|
#include <quic/api/test/Mocks.h>
|
|
#include <quic/common/test/TestUtils.h>
|
|
#include <quic/fizz/server/handshake/FizzServerQuicHandshakeContext.h>
|
|
#include <quic/logging/FileQLogger.h>
|
|
#include <quic/logging/QLoggerConstants.h>
|
|
#include <quic/server/state/ServerStateMachine.h>
|
|
#include <quic/state/test/MockQuicStats.h>
|
|
#include <quic/state/test/Mocks.h>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
using namespace folly;
|
|
using namespace testing;
|
|
|
|
namespace quic {
|
|
namespace test {
|
|
|
|
using PacketStreamDetails = OutstandingPacketMetadata::StreamDetails;
|
|
|
|
uint64_t writeProbingDataToSocketForTest(
|
|
folly::AsyncUDPSocket& sock,
|
|
QuicConnectionStateBase& conn,
|
|
uint8_t probesToSend,
|
|
const Aead& aead,
|
|
const PacketNumberCipher& headerCipher,
|
|
QuicVersion version) {
|
|
FrameScheduler scheduler = std::move(FrameScheduler::Builder(
|
|
conn,
|
|
EncryptionLevel::AppData,
|
|
PacketNumberSpace::AppData,
|
|
"test")
|
|
.streamFrames()
|
|
.cryptoFrames())
|
|
.build();
|
|
return writeProbingDataToSocket(
|
|
sock,
|
|
conn,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
ShortHeaderBuilder(),
|
|
EncryptionLevel::AppData,
|
|
PacketNumberSpace::AppData,
|
|
scheduler,
|
|
probesToSend,
|
|
aead,
|
|
headerCipher,
|
|
version);
|
|
}
|
|
|
|
void writeCryptoDataProbesToSocketForTest(
|
|
folly::AsyncUDPSocket& sock,
|
|
QuicConnectionStateBase& conn,
|
|
uint8_t probesToSend,
|
|
const Aead& aead,
|
|
const PacketNumberCipher& headerCipher,
|
|
QuicVersion version,
|
|
LongHeader::Types type = LongHeader::Types::Initial) {
|
|
auto encryptionLevel =
|
|
protectionTypeToEncryptionLevel(longHeaderTypeToProtectionType(type));
|
|
auto pnSpace = LongHeader::typeToPacketNumberSpace(type);
|
|
auto scheduler = std::move(FrameScheduler::Builder(
|
|
conn, encryptionLevel, pnSpace, "Crypto")
|
|
.cryptoFrames())
|
|
.build();
|
|
writeProbingDataToSocket(
|
|
sock,
|
|
conn,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
LongHeaderBuilder(type),
|
|
protectionTypeToEncryptionLevel(longHeaderTypeToProtectionType(type)),
|
|
LongHeader::typeToPacketNumberSpace(type),
|
|
scheduler,
|
|
probesToSend,
|
|
aead,
|
|
headerCipher,
|
|
version);
|
|
}
|
|
|
|
auto buildEmptyPacket(
|
|
QuicServerConnectionState& conn,
|
|
PacketNumberSpace pnSpace,
|
|
bool shortHeader = false) {
|
|
folly::Optional<PacketHeader> header;
|
|
if (shortHeader) {
|
|
header = ShortHeader(
|
|
ProtectionType::KeyPhaseZero,
|
|
*conn.clientConnectionId,
|
|
conn.ackStates.appDataAckState.nextPacketNum);
|
|
} else {
|
|
if (pnSpace == PacketNumberSpace::Initial) {
|
|
header = LongHeader(
|
|
LongHeader::Types::Initial,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
conn.ackStates.initialAckState.nextPacketNum,
|
|
*conn.version);
|
|
} else if (pnSpace == PacketNumberSpace::Handshake) {
|
|
header = LongHeader(
|
|
LongHeader::Types::Handshake,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
conn.ackStates.handshakeAckState.nextPacketNum,
|
|
*conn.version);
|
|
} else if (pnSpace == PacketNumberSpace::AppData) {
|
|
header = LongHeader(
|
|
LongHeader::Types::ZeroRtt,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
conn.ackStates.appDataAckState.nextPacketNum,
|
|
*conn.version);
|
|
}
|
|
}
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen,
|
|
std::move(*header),
|
|
getAckState(conn, pnSpace).largestAckedByPeer.value_or(0));
|
|
builder.encodePacketHeader();
|
|
DCHECK(builder.canBuildPacket());
|
|
return std::move(builder).buildPacket();
|
|
}
|
|
|
|
uint64_t getEncodedSize(const RegularQuicPacketBuilder::Packet& packet) {
|
|
// calculate size as the plaintext size
|
|
uint32_t encodedSize = 0;
|
|
if (packet.header) {
|
|
encodedSize += packet.header->computeChainDataLength();
|
|
}
|
|
if (packet.body) {
|
|
encodedSize += packet.body->computeChainDataLength();
|
|
}
|
|
return encodedSize;
|
|
}
|
|
|
|
uint64_t getEncodedBodySize(const RegularQuicPacketBuilder::Packet& packet) {
|
|
// calculate size as the plaintext size
|
|
uint32_t encodedBodySize = 0;
|
|
if (packet.body) {
|
|
encodedBodySize += packet.body->computeChainDataLength();
|
|
}
|
|
return encodedBodySize;
|
|
}
|
|
|
|
class QuicTransportFunctionsTest : public Test {
|
|
public:
|
|
void SetUp() override {
|
|
aead = test::createNoOpAead();
|
|
headerCipher = test::createNoOpHeaderCipher();
|
|
quicStats_ = std::make_unique<NiceMock<MockQuicStats>>();
|
|
}
|
|
|
|
std::unique_ptr<QuicServerConnectionState> createConn() {
|
|
auto conn = std::make_unique<QuicServerConnectionState>(
|
|
FizzServerQuicHandshakeContext::Builder().build());
|
|
conn->serverConnectionId = getTestConnectionId();
|
|
conn->clientConnectionId = getTestConnectionId();
|
|
conn->version = QuicVersion::MVFST;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiLocal =
|
|
kDefaultStreamWindowSize * 1000;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote =
|
|
kDefaultStreamWindowSize * 1000;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetUni =
|
|
kDefaultStreamWindowSize * 1000;
|
|
conn->flowControlState.peerAdvertisedMaxOffset =
|
|
kDefaultConnectionWindowSize * 1000;
|
|
conn->statsCallback = quicStats_.get();
|
|
conn->initialWriteCipher = createNoOpAead();
|
|
conn->initialHeaderCipher = createNoOpHeaderCipher();
|
|
conn->streamManager->setMaxLocalBidirectionalStreams(
|
|
kDefaultMaxStreamsBidirectional);
|
|
conn->streamManager->setMaxLocalUnidirectionalStreams(
|
|
kDefaultMaxStreamsUnidirectional);
|
|
return conn;
|
|
}
|
|
|
|
QuicVersion getVersion(QuicServerConnectionState& conn) {
|
|
return conn.version.value_or(*conn.originalVersion);
|
|
}
|
|
|
|
std::unique_ptr<Aead> aead;
|
|
std::unique_ptr<PacketNumberCipher> headerCipher;
|
|
std::unique_ptr<MockQuicStats> quicStats_;
|
|
};
|
|
|
|
TEST_F(QuicTransportFunctionsTest, PingPacketGoesToOPList) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet.packet.frames.push_back(PingFrame());
|
|
EXPECT_EQ(0, conn->outstandings.packets.size());
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
Clock::now(),
|
|
50,
|
|
0,
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
// But it won't set loss detection alarm
|
|
EXPECT_FALSE(conn->pendingEvents.setLossDetectionAlarm);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnection) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
// Builds a fake packet to test with.
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
|
|
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 = IOBuf::copyBuffer("hey whats up");
|
|
EXPECT_CALL(*quicStats_, onPacketRetransmission()).Times(2);
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
writeDataToQuicStream(*stream2, buf->clone(), true);
|
|
|
|
WriteStreamFrame writeStreamFrame1(stream1->id, 0, 5, false);
|
|
WriteStreamFrame writeStreamFrame2(stream2->id, 0, 12, true);
|
|
packet.packet.frames.push_back(std::move(writeStreamFrame1));
|
|
packet.packet.frames.push_back(std::move(writeStreamFrame2));
|
|
|
|
auto currentNextInitialPacketNum =
|
|
conn->ackStates.initialAckState.nextPacketNum;
|
|
auto currentNextHandshakePacketNum =
|
|
conn->ackStates.handshakeAckState.nextPacketNum;
|
|
auto currentNextAppDataPacketNum =
|
|
conn->ackStates.appDataAckState.nextPacketNum;
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
EXPECT_CALL(*rawCongestionController, isAppLimited())
|
|
.Times(1)
|
|
.WillOnce(Return(true));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint{},
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
EXPECT_EQ(
|
|
conn->ackStates.initialAckState.nextPacketNum,
|
|
currentNextInitialPacketNum);
|
|
EXPECT_GT(
|
|
conn->ackStates.handshakeAckState.nextPacketNum,
|
|
currentNextHandshakePacketNum);
|
|
EXPECT_EQ(
|
|
conn->ackStates.appDataAckState.nextPacketNum,
|
|
currentNextAppDataPacketNum);
|
|
EXPECT_TRUE(conn->outstandings.packets.back().isAppLimited);
|
|
|
|
EXPECT_EQ(stream1->currentWriteOffset, 5);
|
|
EXPECT_EQ(stream2->currentWriteOffset, 13);
|
|
EXPECT_EQ(conn->flowControlState.sumCurWriteOffset, 17);
|
|
|
|
IOBufEqualTo eq;
|
|
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 1);
|
|
auto& rt1 = *stream1->retransmissionBuffer.at(0);
|
|
EXPECT_EQ(rt1.offset, 0);
|
|
EXPECT_TRUE(eq(*IOBuf::copyBuffer("hey w"), *rt1.data.front()));
|
|
|
|
EXPECT_EQ(stream2->retransmissionBuffer.size(), 1);
|
|
auto& rt2 = *stream2->retransmissionBuffer.at(0);
|
|
EXPECT_EQ(rt2.offset, 0);
|
|
EXPECT_TRUE(eq(*buf, *rt2.data.front()));
|
|
EXPECT_TRUE(rt2.eof);
|
|
|
|
// Testing retransmission
|
|
stream1->lossBuffer.push_back(std::move(rt1));
|
|
stream1->retransmissionBuffer.clear();
|
|
stream2->lossBuffer.push_back(std::move(rt2));
|
|
stream2->retransmissionBuffer.clear();
|
|
conn->streamManager->addLoss(stream1->id);
|
|
conn->streamManager->addLoss(stream2->id);
|
|
|
|
// Write the remainder of the data with retransmission
|
|
auto packet2 = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
WriteStreamFrame writeStreamFrame3(stream1->id, 5, 7, true);
|
|
WriteStreamFrame writeStreamFrame4(stream1->id, 0, 5, false);
|
|
WriteStreamFrame writeStreamFrame5(stream2->id, 0, 6, false);
|
|
packet2.packet.frames.push_back(std::move(writeStreamFrame3));
|
|
packet2.packet.frames.push_back(std::move(writeStreamFrame4));
|
|
packet2.packet.frames.push_back(std::move(writeStreamFrame5));
|
|
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
currentNextInitialPacketNum = conn->ackStates.initialAckState.nextPacketNum;
|
|
currentNextHandshakePacketNum =
|
|
conn->ackStates.handshakeAckState.nextPacketNum;
|
|
currentNextAppDataPacketNum = conn->ackStates.appDataAckState.nextPacketNum;
|
|
EXPECT_CALL(*rawCongestionController, isAppLimited())
|
|
.Times(1)
|
|
.WillOnce(Return(false));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(
|
|
conn->ackStates.initialAckState.nextPacketNum,
|
|
currentNextInitialPacketNum);
|
|
EXPECT_GT(
|
|
conn->ackStates.handshakeAckState.nextPacketNum,
|
|
currentNextHandshakePacketNum);
|
|
EXPECT_EQ(
|
|
conn->ackStates.appDataAckState.nextPacketNum,
|
|
currentNextAppDataPacketNum);
|
|
EXPECT_FALSE(conn->outstandings.packets.back().isAppLimited);
|
|
|
|
EXPECT_EQ(stream1->currentWriteOffset, 13);
|
|
EXPECT_EQ(stream2->currentWriteOffset, 13);
|
|
EXPECT_EQ(conn->flowControlState.sumCurWriteOffset, 24);
|
|
|
|
EXPECT_EQ(stream1->lossBuffer.size(), 0);
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 2);
|
|
auto& rt3 = *stream1->retransmissionBuffer.at(5);
|
|
EXPECT_TRUE(eq(IOBuf::copyBuffer("hats up"), rt3.data.move()));
|
|
|
|
auto& rt4 = *stream1->retransmissionBuffer.at(0);
|
|
EXPECT_TRUE(eq(*IOBuf::copyBuffer("hey w"), *rt4.data.front()));
|
|
|
|
// loss buffer should be split into 2. Part in retransmission buffer and
|
|
// part remains in loss buffer.
|
|
EXPECT_EQ(stream2->lossBuffer.size(), 1);
|
|
EXPECT_EQ(stream2->retransmissionBuffer.size(), 1);
|
|
auto& rt5 = *stream2->retransmissionBuffer.at(0);
|
|
EXPECT_TRUE(eq(*IOBuf::copyBuffer("hey wh"), *rt5.data.front()));
|
|
EXPECT_EQ(rt5.offset, 0);
|
|
EXPECT_EQ(rt5.eof, 0);
|
|
|
|
auto& rt6 = stream2->lossBuffer.front();
|
|
EXPECT_TRUE(eq(*IOBuf::copyBuffer("ats up"), *rt6.data.front()));
|
|
EXPECT_EQ(rt6.offset, 6);
|
|
EXPECT_EQ(rt6.eof, 1);
|
|
|
|
// verify handshake packets stored in QLogger
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 2);
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
auto p1 = std::move(qLogger->logs[indices[i]]);
|
|
auto event = dynamic_cast<QLogPacketEvent*>(p1.get());
|
|
EXPECT_EQ(event->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event->packetSize, getEncodedSize(packet));
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
|
|
if (i == 0) {
|
|
EXPECT_EQ(event->frames.size(), 2);
|
|
auto frame = static_cast<StreamFrameLog*>(event->frames[0].get());
|
|
EXPECT_EQ(frame->streamId, stream1->id);
|
|
EXPECT_EQ(frame->offset, 0);
|
|
EXPECT_EQ(frame->len, 5);
|
|
EXPECT_FALSE(frame->fin);
|
|
} else if (i == 1) {
|
|
EXPECT_EQ(event->frames.size(), 3);
|
|
auto frame = static_cast<StreamFrameLog*>(event->frames[0].get());
|
|
EXPECT_EQ(frame->streamId, stream1->id);
|
|
EXPECT_EQ(frame->offset, 5);
|
|
EXPECT_EQ(frame->len, 7);
|
|
EXPECT_TRUE(frame->fin);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionPacketRetrans) {
|
|
const IOBufEqualTo eq;
|
|
|
|
auto conn = createConn();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
|
|
// two streams, both writing "hey whats up"
|
|
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 = IOBuf::copyBuffer("hey whats up");
|
|
writeDataToQuicStream(*stream1, buf->clone(), true /* eof */);
|
|
writeDataToQuicStream(*stream2, buf->clone(), true /* eof */);
|
|
WriteStreamFrame writeStreamFrame1(stream1->id, 0, 12, true /* eom */);
|
|
WriteStreamFrame writeStreamFrame2(stream2->id, 0, 12, true /* eom */);
|
|
EXPECT_EQ(stream1->currentWriteOffset, 0);
|
|
EXPECT_EQ(stream2->currentWriteOffset, 0);
|
|
EXPECT_EQ(conn->flowControlState.sumCurWriteOffset, 0);
|
|
|
|
// add both stream frames into AppData packet1
|
|
auto packet1 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet1.packet.frames.push_back(writeStreamFrame1);
|
|
packet1.packet.frames.push_back(writeStreamFrame2);
|
|
|
|
// mimic send, call updateConnection
|
|
auto currentNextInitialPacketNum =
|
|
conn->ackStates.initialAckState.nextPacketNum;
|
|
auto currentNextHandshakePacketNum =
|
|
conn->ackStates.handshakeAckState.nextPacketNum;
|
|
auto currentNextAppDataPacketNum =
|
|
conn->ackStates.appDataAckState.nextPacketNum;
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
EXPECT_CALL(*rawCongestionController, isAppLimited())
|
|
.Times(1)
|
|
.WillOnce(Return(true));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet1.packet,
|
|
TimePoint{},
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */);
|
|
|
|
// appData packet number should increase
|
|
EXPECT_EQ(
|
|
conn->ackStates.initialAckState.nextPacketNum,
|
|
currentNextInitialPacketNum); // no change
|
|
EXPECT_EQ(
|
|
conn->ackStates.handshakeAckState.nextPacketNum,
|
|
currentNextHandshakePacketNum); // no change
|
|
EXPECT_GT(
|
|
conn->ackStates.appDataAckState.nextPacketNum,
|
|
currentNextAppDataPacketNum); // increased
|
|
EXPECT_TRUE(conn->outstandings.packets.back().isAppLimited);
|
|
|
|
// offsets should be 13 (len + EOF) and 24 (bytes without EOF)
|
|
EXPECT_EQ(stream1->currentWriteOffset, 13); // len (12) + EOF (1)
|
|
EXPECT_EQ(stream2->currentWriteOffset, 13); // len (12) + EOF (1)
|
|
EXPECT_EQ(conn->flowControlState.sumCurWriteOffset, 24); // sum(len)
|
|
|
|
// verify retransmission buffer and mark stream bytes in packet1 lost
|
|
{
|
|
ASSERT_EQ(stream1->retransmissionBuffer.size(), 1);
|
|
auto& rt = *stream1->retransmissionBuffer.at(0);
|
|
EXPECT_EQ(rt.offset, 0);
|
|
EXPECT_TRUE(eq(*buf, *rt.data.front()));
|
|
EXPECT_TRUE(rt.eof);
|
|
stream1->lossBuffer.push_back(std::move(rt));
|
|
}
|
|
{
|
|
ASSERT_EQ(stream2->retransmissionBuffer.size(), 1);
|
|
auto& rt = *stream2->retransmissionBuffer.at(0);
|
|
EXPECT_EQ(rt.offset, 0);
|
|
EXPECT_TRUE(eq(*buf, *rt.data.front()));
|
|
EXPECT_TRUE(rt.eof);
|
|
stream2->lossBuffer.push_back(std::move(rt));
|
|
}
|
|
stream1->retransmissionBuffer.clear();
|
|
stream2->retransmissionBuffer.clear();
|
|
conn->streamManager->addLoss(stream1->id);
|
|
conn->streamManager->addLoss(stream2->id);
|
|
|
|
// retransmit the lost frames in AppData packet2
|
|
auto packet2 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet2.packet.frames.push_back(writeStreamFrame1);
|
|
packet2.packet.frames.push_back(writeStreamFrame2);
|
|
|
|
// mimic send, call updateConnection
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
currentNextInitialPacketNum = conn->ackStates.initialAckState.nextPacketNum;
|
|
currentNextHandshakePacketNum =
|
|
conn->ackStates.handshakeAckState.nextPacketNum;
|
|
currentNextAppDataPacketNum = conn->ackStates.appDataAckState.nextPacketNum;
|
|
EXPECT_CALL(*rawCongestionController, isAppLimited())
|
|
.Times(1)
|
|
.WillOnce(Return(false));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(
|
|
conn->ackStates.initialAckState.nextPacketNum,
|
|
currentNextInitialPacketNum); // no change
|
|
EXPECT_EQ(
|
|
conn->ackStates.handshakeAckState.nextPacketNum,
|
|
currentNextHandshakePacketNum); // no change
|
|
EXPECT_GT(
|
|
conn->ackStates.appDataAckState.nextPacketNum,
|
|
currentNextAppDataPacketNum); // increased
|
|
EXPECT_FALSE(conn->outstandings.packets.back().isAppLimited);
|
|
|
|
// since retransmission with no new data, no change in offsets
|
|
EXPECT_EQ(stream1->currentWriteOffset, 13); // len (12) + EOF (1)
|
|
EXPECT_EQ(stream2->currentWriteOffset, 13); // len (12) + EOF (1)
|
|
EXPECT_EQ(conn->flowControlState.sumCurWriteOffset, 24); // sum(len)
|
|
|
|
// check loss state
|
|
CHECK_EQ(
|
|
conn->lossState.totalBytesSent,
|
|
getEncodedSize(packet1) + getEncodedSize(packet2));
|
|
CHECK_EQ(
|
|
conn->lossState.totalBodyBytesSent,
|
|
getEncodedBodySize(packet1) + getEncodedBodySize(packet2));
|
|
CHECK_EQ(conn->lossState.totalPacketsSent, 2);
|
|
|
|
// totalStreamBytesSent:
|
|
// the first packet contained 12 + 12
|
|
// the second packet contained 12 + 12
|
|
// total = 48
|
|
EXPECT_EQ(conn->lossState.totalStreamBytesSent, 48); // sum(len)
|
|
|
|
// totalNewStreamBytesSent: just sum(len)
|
|
EXPECT_EQ(conn->lossState.totalNewStreamBytesSent, 24);
|
|
EXPECT_EQ(
|
|
conn->lossState.totalNewStreamBytesSent,
|
|
conn->flowControlState.sumCurWriteOffset);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicTransportFunctionsTest,
|
|
TestUpdateConnectionPacketRetransWithNewData) {
|
|
const IOBufEqualTo eq;
|
|
|
|
auto conn = createConn();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
|
|
// three streams, all writing "hey whats up"
|
|
//
|
|
// streams1 and 2 EOF after writing buffer, stream3 does not
|
|
//
|
|
// frames:
|
|
// stream1,frame1 contains the entire string with EOM
|
|
// stream2,frame1 contains "hey w", and thus no EOM
|
|
// stream3,frame1 contains the entire string, but no EOM given no EOF yet
|
|
//
|
|
// we'll write additional data to stream3 later
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream2Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream3Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
auto stream2 = conn->streamManager->findStream(stream2Id);
|
|
auto stream3 = conn->streamManager->findStream(stream3Id);
|
|
auto buf = IOBuf::copyBuffer("hey whats up");
|
|
writeDataToQuicStream(*stream1, buf->clone(), true /* eof */);
|
|
writeDataToQuicStream(*stream2, buf->clone(), true /* eof */);
|
|
writeDataToQuicStream(*stream3, buf->clone(), false /* eof */);
|
|
WriteStreamFrame writeStreamFrame1(stream1->id, 0, 12, true /* eom */);
|
|
WriteStreamFrame writeStreamFrame2(stream2->id, 0, 5, false /* eom */);
|
|
WriteStreamFrame writeStreamFrame3(stream3->id, 0, 12, false /* eom */);
|
|
EXPECT_EQ(stream1->currentWriteOffset, 0);
|
|
EXPECT_EQ(stream2->currentWriteOffset, 0);
|
|
EXPECT_EQ(stream3->currentWriteOffset, 0);
|
|
EXPECT_EQ(conn->flowControlState.sumCurWriteOffset, 0);
|
|
|
|
// add all stream frames into AppData packet1
|
|
auto packet1 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet1.packet.frames.push_back(writeStreamFrame1);
|
|
packet1.packet.frames.push_back(writeStreamFrame2);
|
|
packet1.packet.frames.push_back(writeStreamFrame3);
|
|
|
|
// mimic send, call updateConnection
|
|
auto currentNextInitialPacketNum =
|
|
conn->ackStates.initialAckState.nextPacketNum;
|
|
auto currentNextHandshakePacketNum =
|
|
conn->ackStates.handshakeAckState.nextPacketNum;
|
|
auto currentNextAppDataPacketNum =
|
|
conn->ackStates.appDataAckState.nextPacketNum;
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
EXPECT_CALL(*rawCongestionController, isAppLimited())
|
|
.Times(1)
|
|
.WillOnce(Return(true));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet1.packet,
|
|
TimePoint{},
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */);
|
|
|
|
// appData packet number should increase
|
|
EXPECT_EQ(
|
|
conn->ackStates.initialAckState.nextPacketNum,
|
|
currentNextInitialPacketNum); // no change
|
|
EXPECT_EQ(
|
|
conn->ackStates.handshakeAckState.nextPacketNum,
|
|
currentNextHandshakePacketNum); // no change
|
|
EXPECT_GT(
|
|
conn->ackStates.appDataAckState.nextPacketNum,
|
|
currentNextAppDataPacketNum); // increased
|
|
EXPECT_TRUE(conn->outstandings.packets.back().isAppLimited);
|
|
|
|
// check offsets
|
|
EXPECT_EQ(stream1->currentWriteOffset, 13); // len (12) + EOF (1)
|
|
EXPECT_EQ(stream2->currentWriteOffset, 5); // len (5)
|
|
EXPECT_EQ(stream3->currentWriteOffset, 12); // len (12)
|
|
EXPECT_EQ(conn->flowControlState.sumCurWriteOffset, 29); // sum(len)
|
|
|
|
// verify retransmission buffer and mark stream bytes in packet1 lost
|
|
{
|
|
ASSERT_EQ(stream1->retransmissionBuffer.size(), 1);
|
|
auto& rt = *stream1->retransmissionBuffer.at(0);
|
|
EXPECT_EQ(rt.offset, 0);
|
|
EXPECT_TRUE(eq(*buf, *rt.data.front()));
|
|
EXPECT_TRUE(rt.eof);
|
|
stream1->lossBuffer.push_back(std::move(rt));
|
|
}
|
|
{
|
|
ASSERT_EQ(stream2->retransmissionBuffer.size(), 1);
|
|
auto& rt = *stream2->retransmissionBuffer.at(0);
|
|
EXPECT_EQ(rt.offset, 0);
|
|
EXPECT_TRUE(eq(*IOBuf::copyBuffer("hey w"), *rt.data.front()));
|
|
EXPECT_FALSE(rt.eof);
|
|
stream2->lossBuffer.push_back(std::move(rt));
|
|
}
|
|
{
|
|
ASSERT_EQ(stream3->retransmissionBuffer.size(), 1);
|
|
auto& rt = *stream3->retransmissionBuffer.at(0);
|
|
EXPECT_EQ(rt.offset, 0);
|
|
EXPECT_TRUE(eq(*buf, *rt.data.front()));
|
|
EXPECT_FALSE(rt.eof);
|
|
stream3->lossBuffer.push_back(std::move(rt));
|
|
}
|
|
stream1->retransmissionBuffer.clear();
|
|
stream2->retransmissionBuffer.clear();
|
|
stream3->retransmissionBuffer.clear();
|
|
conn->streamManager->addLoss(stream1->id);
|
|
conn->streamManager->addLoss(stream2->id);
|
|
conn->streamManager->addLoss(stream3->id);
|
|
|
|
// add some additional data
|
|
// write a "?" to stream3 and set eof
|
|
auto buf2 = IOBuf::copyBuffer("?");
|
|
writeDataToQuicStream(*stream3, buf->clone(), true /* eof */);
|
|
|
|
// packet2 contains orignally transmitted frames + new data frames
|
|
auto packet2 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet2.packet.frames.push_back(writeStreamFrame1);
|
|
packet2.packet.frames.push_back(writeStreamFrame2);
|
|
packet2.packet.frames.push_back(writeStreamFrame3);
|
|
packet2.packet.frames.push_back(WriteStreamFrame(
|
|
stream2->id, 5 /* offset */, 7 /* len */, true /* eom */));
|
|
packet2.packet.frames.push_back(WriteStreamFrame(
|
|
stream3->id, 12 /* offset */, 1 /* len */, true /* eom */));
|
|
|
|
// mimic send, call updateConnection
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
currentNextInitialPacketNum = conn->ackStates.initialAckState.nextPacketNum;
|
|
currentNextHandshakePacketNum =
|
|
conn->ackStates.handshakeAckState.nextPacketNum;
|
|
currentNextAppDataPacketNum = conn->ackStates.appDataAckState.nextPacketNum;
|
|
EXPECT_CALL(*rawCongestionController, isAppLimited())
|
|
.Times(1)
|
|
.WillOnce(Return(false));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(
|
|
conn->ackStates.initialAckState.nextPacketNum,
|
|
currentNextInitialPacketNum); // no change
|
|
EXPECT_EQ(
|
|
conn->ackStates.handshakeAckState.nextPacketNum,
|
|
currentNextHandshakePacketNum); // no change
|
|
EXPECT_GT(
|
|
conn->ackStates.appDataAckState.nextPacketNum,
|
|
currentNextAppDataPacketNum); // increased
|
|
EXPECT_FALSE(conn->outstandings.packets.back().isAppLimited);
|
|
|
|
// check offsets
|
|
EXPECT_EQ(stream1->currentWriteOffset, 13); // len (12) + EOF (1)
|
|
EXPECT_EQ(stream2->currentWriteOffset, 13); // len (12) + EOF (1)
|
|
EXPECT_EQ(stream3->currentWriteOffset, 14); // len (13) + EOF (1)
|
|
EXPECT_EQ(conn->flowControlState.sumCurWriteOffset, 37); // sum(len)
|
|
|
|
// check loss state
|
|
CHECK_EQ(
|
|
conn->lossState.totalBytesSent,
|
|
getEncodedSize(packet1) + getEncodedSize(packet2));
|
|
CHECK_EQ(
|
|
conn->lossState.totalBodyBytesSent,
|
|
getEncodedBodySize(packet1) + getEncodedBodySize(packet2));
|
|
CHECK_EQ(conn->lossState.totalPacketsSent, 2);
|
|
|
|
// totalStreamBytesSent:
|
|
// the first packet contained 12 + 5 + 12 stream bytes
|
|
// the second packet contained 12 + 12 + 13 stream bytes
|
|
// total = 66
|
|
EXPECT_EQ(conn->lossState.totalStreamBytesSent, 66); // sum(len)
|
|
|
|
// totalNewStreamBytesSent: just sum(len)
|
|
EXPECT_EQ(conn->lossState.totalNewStreamBytesSent, 37);
|
|
EXPECT_EQ(
|
|
conn->lossState.totalNewStreamBytesSent,
|
|
conn->flowControlState.sumCurWriteOffset);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionD6DNotConsumeSendPing) {
|
|
auto conn = createConn();
|
|
conn->pendingEvents.sendPing = true; // Simulate application sendPing()
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet.packet.frames.push_back(PingFrame());
|
|
auto packetNum = packet.packet.header.getPacketSequenceNum();
|
|
conn->d6d.lastProbe = D6DProbePacket(packetNum, 50);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
Clock::now(),
|
|
50,
|
|
0,
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
EXPECT_TRUE(conn->outstandings.packets.front().metadata.isD6DProbe);
|
|
EXPECT_EQ(1, conn->d6d.outstandingProbes);
|
|
// sendPing should still be active since d6d probe should be "hidden" from
|
|
// application
|
|
EXPECT_TRUE(conn->pendingEvents.sendPing);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionD6DNeedsAppDataPNSpace) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
packet.packet.frames.push_back(PingFrame());
|
|
auto packetNum = packet.packet.header.getPacketSequenceNum();
|
|
conn->d6d.lastProbe = D6DProbePacket(packetNum, 50);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
Clock::now(),
|
|
50,
|
|
0,
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
EXPECT_FALSE(conn->outstandings.packets.front().metadata.isD6DProbe);
|
|
EXPECT_EQ(0, conn->d6d.outstandingProbes);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionPacketSorting) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
conn->ackStates.initialAckState.nextPacketNum = 0;
|
|
conn->ackStates.handshakeAckState.nextPacketNum = 1;
|
|
conn->ackStates.appDataAckState.nextPacketNum = 2;
|
|
auto initialPacket = buildEmptyPacket(*conn, PacketNumberSpace::Initial);
|
|
auto handshakePacket = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto appDataPacket = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
writeDataToQuicStream(
|
|
*stream,
|
|
folly::IOBuf::copyBuffer("The sun is cold and the rain is hard."),
|
|
true);
|
|
WriteStreamFrame writeStreamFrame(stream->id, 0, 5, false);
|
|
initialPacket.packet.frames.push_back(writeStreamFrame);
|
|
handshakePacket.packet.frames.push_back(writeStreamFrame);
|
|
appDataPacket.packet.frames.push_back(writeStreamFrame);
|
|
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
handshakePacket.packet,
|
|
TimePoint{},
|
|
getEncodedSize(handshakePacket),
|
|
getEncodedBodySize(handshakePacket),
|
|
false /* isDSRPacket */);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
initialPacket.packet,
|
|
TimePoint{},
|
|
getEncodedSize(initialPacket),
|
|
getEncodedBodySize(initialPacket),
|
|
false /* isDSRPacket */);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
appDataPacket.packet,
|
|
TimePoint{},
|
|
getEncodedSize(appDataPacket),
|
|
getEncodedBodySize(appDataPacket),
|
|
false /* isDSRPacket */);
|
|
// verify qLogger added correct logs
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 3);
|
|
|
|
auto l1 = std::move(qLogger->logs[indices[0]]);
|
|
auto l2 = std::move(qLogger->logs[indices[1]]);
|
|
auto l3 = std::move(qLogger->logs[indices[2]]);
|
|
auto event1 = dynamic_cast<QLogPacketEvent*>(l1.get());
|
|
auto event2 = dynamic_cast<QLogPacketEvent*>(l2.get());
|
|
auto event3 = dynamic_cast<QLogPacketEvent*>(l3.get());
|
|
|
|
EXPECT_EQ(event1->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event2->packetType, toQlogString(LongHeader::Types::Initial));
|
|
EXPECT_EQ(event3->packetType, toQlogString(LongHeader::Types::ZeroRtt));
|
|
|
|
EXPECT_EQ(3, conn->outstandings.packets.size());
|
|
auto& firstHeader = conn->outstandings.packets.front().packet.header;
|
|
auto firstPacketNum = firstHeader.getPacketSequenceNum();
|
|
EXPECT_EQ(0, firstPacketNum);
|
|
EXPECT_EQ(1, event1->packetNum);
|
|
|
|
EXPECT_EQ(PacketNumberSpace::Initial, firstHeader.getPacketNumberSpace());
|
|
|
|
auto& lastHeader = conn->outstandings.packets.back().packet.header;
|
|
|
|
auto lastPacketNum = lastHeader.getPacketSequenceNum();
|
|
|
|
EXPECT_EQ(2, lastPacketNum);
|
|
EXPECT_EQ(2, event3->packetNum);
|
|
|
|
EXPECT_EQ(PacketNumberSpace::AppData, lastHeader.getPacketNumberSpace());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionFinOnly) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
|
|
writeDataToQuicStream(*stream1, nullptr, true);
|
|
packet.packet.frames.push_back(WriteStreamFrame(stream1->id, 0, 0, true));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
// verify QLogger contains correct packet information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event->packetSize, getEncodedSize(packet));
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
|
|
// verify QLogger contains correct frame information
|
|
EXPECT_EQ(event->frames.size(), 1);
|
|
auto frame = static_cast<StreamFrameLog*>(event->frames[0].get());
|
|
EXPECT_EQ(frame->streamId, stream1->id);
|
|
EXPECT_EQ(frame->offset, 0);
|
|
EXPECT_EQ(frame->len, 0);
|
|
EXPECT_TRUE(frame->fin);
|
|
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 1);
|
|
auto& rt1 = *stream1->retransmissionBuffer.at(0);
|
|
|
|
EXPECT_EQ(stream1->currentWriteOffset, 1);
|
|
EXPECT_EQ(rt1.offset, 0);
|
|
EXPECT_EQ(rt1.data.front()->computeChainDataLength(), 0);
|
|
EXPECT_TRUE(rt1.eof);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionAllBytesExceptFin) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
|
|
auto stream1 = conn->streamManager->createNextUnidirectionalStream().value();
|
|
|
|
auto buf = IOBuf::copyBuffer("Bluberries are purple");
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
|
|
packet.packet.frames.push_back(
|
|
WriteStreamFrame(stream1->id, 0, buf->computeChainDataLength(), false));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
// verify QLogger contains correct packet information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
|
|
EXPECT_EQ(event->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event->packetSize, getEncodedSize(packet));
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
|
|
// verify QLogger contains correct frame information
|
|
EXPECT_EQ(event->frames.size(), 1);
|
|
auto frame = static_cast<StreamFrameLog*>(event->frames[0].get());
|
|
EXPECT_EQ(frame->streamId, stream1->id);
|
|
EXPECT_EQ(frame->offset, 0);
|
|
EXPECT_EQ(frame->len, buf->computeChainDataLength());
|
|
EXPECT_FALSE(frame->fin);
|
|
|
|
EXPECT_EQ(stream1->currentWriteOffset, buf->computeChainDataLength());
|
|
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 1);
|
|
auto& rt1 = *stream1->retransmissionBuffer.at(0);
|
|
EXPECT_EQ(rt1.offset, 0);
|
|
EXPECT_EQ(
|
|
rt1.data.front()->computeChainDataLength(),
|
|
buf->computeChainDataLength());
|
|
EXPECT_FALSE(rt1.eof);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionEmptyAckWriteResult) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
// None of the largestAckScheduled should be changed. But since
|
|
// buildEmptyPacket() builds a Handshake packet, we use handshakeAckState to
|
|
// verify.
|
|
auto currentPendingLargestAckScheduled =
|
|
conn->ackStates.handshakeAckState.largestAckScheduled;
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
// verify QLogger contains correct packet information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event->packetSize, getEncodedSize(packet));
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
|
|
EXPECT_EQ(
|
|
currentPendingLargestAckScheduled,
|
|
conn->ackStates.handshakeAckState.largestAckScheduled);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionPureAckCounter) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
writeDataToQuicStream(*stream, nullptr, true);
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto packetEncodedSize =
|
|
packet.header ? packet.header->computeChainDataLength() : 0;
|
|
packetEncodedSize += packet.body ? packet.body->computeChainDataLength() : 0;
|
|
|
|
WriteAckFrame ackFrame;
|
|
ackFrame.ackBlocks.emplace_back(0, 100);
|
|
packet.packet.frames.push_back(std::move(ackFrame));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
auto nonHandshake = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
packetEncodedSize =
|
|
nonHandshake.header ? nonHandshake.header->computeChainDataLength() : 0;
|
|
packetEncodedSize +=
|
|
nonHandshake.body ? nonHandshake.body->computeChainDataLength() : 0;
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
writeDataToQuicStream(*stream1, nullptr, true);
|
|
|
|
conn->pendingEvents.resets.emplace(
|
|
1, RstStreamFrame(1, GenericApplicationErrorCode::UNKNOWN, 0));
|
|
auto packet2 = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
RstStreamFrame rstFrame(1, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
packet2.packet.frames.push_back(std::move(rstFrame));
|
|
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
// verify QLogger contains correct packet and frame information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 2);
|
|
for (int i = 0; i < 2; ++i) {
|
|
auto tmp = std::move(qLogger->logs[indices[i]]);
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event->packetSize, getEncodedSize(packet));
|
|
EXPECT_EQ(event->frames.size(), 1);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestPaddingPureAckPacketIsStillPureAck) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto packetEncodedSize =
|
|
packet.header ? packet.header->computeChainDataLength() : 0;
|
|
packetEncodedSize += packet.body ? packet.body->computeChainDataLength() : 0;
|
|
|
|
WriteAckFrame ackFrame;
|
|
ackFrame.ackBlocks.emplace_back(0, 100);
|
|
packet.packet.frames.push_back(std::move(ackFrame));
|
|
packet.packet.frames.push_back(PaddingFrame());
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
// verify QLogger contains correct packet and frames information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event->packetSize, getEncodedSize(packet));
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
EXPECT_EQ(event->frames.size(), 2);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestImplicitAck) {
|
|
auto conn = createConn();
|
|
auto data = IOBuf::copyBuffer("totally real crypto data");
|
|
data->coalesce();
|
|
|
|
auto initialStream =
|
|
getCryptoStream(*conn->cryptoState, EncryptionLevel::Initial);
|
|
ASSERT_TRUE(initialStream->writeBuffer.empty());
|
|
ASSERT_TRUE(initialStream->retransmissionBuffer.empty());
|
|
ASSERT_TRUE(initialStream->lossBuffer.empty());
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Initial);
|
|
packet.packet.frames.push_back(WriteCryptoFrame(0, data->length()));
|
|
initialStream->writeBuffer.append(data->clone());
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::Initial]);
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
EXPECT_EQ(1, initialStream->retransmissionBuffer.size());
|
|
|
|
packet = buildEmptyPacket(*conn, PacketNumberSpace::Initial);
|
|
packet.packet.frames.push_back(
|
|
WriteCryptoFrame(data->length(), data->length()));
|
|
packet.packet.frames.push_back(
|
|
WriteCryptoFrame(data->length() * 2, data->length()));
|
|
initialStream->writeBuffer.append(data->clone());
|
|
initialStream->writeBuffer.append(data->clone());
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(2, conn->outstandings.packetCount[PacketNumberSpace::Initial]);
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
EXPECT_EQ(3, initialStream->retransmissionBuffer.size());
|
|
EXPECT_TRUE(initialStream->writeBuffer.empty());
|
|
EXPECT_TRUE(initialStream->lossBuffer.empty());
|
|
|
|
// Fake loss.
|
|
Buf firstBuf =
|
|
initialStream->retransmissionBuffer.find(0)->second->data.move();
|
|
initialStream->retransmissionBuffer.erase(0);
|
|
initialStream->lossBuffer.emplace_back(std::move(firstBuf), 0, false);
|
|
conn->outstandings.packets.pop_front();
|
|
conn->outstandings.packetCount[PacketNumberSpace::Initial]--;
|
|
|
|
auto handshakeStream =
|
|
getCryptoStream(*conn->cryptoState, EncryptionLevel::Handshake);
|
|
ASSERT_TRUE(handshakeStream->writeBuffer.empty());
|
|
ASSERT_TRUE(handshakeStream->retransmissionBuffer.empty());
|
|
ASSERT_TRUE(handshakeStream->lossBuffer.empty());
|
|
packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
packet.packet.frames.push_back(WriteCryptoFrame(0, data->length()));
|
|
handshakeStream->writeBuffer.append(data->clone());
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::Initial]);
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
EXPECT_EQ(1, handshakeStream->retransmissionBuffer.size());
|
|
|
|
packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
packet.packet.frames.push_back(
|
|
WriteCryptoFrame(data->length(), data->length()));
|
|
handshakeStream->writeBuffer.append(data->clone());
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::Initial]);
|
|
EXPECT_EQ(2, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(3, conn->outstandings.packets.size());
|
|
EXPECT_EQ(2, handshakeStream->retransmissionBuffer.size());
|
|
EXPECT_TRUE(handshakeStream->writeBuffer.empty());
|
|
EXPECT_TRUE(handshakeStream->lossBuffer.empty());
|
|
|
|
// Fake loss.
|
|
firstBuf = handshakeStream->retransmissionBuffer.find(0)->second->data.move();
|
|
handshakeStream->retransmissionBuffer.erase(0);
|
|
handshakeStream->lossBuffer.emplace_back(std::move(firstBuf), 0, false);
|
|
auto& op = conn->outstandings.packets.front();
|
|
ASSERT_EQ(
|
|
op.packet.header.getPacketNumberSpace(), PacketNumberSpace::Handshake);
|
|
auto frame = op.packet.frames[0].asWriteCryptoFrame();
|
|
EXPECT_EQ(frame->offset, 0);
|
|
conn->outstandings.packets.pop_front();
|
|
conn->outstandings.packetCount[PacketNumberSpace::Handshake]--;
|
|
|
|
implicitAckCryptoStream(*conn, EncryptionLevel::Initial);
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Initial]);
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
EXPECT_TRUE(initialStream->retransmissionBuffer.empty());
|
|
EXPECT_TRUE(initialStream->writeBuffer.empty());
|
|
EXPECT_TRUE(initialStream->lossBuffer.empty());
|
|
|
|
implicitAckCryptoStream(*conn, EncryptionLevel::Handshake);
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Initial]);
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_TRUE(conn->outstandings.packets.empty());
|
|
EXPECT_TRUE(handshakeStream->retransmissionBuffer.empty());
|
|
EXPECT_TRUE(handshakeStream->writeBuffer.empty());
|
|
EXPECT_TRUE(handshakeStream->lossBuffer.empty());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionHandshakeCounter) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
writeDataToQuicStream(*stream, nullptr, true);
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto packetEncodedSize =
|
|
packet.header ? packet.header->computeChainDataLength() : 0;
|
|
packetEncodedSize += packet.body ? packet.body->computeChainDataLength() : 0;
|
|
|
|
packet.packet.frames.push_back(WriteCryptoFrame(0, 0));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
|
|
auto nonHandshake = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packetEncodedSize =
|
|
nonHandshake.header ? nonHandshake.header->computeChainDataLength() : 0;
|
|
packetEncodedSize +=
|
|
nonHandshake.body ? nonHandshake.body->computeChainDataLength() : 0;
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
writeDataToQuicStream(*stream1, nullptr, true);
|
|
|
|
nonHandshake.packet.frames.push_back(
|
|
WriteStreamFrame(stream1->id, 0, 0, true));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
nonHandshake.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
// verify QLogger contains correct packet information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 2);
|
|
std::vector<std::string> packetTypes = {
|
|
std::string(toQlogString(LongHeader::Types::Handshake)),
|
|
std::string(toQlogString(LongHeader::Types::ZeroRtt))};
|
|
for (int i = 0; i < 2; ++i) {
|
|
auto tmp = std::move(qLogger->logs[indices[i]]);
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetType, packetTypes[i]);
|
|
EXPECT_EQ(event->packetSize, packetEncodedSize);
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
|
|
if (i == 0) {
|
|
EXPECT_EQ(event->frames.size(), 1);
|
|
auto gotFrame = static_cast<CryptoFrameLog*>(event->frames[0].get());
|
|
gotFrame->offset = 0;
|
|
gotFrame->len = 0;
|
|
} else if (i == 1) {
|
|
EXPECT_EQ(event->frames.size(), 1);
|
|
auto gotFrame = static_cast<StreamFrameLog*>(event->frames[0].get());
|
|
EXPECT_EQ(gotFrame->streamId, stream1->id);
|
|
EXPECT_EQ(gotFrame->offset, 0);
|
|
EXPECT_EQ(gotFrame->len, 0);
|
|
EXPECT_TRUE(gotFrame->fin);
|
|
EXPECT_EQ(
|
|
1, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionForOneRttCryptoData) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
writeDataToQuicStream(*stream, nullptr, true);
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
|
|
// Packet with CryptoFrame in AppData pn space
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData, true);
|
|
auto packetEncodedSize =
|
|
packet.header ? packet.header->computeChainDataLength() : 0;
|
|
packetEncodedSize += packet.body ? packet.body->computeChainDataLength() : 0;
|
|
|
|
packet.packet.frames.push_back(WriteCryptoFrame(0, 0));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
|
|
auto nonHandshake = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packetEncodedSize =
|
|
nonHandshake.header ? nonHandshake.header->computeChainDataLength() : 0;
|
|
packetEncodedSize +=
|
|
nonHandshake.body ? nonHandshake.body->computeChainDataLength() : 0;
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
writeDataToQuicStream(*stream1, nullptr, true);
|
|
|
|
nonHandshake.packet.frames.push_back(
|
|
WriteStreamFrame(stream1->id, 0, 0, true));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
nonHandshake.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
// verify QLogger contains correct packet information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 2);
|
|
std::vector<std::string> packetTypes = {
|
|
kShortHeaderPacketType.str(),
|
|
std::string(toQlogString(LongHeader::Types::ZeroRtt))};
|
|
for (int i = 0; i < 2; ++i) {
|
|
auto tmp = std::move(qLogger->logs[indices[i]]);
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetType, packetTypes[i]);
|
|
EXPECT_EQ(event->packetSize, getEncodedSize(packet));
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
|
|
if (i == 0) {
|
|
EXPECT_EQ(event->frames.size(), 1);
|
|
auto frame = static_cast<CryptoFrameLog*>(event->frames[0].get());
|
|
EXPECT_EQ(frame->offset, 0);
|
|
EXPECT_EQ(frame->len, 0);
|
|
} else if (i == 1) {
|
|
EXPECT_EQ(event->frames.size(), 1);
|
|
auto frame = static_cast<StreamFrameLog*>(event->frames[0].get());
|
|
EXPECT_EQ(frame->streamId, stream1->id);
|
|
EXPECT_EQ(frame->offset, 0);
|
|
EXPECT_EQ(frame->len, 0);
|
|
EXPECT_TRUE(frame->fin);
|
|
}
|
|
}
|
|
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionWithPureAck) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto mockPacer = std::make_unique<NiceMock<MockPacer>>();
|
|
auto rawPacer = mockPacer.get();
|
|
conn->pacer = std::move(mockPacer);
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EXPECT_EQ(0, conn->lossState.totalPacketsSent);
|
|
EXPECT_EQ(0, conn->lossState.totalAckElicitingPacketsSent);
|
|
EXPECT_EQ(0, conn->outstandings.packets.size());
|
|
ASSERT_EQ(0, conn->lossState.totalBytesAcked);
|
|
WriteAckFrame ackFrame;
|
|
ackFrame.ackBlocks.emplace_back(0, 10);
|
|
packet.packet.frames.push_back(std::move(ackFrame));
|
|
EXPECT_CALL(*rawController, onPacketSent(_)).Times(0);
|
|
EXPECT_CALL(*rawPacer, onPacketSent()).Times(0);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(1, conn->lossState.totalPacketsSent);
|
|
EXPECT_EQ(0, conn->lossState.totalAckElicitingPacketsSent);
|
|
EXPECT_EQ(0, conn->outstandings.packets.size());
|
|
EXPECT_EQ(0, conn->lossState.totalBytesAcked);
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
// verify QLogger contains correct packet information
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event->packetSize, getEncodedSize(packet));
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
// verify QLogger contains correct frame information
|
|
EXPECT_EQ(event->frames.size(), 1);
|
|
auto frame = static_cast<WriteAckFrameLog*>(event->frames[0].get());
|
|
EXPECT_EQ(frame->ackBlocks.size(), 1);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionWithBytesStats) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
// This is clearly not 555 bytes. I just need some data inside the packet.
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("Im gonna cut your hair."), true);
|
|
WriteStreamFrame writeStreamFrame(stream->id, 0, 5, false);
|
|
packet.packet.frames.push_back(std::move(writeStreamFrame));
|
|
conn->lossState.totalBytesSent = 13579;
|
|
conn->lossState.totalBodyBytesSent = 13000;
|
|
conn->lossState.inflightBytes = 16000;
|
|
auto currentTime = Clock::now();
|
|
conn->lossState.lastAckedTime = currentTime - 123s;
|
|
conn->lossState.adjustedLastAckedTime = currentTime - 123s;
|
|
conn->lossState.lastAckedPacketSentTime = currentTime - 234s;
|
|
conn->lossState.totalBytesSentAtLastAck = 10000;
|
|
conn->lossState.totalBytesAckedAtLastAck = 5000;
|
|
conn->lossState.totalPacketsSent = 20;
|
|
conn->lossState.totalAckElicitingPacketsSent = 15;
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
555,
|
|
500,
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(21, conn->lossState.totalPacketsSent);
|
|
EXPECT_EQ(16, conn->lossState.totalAckElicitingPacketsSent);
|
|
|
|
// verify QLogger contains correct packet information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
|
|
EXPECT_EQ(event->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event->packetSize, 555);
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
|
|
EXPECT_EQ(
|
|
13579 + 555,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.totalBytesSent);
|
|
EXPECT_EQ(
|
|
13000 + 500,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.totalBodyBytesSent);
|
|
EXPECT_EQ(
|
|
16000 + 555,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.inflightBytes);
|
|
EXPECT_EQ(
|
|
1,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.packetsInflight);
|
|
EXPECT_EQ(
|
|
555,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.encodedSize);
|
|
EXPECT_EQ(
|
|
500,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.encodedBodySize);
|
|
EXPECT_EQ(
|
|
20 + 1,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.totalPacketsSent);
|
|
EXPECT_EQ(
|
|
15 + 1,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.totalAckElicitingPacketsSent);
|
|
|
|
EXPECT_TRUE(getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->lastAckedPacketInfo.has_value());
|
|
EXPECT_EQ(
|
|
currentTime - 123s,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->lastAckedPacketInfo->ackTime);
|
|
EXPECT_EQ(
|
|
currentTime -= 234s,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->lastAckedPacketInfo->sentTime);
|
|
EXPECT_EQ(
|
|
10000,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->lastAckedPacketInfo->totalBytesSent);
|
|
EXPECT_EQ(
|
|
5000,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->lastAckedPacketInfo->totalBytesAcked);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionWithCloneResult) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
ShortHeader shortHeader(
|
|
ProtectionType::KeyPhaseZero,
|
|
*conn->clientConnectionId,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
auto thisMoment = Clock::now();
|
|
MockClock::mockNow = [=]() { return thisMoment; };
|
|
RegularQuicWritePacket writePacket(std::move(shortHeader));
|
|
// Add a dummy frame into the packet so we don't treat it as pureAck
|
|
auto maxDataAmt = 1000 + conn->flowControlState.advertisedMaxOffset;
|
|
MaxDataFrame maxDataFrame(maxDataAmt);
|
|
conn->pendingEvents.connWindowUpdate = true;
|
|
writePacket.frames.push_back(std::move(maxDataFrame));
|
|
PacketEvent event(PacketNumberSpace::AppData, 1);
|
|
conn->outstandings.packetEvents.insert(event);
|
|
auto futureMoment = thisMoment + 50ms;
|
|
MockClock::mockNow = [=]() { return futureMoment; };
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
updateConnection(
|
|
*conn,
|
|
event,
|
|
std::move(writePacket),
|
|
MockClock::now(),
|
|
1500,
|
|
1400,
|
|
false /* isDSRPacket */);
|
|
// verify QLogger contains correct packet information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto qLogEvent = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
EXPECT_EQ(qLogEvent->packetType, kShortHeaderPacketType.str());
|
|
EXPECT_EQ(qLogEvent->packetSize, 1500);
|
|
EXPECT_EQ(qLogEvent->eventType, QLogEventType::PacketSent);
|
|
|
|
// verify QLogger contains correct frame information
|
|
EXPECT_EQ(qLogEvent->frames.size(), 1);
|
|
auto frame = static_cast<MaxDataFrameLog*>(qLogEvent->frames[0].get());
|
|
EXPECT_EQ(frame->maximumData, maxDataAmt);
|
|
EXPECT_EQ(
|
|
futureMoment,
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata.time);
|
|
EXPECT_EQ(
|
|
1500,
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata.encodedSize);
|
|
EXPECT_EQ(
|
|
1400,
|
|
getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata.encodedBodySize);
|
|
EXPECT_EQ(
|
|
event,
|
|
*getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->associatedEvent);
|
|
EXPECT_TRUE(conn->pendingEvents.setLossDetectionAlarm);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionStreamWindowUpdate) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
MaxStreamDataFrame streamWindowUpdate(stream->id, 0);
|
|
conn->streamManager->queueWindowUpdate(stream->id);
|
|
packet.packet.frames.push_back(std::move(streamWindowUpdate));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
// verify QLogger contains correct packet information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
|
|
EXPECT_EQ(event->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event->packetSize, getEncodedSize(packet));
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
|
|
// verify QLogger contains correct frame information
|
|
EXPECT_EQ(event->frames.size(), 1);
|
|
auto frame = static_cast<MaxStreamDataFrameLog*>(event->frames[0].get());
|
|
EXPECT_EQ(frame->streamId, stream->id);
|
|
EXPECT_EQ(frame->maximumData, 0);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestUpdateConnectionConnWindowUpdate) {
|
|
auto conn = createConn();
|
|
conn->qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Client);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
conn->pendingEvents.connWindowUpdate = true;
|
|
MaxDataFrame connWindowUpdate(conn->flowControlState.advertisedMaxOffset);
|
|
packet.packet.frames.push_back(std::move(connWindowUpdate));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
// verify QLogger contains correct packet information
|
|
std::shared_ptr<quic::FileQLogger> qLogger =
|
|
std::dynamic_pointer_cast<quic::FileQLogger>(conn->qLogger);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketSent, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketEvent*>(tmp.get());
|
|
|
|
EXPECT_EQ(event->packetType, toQlogString(LongHeader::Types::Handshake));
|
|
EXPECT_EQ(event->packetSize, getEncodedSize(packet));
|
|
EXPECT_EQ(event->eventType, QLogEventType::PacketSent);
|
|
// verify QLogger contains correct frame information
|
|
EXPECT_EQ(event->frames.size(), 1);
|
|
auto frame = static_cast<MaxDataFrameLog*>(event->frames[0].get());
|
|
EXPECT_EQ(frame->maximumData, conn->flowControlState.advertisedMaxOffset);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, StreamDetailsEmptyPacket) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
// Since there is no ACK eliciting frame in this packet,
|
|
// it is not included as an outstanding packet, and there's no StreamDetails
|
|
EXPECT_EQ(0, conn->outstandings.packets.size());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, StreamDetailsNoStreamsInPacket) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
StreamDataBlockedFrame blockedFrame(stream->id, 1000);
|
|
packet.packet.frames.push_back(blockedFrame);
|
|
packet.packet.frames.push_back(PingFrame());
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
// If we have only control frames sent, there should be no stream data in the
|
|
// outstanding packet.
|
|
ASSERT_EQ(1, conn->outstandings.packets.size());
|
|
const auto& detailsPerStream =
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->metadata.detailsPerStream;
|
|
EXPECT_THAT(detailsPerStream, IsEmpty());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, StreamDetailsSingleStream) {
|
|
uint64_t frameOffset = 0;
|
|
uint64_t frameLen = 10;
|
|
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
writeDataToQuicStream(*stream, folly::IOBuf::copyBuffer("abcdefghij"), true);
|
|
WriteStreamFrame writeStreamFrame(
|
|
stream->id, frameOffset, frameLen, false /* fin */);
|
|
packet.packet.frames.push_back(writeStreamFrame);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
const auto streamMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frameLen),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(frameOffset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frameOffset, frameOffset + frameLen - 1)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(1)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(streamMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, ElementsAre(pktMatcher));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, StreamDetailsSingleStreamMultipleFrames) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("abcdefghijklmno"), true);
|
|
WriteStreamFrame writeStreamFrame1(
|
|
stream->id, 0 /* offset */, 10 /* length */, false /* fin */);
|
|
WriteStreamFrame writeStreamFrame2(
|
|
stream->id, 10 /* offset */, 5 /* length */, true /* fin */);
|
|
packet.packet.frames.push_back(writeStreamFrame1);
|
|
packet.packet.frames.push_back(writeStreamFrame2);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
const auto streamMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, true),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, 15),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 15),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(0)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(0, 14)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(1)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(streamMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, ElementsAre(pktMatcher));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, StreamDetailsSingleStreamRetransmit) {
|
|
auto conn = createConn();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("abcdefghij"), false /* eof */);
|
|
uint64_t frame1Offset = 0;
|
|
uint64_t frame1Len = 10;
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame1(stream->id, frame1Offset, frame1Len, false /* fin */);
|
|
packet.packet.frames.push_back(frame1);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
ASSERT_EQ(1, conn->outstandings.packets.size());
|
|
|
|
// The first outstanding packet is the one with new data
|
|
{
|
|
const auto streamMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frame1Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, frame1Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(frame1Offset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frame1Offset, frame1Offset + frame1Len - 1)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(1)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(streamMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, Contains(pktMatcher));
|
|
}
|
|
|
|
// retransmit the same frame1 again.
|
|
packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet.packet.frames.push_back(frame1);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
ASSERT_EQ(2, conn->outstandings.packets.size());
|
|
|
|
// The second outstanding packet is the one with retransmit data
|
|
{
|
|
const auto streamMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frame1Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frame1Offset, frame1Offset + frame1Len - 1)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(2)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(streamMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, Contains(pktMatcher));
|
|
}
|
|
|
|
// Retransmit frame1 and send new data in frame2.
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("klmnopqrstuvwxy"), false /* eof */);
|
|
uint64_t frame2Offset = 10;
|
|
uint64_t frame2Len = 15;
|
|
packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame2(stream->id, frame2Offset, frame2Len, false /* fin */);
|
|
packet.packet.frames.push_back(frame1);
|
|
packet.packet.frames.push_back(frame2);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
ASSERT_EQ(3, conn->outstandings.packets.size());
|
|
|
|
// The third outstanding packet will have both new and retransmitted data.
|
|
{
|
|
const auto streamMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamBytesSent, frame1Len + frame2Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, frame2Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(frame2Offset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frame1Offset, frame2Offset + frame2Len - 1)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(3)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(streamMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, Contains(pktMatcher));
|
|
}
|
|
|
|
// Retransmit frame1 aand frame2.
|
|
packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet.packet.frames.push_back(frame1);
|
|
packet.packet.frames.push_back(frame2);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
|
|
ASSERT_EQ(4, conn->outstandings.packets.size());
|
|
|
|
// The forth outstanding packet will have only retransmit data.
|
|
{
|
|
const auto streamMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamBytesSent, frame1Len + frame2Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frame1Offset, frame2Offset + frame2Len - 1)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(4)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(streamMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, Contains(pktMatcher));
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, StreamDetailsSingleStreamFinWithRetransmit) {
|
|
auto conn = createConn();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
const uint64_t frameLen = 1;
|
|
|
|
// write two packets, each containing one byte of frame data
|
|
// second packet contains FIN
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("a"), false /* eof */);
|
|
uint64_t frame1Offset = 0;
|
|
auto packet1 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame1(stream->id, frame1Offset, frameLen, false /* fin */);
|
|
packet1.packet.frames.push_back(frame1);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet1.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */);
|
|
|
|
writeDataToQuicStream(*stream, folly::IOBuf::copyBuffer("b"), true /* eof */);
|
|
uint64_t frame2Offset = (frameLen * 2) - 1;
|
|
auto packet2 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame2(stream->id, frame2Offset, frameLen, true /* fin */);
|
|
packet2.packet.frames.push_back(frame2);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */);
|
|
|
|
// Should be two packets at this point, each with 1 frame of data
|
|
EXPECT_THAT(conn->outstandings.packets, SizeIs(2));
|
|
{
|
|
auto getStreamDetailsMatcher = [&stream, &frameLen](
|
|
auto frameOffset, bool finObserved) {
|
|
return testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, finObserved),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(frameOffset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frameOffset, frameOffset + frameLen - 1)))));
|
|
};
|
|
|
|
const auto pkt1Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(1)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(getStreamDetailsMatcher(
|
|
frame1Offset, false /* finObserved */)))));
|
|
const auto pkt2Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(2)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(getStreamDetailsMatcher(
|
|
frame2Offset, true /* finObserved */)))));
|
|
EXPECT_THAT(
|
|
conn->outstandings.packets, ElementsAre(pkt1Matcher, pkt2Matcher));
|
|
}
|
|
|
|
// retransmit both frames in packet3
|
|
auto packet3 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet3.packet.frames.push_back(frame1);
|
|
packet3.packet.frames.push_back(frame2);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet3.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet3),
|
|
getEncodedBodySize(packet3),
|
|
false /* isDSRPacket */);
|
|
|
|
// Should be three packets at this point
|
|
//
|
|
// StreamDetails should report fin since frame2 is in packet3
|
|
EXPECT_THAT(conn->outstandings.packets, SizeIs(3));
|
|
{
|
|
auto streamDetailsMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, true),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frameLen * 2),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
// contains frame1 and frame2
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frame1Offset, frame2Offset + frameLen - 1)))));
|
|
|
|
const auto pkt3Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(3)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(streamDetailsMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, Contains(pkt3Matcher));
|
|
}
|
|
}
|
|
|
|
TEST_F(
|
|
QuicTransportFunctionsTest,
|
|
StreamDetailsSingleStreamSingleBytePktsPartialAckRetransmit) {
|
|
auto conn = createConn();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
const uint64_t frameLen = 1;
|
|
|
|
// write three packets, each containing one byte of frame data
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("a"), false /* eof */);
|
|
uint64_t frame1Offset = 0;
|
|
auto packet1 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame1(stream->id, frame1Offset, frameLen, false /* fin */);
|
|
packet1.packet.frames.push_back(frame1);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet1.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */);
|
|
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("b"), false /* eof */);
|
|
uint64_t frame2Offset = frameLen;
|
|
auto packet2 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame2(stream->id, frame2Offset, frameLen, false /* fin */);
|
|
packet2.packet.frames.push_back(frame2);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */);
|
|
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("c"), false /* eof */);
|
|
uint64_t frame3Offset = frameLen * 2;
|
|
auto packet3 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame3(stream->id, frame3Offset, frameLen, false /* fin */);
|
|
packet3.packet.frames.push_back(frame3);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet3.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet3),
|
|
getEncodedBodySize(packet3),
|
|
false /* isDSRPacket */);
|
|
|
|
// Should be three packets at this point, each with 1 frame of data
|
|
EXPECT_THAT(conn->outstandings.packets, SizeIs(3));
|
|
{
|
|
auto getStreamDetailsMatcher = [&stream, &frameLen](auto frameOffset) {
|
|
return testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(frameOffset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frameOffset, frameOffset + frameLen - 1)))));
|
|
};
|
|
|
|
const auto pkt1Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(1)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame1Offset)))));
|
|
const auto pkt2Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(2)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame2Offset)))));
|
|
const auto pkt3Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(3)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame3Offset)))));
|
|
EXPECT_THAT(
|
|
conn->outstandings.packets,
|
|
ElementsAre(pkt1Matcher, pkt2Matcher, pkt3Matcher));
|
|
}
|
|
|
|
// retransmit contents of packet1 and packet3 (frame1 and frame3) in packet4
|
|
// simulates ACK of packet2 and loss of packet1 and packet3
|
|
auto packet4 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet4.packet.frames.push_back(frame1);
|
|
packet4.packet.frames.push_back(frame3);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet4.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet4),
|
|
getEncodedBodySize(packet4),
|
|
false /* isDSRPacket */);
|
|
|
|
// Should be four packets at this point
|
|
EXPECT_THAT(conn->outstandings.packets, SizeIs(4));
|
|
{
|
|
auto streamDetailsMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frameLen * 2),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
// contains frame1 and frame 3
|
|
testing::ElementsAre(
|
|
Interval<uint64_t>(
|
|
frame1Offset, frame1Offset + frameLen - 1),
|
|
Interval<uint64_t>(
|
|
frame3Offset, frame3Offset + frameLen - 1)))));
|
|
|
|
const auto pkt4Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(4)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(streamDetailsMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, Contains(pkt4Matcher));
|
|
}
|
|
}
|
|
|
|
TEST_F(
|
|
QuicTransportFunctionsTest,
|
|
StreamDetailsSingleStreamTwoBytePktsPartialAckRetransmit) {
|
|
auto conn = createConn();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
const uint64_t frameLen = 2;
|
|
|
|
// write three packets, each containing two bytes of frame data
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("ab"), false /* eof */);
|
|
uint64_t frame1Offset = 0;
|
|
auto packet1 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame1(stream->id, frame1Offset, frameLen, false /* fin */);
|
|
packet1.packet.frames.push_back(frame1);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet1.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */);
|
|
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("cd"), false /* eof */);
|
|
uint64_t frame2Offset = frameLen;
|
|
LOG(INFO) << "frame2Offset = " << frame2Offset;
|
|
auto packet2 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame2(stream->id, frame2Offset, frameLen, false /* fin */);
|
|
packet2.packet.frames.push_back(frame2);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */);
|
|
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("ef"), false /* eof */);
|
|
uint64_t frame3Offset = (frameLen * 2);
|
|
LOG(INFO) << "frame3Offset = " << frame3Offset;
|
|
auto packet3 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame3(stream->id, frame3Offset, frameLen, false /* fin */);
|
|
packet3.packet.frames.push_back(frame3);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet3.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet3),
|
|
getEncodedBodySize(packet3),
|
|
false /* isDSRPacket */);
|
|
|
|
// Should be three packets at this point, each with 1 frame of data
|
|
EXPECT_THAT(conn->outstandings.packets, SizeIs(3));
|
|
{
|
|
auto getStreamDetailsMatcher = [&stream, &frameLen](auto frameOffset) {
|
|
return testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(frameOffset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frameOffset, frameOffset + frameLen - 1)))));
|
|
};
|
|
|
|
const auto pkt1Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(1)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame1Offset)))));
|
|
const auto pkt2Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(2)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame2Offset)))));
|
|
const auto pkt3Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(3)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame3Offset)))));
|
|
EXPECT_THAT(
|
|
conn->outstandings.packets,
|
|
ElementsAre(pkt1Matcher, pkt2Matcher, pkt3Matcher));
|
|
}
|
|
|
|
// retransmit contents of packet1 and packet3 (frame1 and frame3) in packet4
|
|
// simulates ACK of packet2 and loss of packet1 and packet3
|
|
auto packet4 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet4.packet.frames.push_back(frame1);
|
|
packet4.packet.frames.push_back(frame3);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet4.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet4),
|
|
getEncodedBodySize(packet4),
|
|
false /* isDSRPacket */);
|
|
|
|
// Should be four packets at this point
|
|
EXPECT_THAT(conn->outstandings.packets, SizeIs(4));
|
|
{
|
|
auto streamDetailsMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frameLen * 2),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
// contains frame1 and frame 3
|
|
testing::ElementsAre(
|
|
Interval<uint64_t>(
|
|
frame1Offset, frame1Offset + frameLen - 1),
|
|
Interval<uint64_t>(
|
|
frame3Offset, frame3Offset + frameLen - 1)))));
|
|
|
|
const auto pkt4Matcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(4)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(streamDetailsMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, Contains(pkt4Matcher));
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, StreamDetailsMultipleStreams) {
|
|
auto conn = createConn();
|
|
auto stream1Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream2Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream3Id =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream1 = conn->streamManager->findStream(stream1Id);
|
|
auto stream2 = conn->streamManager->findStream(stream2Id);
|
|
auto stream3 = conn->streamManager->findStream(stream3Id);
|
|
ASSERT_NE(nullptr, stream1);
|
|
ASSERT_NE(nullptr, stream2);
|
|
ASSERT_NE(nullptr, stream3);
|
|
|
|
auto buf = IOBuf::copyBuffer("hey whats up");
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
writeDataToQuicStream(*stream2, buf->clone(), true);
|
|
writeDataToQuicStream(*stream3, buf->clone(), true);
|
|
|
|
uint64_t stream1Offset = 0;
|
|
uint64_t stream2Offset = 0;
|
|
uint64_t stream3Offset = 0;
|
|
uint64_t stream1Len = 5;
|
|
uint64_t stream2Len = 12;
|
|
uint64_t stream3Len = 5;
|
|
|
|
auto packet1 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame writeStreamFrame1(
|
|
stream1->id, stream1Offset, stream1Len, false);
|
|
WriteStreamFrame writeStreamFrame2(
|
|
stream2->id, stream2Offset, stream2Len, true);
|
|
WriteStreamFrame writeStreamFrame3(
|
|
stream3->id, stream3Offset, stream3Len, true);
|
|
packet1.packet.frames.push_back(writeStreamFrame1);
|
|
packet1.packet.frames.push_back(writeStreamFrame2);
|
|
packet1.packet.frames.push_back(writeStreamFrame3);
|
|
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet1.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */);
|
|
|
|
// check stream details for the sent packet
|
|
{
|
|
auto stream1DetailsMatcher = testing::Pair(
|
|
stream1Id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, stream1Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, stream1Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(stream1Offset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
stream1Offset, stream1Offset + stream1Len - 1)))));
|
|
auto stream2DetailsMatcher = testing::Pair(
|
|
stream2Id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, true),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, stream2Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, stream2Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(stream2Offset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
stream2Offset, stream2Offset + stream2Len - 1)))));
|
|
auto stream3DetailsMatcher = testing::Pair(
|
|
stream3Id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, true),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, stream3Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, stream3Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(stream3Offset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
stream3Offset, stream3Offset + stream3Len - 1)))));
|
|
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(1)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
stream1DetailsMatcher,
|
|
stream2DetailsMatcher,
|
|
stream3DetailsMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, ElementsAre(pktMatcher));
|
|
}
|
|
|
|
// retransmit the packet
|
|
auto packet2 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet2.packet.frames.push_back(writeStreamFrame1);
|
|
packet2.packet.frames.push_back(writeStreamFrame2);
|
|
packet2.packet.frames.push_back(writeStreamFrame3);
|
|
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */);
|
|
|
|
// check stream details for the retransmitted packet
|
|
EXPECT_THAT(conn->outstandings.packets, SizeIs(2));
|
|
{
|
|
auto stream1DetailsMatcher = testing::Pair(
|
|
stream1Id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, false),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, stream1Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
stream1Offset, stream1Offset + stream1Len - 1)))));
|
|
auto stream2DetailsMatcher = testing::Pair(
|
|
stream2Id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, true),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, stream2Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
stream2Offset, stream2Offset + stream2Len - 1)))));
|
|
auto stream3DetailsMatcher = testing::Pair(
|
|
stream3Id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::finObserved, true),
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, stream3Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
folly::Optional<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
stream3Offset, stream3Offset + stream3Len - 1)))));
|
|
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacket::metadata,
|
|
testing::AllOf(
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::totalPacketsSent, testing::Eq(2)),
|
|
testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
stream1DetailsMatcher,
|
|
stream2DetailsMatcher,
|
|
stream3DetailsMatcher))));
|
|
EXPECT_THAT(conn->outstandings.packets, Contains(pktMatcher));
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteQuicDataToSocketWithCC) {
|
|
auto conn = createConn();
|
|
conn->udpSendPacketLen = 30;
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf =
|
|
IOBuf::copyBuffer("0123456789012012345678901201234567890120123456789012");
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
|
|
uint64_t writableBytes = 30;
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(
|
|
InvokeWithoutArgs([&writableBytes]() { return writableBytes; }));
|
|
EXPECT_CALL(*rawSocket, write(_, _))
|
|
.WillRepeatedly(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& iobuf) {
|
|
EXPECT_LE(iobuf->computeChainDataLength(), 30);
|
|
writableBytes -= iobuf->computeChainDataLength();
|
|
return iobuf->computeChainDataLength();
|
|
}));
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
EXPECT_CALL(*quicStats_, onWrite(_));
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteQuicdataToSocketWithPacer) {
|
|
auto conn = createConn();
|
|
auto mockPacer = std::make_unique<NiceMock<MockPacer>>();
|
|
auto rawPacer = mockPacer.get();
|
|
conn->pacer = std::move(mockPacer);
|
|
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf =
|
|
IOBuf::copyBuffer("0123456789012012345678901201234567890120123456789012");
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
|
|
EXPECT_CALL(*rawPacer, onPacketSent()).Times(1);
|
|
EXPECT_CALL(*quicStats_, onWrite(_));
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteQuicDataToSocketLimitTest) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
conn->udpSendPacketLen = aead->getCipherOverhead() + 50;
|
|
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
// ~50 bytes
|
|
auto buf =
|
|
IOBuf::copyBuffer("0123456789012012345678901201234567890120123456789012");
|
|
writeDataToQuicStream(*stream1, buf->clone(), false);
|
|
uint64_t writableBytes = 30;
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(
|
|
InvokeWithoutArgs([&writableBytes]() { return writableBytes; }));
|
|
|
|
// Limit to zero
|
|
conn->transportSettings.writeConnectionDataPacketsLimit = 0;
|
|
EXPECT_CALL(*rawSocket, write(_, _)).Times(0);
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(0);
|
|
EXPECT_CALL(*quicStats_, onWrite(_)).Times(0);
|
|
auto res = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(0, res.packetsWritten);
|
|
EXPECT_EQ(0, res.probesWritten);
|
|
|
|
// Normal limit
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Initial] = 0;
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Handshake] = 0;
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData] = 0;
|
|
conn->transportSettings.writeConnectionDataPacketsLimit =
|
|
kDefaultWriteConnectionDataPacketLimit;
|
|
EXPECT_CALL(*rawSocket, write(_, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& iobuf) {
|
|
writableBytes -= iobuf->computeChainDataLength();
|
|
return iobuf->computeChainDataLength();
|
|
}));
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
EXPECT_CALL(*quicStats_, onWrite(_)).Times(1);
|
|
res = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
|
|
EXPECT_EQ(1, res.packetsWritten);
|
|
EXPECT_EQ(0, res.probesWritten);
|
|
|
|
// Probing can exceed packet limit. In practice we limit it to
|
|
// kPacketToSendForPTO
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData] =
|
|
kDefaultWriteConnectionDataPacketLimit * 2;
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
writableBytes = 10000;
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(
|
|
InvokeWithoutArgs([&writableBytes]() { return writableBytes; }));
|
|
EXPECT_CALL(*rawSocket, write(_, _))
|
|
.Times(kDefaultWriteConnectionDataPacketLimit * 2)
|
|
.WillRepeatedly(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& iobuf) {
|
|
return iobuf->computeChainDataLength();
|
|
}));
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.Times(kDefaultWriteConnectionDataPacketLimit * 2);
|
|
EXPECT_CALL(*quicStats_, onWrite(_))
|
|
.Times(kDefaultWriteConnectionDataPacketLimit * 2);
|
|
res = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
|
|
EXPECT_EQ(0, res.packetsWritten);
|
|
EXPECT_EQ(kDefaultWriteConnectionDataPacketLimit * 2, res.probesWritten);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicTransportFunctionsTest,
|
|
WriteQuicDataToSocketWhenInFlightBytesAreLimited) {
|
|
auto conn = createConn();
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf =
|
|
IOBuf::copyBuffer("0123456789012012345678901201234567890120123456789012");
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
|
|
conn->writableBytesLimit = 100;
|
|
uint64_t writableBytes = 5 * *conn->writableBytesLimit;
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(
|
|
InvokeWithoutArgs([&writableBytes]() { return writableBytes; }));
|
|
EXPECT_CALL(*rawSocket, write(_, _))
|
|
.WillRepeatedly(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& iobuf) {
|
|
EXPECT_LE(
|
|
iobuf->computeChainDataLength(),
|
|
*conn->writableBytesLimit - conn->lossState.totalBytesSent);
|
|
writableBytes -= iobuf->computeChainDataLength();
|
|
return iobuf->computeChainDataLength();
|
|
}));
|
|
EXPECT_NE(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteQuicDataToSocketWithNoBytesForHeader) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = IOBuf::copyBuffer("0123456789012");
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(0));
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(0);
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
// No header space left. Should send nothing.
|
|
EXPECT_TRUE(conn->outstandings.packets.empty());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteQuicDataToSocketRetxBufferSorted) {
|
|
EventBase evb;
|
|
NiceMock<folly::test::MockAsyncUDPSocket> socket(&evb);
|
|
auto conn = createConn();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf1 = IOBuf::copyBuffer("Whatsapp");
|
|
writeDataToQuicStream(*stream, std::move(buf1), false);
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(1, stream->retransmissionBuffer.size());
|
|
|
|
auto buf2 = IOBuf::copyBuffer("Google Buzz");
|
|
writeDataToQuicStream(*stream, std::move(buf2), false);
|
|
writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(2, stream->retransmissionBuffer.size());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, NothingWritten) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
|
|
// 18 isn't enough to write 3 ack blocks, but is enough to write a pure
|
|
// header packet, which we shouldn't write
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(18));
|
|
|
|
addAckStatesWithCurrentTimestamps(conn->ackStates.initialAckState, 0, 1000);
|
|
addAckStatesWithCurrentTimestamps(
|
|
conn->ackStates.initialAckState, 1500, 2000);
|
|
addAckStatesWithCurrentTimestamps(
|
|
conn->ackStates.initialAckState, 2500, 3000);
|
|
auto res = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(0, res.packetsWritten);
|
|
EXPECT_EQ(0, res.probesWritten);
|
|
}
|
|
|
|
const QuicWriteFrame& getFirstFrameInOutstandingPackets(
|
|
const std::deque<OutstandingPacket>& outstandingPackets,
|
|
QuicWriteFrame::Type frameType) {
|
|
for (const auto& packet : outstandingPackets) {
|
|
for (const auto& frame : packet.packet.frames) {
|
|
if (frame.type() == frameType) {
|
|
return frame;
|
|
}
|
|
}
|
|
}
|
|
throw std::runtime_error("Frame not present");
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteBlockedFrameWhenBlocked) {
|
|
auto conn = createConn();
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = buildRandomInputData(200);
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
|
|
auto originalNextSeq = conn->ackStates.appDataAckState.nextPacketNum;
|
|
uint64_t sentBytes = 0;
|
|
EXPECT_CALL(*rawSocket, write(_, _))
|
|
.WillRepeatedly(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& iobuf) {
|
|
auto len = iobuf->computeChainDataLength();
|
|
sentBytes += len;
|
|
return len;
|
|
}));
|
|
|
|
// Artificially Block the stream
|
|
stream1->flowControlState.peerAdvertisedMaxOffset = 10;
|
|
// writes blocked frame in additionally
|
|
EXPECT_CALL(*quicStats_, onWrite(_)).Times(2);
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_LT(sentBytes, 200);
|
|
|
|
EXPECT_GT(conn->ackStates.appDataAckState.nextPacketNum, originalNextSeq);
|
|
auto blocked = *getFirstFrameInOutstandingPackets(
|
|
conn->outstandings.packets,
|
|
QuicWriteFrame::Type::StreamDataBlockedFrame)
|
|
.asStreamDataBlockedFrame();
|
|
EXPECT_EQ(blocked.streamId, stream1->id);
|
|
|
|
// Since everything is blocked, we shouldn't write a blocked again, so we
|
|
// won't have any new packets to write if we trigger a write.
|
|
auto previousPackets = conn->outstandings.packets.size();
|
|
EXPECT_CALL(*quicStats_, onWrite(_)).Times(0);
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(previousPackets, conn->outstandings.packets.size());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteProbingNewData) {
|
|
auto conn = createConn();
|
|
// writeProbingDataToSocketForTest writes ShortHeader, thus it writes at
|
|
// AppTraffic level
|
|
auto currentPacketSeqNum = conn->ackStates.appDataAckState.nextPacketNum;
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = buildRandomInputData(conn->udpSendPacketLen * 2);
|
|
writeDataToQuicStream(*stream1, buf->clone(), true /* eof */);
|
|
|
|
auto currentStreamWriteOffset = stream1->currentWriteOffset;
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
EXPECT_CALL(*rawSocket, write(_, _))
|
|
.WillOnce(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& iobuf) {
|
|
auto len = iobuf->computeChainDataLength();
|
|
EXPECT_EQ(conn->udpSendPacketLen - aead->getCipherOverhead(), len);
|
|
return len;
|
|
}));
|
|
writeProbingDataToSocketForTest(
|
|
*rawSocket, *conn, 1, *aead, *headerCipher, getVersion(*conn));
|
|
EXPECT_LT(currentPacketSeqNum, conn->ackStates.appDataAckState.nextPacketNum);
|
|
EXPECT_FALSE(conn->outstandings.packets.empty());
|
|
EXPECT_EQ(
|
|
conn->outstandings.packets.back().packet.header.getPacketSequenceNum(),
|
|
currentPacketSeqNum + 1);
|
|
EXPECT_TRUE(conn->pendingEvents.setLossDetectionAlarm);
|
|
EXPECT_GT(stream1->currentWriteOffset, currentStreamWriteOffset);
|
|
EXPECT_FALSE(stream1->retransmissionBuffer.empty());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteProbingOldData) {
|
|
auto conn = createConn();
|
|
conn->congestionController.reset();
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
EXPECT_CALL(*rawSocket, write(_, _)).WillRepeatedly(Return(100));
|
|
auto capturingAead = std::make_unique<MockAead>();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = folly::IOBuf::copyBuffer("Where you wanna go");
|
|
writeDataToQuicStream(*stream, buf->clone(), true);
|
|
|
|
folly::IOBuf pktBodyCaptured;
|
|
EXPECT_CALL(*capturingAead, _inplaceEncrypt(_, _, _))
|
|
.WillRepeatedly(Invoke([&](auto& buf, auto, auto) {
|
|
if (buf) {
|
|
pktBodyCaptured.prependChain(buf->clone());
|
|
return buf->clone();
|
|
} else {
|
|
return folly::IOBuf::create(0);
|
|
}
|
|
}));
|
|
EXPECT_EQ(
|
|
1,
|
|
writeProbingDataToSocketForTest(
|
|
*rawSocket, *conn, 1, *aead, *headerCipher, getVersion(*conn)));
|
|
// Now we have no new data, let's probe again, and verify the same old data
|
|
// is sent.
|
|
folly::IOBuf secondBodyCaptured;
|
|
EXPECT_CALL(*capturingAead, _inplaceEncrypt(_, _, _))
|
|
.WillRepeatedly(Invoke([&](auto& buf, auto, auto) {
|
|
if (buf) {
|
|
secondBodyCaptured.prependChain(buf->clone());
|
|
return buf->clone();
|
|
} else {
|
|
return folly::IOBuf::create(0);
|
|
}
|
|
}));
|
|
EXPECT_EQ(
|
|
1,
|
|
writeProbingDataToSocketForTest(
|
|
*rawSocket, *conn, 1, *aead, *headerCipher, getVersion(*conn)));
|
|
// Verify two pacekts have the same body
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(pktBodyCaptured, secondBodyCaptured));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteProbingCryptoData) {
|
|
QuicServerConnectionState conn(
|
|
FizzServerQuicHandshakeContext::Builder().build());
|
|
conn.serverConnectionId = getTestConnectionId();
|
|
conn.clientConnectionId = getTestConnectionId();
|
|
// writeCryptoDataProbesToSocketForTest writes Initial LongHeader, thus it
|
|
// writes at Initial level.
|
|
auto currentPacketSeqNum = conn.ackStates.initialAckState.nextPacketNum;
|
|
// Replace real congestionController with MockCongestionController:
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn.congestionController = std::move(mockCongestionController);
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
auto cryptoStream = &conn.cryptoState->initialStream;
|
|
auto buf = buildRandomInputData(conn.udpSendPacketLen * 2);
|
|
writeDataToQuicStream(*cryptoStream, buf->clone());
|
|
|
|
auto currentStreamWriteOffset = cryptoStream->currentWriteOffset;
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
EXPECT_CALL(*rawSocket, write(_, _))
|
|
.WillOnce(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& iobuf) {
|
|
auto len = iobuf->computeChainDataLength();
|
|
EXPECT_EQ(conn.udpSendPacketLen - aead->getCipherOverhead(), len);
|
|
return len;
|
|
}));
|
|
writeCryptoDataProbesToSocketForTest(
|
|
*rawSocket, conn, 1, *aead, *headerCipher, getVersion(conn));
|
|
EXPECT_LT(currentPacketSeqNum, conn.ackStates.initialAckState.nextPacketNum);
|
|
EXPECT_FALSE(conn.outstandings.packets.empty());
|
|
EXPECT_TRUE(conn.pendingEvents.setLossDetectionAlarm);
|
|
EXPECT_GT(cryptoStream->currentWriteOffset, currentStreamWriteOffset);
|
|
EXPECT_FALSE(cryptoStream->retransmissionBuffer.empty());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ProbingNotFallbackToPingWhenNoQuota) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(0);
|
|
EXPECT_CALL(*rawSocket, write(_, _)).Times(0);
|
|
uint8_t probesToSend = 0;
|
|
EXPECT_EQ(
|
|
0,
|
|
writeProbingDataToSocketForTest(
|
|
*rawSocket,
|
|
*conn,
|
|
probesToSend,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn)));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ProbingFallbackToPing) {
|
|
auto conn = createConn();
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
EXPECT_CALL(*rawSocket, write(_, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& iobuf) {
|
|
return iobuf->computeChainDataLength();
|
|
}));
|
|
uint8_t probesToSend = 1;
|
|
EXPECT_EQ(
|
|
1,
|
|
writeProbingDataToSocketForTest(
|
|
*rawSocket,
|
|
*conn,
|
|
probesToSend,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn)));
|
|
// Ping is the only non-retransmittable packet that will go into OP list
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TestCryptoWritingIsHandshakeInOutstanding) {
|
|
auto conn = createConn();
|
|
auto cryptoStream = &conn->cryptoState->initialStream;
|
|
auto buf = buildRandomInputData(200);
|
|
writeDataToQuicStream(*cryptoStream, buf->clone());
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
auto res = writeCryptoAndAckDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
LongHeader::Types::Initial,
|
|
*conn->initialWriteCipher,
|
|
*conn->initialHeaderCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
|
|
EXPECT_EQ(1, res.packetsWritten);
|
|
EXPECT_EQ(0, res.probesWritten);
|
|
EXPECT_GE(res.bytesWritten, buf->computeChainDataLength());
|
|
ASSERT_EQ(1, conn->outstandings.packets.size());
|
|
EXPECT_TRUE(getFirstOutstandingPacket(*conn, PacketNumberSpace::Initial)
|
|
->metadata.isHandshake);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, NoCryptoProbeWriteIfNoProbeCredit) {
|
|
auto conn = createConn();
|
|
auto cryptoStream = &conn->cryptoState->initialStream;
|
|
auto buf = buildRandomInputData(200);
|
|
writeDataToQuicStream(*cryptoStream, buf->clone());
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
auto res = writeCryptoAndAckDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
LongHeader::Types::Initial,
|
|
*conn->initialWriteCipher,
|
|
*conn->initialHeaderCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_GE(res.bytesWritten, buf->computeChainDataLength());
|
|
|
|
EXPECT_EQ(1, res.packetsWritten);
|
|
EXPECT_EQ(0, res.probesWritten);
|
|
ASSERT_EQ(1, conn->outstandings.packets.size());
|
|
EXPECT_TRUE(getFirstOutstandingPacket(*conn, PacketNumberSpace::Initial)
|
|
->metadata.isHandshake);
|
|
ASSERT_EQ(1, cryptoStream->retransmissionBuffer.size());
|
|
ASSERT_TRUE(cryptoStream->writeBuffer.empty());
|
|
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Initial] = 0;
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Handshake] = 0;
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData] = 0;
|
|
res = writeCryptoAndAckDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
LongHeader::Types::Initial,
|
|
*conn->initialWriteCipher,
|
|
*conn->initialHeaderCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(0, res.bytesWritten);
|
|
EXPECT_EQ(0, res.packetsWritten);
|
|
EXPECT_EQ(0, res.probesWritten);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ResetNumProbePackets) {
|
|
auto conn = createConn();
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Initial] = 2;
|
|
auto writeRes1 = writeCryptoAndAckDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
LongHeader::Types::Initial,
|
|
*conn->initialWriteCipher,
|
|
*conn->initialHeaderCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_FALSE(conn->pendingEvents.anyProbePackets());
|
|
EXPECT_EQ(0, writeRes1.bytesWritten);
|
|
|
|
conn->handshakeWriteCipher = createNoOpAead();
|
|
conn->handshakeWriteHeaderCipher = createNoOpHeaderCipher();
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Handshake] = 2;
|
|
auto writeRes2 = writeCryptoAndAckDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
LongHeader::Types::Handshake,
|
|
*conn->handshakeWriteCipher,
|
|
*conn->handshakeWriteHeaderCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_FALSE(conn->pendingEvents.anyProbePackets());
|
|
EXPECT_EQ(0, writeRes2.bytesWritten);
|
|
|
|
conn->oneRttWriteCipher = createNoOpAead();
|
|
conn->oneRttWriteHeaderCipher = createNoOpHeaderCipher();
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData] = 2;
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*conn->oneRttWriteCipher,
|
|
*conn->oneRttWriteHeaderCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_FALSE(conn->pendingEvents.anyProbePackets());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WritePureAckWhenNoWritableBytes) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = IOBuf::copyBuffer("0123456789012");
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
|
|
addAckStatesWithCurrentTimestamps(conn->ackStates.appDataAckState, 0, 100);
|
|
conn->ackStates.appDataAckState.needsToSendAckImmediately = true;
|
|
conn->ackStates.appDataAckState.largestAckScheduled = 50;
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(0));
|
|
|
|
EXPECT_CALL(*rawSocket, write(_, _))
|
|
.WillRepeatedly(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& iobuf) {
|
|
EXPECT_LE(iobuf->computeChainDataLength(), 30);
|
|
return iobuf->computeChainDataLength();
|
|
}));
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(0);
|
|
auto res = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_GT(res.packetsWritten, 0);
|
|
EXPECT_EQ(0, conn->outstandings.packets.size());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ShouldWriteDataTest) {
|
|
auto conn = createConn();
|
|
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(1500));
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
|
|
// Pure acks without an oneRttCipher
|
|
CHECK(!conn->oneRttWriteCipher);
|
|
conn->ackStates.appDataAckState.needsToSendAckImmediately = true;
|
|
addAckStatesWithCurrentTimestamps(conn->ackStates.appDataAckState, 1, 20);
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
EXPECT_CALL(*quicStats_, onCwndBlocked()).Times(0);
|
|
EXPECT_NE(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = IOBuf::copyBuffer("0123456789");
|
|
writeDataToQuicStream(*stream1, buf->clone(), false);
|
|
EXPECT_NE(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
// Congestion control
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(0));
|
|
EXPECT_CALL(*quicStats_, onCwndBlocked());
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
EXPECT_CALL(*quicStats_, onCwndBlocked());
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ShouldWriteDataTestDuringPathValidation) {
|
|
auto conn = createConn();
|
|
|
|
// Create the CC.
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
|
|
// Create an outstandingPathValidation + limiter so this will be applied.
|
|
auto pathValidationLimiter = std::make_unique<MockPendingPathRateLimiter>();
|
|
MockPendingPathRateLimiter* rawLimiter = pathValidationLimiter.get();
|
|
conn->pathValidationLimiter = std::move(pathValidationLimiter);
|
|
conn->outstandingPathValidation = PathChallengeFrame(1000);
|
|
|
|
// Have stream data queued up during the test so there's something TO write.
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = IOBuf::copyBuffer("0123456789");
|
|
writeDataToQuicStream(*stream1, buf->clone(), false);
|
|
|
|
// Only case that we allow the write; both CC / PathLimiter have
|
|
// writablebytes
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes()).WillOnce(Return(1));
|
|
EXPECT_CALL(*rawLimiter, currentCredit(_, _)).WillOnce(Return(1));
|
|
|
|
EXPECT_CALL(*quicStats_, onCwndBlocked()).Times(0);
|
|
EXPECT_NE(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
// CC has writableBytes, but PathLimiter doesn't.
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes()).WillOnce(Return(1));
|
|
EXPECT_CALL(*rawLimiter, currentCredit(_, _)).WillOnce(Return(0));
|
|
|
|
EXPECT_CALL(*quicStats_, onCwndBlocked());
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
// PathLimiter has writableBytes, CC doesn't
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes()).WillOnce(Return(0));
|
|
EXPECT_CALL(*rawLimiter, currentCredit(_, _)).WillOnce(Return(1));
|
|
|
|
EXPECT_CALL(*quicStats_, onCwndBlocked());
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
// Neither PathLimiter or CC have writablebytes
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes()).WillOnce(Return(0));
|
|
EXPECT_CALL(*rawLimiter, currentCredit(_, _)).WillOnce(Return(0));
|
|
|
|
EXPECT_CALL(*quicStats_, onCwndBlocked());
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ShouldWriteStreamsNoCipher) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(1500));
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = IOBuf::copyBuffer("0123456789");
|
|
writeDataToQuicStream(*stream1, buf->clone(), false);
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ShouldWritePureAcksNoCipher) {
|
|
auto conn = createConn();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(1500));
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
|
|
conn->ackStates.appDataAckState.needsToSendAckImmediately = true;
|
|
addAckStatesWithCurrentTimestamps(conn->ackStates.appDataAckState, 1, 20);
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ShouldWriteDataNoConnFlowControl) {
|
|
auto conn = createConn();
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(1500));
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = IOBuf::copyBuffer("0123456789");
|
|
writeDataToQuicStream(*stream1, buf->clone(), false);
|
|
EXPECT_NE(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
// Artificially limit the connection flow control.
|
|
conn->flowControlState.peerAdvertisedMaxOffset = 0;
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ShouldWriteDataNoConnFlowControlLoss) {
|
|
auto conn = createConn();
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(1500));
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = IOBuf::copyBuffer("0123456789");
|
|
writeDataToQuicStream(*stream1, buf->clone(), false);
|
|
EXPECT_NE(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
// Artificially limit the connection flow control.
|
|
conn->streamManager->addLoss(stream1->id);
|
|
conn->flowControlState.peerAdvertisedMaxOffset = 0;
|
|
EXPECT_NE(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, HasAckDataToWriteCipherAndAckStateMatch) {
|
|
auto conn = createConn();
|
|
EXPECT_FALSE(hasAckDataToWrite(*conn));
|
|
conn->initialWriteCipher = test::createNoOpAead();
|
|
EXPECT_FALSE(hasAckDataToWrite(*conn));
|
|
conn->ackStates.appDataAckState.needsToSendAckImmediately = true;
|
|
conn->ackStates.appDataAckState.acks.insert(0, 100);
|
|
EXPECT_FALSE(hasAckDataToWrite(*conn));
|
|
conn->ackStates.initialAckState.needsToSendAckImmediately = true;
|
|
conn->ackStates.initialAckState.acks.insert(0, 100);
|
|
EXPECT_TRUE(hasAckDataToWrite(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, HasAckDataToWriteNoImmediateAcks) {
|
|
auto conn = createConn();
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
conn->ackStates.appDataAckState.acks.insert(0, 100);
|
|
conn->ackStates.appDataAckState.needsToSendAckImmediately = false;
|
|
EXPECT_FALSE(hasAckDataToWrite(*conn));
|
|
conn->ackStates.appDataAckState.needsToSendAckImmediately = true;
|
|
EXPECT_TRUE(hasAckDataToWrite(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, HasAckDataToWriteNoAcksScheduled) {
|
|
auto conn = createConn();
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
conn->ackStates.initialAckState.needsToSendAckImmediately = true;
|
|
EXPECT_FALSE(hasAckDataToWrite(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, HasAckDataToWrite) {
|
|
auto conn = createConn();
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
conn->ackStates.initialAckState.needsToSendAckImmediately = true;
|
|
conn->ackStates.initialAckState.acks.insert(0);
|
|
EXPECT_TRUE(hasAckDataToWrite(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, HasAckDataToWriteMismatch) {
|
|
// When one ack space has needsToSendAckImmediately = true and another has
|
|
// hasAckToSchedule = true, but no ack space has both of them to true, we
|
|
// should not send.
|
|
auto conn = createConn();
|
|
EXPECT_FALSE(hasAckDataToWrite(*conn));
|
|
conn->ackStates.initialAckState.needsToSendAckImmediately = true;
|
|
EXPECT_FALSE(hasAckDataToWrite(*conn));
|
|
conn->ackStates.handshakeAckState.acks.insert(0, 10);
|
|
conn->handshakeWriteCipher = test::createNoOpAead();
|
|
EXPECT_FALSE(hasAckDataToWrite(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, HasCryptoDataToWrite) {
|
|
auto conn = createConn();
|
|
conn->cryptoState->initialStream.lossBuffer.emplace_back(
|
|
folly::IOBuf::copyBuffer("Grab your coat and get your hat"), 0, false);
|
|
EXPECT_EQ(WriteDataReason::CRYPTO_STREAM, hasNonAckDataToWrite(*conn));
|
|
conn->initialWriteCipher.reset();
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, hasNonAckDataToWrite(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, HasControlFramesToWrite) {
|
|
auto conn = createConn();
|
|
conn->streamManager->queueBlocked(1, 100);
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, hasNonAckDataToWrite(*conn));
|
|
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
EXPECT_EQ(WriteDataReason::BLOCKED, hasNonAckDataToWrite(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, FlowControlBlocked) {
|
|
auto conn = createConn();
|
|
conn->flowControlState.peerAdvertisedMaxOffset = 1000;
|
|
conn->flowControlState.sumCurWriteOffset = 1000;
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, hasNonAckDataToWrite(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, HasAppDataToWrite) {
|
|
auto conn = createConn();
|
|
conn->flowControlState.peerAdvertisedMaxOffset = 1000;
|
|
conn->flowControlState.sumCurWriteOffset = 800;
|
|
QuicStreamState stream(0, *conn);
|
|
writeDataToQuicStream(stream, folly::IOBuf::copyBuffer("I'm a devil"), true);
|
|
conn->streamManager->addWritable(stream);
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, hasNonAckDataToWrite(*conn));
|
|
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
EXPECT_EQ(WriteDataReason::STREAM, hasNonAckDataToWrite(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, HasDatagramsToWrite) {
|
|
auto conn = createConn();
|
|
conn->oneRttWriteCipher = test::createNoOpAead();
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, hasNonAckDataToWrite(*conn));
|
|
conn->datagramState.writeBuffer.emplace_back(
|
|
folly::IOBuf::copyBuffer("I'm an unreliable Datagram"));
|
|
EXPECT_EQ(WriteDataReason::DATAGRAM, hasNonAckDataToWrite(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, UpdateConnectionCloneCounterAppData) {
|
|
auto conn = createConn();
|
|
ASSERT_EQ(
|
|
0, conn->outstandings.clonedPacketCount[PacketNumberSpace::AppData]);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto connWindowUpdate =
|
|
MaxDataFrame(conn->flowControlState.advertisedMaxOffset);
|
|
conn->pendingEvents.connWindowUpdate = true;
|
|
packet.packet.frames.emplace_back(connWindowUpdate);
|
|
PacketEvent packetEvent(PacketNumberSpace::AppData, 100);
|
|
conn->outstandings.packetEvents.insert(packetEvent);
|
|
updateConnection(
|
|
*conn,
|
|
packetEvent,
|
|
packet.packet,
|
|
TimePoint(),
|
|
123,
|
|
100,
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(
|
|
0, conn->outstandings.clonedPacketCount[PacketNumberSpace::Initial]);
|
|
EXPECT_EQ(
|
|
0, conn->outstandings.clonedPacketCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(
|
|
1, conn->outstandings.clonedPacketCount[PacketNumberSpace::AppData]);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, UpdateConnectionCloneCounterHandshake) {
|
|
auto conn = createConn();
|
|
ASSERT_EQ(
|
|
0, conn->outstandings.clonedPacketCount[PacketNumberSpace::Handshake]);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto connWindowUpdate =
|
|
MaxDataFrame(conn->flowControlState.advertisedMaxOffset);
|
|
conn->pendingEvents.connWindowUpdate = true;
|
|
packet.packet.frames.emplace_back(connWindowUpdate);
|
|
PacketEvent packetEvent(PacketNumberSpace::AppData, 100);
|
|
conn->outstandings.packetEvents.insert(packetEvent);
|
|
updateConnection(
|
|
*conn,
|
|
packetEvent,
|
|
packet.packet,
|
|
TimePoint(),
|
|
123,
|
|
123,
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(
|
|
0, conn->outstandings.clonedPacketCount[PacketNumberSpace::Initial]);
|
|
EXPECT_EQ(
|
|
1, conn->outstandings.clonedPacketCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(
|
|
0, conn->outstandings.clonedPacketCount[PacketNumberSpace::AppData]);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, UpdateConnectionCloneCounterInitial) {
|
|
auto conn = createConn();
|
|
ASSERT_EQ(
|
|
0, conn->outstandings.clonedPacketCount[PacketNumberSpace::Initial]);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Initial);
|
|
auto connWindowUpdate =
|
|
MaxDataFrame(conn->flowControlState.advertisedMaxOffset);
|
|
conn->pendingEvents.connWindowUpdate = true;
|
|
packet.packet.frames.emplace_back(connWindowUpdate);
|
|
PacketEvent packetEvent(PacketNumberSpace::AppData, 100);
|
|
conn->outstandings.packetEvents.insert(packetEvent);
|
|
updateConnection(
|
|
*conn,
|
|
packetEvent,
|
|
packet.packet,
|
|
TimePoint(),
|
|
123,
|
|
123,
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(
|
|
1, conn->outstandings.clonedPacketCount[PacketNumberSpace::Initial]);
|
|
EXPECT_EQ(
|
|
0, conn->outstandings.clonedPacketCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(
|
|
0, conn->outstandings.clonedPacketCount[PacketNumberSpace::AppData]);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ClearBlockedFromPendingEvents) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
StreamDataBlockedFrame blockedFrame(stream->id, 1000);
|
|
packet.packet.frames.push_back(blockedFrame);
|
|
conn->streamManager->queueBlocked(stream->id, 1000);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_FALSE(conn->streamManager->hasBlocked());
|
|
EXPECT_FALSE(conn->outstandings.packets.empty());
|
|
EXPECT_EQ(0, conn->outstandings.numClonedPackets());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ClonedBlocked) {
|
|
auto conn = createConn();
|
|
PacketEvent packetEvent(
|
|
PacketNumberSpace::AppData,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
StreamDataBlockedFrame blockedFrame(stream->id, 1000);
|
|
packet.packet.frames.emplace_back(blockedFrame);
|
|
conn->outstandings.packetEvents.insert(packetEvent);
|
|
// This shall not crash
|
|
updateConnection(
|
|
*conn,
|
|
packetEvent,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_FALSE(conn->outstandings.packets.empty());
|
|
EXPECT_EQ(
|
|
1, conn->outstandings.clonedPacketCount[PacketNumberSpace::AppData]);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TwoConnWindowUpdateWillCrash) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
MaxDataFrame connWindowUpdate(
|
|
1000 + conn->flowControlState.advertisedMaxOffset);
|
|
packet.packet.frames.emplace_back(connWindowUpdate);
|
|
packet.packet.frames.emplace_back(connWindowUpdate);
|
|
conn->pendingEvents.connWindowUpdate = true;
|
|
EXPECT_DEATH(
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */),
|
|
".*Send more than one connection window update.*");
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteStreamFrameIsNotPureAck) {
|
|
auto conn = createConn();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("I feel like a million bucks."), true);
|
|
WriteStreamFrame writeStreamFrame(stream->id, 0, 5, false);
|
|
packet.packet.frames.push_back(std::move(writeStreamFrame));
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_FALSE(conn->outstandings.packets.empty());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ClearRstFromPendingEvents) {
|
|
auto conn = createConn();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
RstStreamFrame rstStreamFrame(
|
|
stream->id, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
packet.packet.frames.push_back(rstStreamFrame);
|
|
conn->pendingEvents.resets.emplace(stream->id, rstStreamFrame);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_TRUE(conn->pendingEvents.resets.empty());
|
|
EXPECT_FALSE(conn->outstandings.packets.empty());
|
|
EXPECT_EQ(0, conn->outstandings.numClonedPackets());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ClonedRst) {
|
|
auto conn = createConn();
|
|
PacketEvent packetEvent(
|
|
PacketNumberSpace::AppData,
|
|
conn->ackStates.appDataAckState.nextPacketNum);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
RstStreamFrame rstStreamFrame(
|
|
stream->id, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
packet.packet.frames.emplace_back(std::move(rstStreamFrame));
|
|
conn->outstandings.packetEvents.insert(packetEvent);
|
|
// This shall not crash
|
|
updateConnection(
|
|
*conn,
|
|
packetEvent,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
EXPECT_FALSE(conn->outstandings.packets.empty());
|
|
EXPECT_EQ(1, conn->outstandings.numClonedPackets());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TotalBytesSentUpdate) {
|
|
auto conn = createConn();
|
|
conn->lossState.totalBytesSent = 1234;
|
|
conn->lossState.totalBodyBytesSent = 1000;
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint{},
|
|
4321,
|
|
4000,
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(5555, conn->lossState.totalBytesSent);
|
|
EXPECT_EQ(5000, conn->lossState.totalBodyBytesSent);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TotalPacketsSentUpdate) {
|
|
const auto startTotalPacketsSent = 1234;
|
|
auto conn = createConn();
|
|
conn->lossState.totalPacketsSent = startTotalPacketsSent;
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint{},
|
|
4321,
|
|
0,
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(startTotalPacketsSent + 1, conn->lossState.totalPacketsSent);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, TimeoutBasedRetxCountUpdate) {
|
|
auto conn = createConn();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
conn->lossState.timeoutBasedRtxCount = 246;
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
RstStreamFrame rstStreamFrame(
|
|
stream->id, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
packet.packet.frames.push_back(rstStreamFrame);
|
|
PacketEvent packetEvent(PacketNumberSpace::AppData, 100);
|
|
conn->outstandings.packetEvents.insert(packetEvent);
|
|
updateConnection(
|
|
*conn,
|
|
packetEvent,
|
|
packet.packet,
|
|
TimePoint(),
|
|
0,
|
|
0,
|
|
false /* isDSRPacket */);
|
|
EXPECT_EQ(247, conn->lossState.timeoutBasedRtxCount);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteLimitBytRttFraction) {
|
|
auto conn = createConn();
|
|
conn->lossState.srtt = 50ms;
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
conn->transportSettings.batchingMode = QuicBatchingMode::BATCHING_MODE_NONE;
|
|
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = buildRandomInputData(2048 * 2048);
|
|
writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
|
|
EXPECT_CALL(*rawSocket, write(_, _)).WillRepeatedly(Return(1));
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(50));
|
|
auto writeLoopBeginTime = Clock::now();
|
|
auto res = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
1000 /* packetLimit */,
|
|
writeLoopBeginTime);
|
|
|
|
EXPECT_GT(1000, res.packetsWritten);
|
|
EXPECT_EQ(res.probesWritten, 0);
|
|
|
|
res = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
1000 /* packetLimit */,
|
|
writeLoopBeginTime);
|
|
EXPECT_EQ(
|
|
conn->transportSettings.writeConnectionDataPacketsLimit,
|
|
res.packetsWritten);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, CongestionControlWritableBytesRoundUp) {
|
|
auto conn = createConn();
|
|
conn->udpSendPacketLen = 2000;
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes()).WillOnce(Return(1));
|
|
EXPECT_EQ(conn->udpSendPacketLen, congestionControlWritableBytes(*conn));
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillOnce(Return(1000));
|
|
EXPECT_EQ(conn->udpSendPacketLen, congestionControlWritableBytes(*conn));
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes()).WillOnce(Return(0));
|
|
EXPECT_EQ(0, congestionControlWritableBytes(*conn));
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillOnce(Return(2000));
|
|
EXPECT_EQ(conn->udpSendPacketLen, congestionControlWritableBytes(*conn));
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillOnce(Return(2001));
|
|
EXPECT_EQ(conn->udpSendPacketLen * 2, congestionControlWritableBytes(*conn));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, HandshakeConfirmedDropCipher) {
|
|
auto conn = createConn();
|
|
conn->readCodec = std::make_unique<QuicReadCodec>(QuicNodeType::Server);
|
|
EventBase evb;
|
|
auto socket =
|
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto initialStream =
|
|
getCryptoStream(*conn->cryptoState, EncryptionLevel::Initial);
|
|
auto handshakeStream =
|
|
getCryptoStream(*conn->cryptoState, EncryptionLevel::Handshake);
|
|
writeDataToQuicStream(
|
|
*initialStream, folly::IOBuf::copyBuffer("LittleRemedies"));
|
|
writeDataToQuicStream(
|
|
*handshakeStream,
|
|
folly::IOBuf::copyBuffer("Where should I join the meeting"));
|
|
ASSERT_NE(nullptr, conn->initialWriteCipher);
|
|
conn->handshakeWriteCipher = createNoOpAead();
|
|
conn->readCodec->setInitialReadCipher(createNoOpAead());
|
|
conn->readCodec->setInitialHeaderCipher(createNoOpHeaderCipher());
|
|
conn->readCodec->setHandshakeReadCipher(createNoOpAead());
|
|
conn->readCodec->setHandshakeHeaderCipher(createNoOpHeaderCipher());
|
|
conn->oneRttWriteCipher = createNoOpAead();
|
|
conn->oneRttWriteHeaderCipher = createNoOpHeaderCipher();
|
|
conn->readCodec->setOneRttReadCipher(createNoOpAead());
|
|
conn->readCodec->setOneRttHeaderCipher(createNoOpHeaderCipher());
|
|
writeCryptoDataProbesToSocketForTest(
|
|
*socket,
|
|
*conn,
|
|
1,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
LongHeader::Types::Initial);
|
|
writeCryptoDataProbesToSocketForTest(
|
|
*socket,
|
|
*conn,
|
|
1,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
LongHeader::Types::Handshake);
|
|
ASSERT_FALSE(initialStream->retransmissionBuffer.empty());
|
|
ASSERT_FALSE(handshakeStream->retransmissionBuffer.empty());
|
|
initialStream->insertIntoLossBuffer(std::make_unique<StreamBuffer>(
|
|
folly::IOBuf::copyBuffer(
|
|
"I don't see the dialup info in the meeting invite"),
|
|
0,
|
|
false));
|
|
handshakeStream->insertIntoLossBuffer(std::make_unique<StreamBuffer>(
|
|
folly::IOBuf::copyBuffer("Traffic Protocol Weekly Sync"), 0, false));
|
|
|
|
handshakeConfirmed(*conn);
|
|
EXPECT_TRUE(initialStream->writeBuffer.empty());
|
|
EXPECT_TRUE(initialStream->retransmissionBuffer.empty());
|
|
EXPECT_TRUE(initialStream->lossBuffer.empty());
|
|
EXPECT_TRUE(handshakeStream->writeBuffer.empty());
|
|
EXPECT_TRUE(handshakeStream->retransmissionBuffer.empty());
|
|
EXPECT_TRUE(handshakeStream->lossBuffer.empty());
|
|
EXPECT_EQ(nullptr, conn->initialWriteCipher);
|
|
EXPECT_EQ(nullptr, conn->handshakeWriteCipher);
|
|
EXPECT_EQ(nullptr, conn->readCodec->getInitialCipher());
|
|
EXPECT_EQ(nullptr, conn->readCodec->getInitialHeaderCipher());
|
|
EXPECT_EQ(nullptr, conn->readCodec->getHandshakeReadCipher());
|
|
EXPECT_EQ(nullptr, conn->readCodec->getHandshakeHeaderCipher());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ProbeWriteNewFunctionalFrames) {
|
|
auto conn = createConn();
|
|
conn->udpSendPacketLen = 1200;
|
|
EventBase evb;
|
|
auto sock = std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&evb);
|
|
auto rawSocket = sock.get();
|
|
|
|
EXPECT_CALL(*rawSocket, write(_, _))
|
|
.WillRepeatedly(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& iobuf) {
|
|
return iobuf->computeChainDataLength();
|
|
}));
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = folly::IOBuf::copyBuffer("Drug facts");
|
|
writeDataToQuicStream(*stream, buf->clone(), true);
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
ASSERT_EQ(1, stream->retransmissionBuffer.size());
|
|
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData] = 1;
|
|
conn->flowControlState.windowSize *= 2;
|
|
conn->flowControlState.timeOfLastFlowControlUpdate = Clock::now() - 20s;
|
|
maybeSendConnWindowUpdate(*conn, Clock::now());
|
|
writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
1 /* limit to 1 packet */);
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
EXPECT_EQ(1, conn->outstandings.packets[1].packet.frames.size());
|
|
EXPECT_EQ(
|
|
QuicWriteFrame::Type::MaxDataFrame,
|
|
conn->outstandings.packets[1].packet.frames[0].type());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteWithInplaceBuilder) {
|
|
auto conn = createConn();
|
|
conn->transportSettings.dataPathType = DataPathType::ContinuousMemory;
|
|
auto simpleBufAccessor =
|
|
std::make_unique<SimpleBufAccessor>(conn->udpSendPacketLen * 16);
|
|
auto outputBuf = simpleBufAccessor->obtain();
|
|
auto bufPtr = outputBuf.get();
|
|
simpleBufAccessor->release(std::move(outputBuf));
|
|
conn->bufAccessor = simpleBufAccessor.get();
|
|
conn->transportSettings.batchingMode = QuicBatchingMode::BATCHING_MODE_GSO;
|
|
EventBase evb;
|
|
folly::test::MockAsyncUDPSocket mockSock(&evb);
|
|
EXPECT_CALL(mockSock, getGSO()).WillRepeatedly(Return(true));
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = folly::IOBuf::copyBuffer("Andante in C minor");
|
|
writeDataToQuicStream(*stream, buf->clone(), true);
|
|
EXPECT_CALL(mockSock, write(_, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& sockBuf) {
|
|
EXPECT_GT(bufPtr->length(), 0);
|
|
EXPECT_GE(sockBuf->length(), buf->length());
|
|
EXPECT_EQ(sockBuf.get(), bufPtr);
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*sockBuf, *bufPtr));
|
|
EXPECT_FALSE(sockBuf->isChained());
|
|
return sockBuf->computeChainDataLength();
|
|
}));
|
|
writeQuicDataToSocket(
|
|
mockSock,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(0, bufPtr->length());
|
|
EXPECT_EQ(0, bufPtr->headroom());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteWithInplaceBuilderRollbackBuf) {
|
|
auto conn = createConn();
|
|
conn->transportSettings.dataPathType = DataPathType::ContinuousMemory;
|
|
auto simpleBufAccessor =
|
|
std::make_unique<SimpleBufAccessor>(conn->udpSendPacketLen * 16);
|
|
auto outputBuf = simpleBufAccessor->obtain();
|
|
auto bufPtr = outputBuf.get();
|
|
simpleBufAccessor->release(std::move(outputBuf));
|
|
conn->bufAccessor = simpleBufAccessor.get();
|
|
conn->transportSettings.batchingMode = QuicBatchingMode::BATCHING_MODE_GSO;
|
|
EventBase evb;
|
|
folly::test::MockAsyncUDPSocket mockSock(&evb);
|
|
EXPECT_CALL(mockSock, getGSO()).WillRepeatedly(Return(true));
|
|
EXPECT_CALL(mockSock, write(_, _)).Times(0);
|
|
writeQuicDataToSocket(
|
|
mockSock,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(0, bufPtr->length());
|
|
EXPECT_EQ(0, bufPtr->headroom());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteWithInplaceBuilderGSOMultiplePackets) {
|
|
auto conn = createConn();
|
|
conn->transportSettings.dataPathType = DataPathType::ContinuousMemory;
|
|
auto simpleBufAccessor =
|
|
std::make_unique<SimpleBufAccessor>(conn->udpSendPacketLen * 16);
|
|
auto outputBuf = simpleBufAccessor->obtain();
|
|
auto bufPtr = outputBuf.get();
|
|
simpleBufAccessor->release(std::move(outputBuf));
|
|
conn->bufAccessor = simpleBufAccessor.get();
|
|
conn->transportSettings.batchingMode = QuicBatchingMode::BATCHING_MODE_GSO;
|
|
EventBase evb;
|
|
folly::test::MockAsyncUDPSocket mockSock(&evb);
|
|
EXPECT_CALL(mockSock, getGSO()).WillRepeatedly(Return(true));
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = buildRandomInputData(conn->udpSendPacketLen * 10);
|
|
writeDataToQuicStream(*stream, buf->clone(), true);
|
|
EXPECT_CALL(mockSock, writeGSO(_, _, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const folly::SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& sockBuf,
|
|
int gso) {
|
|
EXPECT_LE(gso, conn->udpSendPacketLen);
|
|
EXPECT_GT(bufPtr->length(), 0);
|
|
EXPECT_EQ(sockBuf.get(), bufPtr);
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*sockBuf, *bufPtr));
|
|
EXPECT_FALSE(sockBuf->isChained());
|
|
return sockBuf->length();
|
|
}));
|
|
writeQuicDataToSocket(
|
|
mockSock,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(0, bufPtr->length());
|
|
EXPECT_EQ(0, bufPtr->headroom());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteProbingWithInplaceBuilder) {
|
|
auto conn = createConn();
|
|
conn->transportSettings.dataPathType = DataPathType::ContinuousMemory;
|
|
conn->transportSettings.batchingMode = QuicBatchingMode::BATCHING_MODE_GSO;
|
|
EventBase evb;
|
|
folly::test::MockAsyncUDPSocket mockSock(&evb);
|
|
EXPECT_CALL(mockSock, getGSO()).WillRepeatedly(Return(true));
|
|
|
|
SimpleBufAccessor bufAccessor(
|
|
conn->udpSendPacketLen * conn->transportSettings.maxBatchSize);
|
|
conn->bufAccessor = &bufAccessor;
|
|
auto buf = bufAccessor.obtain();
|
|
auto bufPtr = buf.get();
|
|
bufAccessor.release(std::move(buf));
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto inputBuf = buildRandomInputData(
|
|
conn->udpSendPacketLen *
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
writeDataToQuicStream(*stream, inputBuf->clone(), true);
|
|
EXPECT_CALL(mockSock, writeGSO(_, _, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const folly::SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& sockBuf,
|
|
int gso) {
|
|
EXPECT_LE(gso, conn->udpSendPacketLen);
|
|
EXPECT_GE(
|
|
bufPtr->length(),
|
|
conn->udpSendPacketLen *
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_EQ(sockBuf.get(), bufPtr);
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*sockBuf, *bufPtr));
|
|
EXPECT_FALSE(sockBuf->isChained());
|
|
return sockBuf->length();
|
|
}));
|
|
writeQuicDataToSocket(
|
|
mockSock,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit + 1);
|
|
ASSERT_EQ(0, bufPtr->length());
|
|
ASSERT_EQ(0, bufPtr->headroom());
|
|
EXPECT_GE(conn->outstandings.packets.size(), 5);
|
|
// Make sure there no more new data to write:
|
|
StreamFrameScheduler streamScheduler(*conn);
|
|
ASSERT_FALSE(streamScheduler.hasPendingData());
|
|
|
|
// The first packet has be a full packet
|
|
auto firstPacketSize =
|
|
conn->outstandings.packets.front().metadata.encodedSize;
|
|
auto outstandingPacketsCount = conn->outstandings.packets.size();
|
|
ASSERT_EQ(firstPacketSize, conn->udpSendPacketLen);
|
|
EXPECT_CALL(mockSock, write(_, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const folly::SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& buf) {
|
|
EXPECT_FALSE(buf->isChained());
|
|
EXPECT_EQ(buf->length(), firstPacketSize);
|
|
return buf->length();
|
|
}));
|
|
writeProbingDataToSocketForTest(
|
|
mockSock,
|
|
*conn,
|
|
1 /* probesToSend */,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn));
|
|
EXPECT_EQ(conn->outstandings.packets.size(), outstandingPacketsCount + 1);
|
|
EXPECT_EQ(0, bufPtr->length());
|
|
EXPECT_EQ(0, bufPtr->headroom());
|
|
|
|
// Clone again, this time 2 pacckets.
|
|
EXPECT_CALL(mockSock, writeGSO(_, _, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const folly::SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& buf,
|
|
int gso) {
|
|
EXPECT_FALSE(buf->isChained());
|
|
EXPECT_EQ(conn->udpSendPacketLen, gso);
|
|
EXPECT_EQ(buf->length(), conn->udpSendPacketLen * 2);
|
|
return buf->length();
|
|
}));
|
|
writeProbingDataToSocketForTest(
|
|
mockSock,
|
|
*conn,
|
|
2 /* probesToSend */,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn));
|
|
EXPECT_EQ(0, bufPtr->length());
|
|
EXPECT_EQ(0, bufPtr->headroom());
|
|
EXPECT_EQ(conn->outstandings.packets.size(), outstandingPacketsCount + 3);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteD6DProbesWithInplaceBuilder) {
|
|
auto conn = createConn();
|
|
conn->transportSettings.dataPathType = DataPathType::ContinuousMemory;
|
|
conn->d6d.currentProbeSize = 1450;
|
|
conn->pendingEvents.d6d.sendProbePacket = true;
|
|
auto simpleBufAccessor =
|
|
std::make_unique<SimpleBufAccessor>(kDefaultMaxUDPPayload * 16);
|
|
auto outputBuf = simpleBufAccessor->obtain();
|
|
auto bufPtr = outputBuf.get();
|
|
simpleBufAccessor->release(std::move(outputBuf));
|
|
conn->bufAccessor = simpleBufAccessor.get();
|
|
conn->transportSettings.batchingMode = QuicBatchingMode::BATCHING_MODE_GSO;
|
|
EventBase evb;
|
|
folly::test::MockAsyncUDPSocket mockSock(&evb);
|
|
EXPECT_CALL(mockSock, getGSO()).WillRepeatedly(Return(true));
|
|
EXPECT_CALL(mockSock, write(_, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const SocketAddress&,
|
|
const std::unique_ptr<folly::IOBuf>& sockBuf) {
|
|
EXPECT_EQ(sockBuf->length(), conn->d6d.currentProbeSize);
|
|
EXPECT_EQ(sockBuf.get(), bufPtr);
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*sockBuf, *bufPtr));
|
|
EXPECT_FALSE(sockBuf->isChained());
|
|
return sockBuf->computeChainDataLength();
|
|
}));
|
|
writeD6DProbeToSocket(
|
|
mockSock,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn));
|
|
EXPECT_EQ(0, bufPtr->length());
|
|
EXPECT_EQ(0, bufPtr->headroom());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, UpdateConnectionWithBufferMeta) {
|
|
auto conn = createConn();
|
|
// Builds a fake packet to test with.
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
|
|
auto streamId =
|
|
conn->streamManager->createNextBidirectionalStream().value()->id;
|
|
auto stream = conn->streamManager->findStream(streamId);
|
|
EXPECT_TRUE(stream->retransmissionBufMetas.empty());
|
|
writeDataToQuicStream(
|
|
*stream, IOBuf::copyBuffer("Wear a face mask please!"), false /* eof */);
|
|
BufferMeta bufMeta(2000);
|
|
writeBufMetaToQuicStream(*stream, bufMeta, true /* eof */);
|
|
EXPECT_TRUE(stream->writeBufMeta.eof);
|
|
EXPECT_EQ(2000, stream->writeBufMeta.length);
|
|
auto bufMetaStartingOffset = stream->writeBufMeta.offset;
|
|
WriteStreamFrame writeStreamFrame(
|
|
streamId, bufMetaStartingOffset, 1000, false /* fin */);
|
|
writeStreamFrame.fromBufMeta = true;
|
|
packet.packet.frames.push_back(writeStreamFrame);
|
|
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
true /* dsr */);
|
|
EXPECT_EQ(1000 + bufMetaStartingOffset, stream->writeBufMeta.offset);
|
|
EXPECT_EQ(1000, stream->writeBufMeta.length);
|
|
EXPECT_FALSE(stream->retransmissionBufMetas.empty());
|
|
auto retxBufMetaIter =
|
|
stream->retransmissionBufMetas.find(bufMetaStartingOffset);
|
|
EXPECT_NE(retxBufMetaIter, stream->retransmissionBufMetas.end());
|
|
EXPECT_EQ(bufMetaStartingOffset, retxBufMetaIter->second.offset);
|
|
EXPECT_EQ(1000, retxBufMetaIter->second.length);
|
|
EXPECT_FALSE(retxBufMetaIter->second.eof);
|
|
EXPECT_TRUE(conn->outstandings.packets.back().isDSRPacket);
|
|
|
|
// Manually lose this packet:
|
|
stream->lossBufMetas.push_back(retxBufMetaIter->second);
|
|
stream->retransmissionBufMetas.erase(retxBufMetaIter);
|
|
ASSERT_FALSE(stream->lossBufMetas.empty());
|
|
ASSERT_TRUE(stream->retransmissionBufMetas.empty());
|
|
|
|
// Retransmit it:
|
|
auto retxPacket = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
// Retx of the stream looks exactly the same
|
|
retxPacket.packet.frames.push_back(writeStreamFrame);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
retxPacket.packet,
|
|
TimePoint(),
|
|
getEncodedSize(retxPacket),
|
|
getEncodedBodySize(packet),
|
|
true /* dsr */);
|
|
EXPECT_TRUE(stream->lossBufMetas.empty());
|
|
retxBufMetaIter = stream->retransmissionBufMetas.find(bufMetaStartingOffset);
|
|
EXPECT_NE(retxBufMetaIter, stream->retransmissionBufMetas.end());
|
|
EXPECT_EQ(bufMetaStartingOffset, retxBufMetaIter->second.offset);
|
|
EXPECT_EQ(1000, retxBufMetaIter->second.length);
|
|
EXPECT_FALSE(retxBufMetaIter->second.eof);
|
|
EXPECT_TRUE(conn->outstandings.packets.back().isDSRPacket);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, MissingStreamFrameBytes) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
writeDataToQuicStream(*stream, folly::IOBuf::copyBuffer("abcdefghij"), true);
|
|
|
|
// write frame with bytes 0 -> 3 (start at offset 0, write 4 bytes)
|
|
{
|
|
WriteStreamFrame writeStreamFrame(
|
|
stream->id, 0 /* offset */, 4 /* len */, false /* fin */);
|
|
packet.packet.frames.push_back(writeStreamFrame);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
}
|
|
|
|
// write frame with bytes 5 -> 6 (start at offset 5, write 2 bytes)
|
|
// should throw since we never wrote byte offset 4
|
|
{
|
|
WriteStreamFrame writeStreamFrame(
|
|
stream->id, 5 /* offset */, 2 /* len */, false /* fin */);
|
|
packet.packet.frames.push_back(writeStreamFrame);
|
|
EXPECT_ANY_THROW(updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */));
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, MissingStreamFrameBytesEof) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
const std::string str = "abcdefg";
|
|
writeDataToQuicStream(*stream, folly::IOBuf::copyBuffer(str), true);
|
|
|
|
// write frame with bytes 0 -> 3 (start at offset 0, write 4 bytes)
|
|
{
|
|
WriteStreamFrame writeStreamFrame(
|
|
stream->id, 0 /* offset */, 4 /* len */, false /* fin */);
|
|
packet.packet.frames.push_back(writeStreamFrame);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
}
|
|
|
|
// write frame with bytes 5 -> 6 (start at offset 5, write 2 bytes)
|
|
// offset 6 should be last byte in original stream, so we'll mark fin
|
|
//
|
|
// should throw since we never wrote byte offset 4
|
|
{
|
|
const auto offset = 5;
|
|
const auto len = 2;
|
|
EXPECT_EQ(str.length(), offset + len); // should be end of string
|
|
WriteStreamFrame writeStreamFrame(
|
|
stream->id, offset /* offset */, len /* len */, true /* fin */);
|
|
packet.packet.frames.push_back(writeStreamFrame);
|
|
EXPECT_ANY_THROW(updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */));
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, MissingStreamFrameBytesSingleByteWrite) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
const std::string str = "abcdefg";
|
|
writeDataToQuicStream(*stream, folly::IOBuf::copyBuffer(str), true);
|
|
|
|
// write frame with bytes 0 -> 3 (start at offset 0, write 4 bytes)
|
|
{
|
|
WriteStreamFrame writeStreamFrame(
|
|
stream->id, 0 /* offset */, 4 /* len */, false /* fin */);
|
|
packet.packet.frames.push_back(writeStreamFrame);
|
|
updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
}
|
|
|
|
// write frame with bytes 5 -> 5 (start at offset 5, write 1 byte)
|
|
// should throw since we never wrote byte offset 4
|
|
{
|
|
WriteStreamFrame writeStreamFrame(
|
|
stream->id, 5 /* offset */, 1 /* len */, false /* fin */);
|
|
packet.packet.frames.push_back(writeStreamFrame);
|
|
EXPECT_ANY_THROW(updateConnection(
|
|
*conn,
|
|
folly::none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */));
|
|
}
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace quic
|