mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-09 20:42:44 +03:00
Summary: Instead of using std::pair everywhere Reviewed By: mjoras Differential Revision: D34146686 fbshipit-source-id: dfe48f43775de868aba06a5b9b5a004e5793bdbb
4070 lines
150 KiB
C++
4070 lines
150 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#include <quic/server/test/QuicServerTransportTestUtil.h>
|
|
|
|
#include <quic/codec/QuicPacketBuilder.h>
|
|
#include <quic/dsr/Types.h>
|
|
#include <quic/dsr/test/Mocks.h>
|
|
#include <quic/fizz/handshake/FizzCryptoFactory.h>
|
|
#include <quic/fizz/server/handshake/FizzServerHandshake.h>
|
|
#include <quic/logging/FileQLogger.h>
|
|
#include <quic/server/handshake/ServerHandshake.h>
|
|
#include <quic/state/QuicStreamFunctions.h>
|
|
#include <quic/state/test/Mocks.h>
|
|
|
|
using namespace testing;
|
|
using namespace folly;
|
|
|
|
namespace quic {
|
|
namespace test {
|
|
|
|
namespace {
|
|
using ByteEvent = QuicTransportBase::ByteEvent;
|
|
using PacketDropReason = QuicTransportStatsCallback::PacketDropReason;
|
|
} // namespace
|
|
|
|
folly::Optional<QuicFrame> getFrameIfPresent(
|
|
std::vector<std::unique_ptr<folly::IOBuf>>& socketWrites,
|
|
QuicReadCodec& readCodec,
|
|
QuicFrame::Type frameType) {
|
|
AckStates ackStates;
|
|
for (auto& write : socketWrites) {
|
|
auto packetQueue = bufToQueue(write->clone());
|
|
auto result = readCodec.parsePacket(packetQueue, ackStates);
|
|
auto regularPacket = result.regularPacket();
|
|
if (!regularPacket) {
|
|
continue;
|
|
}
|
|
for (FOLLY_MAYBE_UNUSED auto& frame : regularPacket->frames) {
|
|
if (frame.type() != frameType) {
|
|
continue;
|
|
}
|
|
return frame;
|
|
}
|
|
}
|
|
return folly::none;
|
|
}
|
|
|
|
bool verifyFramePresent(
|
|
std::vector<std::unique_ptr<folly::IOBuf>>& socketWrites,
|
|
QuicReadCodec& readCodec,
|
|
QuicFrame::Type frameType) {
|
|
return getFrameIfPresent(socketWrites, readCodec, frameType).hasValue();
|
|
}
|
|
|
|
struct MigrationParam {
|
|
folly::Optional<uint64_t> clientSentActiveConnIdTransportParam;
|
|
};
|
|
|
|
class QuicServerTransportTest : public QuicServerTransportTestBase {
|
|
public:
|
|
void SetUp() override {
|
|
QuicServerTransportTestBase::SetUp();
|
|
}
|
|
};
|
|
|
|
TEST_F(QuicServerTransportTest, TestReadMultipleStreams) {
|
|
PacketNum clientPacketNum = clientNextAppDataPacketNum++;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientPacketNum);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
auto buf1 = IOBuf::copyBuffer("Aloha");
|
|
auto buf2 = IOBuf::copyBuffer("Hello");
|
|
|
|
auto dataLen = writeStreamFrameHeader(
|
|
builder,
|
|
0x08,
|
|
0,
|
|
buf1->computeChainDataLength(),
|
|
buf1->computeChainDataLength(),
|
|
true,
|
|
folly::none /* skipLenHint */);
|
|
ASSERT_TRUE(dataLen);
|
|
ASSERT_EQ(*dataLen, buf1->computeChainDataLength());
|
|
writeStreamFrameData(builder, buf1->clone(), buf1->computeChainDataLength());
|
|
|
|
dataLen = writeStreamFrameHeader(
|
|
builder,
|
|
0x0C,
|
|
0,
|
|
buf1->computeChainDataLength(),
|
|
buf1->computeChainDataLength(),
|
|
true,
|
|
folly::none /* skipLenHint */);
|
|
ASSERT_TRUE(dataLen);
|
|
ASSERT_EQ(*dataLen, buf1->computeChainDataLength());
|
|
writeStreamFrameData(builder, buf2->clone(), buf2->computeChainDataLength());
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
// Clear out the existing acks to make sure that we are the cause of the acks.
|
|
server->getNonConstConn().ackStates.initialAckState.acks.clear();
|
|
server->getNonConstConn().ackStates.initialAckState.largestRecvdPacketTime =
|
|
folly::none;
|
|
server->getNonConstConn().ackStates.handshakeAckState.acks.clear();
|
|
server->getNonConstConn().ackStates.handshakeAckState.largestRecvdPacketTime =
|
|
folly::none;
|
|
server->getNonConstConn().ackStates.appDataAckState.acks.clear();
|
|
server->getNonConstConn().ackStates.appDataAckState.largestRecvdPacketTime =
|
|
folly::none;
|
|
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(2); // for x08, x0C
|
|
deliverData(packetToBuf(packet));
|
|
|
|
EXPECT_TRUE(
|
|
server->getConn()
|
|
.ackStates.appDataAckState.largestRecvdPacketTime.has_value());
|
|
EXPECT_EQ(server->getConn().ackStates.appDataAckState.acks.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().ackStates.appDataAckState.acks.front().start,
|
|
clientPacketNum);
|
|
EXPECT_EQ(
|
|
server->getConn().ackStates.appDataAckState.acks.front().end,
|
|
clientPacketNum);
|
|
ASSERT_EQ(server->getConn().streamManager->streamCount(), 2);
|
|
IOBufEqualTo eq;
|
|
|
|
auto stream = server->getNonConstConn().streamManager->findStream(0x08);
|
|
ASSERT_TRUE(stream);
|
|
auto streamData = readDataFromQuicStream(*stream);
|
|
EXPECT_TRUE(eq(buf1, streamData.first));
|
|
EXPECT_TRUE(streamData.second);
|
|
|
|
auto stream2 = server->getNonConstConn().streamManager->findStream(0x0C);
|
|
ASSERT_TRUE(stream2);
|
|
auto streamData2 = readDataFromQuicStream(*stream2);
|
|
EXPECT_TRUE(eq(buf2, streamData2.first));
|
|
EXPECT_TRUE(streamData2.second);
|
|
EXPECT_CALL(*quicStats_, onQuicStreamClosed()).Times(2);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestInvalidServerStream) {
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(0);
|
|
StreamId streamId = 0x01;
|
|
auto data = IOBuf::copyBuffer("Aloha");
|
|
EXPECT_THROW(recvEncryptedStream(streamId, *data), std::runtime_error);
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAckled */));
|
|
EXPECT_THROW(deliverData(std::move(packetData)), std::runtime_error);
|
|
ASSERT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, IdleTimerResetOnRecvNewData) {
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
auto packet = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*expected,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
recvEncryptedStream(streamId, *expected);
|
|
ASSERT_TRUE(server->idleTimeout().isScheduled());
|
|
EXPECT_CALL(*quicStats_, onQuicStreamClosed());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, IdleTimerNotResetOnDuplicatePacket) {
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
auto packet = recvEncryptedStream(streamId, *expected);
|
|
ASSERT_TRUE(server->idleTimeout().isScheduled());
|
|
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
// Try delivering the same packet again
|
|
deliverData(packet->clone(), false);
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
EXPECT_CALL(*quicStats_, onQuicStreamClosed());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, IdleTimerNotResetWhenDataOutstanding) {
|
|
// Clear the receivedNewPacketBeforeWrite flag, since we may reveice from
|
|
// client during the SetUp of the test case.
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
server->getNonConstConn().receivedNewPacketBeforeWrite = false;
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
server->writeChain(
|
|
streamId,
|
|
IOBuf::copyBuffer("And if the darkness is to keep us apart"),
|
|
false);
|
|
loopForWrites();
|
|
// It was the first packet
|
|
EXPECT_TRUE(server->idleTimeout().isScheduled());
|
|
|
|
// cancel it and write something else. This time idle timer shouldn't set.
|
|
server->idleTimeout().cancelTimeout();
|
|
EXPECT_FALSE(server->idleTimeout().isScheduled());
|
|
server->writeChain(
|
|
streamId,
|
|
IOBuf::copyBuffer("And if the daylight feels like it's a long way off"),
|
|
false);
|
|
loopForWrites();
|
|
EXPECT_FALSE(server->idleTimeout().isScheduled());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TimeoutsNotSetAfterClose) {
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
auto packet = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*expected,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
server->close(QuicError(
|
|
QuicErrorCode(TransportErrorCode::INTERNAL_ERROR),
|
|
std::string("how about no")));
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
|
|
deliverDataWithoutErrorCheck(packet->clone());
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
ASSERT_FALSE(server->lossTimeout().isScheduled());
|
|
ASSERT_FALSE(server->ackTimeout().isScheduled());
|
|
ASSERT_TRUE(server->drainTimeout().isScheduled());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, InvalidMigrationNoDrain) {
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
auto packet = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*expected,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
server->close(QuicError(
|
|
QuicErrorCode(TransportErrorCode::INVALID_MIGRATION),
|
|
std::string("migration disabled")));
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
|
|
deliverDataWithoutErrorCheck(packet->clone());
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
ASSERT_FALSE(server->lossTimeout().isScheduled());
|
|
ASSERT_FALSE(server->ackTimeout().isScheduled());
|
|
ASSERT_FALSE(server->drainTimeout().isScheduled());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, IdleTimeoutExpired) {
|
|
server->idleTimeout().timeoutExpired();
|
|
|
|
EXPECT_FALSE(server->idleTimeout().isScheduled());
|
|
EXPECT_TRUE(server->isDraining());
|
|
EXPECT_TRUE(server->isClosed());
|
|
auto serverReadCodec = makeClientEncryptedCodec();
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites, *serverReadCodec, QuicFrame::Type::ConnectionCloseFrame));
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites, *serverReadCodec, QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvDataAfterIdleTimeout) {
|
|
server->idleTimeout().timeoutExpired();
|
|
|
|
EXPECT_FALSE(server->idleTimeout().isScheduled());
|
|
EXPECT_TRUE(server->isDraining());
|
|
EXPECT_TRUE(server->isClosed());
|
|
|
|
serverWrites.clear();
|
|
StreamId streamId = 11;
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
recvEncryptedStream(streamId, *expected);
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(true),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestCloseConnectionWithError) {
|
|
server->close(QuicError(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN),
|
|
std::string("stopping")));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestCloseConnectionWithNoError) {
|
|
server->close(QuicError(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN),
|
|
std::string("stopping")));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestClientAddressChanges) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
StreamId streamId = 4;
|
|
clientAddr = folly::SocketAddress("127.0.0.1", 2000);
|
|
auto data = IOBuf::copyBuffer("data");
|
|
EXPECT_THROW(
|
|
recvEncryptedStream(streamId, *data, 0, true), std::runtime_error);
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 29);
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PEER_ADDRESS_CHANGE));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestCloseConnectionWithNoErrorPendingStreams) {
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
|
|
server->writeChain(streamId, IOBuf::copyBuffer("hello"), true);
|
|
loopForWrites();
|
|
|
|
AckBlocks acks;
|
|
auto start = getFirstOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
auto end = getLastOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
acks.insert(start, end);
|
|
deliverData(packetToBuf(createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData)));
|
|
server->close(QuicError(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN),
|
|
std::string("stopping")));
|
|
|
|
EXPECT_THROW(
|
|
recvEncryptedStream(streamId, *IOBuf::copyBuffer("hello")),
|
|
std::runtime_error);
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceivePacketAfterLocalError) {
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
// Deliver a reset to non existent stream to trigger a local conn error
|
|
StreamId streamId = 0x01;
|
|
RstStreamFrame rstFrame(streamId, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
writeFrame(std::move(rstFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
serverWrites.clear();
|
|
|
|
ShortHeader header2(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder2(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header2),
|
|
0 /* largestAcked */);
|
|
builder2.encodePacketHeader();
|
|
RstStreamFrame rstFrame2(streamId, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
writeFrame(std::move(rstFrame2), builder2);
|
|
auto packet2 = std::move(builder2).buildPacket();
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet2));
|
|
EXPECT_TRUE(hasNotReceivedNewPacketsSinceLastCloseSent(server->getConn()));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveCloseAfterLocalError) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
// Deliver a reset to non existent stream to trigger a local conn error
|
|
StreamId streamId = 0x01;
|
|
RstStreamFrame rstFrame(streamId, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
writeFrame(std::move(rstFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
serverWrites.clear();
|
|
|
|
auto currLargestReceivedPacketNum =
|
|
server->getConn().ackStates.appDataAckState.largestReceivedPacketNum;
|
|
EXPECT_TRUE(hasNotReceivedNewPacketsSinceLastCloseSent(server->getConn()));
|
|
|
|
ShortHeader header2(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder2(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header2),
|
|
0 /* largestAcked */);
|
|
builder2.encodePacketHeader();
|
|
std::string errMsg = "Mind the gap";
|
|
ConnectionCloseFrame connClose(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR), errMsg);
|
|
writeFrame(std::move(connClose), builder2);
|
|
|
|
auto packet2 = std::move(builder2).buildPacket();
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet2));
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
EXPECT_GT(
|
|
server->getConn().ackStates.appDataAckState.largestReceivedPacketNum,
|
|
currLargestReceivedPacketNum);
|
|
|
|
// Deliver the same bad data again
|
|
EXPECT_CALL(*quicStats_, onPacketDropped(_));
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
EXPECT_LT(
|
|
server->getConn()
|
|
.ackStates.appDataAckState.largestReceivedAtLastCloseSent,
|
|
server->getConn().ackStates.appDataAckState.largestReceivedPacketNum);
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
checkTransportStateUpdate(
|
|
qLogger, "Server closed by peer reason=Mind the gap");
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, NoDataExceptCloseProcessedAfterClosing) {
|
|
auto packetNum = clientNextAppDataPacketNum++;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
packetNum);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
auto buf = folly::IOBuf::copyBuffer("hello");
|
|
writeStreamFrameHeader(
|
|
builder,
|
|
4,
|
|
0,
|
|
buf->computeChainDataLength(),
|
|
buf->computeChainDataLength(),
|
|
true,
|
|
folly::none /* skipLenHint */);
|
|
writeStreamFrameData(builder, buf->clone(), buf->computeChainDataLength());
|
|
std::string errMsg = "Mind the gap";
|
|
ConnectionCloseFrame connClose(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR), errMsg);
|
|
writeFrame(std::move(connClose), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
server->close(QuicError(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN),
|
|
std::string("hello")));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
EXPECT_TRUE(hasNotReceivedNewPacketsSinceLastCloseSent(server->getConn()));
|
|
serverWrites.clear();
|
|
|
|
// largestReceivedPacketNum won't be accurate because we will throw
|
|
// before updating the ack state.
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
EXPECT_EQ(
|
|
server->getConn().ackStates.appDataAckState.largestReceivedPacketNum,
|
|
packetNum);
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestOpenAckStreamFrame) {
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
auto data = IOBuf::copyBuffer("Aloha");
|
|
|
|
// Remove any packets that might have been queued.
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
server->getNonConstConn().outstandings.packetCount = {};
|
|
server->writeChain(streamId, data->clone(), false);
|
|
loopForWrites();
|
|
server->writeChain(streamId, data->clone(), false);
|
|
server->writeChain(streamId, data->clone(), false);
|
|
loopForWrites();
|
|
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
ASSERT_FALSE(server->getConn().outstandings.packets.empty());
|
|
ASSERT_FALSE(stream->retransmissionBuffer.empty());
|
|
// We need more than one packet for this test.
|
|
ASSERT_FALSE(server->getConn().outstandings.packets.empty());
|
|
|
|
PacketNum packetNum1 =
|
|
getFirstOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
|
|
PacketNum lastPacketNum =
|
|
getLastOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
|
|
uint32_t buffersInPacket1 = 0;
|
|
for (size_t i = 0; i < server->getNonConstConn().outstandings.packets.size();
|
|
++i) {
|
|
auto& packet = server->getNonConstConn().outstandings.packets[i];
|
|
if (packet.packet.header.getPacketNumberSpace() !=
|
|
PacketNumberSpace::AppData) {
|
|
continue;
|
|
}
|
|
PacketNum currentPacket = packet.packet.header.getPacketSequenceNum();
|
|
ASSERT_FALSE(packet.packet.frames.empty());
|
|
for (auto& quicFrame : packet.packet.frames) {
|
|
auto frame = quicFrame.asWriteStreamFrame();
|
|
if (!frame) {
|
|
continue;
|
|
}
|
|
auto it = stream->retransmissionBuffer.find(frame->offset);
|
|
ASSERT_TRUE(it != stream->retransmissionBuffer.end());
|
|
if (currentPacket == packetNum1 && frame->streamId == streamId) {
|
|
buffersInPacket1++;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto originalRetransSize = stream->retransmissionBuffer.size();
|
|
AckBlocks acks = {{packetNum1, packetNum1}};
|
|
auto packet1 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet1));
|
|
EXPECT_EQ(
|
|
stream->retransmissionBuffer.size(),
|
|
originalRetransSize - buffersInPacket1);
|
|
EXPECT_EQ(stream->sendState, StreamSendState::Open);
|
|
EXPECT_EQ(stream->recvState, StreamRecvState::Open);
|
|
|
|
// Dup ack
|
|
auto packet2 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet2));
|
|
|
|
EXPECT_EQ(
|
|
stream->retransmissionBuffer.size(),
|
|
originalRetransSize - buffersInPacket1);
|
|
EXPECT_EQ(stream->sendState, StreamSendState::Open);
|
|
EXPECT_EQ(stream->recvState, StreamRecvState::Open);
|
|
|
|
AckBlocks acks2 = {{packetNum1, lastPacketNum}};
|
|
auto packet3 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks2,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet3));
|
|
|
|
EXPECT_EQ(stream->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream->sendState, StreamSendState::Open);
|
|
EXPECT_EQ(stream->recvState, StreamRecvState::Open);
|
|
|
|
auto empty = IOBuf::create(0);
|
|
server->writeChain(streamId, std::move(empty), true);
|
|
loopForWrites();
|
|
ASSERT_FALSE(server->getConn().outstandings.packets.empty());
|
|
|
|
PacketNum finPacketNum =
|
|
getFirstOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
|
|
AckBlocks acks3 = {{lastPacketNum, finPacketNum}};
|
|
auto packet4 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks3,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet4));
|
|
EXPECT_EQ(stream->sendState, StreamSendState::Closed);
|
|
EXPECT_EQ(stream->recvState, StreamRecvState::Open);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvRstStreamFrameNonexistClientStream) {
|
|
StreamId streamId = 0x00;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
RstStreamFrame rstFrame(streamId, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
writeFrame(std::move(rstFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet));
|
|
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
ASSERT_TRUE(stream->streamReadError.has_value());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveRstStreamNonExistentAndOtherFrame) {
|
|
StreamId clientUnidirectional = 0x02;
|
|
|
|
// Deliver reset on peer unidirectional stream to close the stream.
|
|
RstStreamFrame rstFrame(
|
|
clientUnidirectional, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
writeFrame(rstFrame, builder);
|
|
auto packet = packetToBuf(std::move(builder).buildPacket());
|
|
deliverData(std::move(packet));
|
|
|
|
auto streamId =
|
|
server->createBidirectionalStream(false /* replaySafe */).value();
|
|
|
|
ShortHeader header2(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder2(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header2),
|
|
0 /* largestAcked */);
|
|
builder2.encodePacketHeader();
|
|
writeFrame(rstFrame, builder2);
|
|
|
|
auto data = folly::IOBuf::copyBuffer("hello");
|
|
writeStreamFrameHeader(
|
|
builder2,
|
|
streamId,
|
|
0,
|
|
data->computeChainDataLength(),
|
|
data->computeChainDataLength(),
|
|
false,
|
|
folly::none /* skipLenHint */);
|
|
writeStreamFrameData(builder2, data->clone(), data->computeChainDataLength());
|
|
auto packetObject = std::move(builder2).buildPacket();
|
|
auto packet2 = packetToBuf(std::move(packetObject));
|
|
deliverData(std::move(packet2));
|
|
|
|
auto readData = server->read(streamId, 0);
|
|
ASSERT_TRUE(readData.hasValue());
|
|
ASSERT_NE(readData.value().first, nullptr);
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*readData.value().first, *data));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvRstStreamFrameNonexistServerStream) {
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
StreamId streamId = 0x01;
|
|
RstStreamFrame rstFrame(streamId, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
writeFrame(std::move(rstFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_THROW(deliverData(packetToBuf(packet)), std::runtime_error);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvRstStreamFrame) {
|
|
clientNextAppDataPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId = 0x00;
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
stream->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
writeDataToQuicStream(*stream, IOBuf::copyBuffer(words.at(3)), false);
|
|
stream->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
RstStreamFrame rstFrame(
|
|
streamId,
|
|
GenericApplicationErrorCode::UNKNOWN,
|
|
words.at(0).length() + words.at(1).length());
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(std::move(rstFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet));
|
|
|
|
// Verify stream receive state is cleaned up but send state isn't:
|
|
auto updatedStream =
|
|
server->getNonConstConn().streamManager->findStream(streamId);
|
|
ASSERT_TRUE(updatedStream);
|
|
EXPECT_TRUE(updatedStream->readBuffer.empty());
|
|
// We can verify retx buffer isn't empty here. The writeBuffer though could be
|
|
// empty since deliverData can cause a write synchrously.
|
|
EXPECT_FALSE(updatedStream->retransmissionBuffer.empty());
|
|
EXPECT_EQ(
|
|
words.at(0).length() + words.at(1).length(),
|
|
updatedStream->finalReadOffset.value());
|
|
// updatedStream still writable since receiving rst has no impact on egress
|
|
EXPECT_TRUE(updatedStream->writable());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvStopSendingFrame) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId = 0x00;
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
stream->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(
|
|
connCallback,
|
|
onStopSending(streamId, GenericApplicationErrorCode::UNKNOWN));
|
|
deliverData(packetToBuf(packet));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvStopSendingFrameAfterCloseStream) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId = 0x00;
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
stream->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
server->getNonConstConn().flowControlState.sumCurStreamBufferLen = 100;
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
server->resetStream(streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
EXPECT_CALL(connCallback, onStopSending(_, _)).Times(0);
|
|
deliverData(packetToBuf(packet));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvInvalidMaxStreamData) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId = 0x02;
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
stream->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
server->getNonConstConn().flowControlState.sumCurStreamBufferLen = 100;
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
MaxStreamDataFrame maxStreamDataFrame(streamId, 100);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(std::move(maxStreamDataFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_THROW(deliverData(packetToBuf(packet)), std::runtime_error);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvStopSendingFrameAfterHalfCloseRemote) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId = 0x00;
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
stream->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
auto dataLen = writeStreamFrameHeader(
|
|
builder,
|
|
0x00,
|
|
stream->currentReadOffset,
|
|
0,
|
|
10,
|
|
true,
|
|
folly::none /* skipLenHint */);
|
|
ASSERT_TRUE(dataLen.has_value());
|
|
ASSERT_EQ(*dataLen, 0);
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(
|
|
connCallback,
|
|
onStopSending(streamId, GenericApplicationErrorCode::UNKNOWN));
|
|
deliverData(packetToBuf(packet));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvStopSendingBeforeStream) {
|
|
StreamId streamId = 0x00;
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(connCallback, onNewBidirectionalStream(streamId));
|
|
EXPECT_CALL(
|
|
connCallback,
|
|
onStopSending(streamId, GenericApplicationErrorCode::UNKNOWN));
|
|
deliverData(packetToBuf(packet));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvStopSendingFrameAfterReset) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId1 = 0x00;
|
|
StreamId streamId2 = 0x04;
|
|
auto stream1 = server->getNonConstConn().streamManager->getStream(streamId1);
|
|
stream1->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream1->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream1->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream1->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream1->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream1->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
auto stream2 = server->getNonConstConn().streamManager->getStream(streamId2);
|
|
stream2->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream2->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream2->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream2->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream2->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream2->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
StopSendingFrame stopSendingFrame1(
|
|
streamId1, GenericApplicationErrorCode::UNKNOWN);
|
|
StopSendingFrame stopSendingFrame2(
|
|
streamId2, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame1), builder);
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame2), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(
|
|
connCallback, onStopSending(_, GenericApplicationErrorCode::UNKNOWN))
|
|
.WillOnce(Invoke([&](StreamId /*sid*/, ApplicationErrorCode /*e*/) {
|
|
server->close(folly::none);
|
|
}));
|
|
EXPECT_THROW(deliverData(packetToBuf(packet)), std::runtime_error);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, StopSendingLoss) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
server->getNonConstConn().streamManager->getStream(streamId);
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
server->getConn().ackStates.appDataAckState.largestAckedByPeer.value_or(
|
|
0));
|
|
builder.encodePacketHeader();
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
markPacketLoss(server->getNonConstConn(), packet.packet, false);
|
|
EXPECT_EQ(server->getNonConstConn().pendingEvents.frames.size(), 1);
|
|
StopSendingFrame* stopFrame = server->getNonConstConn()
|
|
.pendingEvents.frames.front()
|
|
.asStopSendingFrame();
|
|
ASSERT_NE(stopFrame, nullptr);
|
|
EXPECT_EQ(*stopFrame, stopSendingFrame);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, StopSendingLossAfterStreamClosed) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
server->getNonConstConn().streamManager->getStream(streamId);
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
server->getConn().ackStates.appDataAckState.largestAckedByPeer.value_or(
|
|
0));
|
|
builder.encodePacketHeader();
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
// clear out all the streams, this is not a great way to simulate closed
|
|
// streams, but good enough for this test.
|
|
server->getNonConstConn().streamManager->clearOpenStreams();
|
|
markPacketLoss(server->getNonConstConn(), packet.packet, false);
|
|
EXPECT_EQ(server->getNonConstConn().pendingEvents.frames.size(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestCloneStopSending) {
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
auto qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
server->getNonConstConn().streamManager->getStream(streamId);
|
|
// knock every handshake outstanding packets out
|
|
server->getNonConstConn().outstandings.packetCount = {};
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
for (auto& t : server->getNonConstConn().lossState.lossTimes) {
|
|
t.reset();
|
|
}
|
|
|
|
server->stopSending(streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
loopForWrites();
|
|
// Find the outstanding StopSending.
|
|
auto packetItr = std::find_if(
|
|
server->getNonConstConn().outstandings.packets.begin(),
|
|
server->getNonConstConn().outstandings.packets.end(),
|
|
findFrameInPacketFunc<QuicSimpleFrame::Type::StopSendingFrame>());
|
|
|
|
ASSERT_TRUE(
|
|
packetItr != server->getNonConstConn().outstandings.packets.end());
|
|
// Force a timeout with no data so that it clones the packet
|
|
server->lossTimeout().timeoutExpired();
|
|
loopForWrites();
|
|
auto numStopSendingPackets = std::count_if(
|
|
server->getNonConstConn().outstandings.packets.begin(),
|
|
server->getNonConstConn().outstandings.packets.end(),
|
|
findFrameInPacketFunc<QuicSimpleFrame::Type::StopSendingFrame>());
|
|
|
|
EXPECT_GT(numStopSendingPackets, 1);
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::TransportStateUpdate, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogTransportStateUpdateEvent*>(tmp.get());
|
|
EXPECT_EQ(event->update, kLossTimeoutExpired);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestAckStopSending) {
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
server->getNonConstConn().streamManager->getStream(streamId);
|
|
server->stopSending(streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
loopForWrites();
|
|
auto match = findFrameInPacketFunc<QuicSimpleFrame::Type::StopSendingFrame>();
|
|
|
|
auto op = findOutstandingPacket(server->getNonConstConn(), match);
|
|
ASSERT_TRUE(op != nullptr);
|
|
PacketNum packetNum = op->packet.header.getPacketSequenceNum();
|
|
AckBlocks acks = {{packetNum, packetNum}};
|
|
auto packet1 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet1));
|
|
op = findOutstandingPacket(server->getNonConstConn(), match);
|
|
EXPECT_TRUE(op == nullptr);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvPathChallenge) {
|
|
auto& conn = server->getNonConstConn();
|
|
|
|
// Add additional peer id so PathResponse completes.
|
|
conn.peerConnectionIds.emplace_back(ConnectionId({1, 2, 3, 4}), 1);
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero, *conn.serverConnectionId, 10);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
PathChallengeFrame pathChallenge(123);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeSimpleFrame(QuicSimpleFrame(pathChallenge), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_TRUE(conn.pendingEvents.frames.empty());
|
|
deliverData(packetToBuf(packet), false);
|
|
EXPECT_EQ(conn.pendingEvents.frames.size(), 2);
|
|
// The RetireConnectionId frame will be enqueued before the PathResponse.
|
|
auto retireFrame = conn.pendingEvents.frames[0].asRetireConnectionIdFrame();
|
|
EXPECT_EQ(retireFrame->sequenceNumber, 0);
|
|
|
|
PathResponseFrame& pathResponse =
|
|
*conn.pendingEvents.frames[1].asPathResponseFrame();
|
|
EXPECT_EQ(pathResponse.pathData, pathChallenge.pathData);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestAckRstStream) {
|
|
auto streamId = server->createUnidirectionalStream().value();
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
auto packetNum = rstStreamAndSendPacket(
|
|
server->getNonConstConn(),
|
|
server->getSocket(),
|
|
*stream,
|
|
GenericApplicationErrorCode::UNKNOWN);
|
|
|
|
AckBlocks acks = {{packetNum, packetNum}};
|
|
auto packet1 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet1));
|
|
// Closed streams should be deleted.
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveConnectionClose) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
std::string errMsg = "Stand clear of the closing doors, please";
|
|
ConnectionCloseFrame connClose(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR), errMsg);
|
|
writeFrame(std::move(connClose), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(connCallback, onConnectionEnd());
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
// Now the transport should be closed
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->code,
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR));
|
|
EXPECT_EQ(
|
|
server->getConn().peerConnectionError->code,
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR));
|
|
auto closedMsg =
|
|
folly::to<std::string>("Server closed by peer reason=", errMsg);
|
|
EXPECT_EQ(server->getConn().peerConnectionError->message, closedMsg);
|
|
EXPECT_TRUE(server->isClosed());
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
checkTransportStateUpdate(qLogger, std::move(closedMsg));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveApplicationClose) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
std::string errMsg = "Stand clear of the closing doors, please";
|
|
ConnectionCloseFrame appClose(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN), errMsg);
|
|
writeFrame(std::move(appClose), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_CALL(
|
|
connCallback,
|
|
onConnectionError(IsAppError(GenericApplicationErrorCode::UNKNOWN)));
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
// Now the transport should be closed
|
|
EXPECT_EQ(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR),
|
|
server->getConn().localConnectionError->code);
|
|
EXPECT_EQ(
|
|
server->getConn().peerConnectionError->code,
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN));
|
|
auto closedMsg =
|
|
folly::to<std::string>("Server closed by peer reason=", errMsg);
|
|
EXPECT_EQ(server->getConn().peerConnectionError->message, closedMsg);
|
|
EXPECT_TRUE(server->isClosed());
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
checkTransportStateUpdate(qLogger, std::move(closedMsg));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveConnectionCloseTwice) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
std::string errMsg = "Mind the gap";
|
|
ConnectionCloseFrame connClose(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR), errMsg);
|
|
writeFrame(std::move(connClose), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(connCallback, onConnectionEnd());
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
// Now the transport should be closed
|
|
EXPECT_EQ(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR),
|
|
server->getConn().localConnectionError->code);
|
|
EXPECT_EQ(
|
|
server->getConn().peerConnectionError->code,
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR));
|
|
auto closedMsg =
|
|
folly::to<std::string>("Server closed by peer reason=", errMsg);
|
|
EXPECT_EQ(server->getConn().peerConnectionError->message, closedMsg);
|
|
EXPECT_TRUE(server->isClosed());
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
serverWrites.clear();
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 29);
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::SERVER_STATE_CLOSED));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, CloseTransportWontUnbound) {
|
|
EXPECT_CALL(routingCallback, onConnectionUnbound(_, _, _)).Times(0);
|
|
server->closeTransport();
|
|
// Need to do this otherwise server transport destructor will still call
|
|
// onConnectionUnbound
|
|
server->setRoutingCallback(nullptr);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, UnboundConnection) {
|
|
EXPECT_CALL(routingCallback, onConnectionUnbound(_, _, _)).Times(1);
|
|
server->unbindConnection();
|
|
// Need to do this otherwise server transport destructor will still call
|
|
// onConnectionUnbound
|
|
server->setRoutingCallback(nullptr);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, DestroyWithoutClosing) {
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
MockReadCallback readCb;
|
|
server->setReadCallback(streamId, &readCb);
|
|
|
|
EXPECT_CALL(connCallback, onConnectionError(_)).Times(0);
|
|
EXPECT_CALL(connCallback, onConnectionEnd()).Times(0);
|
|
MockDeliveryCallback deliveryCallback;
|
|
auto write = IOBuf::copyBuffer("no");
|
|
server->writeChain(streamId, write->clone(), true, &deliveryCallback);
|
|
|
|
EXPECT_CALL(deliveryCallback, onCanceled(_, _));
|
|
EXPECT_CALL(readCb, readError(_, _));
|
|
|
|
server.reset();
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, DestroyWithoutClosingCancelByteEvents) {
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
MockReadCallback readCb;
|
|
server->setReadCallback(streamId, &readCb);
|
|
|
|
EXPECT_CALL(connCallback, onConnectionError(_)).Times(0);
|
|
EXPECT_CALL(connCallback, onConnectionEnd()).Times(0);
|
|
auto write = IOBuf::copyBuffer("no");
|
|
server->writeChain(streamId, write->clone(), true);
|
|
|
|
MockByteEventCallback txCallback;
|
|
MockByteEventCallback deliveryCallback;
|
|
|
|
server->registerByteEventCallback(
|
|
ByteEvent::Type::TX, streamId, 0, &txCallback);
|
|
server->registerByteEventCallback(
|
|
ByteEvent::Type::ACK, streamId, 0, &deliveryCallback);
|
|
|
|
EXPECT_CALL(txCallback, onByteEventCanceled(_));
|
|
EXPECT_CALL(deliveryCallback, onByteEventCanceled(_));
|
|
EXPECT_CALL(readCb, readError(_, _));
|
|
|
|
server.reset();
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, SetCongestionControl) {
|
|
// Default: Cubic
|
|
auto cc = server->getConn().congestionController.get();
|
|
EXPECT_EQ(CongestionControlType::Cubic, cc->type());
|
|
|
|
// Change to Reno
|
|
server->setCongestionControl(CongestionControlType::NewReno);
|
|
cc = server->getConn().congestionController.get();
|
|
EXPECT_EQ(CongestionControlType::NewReno, cc->type());
|
|
|
|
// Change back to Cubic:
|
|
server->setCongestionControl(CongestionControlType::Cubic);
|
|
cc = server->getConn().congestionController.get();
|
|
EXPECT_EQ(CongestionControlType::Cubic, cc->type());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, CongestionControlAggressivenessKnob) {
|
|
// Congestion controller is Cubic as default
|
|
auto cc = server->getConn().congestionController.get();
|
|
EXPECT_EQ(CongestionControlType::Cubic, cc->type());
|
|
|
|
// Set to background mode will no have an effect. It only works on BBR.
|
|
server->handleKnobParams({
|
|
{52395, 75},
|
|
});
|
|
|
|
// Switch to BBR congestion control
|
|
server->writeLooper()->setPacingTimer(TimerHighRes::newTimer(&evb, 1ms));
|
|
server->getNonConstConn().transportSettings.pacingEnabled = true;
|
|
server->setCongestionControl(CongestionControlType::BBR);
|
|
cc = server->getConn().congestionController.get();
|
|
EXPECT_EQ(CongestionControlType::BBR, cc->type());
|
|
// BBR is not in background mode initially
|
|
EXPECT_FALSE(cc->isInBackgroundMode());
|
|
|
|
// Set to background mode.
|
|
server->handleKnobParams({
|
|
{52395, 75},
|
|
});
|
|
// BBR should now be in background mode
|
|
EXPECT_TRUE(cc->isInBackgroundMode());
|
|
|
|
// Turn off background mode and verify it
|
|
server->handleKnobParams({
|
|
{52395, 100},
|
|
});
|
|
EXPECT_FALSE(cc->isInBackgroundMode());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestServerNotDetachable) {
|
|
EXPECT_FALSE(server->isDetachable());
|
|
}
|
|
|
|
TEST_F(
|
|
QuicServerTransportTest,
|
|
ReceiveDataFromChangedPeerAddressWhileMigrationIsDisabled) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
try {
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
FAIL();
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->message, "Migration disabled");
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, SwitchServerCidsNoOtherIds) {
|
|
auto& conn = server->getNonConstConn();
|
|
|
|
EXPECT_EQ(conn.retireAndSwitchPeerConnectionIds(), false);
|
|
EXPECT_EQ(conn.pendingEvents.frames.size(), 0);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, SwitchServerCidsOneOtherCid) {
|
|
auto& conn = server->getNonConstConn();
|
|
auto originalCid = conn.clientConnectionId;
|
|
auto secondCid =
|
|
ConnectionIdData(ConnectionId(std::vector<uint8_t>{5, 6, 7, 8}), 2);
|
|
conn.peerConnectionIds.push_back(secondCid);
|
|
|
|
EXPECT_EQ(conn.retireAndSwitchPeerConnectionIds(), true);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
|
|
EXPECT_EQ(conn.pendingEvents.frames.size(), 1);
|
|
auto retireFrame = conn.pendingEvents.frames[0].asRetireConnectionIdFrame();
|
|
EXPECT_EQ(retireFrame->sequenceNumber, 0);
|
|
|
|
auto replacedCid = conn.clientConnectionId;
|
|
EXPECT_NE(originalCid, *replacedCid);
|
|
EXPECT_EQ(secondCid.connId, *replacedCid);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, SwitchServerCidsMultipleCids) {
|
|
auto& conn = server->getNonConstConn();
|
|
auto originalCid = conn.clientConnectionId;
|
|
auto secondCid =
|
|
ConnectionIdData(ConnectionId(std::vector<uint8_t>{5, 6, 7, 8}), 2);
|
|
auto thirdCid =
|
|
ConnectionIdData(ConnectionId(std::vector<uint8_t>{3, 3, 3, 3}), 3);
|
|
|
|
conn.peerConnectionIds.push_back(secondCid);
|
|
conn.peerConnectionIds.push_back(thirdCid);
|
|
|
|
EXPECT_EQ(conn.retireAndSwitchPeerConnectionIds(), true);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 2);
|
|
|
|
EXPECT_EQ(conn.pendingEvents.frames.size(), 1);
|
|
auto retireFrame = conn.pendingEvents.frames[0].asRetireConnectionIdFrame();
|
|
EXPECT_EQ(retireFrame->sequenceNumber, 0);
|
|
|
|
// Uses the first unused connection id.
|
|
auto replacedCid = conn.clientConnectionId;
|
|
EXPECT_NE(originalCid, *replacedCid);
|
|
EXPECT_EQ(secondCid.connId, *replacedCid);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ShortHeaderPacketWithNoFrames) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
// Use large packet number to make sure packet is long enough to parse
|
|
PacketNum nextPacket = 0x11111111;
|
|
// Add some dummy data to make body parseable
|
|
auto dummyDataLen = 20;
|
|
server->getNonConstConn().serverConnectionId = getTestConnectionId();
|
|
auto aead = dynamic_cast<const MockAead*>(
|
|
server->getNonConstConn().readCodec->getOneRttReadCipher());
|
|
// Override the Aead mock to remove the 20 bytes of dummy data added below
|
|
ON_CALL(*aead, _tryDecrypt(_, _, _))
|
|
.WillByDefault(Invoke([&](auto& buf, auto, auto) {
|
|
buf->trimEnd(20);
|
|
return buf->clone();
|
|
}));
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
nextPacket);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
auto packet = std::move(builder).buildPacket();
|
|
auto buf = packetToBuf(packet);
|
|
buf->coalesce();
|
|
buf->reserve(0, 200);
|
|
buf->append(dummyDataLen);
|
|
EXPECT_THROW(deliverData(std::move(buf)), std::runtime_error);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PROTOCOL_VIOLATION));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ShortHeaderPacketWithNoFramesAfterClose) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
// Use large packet number to make sure packet is long enough to parse
|
|
PacketNum nextPacket = 0x11111111;
|
|
// Add some dummy data to make body parseable
|
|
auto dummyDataLen = 20;
|
|
server->getNonConstConn().serverConnectionId = getTestConnectionId();
|
|
auto aead = dynamic_cast<const MockAead*>(
|
|
server->getNonConstConn().readCodec->getOneRttReadCipher());
|
|
// Override the Aead mock to remove the 20 bytes of dummy data added below
|
|
ON_CALL(*aead, _tryDecrypt(_, _, _))
|
|
.WillByDefault(Invoke([&](auto& buf, auto, auto) {
|
|
buf->trimEnd(20);
|
|
return buf->clone();
|
|
}));
|
|
|
|
// Close the connection
|
|
server->close(QuicError(
|
|
QuicErrorCode(TransportErrorCode::INTERNAL_ERROR),
|
|
std::string("test close")));
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
nextPacket);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
auto packet = std::move(builder).buildPacket();
|
|
auto buf = packetToBuf(packet);
|
|
buf->coalesce();
|
|
buf->reserve(0, 200);
|
|
buf->append(dummyDataLen);
|
|
EXPECT_THROW(deliverData(std::move(buf)), std::runtime_error);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PROTOCOL_VIOLATION));
|
|
}
|
|
|
|
class QuicServerTransportAllowMigrationTest
|
|
: public QuicServerTransportTest,
|
|
public WithParamInterface<MigrationParam> {
|
|
public:
|
|
bool getDisableMigration() override {
|
|
return false;
|
|
}
|
|
|
|
virtual void initializeServerHandshake() override {
|
|
fakeHandshake = new FakeServerHandshake(
|
|
server->getNonConstConn(),
|
|
FizzServerQuicHandshakeContext::Builder().build(),
|
|
false,
|
|
false,
|
|
GetParam().clientSentActiveConnIdTransportParam);
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
QuicServerTransportMigrationTests,
|
|
QuicServerTransportAllowMigrationTest,
|
|
Values(
|
|
MigrationParam{folly::none},
|
|
MigrationParam{2},
|
|
MigrationParam{4},
|
|
MigrationParam{9},
|
|
MigrationParam{50}));
|
|
|
|
TEST_P(
|
|
QuicServerTransportAllowMigrationTest,
|
|
ReceiveProbingPacketFromChangedPeerAddress) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
|
|
// Add additional peer id so PathResponse completes.
|
|
server->getNonConstConn().peerConnectionIds.emplace_back(
|
|
ConnectionId({1, 2, 3, 4}), 1);
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
writeSimpleFrame(PathChallengeFrame(123), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
auto packetData = packetToBuf(packet);
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
try {
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
FAIL();
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->message,
|
|
"Probing not supported yet");
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 29);
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PEER_ADDRESS_CHANGE));
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportAllowMigrationTest,
|
|
ReceiveReorderedDataFromChangedPeerAddress) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto firstPacket = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
auto secondPacket = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
6,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
// Receive second packet first
|
|
deliverData(std::move(secondPacket));
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
|
|
// Receive first packet later from a different address
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(firstPacket), true, &newPeer);
|
|
|
|
// No migration for reordered packet
|
|
EXPECT_EQ(server->getConn().peerAddress, peerAddress);
|
|
}
|
|
|
|
TEST_P(QuicServerTransportAllowMigrationTest, MigrateToUnvalidatedPeer) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
|
|
loopForWrites();
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_TRUE(server->getConn().pathValidationLimiter != nullptr);
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
writeSimpleFrame(
|
|
PathResponseFrame(server->getConn().outstandingPathValidation->pathData),
|
|
builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet), false, &newPeer);
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
}
|
|
|
|
TEST_P(QuicServerTransportAllowMigrationTest, ResetPathRttPathResponse) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
|
|
loopForWrites();
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
writeSimpleFrame(
|
|
PathResponseFrame(server->getConn().outstandingPathValidation->pathData),
|
|
builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet), false, &newPeer);
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
EXPECT_FALSE(server->getConn().writableBytesLimit);
|
|
|
|
// After Pathresponse frame is received, srtt,lrtt = sampleRtt;
|
|
// sampleRtt = time from send of PathChallenge to receiving PathResponse
|
|
EXPECT_NE(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_NE(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_NE(server->getConn().lossState.rttvar, 0us);
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
}
|
|
|
|
TEST_P(QuicServerTransportAllowMigrationTest, IgnoreInvalidPathResponse) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
|
|
EXPECT_TRUE(server->getConn().pathValidationLimiter != nullptr);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
|
|
loopForWrites();
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
writeSimpleFrame(
|
|
PathResponseFrame(
|
|
server->getConn().outstandingPathValidation->pathData ^ 1),
|
|
builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet), false, &newPeer);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportAllowMigrationTest,
|
|
ReceivePathResponseFromDifferentPeerAddress) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
|
|
loopForWrites();
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_TRUE(server->getConn().pathValidationLimiter != nullptr);
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeSimpleFrame(
|
|
PathResponseFrame(server->getConn().outstandingPathValidation->pathData),
|
|
builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
folly::SocketAddress newPeer2("200.101.102.103", 23456);
|
|
try {
|
|
deliverData(packetToBuf(packet), false, &newPeer2);
|
|
FAIL();
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->isClosed());
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->message,
|
|
"Probing not supported yet");
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TooManyMigrations) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
for (size_t i = 0; i < kMaxNumMigrationsAllowed; ++i) {
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456 + i);
|
|
deliverData(packetData->clone(), false, &newPeer);
|
|
}
|
|
|
|
folly::SocketAddress newPeer("200.101.102.103", 23456);
|
|
try {
|
|
deliverData(packetData->clone(), false, &newPeer);
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->message, "Too many migrations");
|
|
EXPECT_TRUE(server->isClosed());
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 0);
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PEER_ADDRESS_CHANGE));
|
|
}
|
|
|
|
TEST_P(QuicServerTransportAllowMigrationTest, MigrateToValidatedPeer) {
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
server->getNonConstConn().migrationState.previousPeerAddresses.push_back(
|
|
newPeer);
|
|
CongestionAndRttState state;
|
|
state.peerAddress = newPeer;
|
|
state.recordTime = Clock::now();
|
|
state.congestionController = ccFactory_->makeCongestionController(
|
|
server->getNonConstConn(),
|
|
server->getNonConstConn().transportSettings.defaultCongestionController);
|
|
state.srtt = 1000us;
|
|
state.lrtt = 2000us;
|
|
state.rttvar = 3000us;
|
|
state.mrtt = 800us;
|
|
server->getNonConstConn().migrationState.lastCongestionAndRtt =
|
|
std::move(state);
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto lastCongestionController =
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get();
|
|
auto lastSrtt = server->getConn().migrationState.lastCongestionAndRtt->srtt;
|
|
auto lastLrtt = server->getConn().migrationState.lastCongestionAndRtt->lrtt;
|
|
auto lastRttvar =
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar;
|
|
auto lastMrtt = server->getConn().migrationState.lastCongestionAndRtt->mrtt;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, lastSrtt);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, lastLrtt);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, lastRttvar);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, lastMrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().congestionController.get(), lastCongestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportAllowMigrationTest,
|
|
MigrateToUnvalidatedPeerOverwritesCachedRttState) {
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
server->getNonConstConn().migrationState.previousPeerAddresses.push_back(
|
|
newPeer);
|
|
CongestionAndRttState state;
|
|
state.peerAddress = newPeer;
|
|
state.recordTime = Clock::now();
|
|
state.congestionController = ccFactory_->makeCongestionController(
|
|
server->getNonConstConn(),
|
|
server->getNonConstConn().transportSettings.defaultCongestionController);
|
|
state.srtt = 1000us;
|
|
state.lrtt = 2000us;
|
|
state.rttvar = 3000us;
|
|
state.mrtt = 800us;
|
|
server->getNonConstConn().migrationState.lastCongestionAndRtt =
|
|
std::move(state);
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
folly::SocketAddress newPeer2("200.101.102.103", 2345);
|
|
deliverData(std::move(packetData), false, &newPeer2);
|
|
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer2);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 2);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.front(), newPeer);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
}
|
|
|
|
TEST_P(QuicServerTransportAllowMigrationTest, MigrateToStaleValidatedPeer) {
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
server->getNonConstConn().migrationState.previousPeerAddresses.push_back(
|
|
newPeer);
|
|
CongestionAndRttState state;
|
|
state.peerAddress = newPeer;
|
|
state.recordTime = Clock::now() - 2 * kTimeToRetainLastCongestionAndRttState;
|
|
state.congestionController = ccFactory_->makeCongestionController(
|
|
server->getNonConstConn(),
|
|
server->getNonConstConn().transportSettings.defaultCongestionController);
|
|
state.srtt = 1000us;
|
|
state.lrtt = 2000us;
|
|
state.rttvar = 3000us;
|
|
state.srtt = 800us;
|
|
server->getNonConstConn().migrationState.lastCongestionAndRtt =
|
|
std::move(state);
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicServerTransportTest,
|
|
MigrateToValidatePeerCancelsPendingPathChallenge) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
|
|
auto packetData2 = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
6,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
deliverData(std::move(packetData2), false);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, srtt);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, lrtt);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, mrtt);
|
|
EXPECT_EQ(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_FALSE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicServerTransportTest,
|
|
MigrateToUnvalidatePeerCancelsOutstandingPathChallenge) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
|
|
auto packetData2 = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
6,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
folly::SocketAddress newPeer2("200.101.102.103", 23456);
|
|
deliverData(std::move(packetData2), false, &newPeer2);
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicServerTransportTest,
|
|
MigrateToValidatePeerCancelsOutstandingPathChallenge) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
|
|
auto packetData2 = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
6,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
deliverData(std::move(packetData2));
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, srtt);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, lrtt);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, mrtt);
|
|
EXPECT_EQ(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_FALSE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ClientPortChangeNATRebinding) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
auto data1 = IOBuf::copyBuffer("Aloha");
|
|
server->writeChain(streamId, data1->clone(), false);
|
|
loopForWrites();
|
|
PacketNum packetNum1 =
|
|
getFirstOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
AckBlocks acks = {{packetNum1, packetNum1}};
|
|
auto packet1 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet1));
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
|
|
folly::SocketAddress newPeer(clientAddr.getIPAddress(), 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_NE(
|
|
server->getConn().lossState.srtt, std::chrono::microseconds::zero());
|
|
EXPECT_NE(
|
|
server->getConn().lossState.lrtt, std::chrono::microseconds::zero());
|
|
EXPECT_NE(
|
|
server->getConn().lossState.rttvar, std::chrono::microseconds::zero());
|
|
EXPECT_NE(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_EQ(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_FALSE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ClientAddressChangeNATRebinding) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
auto data1 = IOBuf::copyBuffer("Aloha");
|
|
server->writeChain(streamId, data1->clone(), false);
|
|
loopForWrites();
|
|
PacketNum packetNum1 =
|
|
getFirstOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
AckBlocks acks = {{packetNum1, packetNum1}};
|
|
auto packet1 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet1));
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
|
|
folly::SocketAddress newPeer("127.0.0.100", 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_NE(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_NE(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_NE(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_NE(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_EQ(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_FALSE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicServerTransportTest,
|
|
ClientNATRebindingWhilePathValidationOutstanding) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
|
|
folly::SocketAddress newPeer("200.0.0.100", 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.srtt, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.lrtt, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.rttvar, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_TRUE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
|
|
auto newCC = server->getConn().congestionController.get();
|
|
folly::SocketAddress newPeer2("200.0.0.200", 12345);
|
|
auto data2 = IOBuf::copyBuffer("bad data");
|
|
auto packetData2 = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
deliverData(std::move(packetData2), true, &newPeer2);
|
|
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer2);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.srtt, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.lrtt, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.rttvar, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_EQ(server->getConn().congestionController.get(), newCC);
|
|
EXPECT_TRUE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, PingIsTreatedAsRetransmittable) {
|
|
PingFrame pingFrame;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
writeFrame(pingFrame, builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet));
|
|
EXPECT_TRUE(server->getConn().pendingEvents.scheduleAckTimeout);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveDatagramFrameAndDiscard) {
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
StringPiece datagramPayload = "do not rely on me. I am unreliable";
|
|
DatagramFrame datagramFrame(
|
|
datagramPayload.size(), IOBuf::copyBuffer(datagramPayload));
|
|
writeFrame(datagramFrame, builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(*quicStats_, onDatagramDroppedOnRead()).Times(1);
|
|
deliverData(packetToBuf(packet));
|
|
ASSERT_EQ(server->getConn().datagramState.readBuffer.size(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveDatagramFrameAndStore) {
|
|
auto& conn = server->getNonConstConn();
|
|
conn.datagramState.maxReadFrameSize = std::numeric_limits<uint16_t>::max();
|
|
conn.datagramState.maxReadBufferSize = 10;
|
|
|
|
EXPECT_CALL(*quicStats_, onDatagramRead(_))
|
|
.Times(conn.datagramState.maxReadBufferSize)
|
|
.WillRepeatedly(Invoke([](uint64_t bytes) { EXPECT_GT(bytes, 0); }));
|
|
EXPECT_CALL(*quicStats_, onDatagramDroppedOnRead())
|
|
.Times(conn.datagramState.maxReadBufferSize);
|
|
for (uint64_t i = 0; i < conn.datagramState.maxReadBufferSize * 2; i++) {
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
StringPiece datagramPayload = "do not rely on me. I am unreliable";
|
|
DatagramFrame datagramFrame(
|
|
datagramPayload.size(), IOBuf::copyBuffer(datagramPayload));
|
|
writeFrame(datagramFrame, builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet));
|
|
if (i < conn.datagramState.maxReadBufferSize) {
|
|
ASSERT_EQ(server->getConn().datagramState.readBuffer.size(), i + 1);
|
|
}
|
|
}
|
|
ASSERT_EQ(
|
|
server->getConn().datagramState.readBuffer.size(),
|
|
conn.datagramState.maxReadBufferSize);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvNewConnectionIdValid) {
|
|
auto& conn = server->getNonConstConn();
|
|
conn.transportSettings.selfActiveConnectionIdLimit = 2;
|
|
|
|
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
NewConnectionIdFrame newConnId(
|
|
1, 0, ConnectionId({2, 4, 2, 3}), StatelessResetToken{9, 8, 7, 6});
|
|
writeSimpleFrame(QuicSimpleFrame(newConnId), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
deliverData(packetToBuf(packet), false);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 2);
|
|
EXPECT_EQ(conn.peerConnectionIds[1].connId, newConnId.connectionId);
|
|
EXPECT_EQ(conn.peerConnectionIds[1].sequenceNumber, newConnId.sequenceNumber);
|
|
EXPECT_EQ(conn.peerConnectionIds[1].token, newConnId.token);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvNewConnectionIdTooManyReceivedIds) {
|
|
auto& conn = server->getNonConstConn();
|
|
conn.transportSettings.selfActiveConnectionIdLimit = 0;
|
|
|
|
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
NewConnectionIdFrame newConnId(
|
|
1, 0, ConnectionId({2, 4, 2, 3}), StatelessResetToken());
|
|
writeSimpleFrame(QuicSimpleFrame(newConnId), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
deliverData(packetToBuf(packet), false);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvNewConnectionIdInvalidRetire) {
|
|
auto& conn = server->getNonConstConn();
|
|
conn.transportSettings.selfActiveConnectionIdLimit = 1;
|
|
|
|
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
NewConnectionIdFrame newConnId(
|
|
1, 3, ConnectionId({2, 4, 2, 3}), StatelessResetToken());
|
|
writeSimpleFrame(QuicSimpleFrame(newConnId), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
EXPECT_THROW(deliverData(packetToBuf(packet), false), std::runtime_error);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvNewConnectionIdNoopValidDuplicate) {
|
|
auto& conn = server->getNonConstConn();
|
|
conn.transportSettings.selfActiveConnectionIdLimit = 1;
|
|
|
|
ConnectionId connId2({5, 5, 5, 5});
|
|
conn.peerConnectionIds.emplace_back(connId2, 1);
|
|
|
|
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
NewConnectionIdFrame newConnId(1, 0, connId2, StatelessResetToken());
|
|
writeSimpleFrame(QuicSimpleFrame(newConnId), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 2);
|
|
deliverData(packetToBuf(packet), false);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 2);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvNewConnectionIdExceptionInvalidDuplicate) {
|
|
auto& conn = server->getNonConstConn();
|
|
|
|
ConnectionId connId2({5, 5, 5, 5});
|
|
conn.peerConnectionIds.emplace_back(connId2, 1);
|
|
|
|
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
NewConnectionIdFrame newConnId(2, 0, connId2, StatelessResetToken());
|
|
writeSimpleFrame(QuicSimpleFrame(newConnId), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 2);
|
|
EXPECT_THROW(deliverData(packetToBuf(packet)), std::runtime_error);
|
|
}
|
|
|
|
class QuicUnencryptedServerTransportTest : public QuicServerTransportTest {
|
|
public:
|
|
void setupConnection() override {}
|
|
};
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, FirstPacketProcessedCallback) {
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
EXPECT_CALL(connSetupCallback, onFirstPeerPacketProcessed()).Times(1);
|
|
recvClientHello();
|
|
loopForWrites();
|
|
AckBlocks acks;
|
|
acks.insert(0);
|
|
auto aead = getInitialCipher();
|
|
auto headerCipher = getInitialHeaderCipher();
|
|
EXPECT_CALL(connSetupCallback, onFirstPeerPacketProcessed()).Times(0);
|
|
deliverData(packetToBufCleartext(
|
|
createAckPacket(
|
|
server->getNonConstConn(),
|
|
clientNextInitialPacketNum,
|
|
acks,
|
|
PacketNumberSpace::Initial,
|
|
aead.get()),
|
|
*aead,
|
|
*headerCipher,
|
|
clientNextInitialPacketNum));
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestUnencryptedStream) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
PacketNum nextPacket = clientNextInitialPacketNum++;
|
|
StreamId streamId = 3;
|
|
auto initialCipher = getInitialCipher();
|
|
auto headerCipher = getInitialHeaderCipher();
|
|
auto packetData = packetToBufCleartext(
|
|
createStreamPacket(
|
|
*clientConnectionId,
|
|
*initialDestinationConnectionId,
|
|
nextPacket,
|
|
streamId,
|
|
*data,
|
|
initialCipher->getCipherOverhead(),
|
|
0 /* largestAcked */,
|
|
std::make_pair(LongHeader::Types::Initial, QuicVersion::MVFST)),
|
|
*initialCipher,
|
|
*headerCipher,
|
|
nextPacket);
|
|
EXPECT_THROW(deliverData(std::move(packetData)), std::runtime_error);
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestUnencryptedAck) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
AckBlocks acks = {{1, 2}};
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
PacketNum nextPacketNum = clientNextInitialPacketNum++;
|
|
LongHeader header(
|
|
LongHeader::Types::Initial,
|
|
*clientConnectionId,
|
|
server->getConn().serverConnectionId.value_or(getTestConnectionId(1)),
|
|
nextPacketNum,
|
|
QuicVersion::MVFST);
|
|
RegularQuicPacketBuilder builder(
|
|
kDefaultUDPSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
DCHECK(builder.canBuildPacket());
|
|
AckFrameMetaData ackData(acks, 0us, 0);
|
|
writeAckFrame(ackData, builder);
|
|
auto packet = packetToBufCleartext(
|
|
std::move(builder).buildPacket(),
|
|
*getInitialCipher(),
|
|
*getInitialHeaderCipher(),
|
|
nextPacketNum);
|
|
EXPECT_NO_THROW(deliverData(std::move(packet)));
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 45);
|
|
EXPECT_EQ(event->dropReason, kCipherUnavailable);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestBadPacketProtectionLevel) {
|
|
// Version negotiation has no protection level.
|
|
auto packet = VersionNegotiationPacketBuilder(
|
|
*clientConnectionId /* src */,
|
|
getTestConnectionId(1) /* dest */,
|
|
{QuicVersion::MVFST})
|
|
.buildPacket();
|
|
EXPECT_CALL(*quicStats_, onPacketDropped(_));
|
|
deliverData(packet.second->clone());
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestBadCleartextEncryption) {
|
|
FizzCryptoFactory cryptoFactory;
|
|
PacketNum nextPacket = clientNextInitialPacketNum++;
|
|
auto aead = cryptoFactory.getServerInitialCipher(
|
|
*clientConnectionId, QuicVersion::MVFST);
|
|
auto packetData = packetToBufCleartext(
|
|
createInitialCryptoPacket(
|
|
*clientConnectionId,
|
|
*initialDestinationConnectionId,
|
|
nextPacket,
|
|
QuicVersion::MVFST,
|
|
*IOBuf::copyBuffer("CHLO"),
|
|
*aead,
|
|
0 /* largestAcked */),
|
|
*aead,
|
|
*getInitialHeaderCipher(),
|
|
nextPacket);
|
|
EXPECT_CALL(*quicStats_, onPacketDropped(_));
|
|
deliverData(std::move(packetData));
|
|
// If crypto data was processed, we would have generated some writes.
|
|
EXPECT_NE(server->getConn().readCodec, nullptr);
|
|
EXPECT_TRUE(server->getConn().cryptoState->initialStream.writeBuffer.empty());
|
|
EXPECT_TRUE(server->getConn()
|
|
.cryptoState->initialStream.retransmissionBuffer.empty());
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestPendingZeroRttData) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
size_t expectedPendingLen =
|
|
server->getConn().transportSettings.maxPacketsToBuffer;
|
|
for (size_t i = 0; i < expectedPendingLen + 10; ++i) {
|
|
StreamId streamId = static_cast<StreamId>(i);
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
server->getConn().serverConnectionId.value_or(getTestConnectionId(1)),
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */,
|
|
std::make_pair(LongHeader::Types::ZeroRtt, QuicVersion::MVFST)));
|
|
EXPECT_CALL(*quicStats_, onPacketDropped(_));
|
|
deliverData(std::move(packetData));
|
|
}
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData->size(), expectedPendingLen);
|
|
|
|
server->getNonConstConn().pendingZeroRttData->clear();
|
|
deliverData(IOBuf::create(0));
|
|
EXPECT_TRUE(server->getConn().pendingZeroRttData->empty());
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestPendingOneRttData) {
|
|
recvClientHello();
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
size_t expectedPendingLen =
|
|
server->getConn().transportSettings.maxPacketsToBuffer;
|
|
for (size_t i = 0; i < expectedPendingLen + 10; ++i) {
|
|
StreamId streamId = static_cast<StreamId>(i);
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
EXPECT_CALL(*quicStats_, onPacketDropped(_));
|
|
deliverData(std::move(packetData));
|
|
}
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData->size(), expectedPendingLen);
|
|
|
|
server->getNonConstConn().pendingOneRttData->clear();
|
|
deliverData(IOBuf::create(0));
|
|
EXPECT_TRUE(server->getConn().pendingOneRttData->empty());
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
TestReceiveClientFinishedFromChangedPeerAddress) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
recvClientHello();
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeUnfinished());
|
|
try {
|
|
recvClientFinished(true, &newPeer);
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->message,
|
|
"Migration not allowed during handshake");
|
|
EXPECT_TRUE(server->isClosed());
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 44);
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PEER_ADDRESS_CHANGE));
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
ReceiveHandshakePacketFromChangedPeerAddress) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
|
|
recvClientHello();
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeUnfinished());
|
|
try {
|
|
recvClientFinished(true, &newPeer);
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->message,
|
|
"Migration not allowed during handshake");
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
ReceiveZeroRttPacketFromChangedPeerAddress) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
fakeHandshake->allowZeroRttKeys();
|
|
|
|
recvClientHello();
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */,
|
|
std::make_pair(
|
|
LongHeader::Types::ZeroRtt, server->getConn().supportedVersions[0]),
|
|
false));
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
try {
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->message,
|
|
"Migration not allowed during handshake");
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
TestNoCipherProcessPendingOneRttDataFromChangedAddress) {
|
|
recvClientHello();
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData->size(), 1);
|
|
|
|
try {
|
|
recvClientFinished();
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->message, "Migration disabled");
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData, nullptr);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData, nullptr);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestSkipAckOnlyCryptoInitial) {
|
|
auto transportSettings = server->getTransportSettings();
|
|
server->setTransportSettings(transportSettings);
|
|
|
|
// bypass doHandshake() in fakeServerHandshake by sending something other than
|
|
// "CHLO"
|
|
recvClientHello(true, QuicVersion::MVFST, "hello :)");
|
|
|
|
// we expect nothing to be written as we're skipping the initial ack-only
|
|
// packet
|
|
EXPECT_EQ(serverWrites.size(), 0);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestNoAckOnlyCryptoInitial) {
|
|
auto transportSettings = server->getTransportSettings();
|
|
server->setTransportSettings(transportSettings);
|
|
|
|
recvClientHello();
|
|
|
|
EXPECT_GE(serverWrites.size(), 1);
|
|
|
|
AckStates ackStates;
|
|
|
|
auto clientCodec = makeClientEncryptedCodec(true);
|
|
for (auto& write : serverWrites) {
|
|
auto packetQueue = bufToQueue(write->clone());
|
|
auto result = clientCodec->parsePacket(packetQueue, ackStates);
|
|
auto& regularPacket = *result.regularPacket();
|
|
ProtectionType protectionType = regularPacket.header.getProtectionType();
|
|
bool handshakePacket = protectionType == ProtectionType::Initial ||
|
|
protectionType == ProtectionType::Handshake;
|
|
EXPECT_GE(regularPacket.frames.size(), 1);
|
|
bool hasCryptoFrame = false;
|
|
bool hasAckFrame = false;
|
|
for (auto& frame : regularPacket.frames) {
|
|
hasCryptoFrame |= frame.asReadCryptoFrame() != nullptr;
|
|
hasAckFrame |= frame.asReadAckFrame() != nullptr;
|
|
}
|
|
|
|
// The packet sent by the server shouldn't be a pure ack (i.e. contains some
|
|
// crypto data as well)
|
|
if (handshakePacket) {
|
|
EXPECT_TRUE(hasCryptoFrame);
|
|
EXPECT_TRUE(hasAckFrame);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestWriteHandshakeAndZeroRtt) {
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
// This should trigger derivation of keys.
|
|
recvClientHello();
|
|
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
server->writeChain(streamId, IOBuf::copyBuffer("hello"), true);
|
|
loopForWrites();
|
|
auto clientCodec = makeClientEncryptedCodec(true);
|
|
|
|
size_t numCryptoFrames = 0;
|
|
size_t numNonCryptoFrames = 0;
|
|
EXPECT_GT(serverWrites.size(), 1);
|
|
AckStates ackStates;
|
|
for (auto& write : serverWrites) {
|
|
auto packetQueue = bufToQueue(write->clone());
|
|
auto result = clientCodec->parsePacket(packetQueue, ackStates);
|
|
auto& regularPacket = *result.regularPacket();
|
|
ProtectionType protectionType = regularPacket.header.getProtectionType();
|
|
bool handshakePacket = protectionType == ProtectionType::Initial ||
|
|
protectionType == ProtectionType::Handshake;
|
|
EXPECT_GE(regularPacket.frames.size(), 1);
|
|
bool hasCryptoFrame = false;
|
|
bool hasNonCryptoStream = false;
|
|
for (auto& frame : regularPacket.frames) {
|
|
hasCryptoFrame |= frame.asReadCryptoFrame() != nullptr;
|
|
hasNonCryptoStream |= frame.asReadStreamFrame() != nullptr;
|
|
}
|
|
if (hasCryptoFrame) {
|
|
EXPECT_TRUE(handshakePacket);
|
|
numCryptoFrames++;
|
|
}
|
|
if (hasNonCryptoStream) {
|
|
EXPECT_FALSE(handshakePacket);
|
|
numNonCryptoFrames++;
|
|
}
|
|
}
|
|
EXPECT_GE(numCryptoFrames, 1);
|
|
EXPECT_GE(numNonCryptoFrames, 1);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestEncryptedDataBeforeCFIN) {
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
// This should trigger derivation of keys.
|
|
recvClientHello();
|
|
|
|
StreamId streamId = 4;
|
|
recvEncryptedStream(streamId, *IOBuf::copyBuffer("hello"));
|
|
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
ASSERT_TRUE(stream->readBuffer.empty());
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
TestClearInFlightBytesLimitationAfterCFIN) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
server->getNonConstConn().transportSettings.zeroRttSourceTokenMatchingPolicy =
|
|
ZeroRttSourceTokenMatchingPolicy::LIMIT_IF_NO_EXACT_MATCH;
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
auto originalUdpSize = server->getConn().udpSendPacketLen;
|
|
|
|
setupClientReadCodec();
|
|
|
|
recvClientHello();
|
|
ASSERT_TRUE(server->getNonConstConn().writableBytesLimit.has_value());
|
|
EXPECT_EQ(
|
|
*server->getNonConstConn().writableBytesLimit,
|
|
server->getConn().transportSettings.limitedCwndInMss * originalUdpSize);
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
recvClientFinished();
|
|
loopForWrites();
|
|
EXPECT_EQ(server->getConn().writableBytesLimit, folly::none);
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::TransportStateUpdate, qLogger);
|
|
EXPECT_EQ(indices.size(), 4);
|
|
std::array<::std::string, 4> updateArray = {
|
|
kDerivedZeroRttReadCipher,
|
|
kDerivedOneRttWriteCipher,
|
|
kTransportReady,
|
|
kDerivedOneRttReadCipher};
|
|
for (int i = 0; i < 4; ++i) {
|
|
auto tmp = std::move(qLogger->logs[indices[i]]);
|
|
auto event = dynamic_cast<QLogTransportStateUpdateEvent*>(tmp.get());
|
|
EXPECT_EQ(event->update, updateArray[i]);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestSendHandshakeDone) {
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
setupClientReadCodec();
|
|
recvClientHello(true, QuicVersion::QUIC_DRAFT);
|
|
recvClientFinished(true, nullptr, QuicVersion::QUIC_DRAFT);
|
|
auto& packets = server->getConn().outstandings.packets;
|
|
ASSERT_FALSE(packets.empty());
|
|
int numHandshakeDone = 0;
|
|
for (auto& p : packets) {
|
|
for (auto& f : p.packet.frames) {
|
|
auto s = f.asQuicSimpleFrame();
|
|
if (s) {
|
|
if (s->asHandshakeDoneFrame()) {
|
|
numHandshakeDone++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EXPECT_EQ(numHandshakeDone, 1);
|
|
}
|
|
|
|
/**
|
|
* Returns the number of new token frames (should either be zero or one).
|
|
*/
|
|
std::pair<int, std::vector<const NewTokenFrame*>> getNewTokenFrame(
|
|
const std::deque<OutstandingPacket>& packets) {
|
|
int numNewTokens = 0;
|
|
std::vector<const NewTokenFrame*> frames;
|
|
|
|
for (auto& p : packets) {
|
|
for (auto& f : p.packet.frames) {
|
|
auto s = f.asQuicSimpleFrame();
|
|
if (s && s->asNewTokenFrame()) {
|
|
numNewTokens++;
|
|
frames.push_back(s->asNewTokenFrame());
|
|
}
|
|
}
|
|
}
|
|
|
|
return std::make_pair(numNewTokens, std::move(frames));
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestSendHandshakeDoneNewTokenFrame) {
|
|
std::array<uint8_t, kRetryTokenSecretLength> secret;
|
|
folly::Random::secureRandom(secret.data(), secret.size());
|
|
server->getNonConstConn().transportSettings.retryTokenSecret = secret;
|
|
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
setupClientReadCodec();
|
|
recvClientHello(true, QuicVersion::QUIC_DRAFT);
|
|
|
|
/**
|
|
* Receiving just the chlo should not issue a NewTokenFrame.
|
|
*/
|
|
EXPECT_EQ(getNewTokenFrame(server->getConn().outstandings.packets).first, 0);
|
|
|
|
EXPECT_CALL(*quicStats_, onNewTokenIssued());
|
|
recvClientFinished(true, nullptr, QuicVersion::QUIC_DRAFT);
|
|
|
|
/**
|
|
* After the handshake is complete, we expect only one NewTokenFrame to be
|
|
* issued.
|
|
*/
|
|
auto serverWriteNewTokenFrame =
|
|
getNewTokenFrame(server->getConn().outstandings.packets);
|
|
EXPECT_EQ(serverWriteNewTokenFrame.first, 1);
|
|
|
|
// Verify that the client parses the same token as what was written
|
|
auto clientParsedFrame = getFrameIfPresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(true),
|
|
QuicFrame::Type::ReadNewTokenFrame);
|
|
|
|
EXPECT_TRUE(
|
|
clientParsedFrame.hasValue() && clientParsedFrame->asReadNewTokenFrame());
|
|
|
|
auto clientReadNewTokenFrame = clientParsedFrame->asReadNewTokenFrame();
|
|
|
|
auto serverToken = serverWriteNewTokenFrame.second[0]->token;
|
|
auto clientToken =
|
|
clientReadNewTokenFrame->token->moveToFbString().toStdString();
|
|
|
|
EXPECT_EQ(clientToken, serverToken);
|
|
loopForWrites();
|
|
|
|
/**
|
|
* Receiving client data post-handshake should not issue any NewTokenFrames.
|
|
*/
|
|
|
|
// Remove any packets that might have been queued.
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
server->getNonConstConn().outstandings.packetCount = {};
|
|
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
auto data = IOBuf::copyBuffer("data");
|
|
auto packet = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
deliverData(std::move(packet));
|
|
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 1);
|
|
EXPECT_EQ(getNewTokenFrame(server->getConn().outstandings.packets).first, 0);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
IncreaseLimitAfterReceivingNewPacket) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
server->getNonConstConn().transportSettings.zeroRttSourceTokenMatchingPolicy =
|
|
ZeroRttSourceTokenMatchingPolicy::LIMIT_IF_NO_EXACT_MATCH;
|
|
|
|
auto originalUdpSize = server->getConn().udpSendPacketLen;
|
|
setupClientReadCodec();
|
|
|
|
recvClientHello();
|
|
EXPECT_EQ(
|
|
*server->getNonConstConn().writableBytesLimit,
|
|
server->getConn().transportSettings.limitedCwndInMss * originalUdpSize);
|
|
|
|
recvClientHello();
|
|
|
|
// in tests the udp packet length changes
|
|
auto expectedLen =
|
|
server->getConn().transportSettings.limitedCwndInMss * originalUdpSize +
|
|
server->getConn().transportSettings.limitedCwndInMss *
|
|
server->getConn().udpSendPacketLen;
|
|
EXPECT_NE(originalUdpSize, server->getConn().udpSendPacketLen);
|
|
EXPECT_EQ(*server->getNonConstConn().writableBytesLimit, expectedLen);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::TransportStateUpdate, qLogger);
|
|
EXPECT_EQ(indices.size(), 3);
|
|
std::array<::std::string, 3> updateArray = {
|
|
kDerivedZeroRttReadCipher, kDerivedOneRttWriteCipher, kTransportReady};
|
|
for (int i = 0; i < 3; ++i) {
|
|
auto tmp = std::move(qLogger->logs[indices[i]]);
|
|
auto event = dynamic_cast<QLogTransportStateUpdateEvent*>(tmp.get());
|
|
EXPECT_EQ(event->update, updateArray[i]);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, MaxReceivePacketSizeTooLarge) {
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
fakeHandshake->maxRecvPacketSize = 4096;
|
|
setupClientReadCodec();
|
|
recvClientHello();
|
|
EXPECT_EQ(server->getConn().udpSendPacketLen, kDefaultUDPSendPacketLen);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestGarbageData) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
PacketNum nextPacket = clientNextInitialPacketNum++;
|
|
auto aead = getInitialCipher();
|
|
auto headerCipher = getInitialHeaderCipher();
|
|
auto packet = createCryptoPacket(
|
|
*clientConnectionId,
|
|
*clientConnectionId,
|
|
nextPacket,
|
|
QuicVersion::QUIC_DRAFT,
|
|
ProtectionType::Initial,
|
|
*IOBuf::copyBuffer("CHLO"),
|
|
*aead,
|
|
0 /* largestAcked */);
|
|
auto packetData =
|
|
packetToBufCleartext(packet, *aead, *headerCipher, nextPacket);
|
|
packetData->prependChain(IOBuf::copyBuffer("garbage in"));
|
|
deliverData(std::move(packetData));
|
|
EXPECT_NE(server->getConn().readCodec, nullptr);
|
|
EXPECT_NE(server->getConn().initialWriteCipher, nullptr);
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketBuffered, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketBufferedEvent*>(tmp.get());
|
|
EXPECT_EQ(event->protectionType, ProtectionType::KeyPhaseZero);
|
|
EXPECT_EQ(event->packetSize, 10);
|
|
}
|
|
|
|
Buf getHandshakePacketWithFrame(
|
|
QuicWriteFrame frame,
|
|
ConnectionId connId,
|
|
Aead& clientWriteCipher,
|
|
PacketNumberCipher& headerCipher) {
|
|
PacketNum clientPacketNum = folly::Random::rand32();
|
|
LongHeader header(
|
|
LongHeader::Types::Handshake,
|
|
connId,
|
|
connId,
|
|
clientPacketNum,
|
|
QuicVersion::MVFST);
|
|
RegularQuicPacketBuilder builder(
|
|
kDefaultUDPSendPacketLen,
|
|
std::move(header),
|
|
clientPacketNum / 2 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
builder.accountForCipherOverhead(clientWriteCipher.getCipherOverhead());
|
|
writeFrame(std::move(frame), builder);
|
|
return packetToBufCleartext(
|
|
std::move(builder).buildPacket(),
|
|
clientWriteCipher,
|
|
headerCipher,
|
|
clientPacketNum);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestNotAllowedInUnencryptedPacket) {
|
|
// This should trigger derivation of keys.
|
|
recvClientHello();
|
|
|
|
StreamId streamId = 4;
|
|
auto data = IOBuf::copyBuffer("data");
|
|
|
|
EXPECT_THROW(
|
|
deliverData(getHandshakePacketWithFrame(
|
|
MaxStreamDataFrame(streamId, 100),
|
|
*clientConnectionId,
|
|
*getInitialCipher(),
|
|
*getInitialHeaderCipher())),
|
|
std::runtime_error);
|
|
EXPECT_TRUE(server->error());
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestCloseWhileAsyncPending) {
|
|
folly::EventBase testLooper;
|
|
setupClientReadCodec();
|
|
getFakeHandshakeLayer()->initialize(&testLooper, server.get());
|
|
|
|
recvClientHello();
|
|
testLooper.loop();
|
|
|
|
// Make sure the test looper worked.
|
|
IOBufEqualTo eq;
|
|
EXPECT_TRUE(eq(getCryptoStreamData(), IOBuf::copyBuffer("SHLO")));
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeUnfinished());
|
|
recvClientFinished();
|
|
|
|
server->close(QuicError(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN),
|
|
std::string("hello")));
|
|
EXPECT_TRUE(server->isClosed());
|
|
testLooper.loop();
|
|
|
|
EXPECT_EQ(server->getConn().oneRttWriteCipher, nullptr);
|
|
|
|
StreamId streamId = 4;
|
|
auto data = IOBuf::copyBuffer("data");
|
|
|
|
EXPECT_THROW(
|
|
deliverData(getHandshakePacketWithFrame(
|
|
MaxStreamDataFrame(streamId, 100),
|
|
*clientConnectionId,
|
|
*getInitialCipher(),
|
|
*getInitialHeaderCipher())),
|
|
std::runtime_error);
|
|
}
|
|
|
|
struct FizzHandshakeParam {
|
|
FizzHandshakeParam(bool argCHLOSync, bool argCFINSync, bool argAcceptZeroRtt)
|
|
: chloSync(argCHLOSync),
|
|
cfinSync(argCFINSync),
|
|
acceptZeroRtt(argAcceptZeroRtt) {}
|
|
bool chloSync;
|
|
bool cfinSync;
|
|
bool acceptZeroRtt;
|
|
};
|
|
|
|
class QuicServerTransportPendingDataTest
|
|
: public QuicUnencryptedServerTransportTest,
|
|
public WithParamInterface<FizzHandshakeParam> {
|
|
public:
|
|
~QuicServerTransportPendingDataTest() override {
|
|
loopForWrites();
|
|
}
|
|
|
|
void initializeServerHandshake() override {
|
|
fakeHandshake = new FakeServerHandshake(
|
|
server->getNonConstConn(),
|
|
FizzServerQuicHandshakeContext::Builder().build(),
|
|
GetParam().chloSync,
|
|
GetParam().cfinSync);
|
|
if (GetParam().acceptZeroRtt) {
|
|
fakeHandshake->allowZeroRttKeys();
|
|
}
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
QuicServerTransportPendingDataTests,
|
|
QuicServerTransportPendingDataTest,
|
|
Values(
|
|
FizzHandshakeParam(false, false, false),
|
|
FizzHandshakeParam(false, false, true),
|
|
FizzHandshakeParam(false, true, false),
|
|
FizzHandshakeParam(false, true, true),
|
|
FizzHandshakeParam(true, false, false),
|
|
FizzHandshakeParam(true, false, true),
|
|
FizzHandshakeParam(true, true, false),
|
|
FizzHandshakeParam(true, true, true)));
|
|
|
|
TEST_P(
|
|
QuicServerTransportPendingDataTest,
|
|
TestNoCipherProcessPendingZeroRttData) {
|
|
server->getNonConstConn().qLogger =
|
|
std::make_shared<quic::FileQLogger>(VantagePoint::Server);
|
|
recvClientHello(false);
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
// Write packet with zero rtt keys
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */,
|
|
std::make_pair(
|
|
LongHeader::Types::ZeroRtt, server->getConn().supportedVersions[0]),
|
|
false));
|
|
deliverData(std::move(packetData), false);
|
|
if (GetParam().acceptZeroRtt) {
|
|
if (!GetParam().chloSync) {
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData->size(), 1);
|
|
loopForWrites();
|
|
}
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 1);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData, nullptr);
|
|
} else {
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData->size(), 1);
|
|
}
|
|
EXPECT_EQ(
|
|
server->getConn().qLogger->scid, server->getConn().serverConnectionId);
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportPendingDataTest,
|
|
TestNoCipherProcessPendingOneRttData) {
|
|
server->getNonConstConn().qLogger =
|
|
std::make_shared<quic::FileQLogger>(VantagePoint::Server);
|
|
recvClientHello();
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
// Write packet with zero rtt keys
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */,
|
|
folly::none,
|
|
false));
|
|
deliverData(std::move(packetData));
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData->size(), 1);
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
recvClientFinished();
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 1);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData, nullptr);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData, nullptr);
|
|
EXPECT_EQ(
|
|
server->getConn().qLogger->scid, server->getConn().serverConnectionId);
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportPendingDataTest,
|
|
TestNoCipherProcessingZeroAndOneRttData) {
|
|
server->getNonConstConn().qLogger =
|
|
std::make_shared<quic::FileQLogger>(VantagePoint::Server);
|
|
recvClientHello(false);
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
// Write packet with zero rtt keys
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */,
|
|
std::make_pair(
|
|
LongHeader::Types::ZeroRtt, server->getConn().supportedVersions[0]),
|
|
false));
|
|
deliverData(std::move(packetData), false);
|
|
if (GetParam().acceptZeroRtt) {
|
|
if (!GetParam().chloSync) {
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData->size(), 1);
|
|
loopForWrites();
|
|
}
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 1);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData, nullptr);
|
|
} else {
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData->size(), 1);
|
|
}
|
|
loopForWrites();
|
|
|
|
StreamId streamId2 = 4;
|
|
// Write packet with zero rtt keys
|
|
auto packetData2 = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
deliverData(std::move(packetData2));
|
|
EXPECT_EQ(
|
|
server->getConn().streamManager->streamCount(),
|
|
GetParam().acceptZeroRtt ? 1 : 0);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData->size(), 1);
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
recvClientFinished();
|
|
EXPECT_EQ(
|
|
server->getConn().streamManager->streamCount(),
|
|
GetParam().acceptZeroRtt ? 2 : 1);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData, nullptr);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData, nullptr);
|
|
EXPECT_EQ(
|
|
server->getConn().qLogger->scid, server->getConn().serverConnectionId);
|
|
}
|
|
|
|
/**
|
|
* Test handshake process with different parameters:
|
|
* sync CHLO processing, sync CFIN processing, accept 0-rtt
|
|
*/
|
|
class QuicServerTransportHandshakeTest
|
|
: public QuicUnencryptedServerTransportTest,
|
|
public WithParamInterface<FizzHandshakeParam> {
|
|
public:
|
|
~QuicServerTransportHandshakeTest() override {
|
|
// We need an extra pump here for some reason.
|
|
loopForWrites();
|
|
}
|
|
|
|
void initializeServerHandshake() override {
|
|
fakeHandshake = new FakeServerHandshake(
|
|
server->getNonConstConn(),
|
|
FizzServerQuicHandshakeContext::Builder().build(),
|
|
GetParam().chloSync,
|
|
GetParam().cfinSync);
|
|
if (GetParam().acceptZeroRtt) {
|
|
fakeHandshake->allowZeroRttKeys();
|
|
}
|
|
}
|
|
|
|
void expectWriteNewSessionTicket() override {
|
|
std::string appParams("APP params");
|
|
server->setEarlyDataAppParamsFunctions(
|
|
[](const folly::Optional<std::string>&, const Buf&) { return false; },
|
|
[=]() -> Buf { return folly::IOBuf::copyBuffer(appParams); });
|
|
EXPECT_CALL(*getFakeHandshakeLayer(), writeNewSessionTicket(_))
|
|
.WillOnce(Invoke([=](const AppToken& appToken) {
|
|
auto& params = appToken.transportParams.parameters;
|
|
|
|
auto initialMaxData = *getIntegerParameter(
|
|
TransportParameterId::initial_max_data, params);
|
|
EXPECT_EQ(
|
|
initialMaxData,
|
|
server->getConn()
|
|
.transportSettings.advertisedInitialConnectionWindowSize);
|
|
|
|
auto initialMaxStreamDataBidiLocal = *getIntegerParameter(
|
|
TransportParameterId::initial_max_stream_data_bidi_local, params);
|
|
auto initialMaxStreamDataBidiRemote = *getIntegerParameter(
|
|
TransportParameterId::initial_max_stream_data_bidi_remote,
|
|
params);
|
|
auto initialMaxStreamDataUni = *getIntegerParameter(
|
|
TransportParameterId::initial_max_stream_data_bidi_remote,
|
|
params);
|
|
EXPECT_EQ(
|
|
initialMaxStreamDataBidiLocal,
|
|
server->getConn()
|
|
.transportSettings
|
|
.advertisedInitialBidiLocalStreamWindowSize);
|
|
EXPECT_EQ(
|
|
initialMaxStreamDataBidiRemote,
|
|
server->getConn()
|
|
.transportSettings
|
|
.advertisedInitialBidiRemoteStreamWindowSize);
|
|
EXPECT_EQ(
|
|
initialMaxStreamDataUni,
|
|
server->getConn()
|
|
.transportSettings.advertisedInitialUniStreamWindowSize);
|
|
|
|
auto initialMaxStreamsBidi = *getIntegerParameter(
|
|
TransportParameterId::initial_max_streams_bidi, params);
|
|
auto initialMaxStreamsUni = *getIntegerParameter(
|
|
TransportParameterId::initial_max_streams_uni, params);
|
|
EXPECT_EQ(
|
|
initialMaxStreamsBidi,
|
|
server->getConn()
|
|
.transportSettings.advertisedInitialMaxStreamsBidi);
|
|
EXPECT_EQ(
|
|
initialMaxStreamsUni,
|
|
server->getConn()
|
|
.transportSettings.advertisedInitialMaxStreamsUni);
|
|
|
|
auto maxRecvPacketSize = *getIntegerParameter(
|
|
TransportParameterId::max_packet_size, params);
|
|
EXPECT_EQ(
|
|
maxRecvPacketSize,
|
|
server->getConn().transportSettings.maxRecvPacketSize);
|
|
|
|
EXPECT_THAT(
|
|
appToken.sourceAddresses, ContainerEq(expectedSourceToken_));
|
|
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(
|
|
appToken.appParams, folly::IOBuf::copyBuffer(appParams)));
|
|
}));
|
|
}
|
|
|
|
void testSetupConnection() {
|
|
// If 0-rtt is accepted, one rtt write cipher will be available after CHLO
|
|
// is processed
|
|
if (GetParam().acceptZeroRtt) {
|
|
EXPECT_CALL(connSetupCallback, onTransportReady());
|
|
}
|
|
recvClientHello();
|
|
|
|
// If 0-rtt is disabled, one rtt write cipher will be available after CFIN
|
|
// is processed
|
|
if (!GetParam().acceptZeroRtt) {
|
|
EXPECT_CALL(connSetupCallback, onTransportReady());
|
|
}
|
|
// onConnectionIdBound is always invoked after CFIN is processed
|
|
EXPECT_CALL(routingCallback, onConnectionIdBound(_));
|
|
// NST is always written after CFIN is processed
|
|
expectWriteNewSessionTicket();
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
recvClientFinished();
|
|
}
|
|
|
|
protected:
|
|
std::vector<folly::IPAddress> expectedSourceToken_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
QuicServerTransportHandshakeTests,
|
|
QuicServerTransportHandshakeTest,
|
|
Values(
|
|
FizzHandshakeParam(false, false, false),
|
|
FizzHandshakeParam(false, false, true),
|
|
FizzHandshakeParam(false, true, false),
|
|
FizzHandshakeParam(false, true, true),
|
|
FizzHandshakeParam(true, false, false),
|
|
FizzHandshakeParam(true, false, true),
|
|
FizzHandshakeParam(true, true, false),
|
|
FizzHandshakeParam(true, true, true)));
|
|
|
|
TEST_P(
|
|
QuicServerTransportHandshakeTest,
|
|
TestConnectionSetupWithoutSourceTokenInPsk) {
|
|
serverCtx->setSendNewSessionTicket(false);
|
|
expectedSourceToken_ = {clientAddr.getIPAddress()};
|
|
testSetupConnection();
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportHandshakeTest,
|
|
TestConnectionSetupWithSourceTokenInPsk) {
|
|
serverCtx->setSendNewSessionTicket(false);
|
|
auto ipAddr = folly::IPAddress("1.2.3.4");
|
|
getFakeHandshakeLayer()->setSourceTokens({ipAddr});
|
|
if (GetParam().acceptZeroRtt) {
|
|
expectedSourceToken_ = {ipAddr, clientAddr.getIPAddress()};
|
|
} else {
|
|
expectedSourceToken_ = {clientAddr.getIPAddress()};
|
|
}
|
|
testSetupConnection();
|
|
}
|
|
|
|
TEST_P(QuicServerTransportHandshakeTest, TestD6DStartCallback) {
|
|
Observer::Config config = {};
|
|
config.pmtuEvents = true;
|
|
auto mockObserver = std::make_unique<MockObserver>(config);
|
|
server->addObserver(mockObserver.get());
|
|
// Set oneRttReader so that maybeStartD6DPriobing passes its check
|
|
auto codec = std::make_unique<QuicReadCodec>(QuicNodeType::Server);
|
|
codec->setOneRttReadCipher(createNoOpAead());
|
|
server->getNonConstConn().readCodec = std::move(codec);
|
|
// And the state too
|
|
server->getNonConstConn().d6d.state = D6DMachineState::BASE;
|
|
EXPECT_CALL(*mockObserver, pmtuProbingStarted(_)).Times(1);
|
|
// CHLO should be enough to trigger probing
|
|
recvClientHello();
|
|
server->removeObserver(mockObserver.get());
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, DuplicateOneRttWriteCipher) {
|
|
setupClientReadCodec();
|
|
recvClientHello();
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
recvClientFinished();
|
|
loopForWrites();
|
|
try {
|
|
recvClientHello();
|
|
recvClientFinished();
|
|
FAIL();
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_THAT(ex.what(), HasSubstr("Crypto error"));
|
|
}
|
|
EXPECT_TRUE(server->isClosed());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestRegisterAndHandleTransportKnobParams) {
|
|
int flag = 0;
|
|
server->registerKnobParamHandler(
|
|
199, [&](QuicServerTransport* /* server_conn */, uint64_t val) {
|
|
EXPECT_EQ(val, 10);
|
|
flag = 1;
|
|
});
|
|
server->registerKnobParamHandler(
|
|
200, [&](QuicServerTransport* /* server_conn */, uint64_t /* val */) {
|
|
flag = 2;
|
|
});
|
|
server->handleKnobParams({
|
|
{199, 10},
|
|
{201, 20},
|
|
});
|
|
|
|
EXPECT_EQ(flag, 1);
|
|
|
|
// ovewrite will fail, the new handler won't be called
|
|
server->registerKnobParamHandler(
|
|
199, [&](QuicServerTransport* /* server_conn */, uint64_t val) {
|
|
EXPECT_EQ(val, 30);
|
|
flag = 3;
|
|
});
|
|
|
|
server->handleKnobParams({
|
|
{199, 10},
|
|
{201, 20},
|
|
});
|
|
EXPECT_EQ(flag, 1);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestRegisterPMTUZeroBlackholeDetection) {
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(
|
|
TransportKnobParamId::ZERO_PMTU_BLACKHOLE_DETECTION),
|
|
1}});
|
|
EXPECT_TRUE(server->getConn().d6d.noBlackholeDetection);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestCCExperimentalKnobHandler) {
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
server->getNonConstConn().congestionController =
|
|
std::move(mockCongestionController);
|
|
|
|
EXPECT_CALL(*rawCongestionController, setExperimental(true)).Times(2);
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::CC_EXPERIMENTAL), 1}});
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::CC_EXPERIMENTAL), 2}});
|
|
|
|
EXPECT_CALL(*rawCongestionController, setExperimental(false)).Times(1);
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::CC_EXPERIMENTAL), 0}});
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestPacerExperimentalKnobHandler) {
|
|
auto mockPacer = std::make_unique<NiceMock<MockPacer>>();
|
|
auto rawPacer = mockPacer.get();
|
|
server->getNonConstConn().pacer = std::move(mockPacer);
|
|
|
|
EXPECT_CALL(*rawPacer, setExperimental(true)).Times(2);
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::PACER_EXPERIMENTAL), 1}});
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::PACER_EXPERIMENTAL), 2}});
|
|
|
|
EXPECT_CALL(*rawPacer, setExperimental(false)).Times(1);
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::PACER_EXPERIMENTAL), 0}});
|
|
}
|
|
|
|
class QuicServerTransportForciblySetUDUPayloadSizeTest
|
|
: public QuicServerTransportTest {
|
|
public:
|
|
bool getCanIgnorePathMTU() override {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
TEST_F(
|
|
QuicServerTransportForciblySetUDUPayloadSizeTest,
|
|
TestHandleTransportKnobParamForciblySetUDPPayloadSize) {
|
|
EXPECT_LT(server->getConn().udpSendPacketLen, 1452);
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(
|
|
TransportKnobParamId::FORCIBLY_SET_UDP_PAYLOAD_SIZE),
|
|
1}});
|
|
EXPECT_EQ(server->getConn().udpSendPacketLen, 1452);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, WriteDSR) {
|
|
EXPECT_EQ(server->getConn().dsrPacketCount, 0);
|
|
// Make sure we are post-handshake
|
|
ASSERT_NE(nullptr, server->getConn().oneRttWriteCipher);
|
|
// Rinse anything pending
|
|
server->writeData();
|
|
loopForWrites();
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
getFakeHandshakeLayer()->setCipherSuite(
|
|
fizz::CipherSuite::TLS_AES_128_GCM_SHA256);
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
server->writeChain(
|
|
streamId, folly::IOBuf::copyBuffer("Allegro Maestoso"), false);
|
|
auto mockDSRSender = std::make_unique<MockDSRPacketizationRequestSender>();
|
|
auto rawDSRSender = mockDSRSender.get();
|
|
server->setDSRPacketizationRequestSender(streamId, std::move(mockDSRSender));
|
|
BufferMeta bufMeta(2000);
|
|
server->writeBufMeta(streamId, bufMeta, true);
|
|
server->writeData();
|
|
EXPECT_FALSE(server->getConn().outstandings.packets.empty());
|
|
EXPECT_TRUE(server->getConn().outstandings.packets.back().isDSRPacket);
|
|
EXPECT_CALL(*rawDSRSender, release()).Times(1);
|
|
server->resetStream(streamId, GenericApplicationErrorCode::NO_ERROR);
|
|
EXPECT_EQ(server->getConn().dsrPacketCount, 1);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace quic
|