mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-04-18 17:24:03 +03:00
Summary: As in title, this is more of a theme on adding an Expected return. Reviewed By: kvtsoy Differential Revision: D72579218 fbshipit-source-id: 25735535368838f1a4315667cd7e9e9b5df1c485
4997 lines
196 KiB
C++
4997 lines
196 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 <folly/Range.h>
|
|
#include <quic/api/QuicTransportFunctions.h>
|
|
|
|
#include <quic/api/test/Mocks.h>
|
|
#include <quic/common/events/FollyQuicEventBase.h>
|
|
#include <quic/common/test/TestUtils.h>
|
|
#include <quic/common/testutil/MockAsyncUDPSocket.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::test {
|
|
|
|
using PacketStreamDetails = OutstandingPacketMetadata::StreamDetails;
|
|
|
|
uint64_t writeProbingDataToSocketForTest(
|
|
QuicAsyncUDPSocket& 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();
|
|
auto result = writeProbingDataToSocket(
|
|
sock,
|
|
conn,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
ShortHeaderBuilder(conn.oneRttWritePhase),
|
|
EncryptionLevel::AppData,
|
|
PacketNumberSpace::AppData,
|
|
scheduler,
|
|
probesToSend,
|
|
aead,
|
|
headerCipher,
|
|
version);
|
|
CHECK(!result.hasError());
|
|
return result->probesWritten;
|
|
}
|
|
|
|
void writeCryptoDataProbesToSocketForTest(
|
|
QuicAsyncUDPSocket& 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();
|
|
CHECK(!writeProbingDataToSocket(
|
|
sock,
|
|
conn,
|
|
*conn.clientConnectionId,
|
|
*conn.serverConnectionId,
|
|
LongHeaderBuilder(type),
|
|
protectionTypeToEncryptionLevel(
|
|
longHeaderTypeToProtectionType(type)),
|
|
LongHeader::typeToPacketNumberSpace(type),
|
|
scheduler,
|
|
probesToSend,
|
|
aead,
|
|
headerCipher,
|
|
version)
|
|
.hasError());
|
|
}
|
|
|
|
RegularQuicWritePacket stripPaddingFrames(RegularQuicWritePacket packet) {
|
|
RegularQuicWritePacket::Vec trimmedFrames{};
|
|
for (auto frame : packet.frames) {
|
|
if (!frame.asPaddingFrame()) {
|
|
trimmedFrames.push_back(frame);
|
|
}
|
|
}
|
|
packet.frames = trimmedFrames;
|
|
return packet;
|
|
}
|
|
|
|
auto buildEmptyPacket(
|
|
QuicServerConnectionState& conn,
|
|
PacketNumberSpace pnSpace,
|
|
bool shortHeader = false) {
|
|
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.empty()) {
|
|
encodedSize += packet.header.computeChainDataLength();
|
|
}
|
|
if (!packet.body.empty()) {
|
|
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.empty()) {
|
|
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 =
|
|
kDefaultStreamFlowControlWindow * 1000;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote =
|
|
kDefaultStreamFlowControlWindow * 1000;
|
|
conn->flowControlState.peerAdvertisedInitialMaxStreamOffsetUni =
|
|
kDefaultStreamFlowControlWindow * 1000;
|
|
conn->flowControlState.peerAdvertisedMaxOffset =
|
|
kDefaultConnectionFlowControlWindow * 1000;
|
|
conn->statsCallback = quicStats_.get();
|
|
conn->initialWriteCipher = createNoOpAead();
|
|
conn->initialHeaderCipher = createNoOpHeaderCipher();
|
|
CHECK(
|
|
!conn->streamManager
|
|
->setMaxLocalBidirectionalStreams(kDefaultMaxStreamsBidirectional)
|
|
.hasError());
|
|
CHECK(!conn->streamManager
|
|
->setMaxLocalUnidirectionalStreams(
|
|
kDefaultMaxStreamsUnidirectional)
|
|
.hasError());
|
|
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, PingPacketGoesToOPListAndLossAlarm) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet.packet.frames.push_back(PingFrame());
|
|
EXPECT_EQ(0, conn->outstandings.packets.size());
|
|
auto result = updateConnection(
|
|
*conn, none, packet.packet, Clock::now(), 50, 0, false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
EXPECT_TRUE(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);
|
|
auto result1 = writeDataToQuicStream(*stream1, buf->clone(), true);
|
|
ASSERT_FALSE(result1.hasError());
|
|
auto result2 = writeDataToQuicStream(*stream2, buf->clone(), true);
|
|
ASSERT_FALSE(result2.hasError());
|
|
|
|
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));
|
|
auto result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint{},
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
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);
|
|
|
|
EXPECT_EQ(stream1->retransmissionBuffer.size(), 1);
|
|
auto& rt1 = *stream1->retransmissionBuffer.at(0);
|
|
EXPECT_EQ(rt1.offset, 0);
|
|
std::string expected = "hey w";
|
|
EXPECT_EQ(
|
|
folly::ByteRange((uint8_t*)expected.data(), expected.size()),
|
|
rt1.data.getHead()->getRange());
|
|
|
|
EXPECT_EQ(stream2->retransmissionBuffer.size(), 1);
|
|
auto& rt2 = *stream2->retransmissionBuffer.at(0);
|
|
EXPECT_EQ(rt2.offset, 0);
|
|
EXPECT_EQ(
|
|
folly::ByteRange(buf->buffer(), buf->length()),
|
|
rt2.data.getHead()->getRange());
|
|
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));
|
|
result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
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);
|
|
expected = "hats up";
|
|
EXPECT_EQ(
|
|
folly::ByteRange((uint8_t*)expected.data(), expected.size()),
|
|
rt3.data.getHead()->getRange());
|
|
|
|
auto& rt4 = *stream1->retransmissionBuffer.at(0);
|
|
expected = "hey w";
|
|
EXPECT_EQ(
|
|
folly::ByteRange((uint8_t*)expected.data(), expected.size()),
|
|
rt4.data.getHead()->getRange());
|
|
|
|
// 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);
|
|
expected = "hey wh";
|
|
EXPECT_EQ(
|
|
folly::ByteRange((uint8_t*)expected.data(), expected.size()),
|
|
rt5.data.getHead()->getRange());
|
|
EXPECT_EQ(rt5.offset, 0);
|
|
EXPECT_EQ(rt5.eof, 0);
|
|
|
|
auto& rt6 = stream2->lossBuffer.front();
|
|
expected = "ats up";
|
|
EXPECT_EQ(
|
|
folly::ByteRange((uint8_t*)expected.data(), expected.size()),
|
|
rt6.data.getHead()->getRange());
|
|
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) {
|
|
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");
|
|
auto result1 = writeDataToQuicStream(*stream1, buf->clone(), true /* eof */);
|
|
ASSERT_FALSE(result1.hasError());
|
|
auto result2 = writeDataToQuicStream(*stream2, buf->clone(), true /* eof */);
|
|
ASSERT_FALSE(result2.hasError());
|
|
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));
|
|
auto result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet1.packet,
|
|
TimePoint{},
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
// 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_EQ(
|
|
folly::ByteRange(buf->data(), buf->length()),
|
|
rt.data.getHead()->getRange());
|
|
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_EQ(
|
|
folly::ByteRange(buf->data(), buf->length()),
|
|
rt.data.getHead()->getRange());
|
|
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));
|
|
result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
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) {
|
|
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");
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream1, buf->clone(), true /* eof */).hasError());
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream2, buf->clone(), true /* eof */).hasError());
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream3, buf->clone(), false /* eof */)
|
|
.hasError());
|
|
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));
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet1.packet,
|
|
TimePoint{},
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// 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_EQ(
|
|
folly::ByteRange(buf->data(), buf->length()),
|
|
rt.data.getHead()->getRange());
|
|
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);
|
|
auto expectedBuf = IOBuf::copyBuffer("hey w");
|
|
EXPECT_EQ(
|
|
folly::ByteRange(expectedBuf->data(), expectedBuf->length()),
|
|
rt.data.getHead()->getRange());
|
|
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_EQ(
|
|
folly::ByteRange(buf->data(), buf->length()),
|
|
rt.data.getHead()->getRange());
|
|
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("?");
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream3, buf->clone(), true /* eof */).hasError());
|
|
|
|
// packet2 contains originally 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));
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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, 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();
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(
|
|
*stream,
|
|
folly::IOBuf::copyBuffer("The sun is cold and the rain is hard."),
|
|
true)
|
|
.hasError());
|
|
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);
|
|
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
handshakePacket.packet,
|
|
TimePoint{},
|
|
getEncodedSize(handshakePacket),
|
|
getEncodedBodySize(handshakePacket),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
initialPacket.packet,
|
|
TimePoint{},
|
|
getEncodedSize(initialPacket),
|
|
getEncodedBodySize(initialPacket),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
appDataPacket.packet,
|
|
TimePoint{},
|
|
getEncodedSize(appDataPacket),
|
|
getEncodedBodySize(appDataPacket),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
// 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();
|
|
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, nullptr, true).hasError());
|
|
packet.packet.frames.push_back(WriteStreamFrame(stream1->id, 0, 0, true));
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// 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.chainLength(), 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");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
|
|
packet.packet.frames.push_back(
|
|
WriteStreamFrame(stream1->id, 0, buf->computeChainDataLength(), false));
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// 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.chainLength(), 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;
|
|
auto result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
// 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();
|
|
auto result1 = writeDataToQuicStream(*stream, nullptr, true);
|
|
ASSERT_FALSE(result1.hasError());
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
|
|
WriteAckFrame ackFrame;
|
|
ackFrame.ackBlocks.emplace_back(0, 100);
|
|
packet.packet.frames.push_back(std::move(ackFrame));
|
|
auto result2 = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result2.hasError());
|
|
|
|
auto nonHandshake = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto result3 = writeDataToQuicStream(*stream1, nullptr, true);
|
|
ASSERT_FALSE(result3.hasError());
|
|
|
|
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));
|
|
|
|
auto result4 = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result4.hasError());
|
|
|
|
// 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);
|
|
|
|
WriteAckFrame ackFrame;
|
|
ackFrame.ackBlocks.emplace_back(0, 100);
|
|
packet.packet.frames.push_back(std::move(ackFrame));
|
|
packet.packet.frames.push_back(PaddingFrame());
|
|
auto result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
// 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->pendingWrites.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->pendingWrites.append(data);
|
|
initialStream->writeBuffer.append(data->clone());
|
|
auto result1 = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result1.hasError());
|
|
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->pendingWrites.append(data);
|
|
initialStream->writeBuffer.append(data->clone());
|
|
initialStream->pendingWrites.append(data);
|
|
initialStream->writeBuffer.append(data->clone());
|
|
auto result2 = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result2.hasError());
|
|
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->pendingWrites.empty());
|
|
EXPECT_TRUE(initialStream->lossBuffer.empty());
|
|
|
|
// Fake loss.
|
|
ChainedByteRangeHead firstBuf(
|
|
std::move(initialStream->retransmissionBuffer.find(0)->second->data));
|
|
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->pendingWrites.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->pendingWrites.append(data);
|
|
handshakeStream->writeBuffer.append(data->clone());
|
|
auto result3 = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result3.hasError());
|
|
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->pendingWrites.append(data);
|
|
handshakeStream->writeBuffer.append(data->clone());
|
|
auto result4 = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result4.hasError());
|
|
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->pendingWrites.empty());
|
|
EXPECT_TRUE(handshakeStream->lossBuffer.empty());
|
|
|
|
// Fake loss.
|
|
firstBuf = ChainedByteRangeHead(
|
|
std::move(handshakeStream->retransmissionBuffer.find(0)->second->data));
|
|
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->pendingWrites.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->pendingWrites.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();
|
|
auto result1 = writeDataToQuicStream(*stream, nullptr, true);
|
|
ASSERT_FALSE(result1.hasError());
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
auto packetEncodedSize =
|
|
!packet.header.empty() ? packet.header.computeChainDataLength() : 0;
|
|
packetEncodedSize +=
|
|
!packet.body.empty() ? packet.body.computeChainDataLength() : 0;
|
|
|
|
packet.packet.frames.push_back(WriteCryptoFrame(0, 0));
|
|
auto result2 = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result2.hasError());
|
|
EXPECT_EQ(1, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
|
|
auto nonHandshake = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packetEncodedSize = !nonHandshake.header.empty()
|
|
? nonHandshake.header.computeChainDataLength()
|
|
: 0;
|
|
packetEncodedSize += !nonHandshake.body.empty()
|
|
? nonHandshake.body.computeChainDataLength()
|
|
: 0;
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto result3 = writeDataToQuicStream(*stream1, nullptr, true);
|
|
ASSERT_FALSE(result3.hasError());
|
|
|
|
nonHandshake.packet.frames.push_back(
|
|
WriteStreamFrame(stream1->id, 0, 0, true));
|
|
auto result4 = updateConnection(
|
|
*conn,
|
|
none,
|
|
nonHandshake.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result4.hasError());
|
|
|
|
// 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();
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream, nullptr, true).hasError());
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
|
|
// Packet with CryptoFrame in AppData pn space
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData, true);
|
|
|
|
packet.packet.frames.push_back(WriteCryptoFrame(0, 0));
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
EXPECT_EQ(0, conn->outstandings.packetCount[PacketNumberSpace::Handshake]);
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
|
|
auto nonHandshake = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, nullptr, true).hasError());
|
|
|
|
nonHandshake.packet.frames.push_back(
|
|
WriteStreamFrame(stream1->id, 0, 0, true));
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
nonHandshake.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// 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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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.
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("Im gonna cut your hair."), true)
|
|
.hasError());
|
|
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;
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
555,
|
|
500,
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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(
|
|
16000 + 555,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.inflightBytes);
|
|
EXPECT_EQ(
|
|
555,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.encodedSize);
|
|
EXPECT_EQ(
|
|
500,
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.encodedBodySize);
|
|
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, TestUpdateConnectionWithAppLimitedStats) {
|
|
auto conn = createConn();
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("Im gonna cut your hair."), true)
|
|
.hasError());
|
|
WriteStreamFrame writeStreamFrame(stream->id, 0, 5, false);
|
|
packet.packet.frames.push_back(std::move(writeStreamFrame));
|
|
|
|
// connections should start off being app limited
|
|
EXPECT_TRUE(conn->appLimitedTracker.isAppLimited());
|
|
|
|
// mark ourselves as not app limited, verify that total app limited time > 0,
|
|
// verify that successive calls to getTotalAppLimitedTime yield same value
|
|
conn->appLimitedTracker.setNotAppLimited();
|
|
EXPECT_LT(0us, conn->appLimitedTracker.getTotalAppLimitedTime());
|
|
EXPECT_EQ(
|
|
conn->appLimitedTracker.getTotalAppLimitedTime(),
|
|
conn->appLimitedTracker.getTotalAppLimitedTime());
|
|
|
|
// record the packet as having been sent
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
555,
|
|
500,
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// should have the current app limited time recorded in metadata
|
|
EXPECT_EQ(
|
|
conn->appLimitedTracker.getTotalAppLimitedTime(),
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.totalAppLimitedTimeUsecs);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicTransportFunctionsTest,
|
|
TestUpdateConnectionWithAppLimitedStats_MultipleTransitions) {
|
|
auto conn = createConn();
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::Handshake);
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("Im gonna cut your hair."), true)
|
|
.hasError());
|
|
WriteStreamFrame writeStreamFrame(stream->id, 0, 5, false);
|
|
packet.packet.frames.push_back(std::move(writeStreamFrame));
|
|
|
|
// connections should start off being app limited
|
|
EXPECT_TRUE(conn->appLimitedTracker.isAppLimited());
|
|
{
|
|
// successive calls should yield different measurements
|
|
const auto time1 = conn->appLimitedTracker.getTotalAppLimitedTime();
|
|
std::this_thread::sleep_for(10ms);
|
|
const auto time2 = conn->appLimitedTracker.getTotalAppLimitedTime();
|
|
EXPECT_NE(time1, time2);
|
|
}
|
|
|
|
// mark ourselves as not app limited, verify that total app limited time > 0,
|
|
// verify that successive calls to getTotalAppLimitedTime yield same value
|
|
conn->appLimitedTracker.setNotAppLimited();
|
|
EXPECT_LT(0us, conn->appLimitedTracker.getTotalAppLimitedTime());
|
|
EXPECT_EQ(
|
|
conn->appLimitedTracker.getTotalAppLimitedTime(),
|
|
conn->appLimitedTracker.getTotalAppLimitedTime());
|
|
const auto appLimitedTime1 = conn->appLimitedTracker.getTotalAppLimitedTime();
|
|
|
|
// become app limited for at least 10ms, then repeat the above
|
|
conn->appLimitedTracker.setAppLimited();
|
|
std::this_thread::sleep_for(10ms);
|
|
conn->appLimitedTracker.setNotAppLimited();
|
|
EXPECT_LE(
|
|
appLimitedTime1 + 10ms, conn->appLimitedTracker.getTotalAppLimitedTime());
|
|
EXPECT_EQ(
|
|
conn->appLimitedTracker.getTotalAppLimitedTime(),
|
|
conn->appLimitedTracker.getTotalAppLimitedTime());
|
|
|
|
// record the packet as having been sent
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
555,
|
|
500,
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// should have the current app limited time recorded in metadata
|
|
EXPECT_EQ(
|
|
conn->appLimitedTracker.getTotalAppLimitedTime(),
|
|
getFirstOutstandingPacket(*conn, PacketNumberSpace::Handshake)
|
|
->metadata.totalAppLimitedTimeUsecs);
|
|
}
|
|
|
|
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));
|
|
ClonedPacketIdentifier clonedPacketIdentifier(PacketNumberSpace::AppData, 1);
|
|
conn->outstandings.clonedPacketIdentifiers.insert(clonedPacketIdentifier);
|
|
auto futureMoment = thisMoment + 50ms;
|
|
MockClock::mockNow = [=]() { return futureMoment; };
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
auto result = updateConnection(
|
|
*conn,
|
|
clonedPacketIdentifier,
|
|
std::move(writePacket),
|
|
MockClock::now(),
|
|
1500,
|
|
1400,
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
// 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(
|
|
clonedPacketIdentifier,
|
|
*getLastOutstandingPacket(*conn, PacketNumberSpace::AppData)
|
|
->maybeClonedPacketIdentifier);
|
|
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));
|
|
auto result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
// 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
|
|
ASSERT_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));
|
|
auto result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
// 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);
|
|
auto result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
// 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());
|
|
auto result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
// 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, TestPingFrameCounter) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
packet.packet.frames.push_back(PingFrame());
|
|
auto result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
ASSERT_EQ(1, conn->numPingFramesSent);
|
|
}
|
|
|
|
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();
|
|
auto result = writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("abcdefghij"), true);
|
|
ASSERT_FALSE(result.hasError());
|
|
WriteStreamFrame writeStreamFrame(
|
|
stream->id, frameOffset, frameLen, false /* fin */);
|
|
packet.packet.frames.push_back(writeStreamFrame);
|
|
result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
const auto streamMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frameLen),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(frameOffset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frameOffset, frameOffset + frameLen - 1)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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();
|
|
auto result = writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("abcdefghijklmno"), true);
|
|
ASSERT_FALSE(result.hasError());
|
|
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);
|
|
result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
const auto streamMatcher = testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, 15),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 15),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(0)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(0, 14)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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();
|
|
|
|
auto result = writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("abcdefghij"), false /* eof */);
|
|
ASSERT_FALSE(result.hasError());
|
|
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);
|
|
result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
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::streamBytesSent, frame1Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, frame1Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(frame1Offset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frame1Offset, frame1Offset + frame1Len - 1)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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);
|
|
result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
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::streamBytesSent, frame1Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frame1Offset, frame1Offset + frame1Len - 1)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(streamMatcher))));
|
|
|
|
EXPECT_THAT(conn->outstandings.packets, Contains(pktMatcher));
|
|
}
|
|
|
|
// Retransmit frame1 and send new data in frame2.
|
|
result = writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("klmnopqrstuvwxy"), false /* eof */);
|
|
ASSERT_FALSE(result.hasError());
|
|
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);
|
|
result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
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::streamBytesSent, frame1Len + frame2Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, frame2Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(frame2Offset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frame1Offset, frame2Offset + frame2Len - 1)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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);
|
|
result = updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */);
|
|
ASSERT_FALSE(result.hasError());
|
|
|
|
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::streamBytesSent, frame1Len + frame2Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frame1Offset, frame2Offset + frame2Len - 1)))));
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("a"), false /* eof */)
|
|
.hasError());
|
|
uint64_t frame1Offset = 0;
|
|
auto packet1 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame1(stream->id, frame1Offset, frameLen, false /* fin */);
|
|
packet1.packet.frames.push_back(frame1);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet1.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("b"), true /* eof */)
|
|
.hasError());
|
|
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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// 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) {
|
|
return testing::Pair(
|
|
stream->id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(frameOffset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frameOffset, frameOffset + frameLen - 1)))));
|
|
};
|
|
|
|
const auto pkt1Matcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame1Offset)))));
|
|
|
|
const auto pkt2Matcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame2Offset)))));
|
|
|
|
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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet3.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet3),
|
|
getEncodedBodySize(packet3),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// 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::streamBytesSent, frameLen * 2),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<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(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("a"), false /* eof */)
|
|
.hasError());
|
|
uint64_t frame1Offset = 0;
|
|
auto packet1 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame1(stream->id, frame1Offset, frameLen, false /* fin */);
|
|
packet1.packet.frames.push_back(frame1);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet1.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("b"), false /* eof */)
|
|
.hasError());
|
|
uint64_t frame2Offset = frameLen;
|
|
auto packet2 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame2(stream->id, frame2Offset, frameLen, false /* fin */);
|
|
packet2.packet.frames.push_back(frame2);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("c"), false /* eof */)
|
|
.hasError());
|
|
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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet3.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet3),
|
|
getEncodedBodySize(packet3),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// 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::streamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(frameOffset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frameOffset, frameOffset + frameLen - 1)))));
|
|
};
|
|
|
|
const auto pkt1Matcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame1Offset)))));
|
|
|
|
const auto pkt2Matcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame2Offset)))));
|
|
|
|
const auto pkt3Matcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet4.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet4),
|
|
getEncodedBodySize(packet4),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// 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::streamBytesSent, frameLen * 2),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<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(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("ab"), false /* eof */)
|
|
.hasError());
|
|
uint64_t frame1Offset = 0;
|
|
auto packet1 = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
WriteStreamFrame frame1(stream->id, frame1Offset, frameLen, false /* fin */);
|
|
packet1.packet.frames.push_back(frame1);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet1.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("cd"), false /* eof */)
|
|
.hasError());
|
|
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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet2),
|
|
getEncodedBodySize(packet2),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("ef"), false /* eof */)
|
|
.hasError());
|
|
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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet3.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet3),
|
|
getEncodedBodySize(packet3),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// 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::streamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, frameLen),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(frameOffset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
frameOffset, frameOffset + frameLen - 1)))));
|
|
};
|
|
|
|
const auto pkt1Matcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame1Offset)))));
|
|
|
|
const auto pkt2Matcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(testing::Field(
|
|
&OutstandingPacketMetadata::detailsPerStream,
|
|
testing::UnorderedElementsAre(
|
|
getStreamDetailsMatcher(frame2Offset)))));
|
|
|
|
const auto pkt3Matcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet4.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet4),
|
|
getEncodedBodySize(packet4),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// 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::streamBytesSent, frameLen * 2),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<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(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream2, buf->clone(), true).hasError());
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream3, buf->clone(), true).hasError());
|
|
|
|
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);
|
|
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet1.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// check stream details for the sent packet
|
|
{
|
|
auto stream1DetailsMatcher = testing::Pair(
|
|
stream1Id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, stream1Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, stream1Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<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::streamBytesSent, stream2Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, stream2Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<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::streamBytesSent, stream3Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::newStreamBytesSent, stream3Len),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(stream3Offset)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
stream3Offset, stream3Offset + stream3Len - 1)))));
|
|
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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);
|
|
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet2.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet1),
|
|
getEncodedBodySize(packet1),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
|
|
// check stream details for the retransmitted packet
|
|
EXPECT_THAT(conn->outstandings.packets, SizeIs(2));
|
|
{
|
|
auto stream1DetailsMatcher = testing::Pair(
|
|
stream1Id,
|
|
testing::AllOf(
|
|
testing::Field(&PacketStreamDetails::streamBytesSent, stream1Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<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::streamBytesSent, stream2Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<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::streamBytesSent, stream3Len),
|
|
testing::Field(&PacketStreamDetails::newStreamBytesSent, 0),
|
|
testing::Field(
|
|
&PacketStreamDetails::maybeFirstNewStreamByteOffset,
|
|
OptionalIntegral<uint64_t>(/* empty */)),
|
|
testing::Field(
|
|
&PacketStreamDetails::streamIntervals,
|
|
testing::ElementsAre(Interval<uint64_t>(
|
|
stream3Offset, stream3Offset + stream3Len - 1)))));
|
|
|
|
const auto pktMatcher = testing::Field(
|
|
&OutstandingPacketWrapper::metadata,
|
|
testing::AllOf(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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf =
|
|
IOBuf::copyBuffer("0123456789012012345678901201234567890120123456789012");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
|
|
uint64_t writableBytes = 30;
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(
|
|
InvokeWithoutArgs([&writableBytes]() { return writableBytes; }));
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.WillRepeatedly(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
size_t totalLen = getTotalIovecLen(vec, iovec_len);
|
|
EXPECT_LE(totalLen, 30);
|
|
writableBytes -= totalLen;
|
|
return totalLen;
|
|
}));
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
EXPECT_CALL(*quicStats_, onWrite(_));
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
}
|
|
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf =
|
|
IOBuf::copyBuffer("0123456789012012345678901201234567890120123456789012");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
|
|
EXPECT_CALL(*rawPacer, onPacketSent()).Times(1);
|
|
EXPECT_CALL(*quicStats_, onWrite(_));
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
}
|
|
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
// ~50 bytes
|
|
auto buf =
|
|
IOBuf::copyBuffer("0123456789012012345678901201234567890120123456789012");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), false).hasError());
|
|
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);
|
|
ASSERT_FALSE(res.hasError());
|
|
EXPECT_EQ(0, res->packetsWritten);
|
|
EXPECT_EQ(0, res->probesWritten);
|
|
EXPECT_EQ(0, res->bytesWritten);
|
|
|
|
// Normal limit
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Initial] = 0;
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::Handshake] = 0;
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData] = 0;
|
|
conn->transportSettings.writeConnectionDataPacketsLimit =
|
|
kDefaultWriteConnectionDataPacketLimit;
|
|
uint64_t actualWritten = 0;
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
writableBytes -= getTotalIovecLen(vec, iovec_len);
|
|
actualWritten += getTotalIovecLen(vec, iovec_len);
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
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);
|
|
ASSERT_FALSE(res.hasError());
|
|
EXPECT_EQ(1, res->packetsWritten);
|
|
EXPECT_EQ(0, res->probesWritten);
|
|
EXPECT_EQ(actualWritten, res->bytesWritten);
|
|
|
|
// Probing can exceed packet limit. In practice we limit it to
|
|
// kPacketToSendForPTO
|
|
actualWritten = 0;
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData] =
|
|
kDefaultWriteConnectionDataPacketLimit * 2;
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
writableBytes = 10000;
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(
|
|
InvokeWithoutArgs([&writableBytes]() { return writableBytes; }));
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.Times(kDefaultWriteConnectionDataPacketLimit * 2)
|
|
.WillRepeatedly(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
actualWritten += getTotalIovecLen(vec, iovec_len);
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_))
|
|
.Times(kDefaultWriteConnectionDataPacketLimit * 2);
|
|
EXPECT_CALL(*quicStats_, onWrite(_)).Times(1);
|
|
res = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
ASSERT_FALSE(res.hasError());
|
|
EXPECT_EQ(0, res->packetsWritten);
|
|
EXPECT_EQ(kDefaultWriteConnectionDataPacketLimit * 2, res->probesWritten);
|
|
EXPECT_EQ(actualWritten, res->bytesWritten);
|
|
}
|
|
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf =
|
|
IOBuf::copyBuffer("0123456789012012345678901201234567890120123456789012");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
|
|
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 struct iovec* vec, size_t iovec_len) {
|
|
EXPECT_LE(
|
|
getTotalIovecLen(vec, iovec_len),
|
|
*conn->writableBytesLimit - conn->lossState.totalBytesSent);
|
|
writableBytes -= getTotalIovecLen(vec, iovec_len);
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
EXPECT_NE(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = IOBuf::copyBuffer("0123456789012");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(0));
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(0);
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
// No header space left. Should send nothing.
|
|
EXPECT_TRUE(conn->outstandings.packets.empty());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteQuicDataToSocketRetxBufferSorted) {
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
quic::test::MockAsyncUDPSocket socket(qEvb);
|
|
auto conn = createConn();
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf1 = IOBuf::copyBuffer("Whatsapp");
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream, std::move(buf1), false).hasError());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_EQ(1, stream->retransmissionBuffer.size());
|
|
|
|
auto buf2 = IOBuf::copyBuffer("Google Buzz");
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream, std::move(buf2), false).hasError());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
socket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
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);
|
|
ASSERT_FALSE(res.hasError());
|
|
EXPECT_EQ(0, res->packetsWritten);
|
|
EXPECT_EQ(0, res->probesWritten);
|
|
EXPECT_EQ(0, res->bytesWritten);
|
|
}
|
|
|
|
const QuicWriteFrame& getFirstFrameInOutstandingPackets(
|
|
const std::deque<OutstandingPacketWrapper>& 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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = buildRandomInputData(200);
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
|
|
auto originalNextSeq = conn->ackStates.appDataAckState.nextPacketNum;
|
|
uint64_t sentBytes = 0;
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.WillRepeatedly(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
auto len = getTotalIovecLen(vec, iovec_len);
|
|
sentBytes += len;
|
|
return len;
|
|
}));
|
|
|
|
// Artificially Block the stream
|
|
stream1->flowControlState.peerAdvertisedMaxOffset = 10;
|
|
// writes blocked frame in additionally
|
|
EXPECT_CALL(*quicStats_, onWrite(_)).Times(1);
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
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);
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
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>>();
|
|
// Probing data is not limited by congestion control, this should not affect
|
|
// anything
|
|
EXPECT_CALL(*mockCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(0));
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = buildRandomInputData(conn->udpSendPacketLen * 2);
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream1, buf->clone(), true /* eof */).hasError());
|
|
|
|
auto currentStreamWriteOffset = stream1->currentWriteOffset;
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(1);
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.WillOnce(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
auto len = getTotalIovecLen(vec, iovec_len);
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
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");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream, buf->clone(), true).hasError());
|
|
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);
|
|
}
|
|
}));
|
|
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);
|
|
}
|
|
}));
|
|
writeProbingDataToSocketForTest(
|
|
*rawSocket, *conn, 1, *aead, *headerCipher, getVersion(*conn));
|
|
// Verify two pacekts have the same body
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(pktBodyCaptured, secondBodyCaptured));
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteProbingOldDataAckFreq) {
|
|
auto conn = createConn();
|
|
conn->congestionController.reset();
|
|
conn->peerMinAckDelay = 1ms;
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
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");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream, buf->clone(), true).hasError());
|
|
|
|
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);
|
|
}
|
|
}));
|
|
writeProbingDataToSocketForTest(
|
|
*rawSocket, *conn, 1, *aead, *headerCipher, getVersion(*conn));
|
|
|
|
auto immAck =
|
|
getFirstFrameInOutstandingPackets(
|
|
conn->outstandings.packets, QuicWriteFrame::Type::ImmediateAckFrame)
|
|
.asImmediateAckFrame();
|
|
EXPECT_TRUE(immAck);
|
|
// 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);
|
|
}
|
|
}));
|
|
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>>();
|
|
// Probing data is not limited by congestion control, this should not affect
|
|
// anything
|
|
EXPECT_CALL(*mockCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(0));
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn.congestionController = std::move(mockCongestionController);
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
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 struct iovec* vec, size_t iovec_len) {
|
|
auto len = getTotalIovecLen(vec, iovec_len);
|
|
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, WriteableBytesLimitedProbingCryptoData) {
|
|
QuicServerConnectionState conn(
|
|
FizzServerQuicHandshakeContext::Builder().build());
|
|
conn.statsCallback = quicStats_.get();
|
|
conn.transportSettings.enableWritableBytesLimit = true;
|
|
conn.writableBytesLimit = 2 * conn.udpSendPacketLen;
|
|
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
auto cryptoStream = &conn.cryptoState->initialStream;
|
|
uint8_t probesToSend = 4;
|
|
auto buf = buildRandomInputData(conn.udpSendPacketLen * probesToSend);
|
|
EXPECT_CALL(*quicStats_, onConnectionWritableBytesLimited())
|
|
.Times(AtLeast(1));
|
|
writeDataToQuicStream(*cryptoStream, buf->clone());
|
|
|
|
auto currentStreamWriteOffset = cryptoStream->currentWriteOffset;
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(2);
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.WillRepeatedly(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
auto len = getTotalIovecLen(vec, iovec_len);
|
|
EXPECT_EQ(conn.udpSendPacketLen - aead->getCipherOverhead(), len);
|
|
return len;
|
|
}));
|
|
writeCryptoDataProbesToSocketForTest(
|
|
*rawSocket, conn, probesToSend, *aead, *headerCipher, getVersion(conn));
|
|
|
|
EXPECT_EQ(conn.numProbesWritableBytesLimited, 2);
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
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, ProbingFallbackToImmediateAck) {
|
|
auto conn = createConn();
|
|
conn->peerMinAckDelay = 1ms;
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
uint8_t probesToSend = 1;
|
|
EXPECT_EQ(
|
|
1,
|
|
writeProbingDataToSocketForTest(
|
|
*rawSocket,
|
|
*conn,
|
|
probesToSend,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn)));
|
|
// Immediate Ack is the only non-retransmittable packet that will go into OP
|
|
// list
|
|
EXPECT_EQ(1, conn->outstandings.packets.size());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, NoCryptoProbeWriteIfNoProbeCredit) {
|
|
auto conn = createConn();
|
|
auto cryptoStream = &conn->cryptoState->initialStream;
|
|
auto buf = buildRandomInputData(200);
|
|
writeDataToQuicStream(*cryptoStream, buf->clone());
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
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);
|
|
ASSERT_FALSE(res.hasError());
|
|
EXPECT_GE(res->bytesWritten, buf->computeChainDataLength());
|
|
|
|
EXPECT_EQ(1, res->packetsWritten);
|
|
EXPECT_EQ(0, res->probesWritten);
|
|
EXPECT_EQ(conn->udpSendPacketLen, res->bytesWritten);
|
|
ASSERT_EQ(1, conn->outstandings.packets.size());
|
|
ASSERT_EQ(1, cryptoStream->retransmissionBuffer.size());
|
|
ASSERT_TRUE(cryptoStream->pendingWrites.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);
|
|
ASSERT_FALSE(res.hasError());
|
|
EXPECT_EQ(0, res->bytesWritten);
|
|
EXPECT_EQ(0, res->packetsWritten);
|
|
EXPECT_EQ(0, res->probesWritten);
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ImmediatelyRetransmitInitialPackets) {
|
|
auto conn = createConn();
|
|
conn->transportSettings.immediatelyRetransmitInitialPackets = true;
|
|
auto cryptoStream = &conn->cryptoState->initialStream;
|
|
auto buf = buildRandomInputData(1600);
|
|
writeDataToQuicStream(*cryptoStream, buf->clone());
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
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);
|
|
ASSERT_FALSE(res.hasError());
|
|
EXPECT_GE(res->bytesWritten, buf->computeChainDataLength());
|
|
|
|
EXPECT_EQ(2, res->packetsWritten);
|
|
EXPECT_EQ(2, res->probesWritten);
|
|
EXPECT_EQ(conn->udpSendPacketLen * 4, res->bytesWritten);
|
|
ASSERT_EQ(4, conn->outstandings.packets.size());
|
|
ASSERT_EQ(2, cryptoStream->retransmissionBuffer.size());
|
|
ASSERT_TRUE(cryptoStream->pendingWrites.empty());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ResetNumProbePackets) {
|
|
auto conn = createConn();
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
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);
|
|
ASSERT_FALSE(writeRes1.hasError());
|
|
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);
|
|
ASSERT_FALSE(writeRes2.hasError());
|
|
EXPECT_FALSE(conn->pendingEvents.anyProbePackets());
|
|
EXPECT_EQ(0, writeRes2->bytesWritten);
|
|
|
|
conn->oneRttWriteCipher = createNoOpAead();
|
|
conn->oneRttWriteHeaderCipher = createNoOpHeaderCipher();
|
|
conn->pendingEvents.numProbePackets[PacketNumberSpace::AppData] = 2;
|
|
auto writeRes3 = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*conn->oneRttWriteCipher,
|
|
*conn->oneRttWriteHeaderCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
ASSERT_FALSE(writeRes3.hasError());
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = IOBuf::copyBuffer("0123456789012");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
|
|
addAckStatesWithCurrentTimestamps(conn->ackStates.appDataAckState, 0, 100);
|
|
conn->ackStates.appDataAckState.needsToSendAckImmediately = true;
|
|
conn->ackStates.appDataAckState.largestAckScheduled = 50;
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(0));
|
|
|
|
uint64_t actualWritten = 0;
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.WillRepeatedly(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
EXPECT_LE(getTotalIovecLen(vec, iovec_len), 30);
|
|
actualWritten += getTotalIovecLen(vec, iovec_len);
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
EXPECT_CALL(*rawCongestionController, onPacketSent(_)).Times(0);
|
|
auto res = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
ASSERT_FALSE(res.hasError());
|
|
EXPECT_GT(res->packetsWritten, 0);
|
|
EXPECT_EQ(res->bytesWritten, actualWritten);
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
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");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), false).hasError());
|
|
EXPECT_NE(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
// Congestion control
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillRepeatedly(Return(0));
|
|
EXPECT_CALL(*quicStats_, onCwndBlocked());
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
EXPECT_EQ(WriteDataReason::NO_WRITE, shouldWriteData(*conn));
|
|
|
|
EXPECT_CALL(*quicStats_, onCwndBlocked());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
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");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), false).hasError());
|
|
|
|
// 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");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), false).hasError());
|
|
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");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), false).hasError());
|
|
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");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), false).hasError());
|
|
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();
|
|
auto dataBuf = folly::IOBuf::copyBuffer("Grab your coat and get your hat");
|
|
conn->cryptoState->initialStream.lossBuffer.emplace_back(
|
|
ChainedByteRangeHead(dataBuf), 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);
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
stream, folly::IOBuf::copyBuffer("I'm a devil"), true)
|
|
.hasError());
|
|
conn->streamManager->updateWritableStreams(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);
|
|
ClonedPacketIdentifier clonedPacketIdentifier(
|
|
PacketNumberSpace::AppData, 100);
|
|
conn->outstandings.clonedPacketIdentifiers.insert(clonedPacketIdentifier);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
clonedPacketIdentifier,
|
|
packet.packet,
|
|
TimePoint(),
|
|
123,
|
|
100,
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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);
|
|
ClonedPacketIdentifier clonedPacketIdentifier(
|
|
PacketNumberSpace::AppData, 100);
|
|
conn->outstandings.clonedPacketIdentifiers.insert(clonedPacketIdentifier);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
clonedPacketIdentifier,
|
|
packet.packet,
|
|
TimePoint(),
|
|
123,
|
|
123,
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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);
|
|
ClonedPacketIdentifier clonedPacketIdentifier(
|
|
PacketNumberSpace::AppData, 100);
|
|
conn->outstandings.clonedPacketIdentifiers.insert(clonedPacketIdentifier);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
clonedPacketIdentifier,
|
|
packet.packet,
|
|
TimePoint(),
|
|
123,
|
|
123,
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
EXPECT_FALSE(conn->streamManager->hasBlocked());
|
|
EXPECT_FALSE(conn->outstandings.packets.empty());
|
|
EXPECT_EQ(0, conn->outstandings.numClonedPackets());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ClonedBlocked) {
|
|
auto conn = createConn();
|
|
ClonedPacketIdentifier clonedPacketIdentifier(
|
|
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.clonedPacketIdentifiers.insert(clonedPacketIdentifier);
|
|
// This shall not crash
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
clonedPacketIdentifier,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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(
|
|
(void)updateConnection(
|
|
*conn,
|
|
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);
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream,
|
|
folly::IOBuf::copyBuffer("I feel like a million bucks."),
|
|
true)
|
|
.hasError());
|
|
WriteStreamFrame writeStreamFrame(stream->id, 0, 5, false);
|
|
packet.packet.frames.push_back(std::move(writeStreamFrame));
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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();
|
|
ClonedPacketIdentifier clonedPacketIdentifier(
|
|
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.clonedPacketIdentifiers.insert(clonedPacketIdentifier);
|
|
// This shall not crash
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
clonedPacketIdentifier,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint{},
|
|
4321,
|
|
4000,
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint{},
|
|
4321,
|
|
0,
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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);
|
|
ClonedPacketIdentifier clonedPacketIdentifier(
|
|
PacketNumberSpace::AppData, 100);
|
|
conn->outstandings.clonedPacketIdentifiers.insert(clonedPacketIdentifier);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
clonedPacketIdentifier,
|
|
packet.packet,
|
|
TimePoint(),
|
|
0,
|
|
0,
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = buildRandomInputData(2048 * 2048);
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
|
|
uint64_t actualWritten = 0;
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.WillRepeatedly(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
actualWritten += getTotalIovecLen(vec, iovec_len);
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
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(actualWritten, res->bytesWritten);
|
|
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, WriteLimitBytRttFractionNoLimit) {
|
|
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;
|
|
conn->transportSettings.writeLimitRttFraction = 0;
|
|
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = socket.get();
|
|
|
|
auto stream1 = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = buildRandomInputData(2048 * 2048);
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream1, buf->clone(), true).hasError());
|
|
|
|
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);
|
|
ASSERT_FALSE(res.hasError());
|
|
|
|
EXPECT_GE(1000, res->packetsWritten);
|
|
EXPECT_EQ(res->probesWritten, 0);
|
|
|
|
res = writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
1000 /* packetLimit */,
|
|
writeLoopBeginTime);
|
|
ASSERT_FALSE(res.hasError());
|
|
EXPECT_EQ(1000, 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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto socket =
|
|
std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
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());
|
|
auto lossBufferData1 = folly::IOBuf::copyBuffer(
|
|
"I don't see the dialup info in the meeting invite");
|
|
initialStream->insertIntoLossBuffer(std::make_unique<WriteStreamBuffer>(
|
|
ChainedByteRangeHead(lossBufferData1), 0, false));
|
|
|
|
auto lossBufferData2 =
|
|
folly::IOBuf::copyBuffer("Traffic Protocol Weekly Sync");
|
|
handshakeStream->insertIntoLossBuffer(std::make_unique<WriteStreamBuffer>(
|
|
ChainedByteRangeHead(lossBufferData2), 0, false));
|
|
|
|
handshakeConfirmed(*conn);
|
|
EXPECT_TRUE(initialStream->pendingWrites.empty());
|
|
EXPECT_TRUE(initialStream->retransmissionBuffer.empty());
|
|
EXPECT_TRUE(initialStream->lossBuffer.empty());
|
|
EXPECT_TRUE(handshakeStream->pendingWrites.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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto sock = std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = sock.get();
|
|
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.WillRepeatedly(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = folly::IOBuf::copyBuffer("Drug facts");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream, buf->clone(), true).hasError());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
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());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
1 /* limit to 1 packet */)
|
|
.hasError());
|
|
EXPECT_EQ(2, conn->outstandings.packets.size());
|
|
auto packet = stripPaddingFrames(conn->outstandings.packets[1].packet);
|
|
EXPECT_EQ(1, packet.frames.size());
|
|
EXPECT_EQ(
|
|
QuicWriteFrame::Type::MaxDataFrame,
|
|
conn->outstandings.packets[1].packet.frames[0].type());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, ProbeWriteNewFunctionalFramesAckFreq) {
|
|
auto conn = createConn();
|
|
conn->udpSendPacketLen = 1200;
|
|
conn->peerMinAckDelay = 1ms;
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
auto sock = std::make_unique<NiceMock<quic::test::MockAsyncUDPSocket>>(qEvb);
|
|
auto rawSocket = sock.get();
|
|
|
|
EXPECT_CALL(*rawSocket, write(_, _, _))
|
|
.WillRepeatedly(Invoke(
|
|
[&](const SocketAddress&, const struct iovec* vec, size_t iovec_len) {
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = folly::IOBuf::copyBuffer("Drug facts");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream, buf->clone(), true).hasError());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
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());
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
*rawSocket,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
1 /* limit to 1 packet */)
|
|
.hasError());
|
|
EXPECT_EQ(3, conn->outstandings.packets.size());
|
|
auto packet = stripPaddingFrames(conn->outstandings.packets[1].packet);
|
|
EXPECT_EQ(1, packet.frames.size());
|
|
EXPECT_EQ(
|
|
QuicWriteFrame::Type::MaxDataFrame,
|
|
conn->outstandings.packets[1].packet.frames[0].type());
|
|
packet = stripPaddingFrames(conn->outstandings.packets[2].packet);
|
|
EXPECT_EQ(1, packet.frames.size());
|
|
EXPECT_EQ(
|
|
QuicWriteFrame::Type::ImmediateAckFrame,
|
|
conn->outstandings.packets[2].packet.frames[0].type());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteWithInplaceBuilder) {
|
|
auto conn = createConn();
|
|
conn->transportSettings.dataPathType = DataPathType::ContinuousMemory;
|
|
auto bufAccessor = std::make_unique<BufAccessor>(conn->udpSendPacketLen * 16);
|
|
auto outputBuf = bufAccessor->obtain();
|
|
auto bufPtr = outputBuf.get();
|
|
bufAccessor->release(std::move(outputBuf));
|
|
conn->bufAccessor = bufAccessor.get();
|
|
conn->transportSettings.batchingMode = QuicBatchingMode::BATCHING_MODE_GSO;
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
quic::test::MockAsyncUDPSocket mockSock(qEvb);
|
|
EXPECT_CALL(mockSock, getGSO()).WillRepeatedly(Return(true));
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = folly::IOBuf::copyBuffer("Andante in C minor");
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream, buf->clone(), true).hasError());
|
|
EXPECT_CALL(mockSock, writeGSO(_, _, _, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const SocketAddress&,
|
|
const struct iovec* vec,
|
|
size_t iovec_len,
|
|
auto) {
|
|
EXPECT_GT(bufPtr->length(), 0);
|
|
EXPECT_GE(vec[0].iov_len, buf->length());
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(
|
|
*folly::IOBuf::wrapIov(vec, iovec_len), *bufPtr));
|
|
EXPECT_EQ(iovec_len, 1);
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
mockSock,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_EQ(0, bufPtr->length());
|
|
EXPECT_EQ(0, bufPtr->headroom());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteWithInplaceBuilderRollbackBuf) {
|
|
auto conn = createConn();
|
|
conn->transportSettings.dataPathType = DataPathType::ContinuousMemory;
|
|
auto bufAccessor = std::make_unique<BufAccessor>(conn->udpSendPacketLen * 16);
|
|
auto outputBuf = bufAccessor->obtain();
|
|
auto bufPtr = outputBuf.get();
|
|
bufAccessor->release(std::move(outputBuf));
|
|
conn->bufAccessor = bufAccessor.get();
|
|
conn->transportSettings.batchingMode = QuicBatchingMode::BATCHING_MODE_GSO;
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
quic::test::MockAsyncUDPSocket mockSock(qEvb);
|
|
EXPECT_CALL(mockSock, getGSO()).WillRepeatedly(Return(true));
|
|
EXPECT_CALL(mockSock, write(_, _, _)).Times(0);
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
mockSock,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
EXPECT_EQ(0, bufPtr->length());
|
|
EXPECT_EQ(0, bufPtr->headroom());
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, WriteWithInplaceBuilderGSOMultiplePackets) {
|
|
auto conn = createConn();
|
|
conn->transportSettings.dataPathType = DataPathType::ContinuousMemory;
|
|
auto bufAccessor = std::make_unique<BufAccessor>(conn->udpSendPacketLen * 16);
|
|
auto outputBuf = bufAccessor->obtain();
|
|
auto bufPtr = outputBuf.get();
|
|
bufAccessor->release(std::move(outputBuf));
|
|
conn->bufAccessor = bufAccessor.get();
|
|
conn->transportSettings.batchingMode = QuicBatchingMode::BATCHING_MODE_GSO;
|
|
EventBase evb;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
quic::test::MockAsyncUDPSocket mockSock(qEvb);
|
|
EXPECT_CALL(mockSock, getGSO()).WillRepeatedly(Return(true));
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
auto buf = buildRandomInputData(conn->udpSendPacketLen * 10);
|
|
ASSERT_FALSE(writeDataToQuicStream(*stream, buf->clone(), true).hasError());
|
|
EXPECT_CALL(mockSock, writeGSO(_, _, _, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const folly::SocketAddress&,
|
|
const struct iovec* vec,
|
|
size_t iovec_len,
|
|
QuicAsyncUDPSocket::WriteOptions options) {
|
|
EXPECT_LE(options.gso, conn->udpSendPacketLen);
|
|
EXPECT_GT(vec[0].iov_len, 0);
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(
|
|
*folly::IOBuf::wrapIov(vec, iovec_len), *bufPtr));
|
|
EXPECT_EQ(iovec_len, 1);
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
mockSock,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit)
|
|
.hasError());
|
|
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;
|
|
std::shared_ptr<FollyQuicEventBase> qEvb =
|
|
std::make_shared<FollyQuicEventBase>(&evb);
|
|
quic::test::MockAsyncUDPSocket mockSock(qEvb);
|
|
EXPECT_CALL(mockSock, getGSO()).WillRepeatedly(Return(true));
|
|
|
|
BufAccessor 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);
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream, inputBuf->clone(), true).hasError());
|
|
EXPECT_CALL(mockSock, writeGSO(_, _, _, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const folly::SocketAddress&,
|
|
const struct iovec* vec,
|
|
size_t iovec_len,
|
|
QuicAsyncUDPSocket::WriteOptions options) {
|
|
EXPECT_LE(options.gso, conn->udpSendPacketLen);
|
|
EXPECT_GE(
|
|
bufPtr->length(),
|
|
conn->udpSendPacketLen *
|
|
conn->transportSettings.writeConnectionDataPacketsLimit);
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(
|
|
*folly::IOBuf::wrapIov(vec, iovec_len), *bufPtr));
|
|
EXPECT_EQ(iovec_len, 1);
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
ASSERT_FALSE(writeQuicDataToSocket(
|
|
mockSock,
|
|
*conn,
|
|
*conn->clientConnectionId,
|
|
*conn->serverConnectionId,
|
|
*aead,
|
|
*headerCipher,
|
|
getVersion(*conn),
|
|
conn->transportSettings.writeConnectionDataPacketsLimit + 1)
|
|
.hasError());
|
|
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, writeGSO(_, _, _, _))
|
|
.Times(1)
|
|
.WillOnce(Invoke([&](const folly::SocketAddress&,
|
|
const struct iovec* vec,
|
|
size_t iovec_len,
|
|
auto) {
|
|
EXPECT_EQ(iovec_len, 1);
|
|
EXPECT_EQ(vec[0].iov_len, firstPacketSize);
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
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 struct iovec* vec,
|
|
size_t iovec_len,
|
|
QuicAsyncUDPSocket::WriteOptions options) {
|
|
EXPECT_EQ(iovec_len, 1);
|
|
EXPECT_EQ(conn->udpSendPacketLen, options.gso);
|
|
EXPECT_EQ(vec[0].iov_len, conn->udpSendPacketLen * 2);
|
|
return getTotalIovecLen(vec, iovec_len);
|
|
}));
|
|
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, 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());
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream,
|
|
IOBuf::copyBuffer("Wear a face mask please!"),
|
|
false /* eof */)
|
|
.hasError());
|
|
BufferMeta bufMeta(2000);
|
|
ASSERT_FALSE(
|
|
writeBufMetaToQuicStream(*stream, bufMeta, true /* eof */).hasError());
|
|
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);
|
|
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
true /* dsr */)
|
|
.hasError());
|
|
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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
retxPacket.packet,
|
|
TimePoint(),
|
|
getEncodedSize(retxPacket),
|
|
getEncodedBodySize(packet),
|
|
true /* dsr */)
|
|
.hasError());
|
|
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();
|
|
ASSERT_FALSE(writeDataToQuicStream(
|
|
*stream, folly::IOBuf::copyBuffer("abcdefghij"), true)
|
|
.hasError());
|
|
|
|
// 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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
}
|
|
|
|
// 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);
|
|
ASSERT_TRUE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, MissingStreamFrameBytesEof) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
const std::string str = "abcdefg";
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream, folly::IOBuf::copyBuffer(str), true)
|
|
.hasError());
|
|
|
|
// 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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
}
|
|
|
|
// 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);
|
|
ASSERT_TRUE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, MissingStreamFrameBytesSingleByteWrite) {
|
|
auto conn = createConn();
|
|
auto packet = buildEmptyPacket(*conn, PacketNumberSpace::AppData);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
const std::string str = "abcdefg";
|
|
ASSERT_FALSE(
|
|
writeDataToQuicStream(*stream, folly::IOBuf::copyBuffer(str), true)
|
|
.hasError());
|
|
|
|
// 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);
|
|
ASSERT_FALSE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
}
|
|
|
|
// 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);
|
|
ASSERT_TRUE(updateConnection(
|
|
*conn,
|
|
none,
|
|
packet.packet,
|
|
TimePoint(),
|
|
getEncodedSize(packet),
|
|
getEncodedBodySize(packet),
|
|
false /* isDSRPacket */)
|
|
.hasError());
|
|
}
|
|
}
|
|
|
|
TEST_F(
|
|
QuicTransportFunctionsTest,
|
|
StaticCapOnWritableBytesFromThrottlingSignalProvider) {
|
|
auto conn = createConn();
|
|
conn->udpSendPacketLen = 2000;
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
conn->congestionController = std::move(mockCongestionController);
|
|
|
|
auto mockThrottlingSignalProvider =
|
|
std::make_shared<MockThrottlingSignalProvider>();
|
|
ThrottlingSignalProvider::ThrottlingSignal expectedSignal;
|
|
expectedSignal.state =
|
|
ThrottlingSignalProvider::ThrottlingSignal::State::Throttled;
|
|
expectedSignal.maybeBytesToSend = 16000;
|
|
mockThrottlingSignalProvider->useFakeThrottlingSignal(expectedSignal);
|
|
conn->throttlingSignalProvider = mockThrottlingSignalProvider;
|
|
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillOnce(Return(10000));
|
|
EXPECT_EQ(10000, congestionControlWritableBytes(*conn));
|
|
|
|
// Since cwnd is larger than available tokens, the writable bytes is capped by
|
|
// the available tokens
|
|
EXPECT_CALL(*rawCongestionController, getWritableBytes())
|
|
.WillOnce(Return(20000));
|
|
EXPECT_EQ(
|
|
expectedSignal.maybeBytesToSend, congestionControlWritableBytes(*conn));
|
|
}
|
|
|
|
TEST_F(
|
|
QuicTransportFunctionsTest,
|
|
onQuicStreamClosedNotCalledOnStreamClearing) {
|
|
{
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
EXPECT_CALL(*quicStats_, onQuicStreamClosed()).Times(1);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
EXPECT_EQ(stream->id, 1);
|
|
EXPECT_EQ(conn->streamManager->streamCount(), 1);
|
|
conn->streamManager->clearOpenStreams();
|
|
EXPECT_EQ(conn->streamManager->streamCount(), 0);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, onQuicStreamClosedCalledOnConnClosure) {
|
|
{
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
EXPECT_CALL(*quicStats_, onQuicStreamClosed()).Times(1);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
EXPECT_EQ(stream->id, 1);
|
|
EXPECT_EQ(conn->streamManager->streamCount(), 1);
|
|
conn.reset();
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicTransportFunctionsTest, onQuicStreamClosed) {
|
|
auto conn = createConn();
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
EXPECT_CALL(*quicStats_, onQuicStreamClosed()).Times(1);
|
|
auto stream = conn->streamManager->createNextBidirectionalStream().value();
|
|
EXPECT_EQ(stream->id, 1);
|
|
EXPECT_EQ(conn->streamManager->streamCount(), 1);
|
|
stream->sendState = StreamSendState::Closed;
|
|
stream->recvState = StreamRecvState::Closed;
|
|
ASSERT_FALSE(conn->streamManager->removeClosedStream(stream->id).hasError());
|
|
EXPECT_EQ(conn->streamManager->streamCount(), 0);
|
|
}
|
|
|
|
} // namespace quic::test
|