mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-09 20:42:44 +03:00
Summary: Implement sending stream limit updates in a windowed fashion, so that as a peer exhausts its streams we will grant it additional credit. This is implemented by having the stream manager check if an update is needed on removing streams, and the api layer potentially sending an update after it initiates the check for closed streams. This also makes some driveby changes to use `std::lower_bound` instead of `std::find` for the sorted collections in the stream manager. Reviewed By: yangchi Differential Revision: D16808229 fbshipit-source-id: f6e3460d43e4d165e362164be00c0cec27cf1e79
398 lines
16 KiB
C++
398 lines
16 KiB
C++
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
*/
|
|
|
|
#include <folly/portability/GTest.h>
|
|
|
|
#include <quic/codec/QuicPacketBuilder.h>
|
|
#include <quic/codec/QuicPacketRebuilder.h>
|
|
#include <quic/common/test/TestUtils.h>
|
|
#include <quic/server/state/ServerStateMachine.h>
|
|
#include <quic/state/QuicStateFunctions.h>
|
|
#include <quic/state/QuicStreamFunctions.h>
|
|
#include <quic/state/StateData.h>
|
|
#include <quic/state/stream/StreamStateFunctions.h>
|
|
|
|
using namespace testing;
|
|
|
|
namespace quic {
|
|
namespace test {
|
|
|
|
OutstandingPacket makeDummyOutstandingPacket(
|
|
const RegularQuicWritePacket& writePacket,
|
|
uint64_t totalBytesSentOnConnection) {
|
|
OutstandingPacket packet(
|
|
writePacket,
|
|
Clock::now(),
|
|
1000,
|
|
false,
|
|
false,
|
|
totalBytesSentOnConnection);
|
|
return packet;
|
|
}
|
|
|
|
class QuicPacketRebuilderTest : public Test {};
|
|
|
|
TEST_F(QuicPacketRebuilderTest, RebuildEmpty) {
|
|
RegularQuicPacketBuilder regularBuilder(
|
|
kDefaultUDPSendPacketLen,
|
|
PacketHeader(
|
|
ShortHeader(ProtectionType::KeyPhaseZero, getTestConnectionId(), 0)),
|
|
0 /* largestAcked */);
|
|
QuicConnectionStateBase conn(QuicNodeType::Client);
|
|
PacketRebuilder rebuilder(regularBuilder, conn);
|
|
auto packet = std::move(regularBuilder).buildPacket();
|
|
EXPECT_TRUE(packet.packet.frames.empty());
|
|
EXPECT_FALSE(packet.header->empty());
|
|
EXPECT_TRUE(!packet.body);
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, RebuildPacket) {
|
|
ShortHeader shortHeader(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
|
|
// Get a bunch frames
|
|
ConnectionCloseFrame connCloseFrame(
|
|
TransportErrorCode::FRAME_ENCODING_ERROR,
|
|
"The sun is in the sky.",
|
|
FrameType::ACK);
|
|
MaxStreamsFrame maxStreamsFrame(4321, true);
|
|
PingFrame pingFrame;
|
|
IntervalSet<PacketNum> ackBlocks;
|
|
ackBlocks.insert(10, 100);
|
|
ackBlocks.insert(200, 1000);
|
|
AckFrameMetaData ackMeta(ackBlocks, 0us, kDefaultAckDelayExponent);
|
|
QuicServerConnectionState conn;
|
|
conn.streamManager->setMaxLocalBidirectionalStreams(10);
|
|
auto stream = conn.streamManager->createNextBidirectionalStream().value();
|
|
auto streamId = stream->id;
|
|
auto buf =
|
|
folly::IOBuf::copyBuffer("You can't deny you are looking for the sunset");
|
|
MaxDataFrame maxDataFrame(1000);
|
|
MaxStreamDataFrame maxStreamDataFrame(streamId, 2000);
|
|
uint64_t cryptoOffset = 0;
|
|
auto cryptoBuf = folly::IOBuf::copyBuffer("NewSessionTicket");
|
|
|
|
// Write them with a regular builder
|
|
writeFrame(connCloseFrame, regularBuilder1);
|
|
writeFrame(maxStreamsFrame, regularBuilder1);
|
|
writeFrame(pingFrame, regularBuilder1);
|
|
writeAckFrame(ackMeta, regularBuilder1);
|
|
writeStreamFrameHeader(
|
|
regularBuilder1,
|
|
streamId,
|
|
0,
|
|
buf->computeChainDataLength(),
|
|
buf->computeChainDataLength(),
|
|
true);
|
|
writeStreamFrameData(
|
|
regularBuilder1, buf->clone(), buf->computeChainDataLength());
|
|
writeFrame(maxDataFrame, regularBuilder1);
|
|
writeFrame(maxStreamDataFrame, regularBuilder1);
|
|
writeCryptoFrame(cryptoOffset, cryptoBuf->clone(), regularBuilder1);
|
|
auto packet1 = std::move(regularBuilder1).buildPacket();
|
|
ASSERT_EQ(8, packet1.packet.frames.size());
|
|
stream->retransmissionBuffer.emplace(
|
|
stream->retransmissionBuffer.begin(), buf->clone(), 0, true);
|
|
conn.cryptoState->oneRttStream.retransmissionBuffer.emplace(
|
|
conn.cryptoState->oneRttStream.retransmissionBuffer.begin(),
|
|
cryptoBuf->clone(),
|
|
0,
|
|
true);
|
|
|
|
// rebuild a packet from the built out packet
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
auto outstanding = makeDummyOutstandingPacket(packet1.packet, 1000);
|
|
EXPECT_TRUE(rebuilder.rebuildFromPacket(outstanding).hasValue());
|
|
auto packet2 = std::move(regularBuilder2).buildPacket();
|
|
// rebuilder writes frames to regularBuilder2
|
|
EXPECT_EQ(packet1.packet.frames.size(), packet2.packet.frames.size());
|
|
auto expectedConnFlowControlValue = std::max(
|
|
conn.flowControlState.sumCurReadOffset + conn.flowControlState.windowSize,
|
|
conn.flowControlState.advertisedMaxOffset);
|
|
auto expectedStreamFlowControlValue = std::max(
|
|
stream->currentReadOffset + stream->flowControlState.windowSize,
|
|
stream->flowControlState.advertisedMaxOffset);
|
|
for (const auto& frame : packet2.packet.frames) {
|
|
folly::variant_match(
|
|
frame,
|
|
[](const ConnectionCloseFrame& closeFrame) {
|
|
EXPECT_EQ(
|
|
TransportErrorCode::FRAME_ENCODING_ERROR, closeFrame.errorCode);
|
|
EXPECT_EQ("The sun is in the sky.", closeFrame.reasonPhrase);
|
|
EXPECT_EQ(FrameType::ACK, closeFrame.closingFrameType);
|
|
},
|
|
[](const QuicSimpleFrame& simpleFrame) {
|
|
auto maxStreamFrame = boost::get<MaxStreamsFrame>(simpleFrame);
|
|
EXPECT_EQ(4321, maxStreamFrame.maxStreams);
|
|
},
|
|
[](const PingFrame& ping) { EXPECT_EQ(PingFrame(), ping); },
|
|
[](const WriteAckFrame& ack) {
|
|
EXPECT_EQ(Interval<PacketNum>(10, 100), ack.ackBlocks.front());
|
|
EXPECT_EQ(Interval<PacketNum>(200, 1000), ack.ackBlocks.back());
|
|
},
|
|
[&buf, &streamId](const WriteStreamFrame& streamFrame) {
|
|
EXPECT_EQ(streamId, streamFrame.streamId);
|
|
EXPECT_EQ(0, streamFrame.offset);
|
|
EXPECT_EQ(buf->computeChainDataLength(), streamFrame.len);
|
|
EXPECT_EQ(true, streamFrame.fin);
|
|
},
|
|
[&cryptoOffset, &cryptoBuf](const WriteCryptoFrame& frame) {
|
|
EXPECT_EQ(frame.offset, cryptoOffset);
|
|
EXPECT_EQ(frame.len, cryptoBuf->computeChainDataLength());
|
|
},
|
|
[&expectedConnFlowControlValue](const MaxDataFrame& maxData) {
|
|
EXPECT_EQ(expectedConnFlowControlValue, maxData.maximumData);
|
|
},
|
|
[&streamId, &expectedStreamFlowControlValue](
|
|
const MaxStreamDataFrame& maxStreamData) {
|
|
EXPECT_EQ(streamId, maxStreamData.streamId);
|
|
EXPECT_EQ(expectedStreamFlowControlValue, maxStreamData.maximumData);
|
|
},
|
|
[](const auto&) {
|
|
EXPECT_TRUE(false); /* should never happen*/
|
|
});
|
|
}
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*packet1.header, *packet2.header));
|
|
// TODO: I don't have a good way to verify body without decode them
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, RebuildAfterResetStream) {
|
|
ShortHeader shortHeader(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
QuicServerConnectionState conn;
|
|
conn.streamManager->setMaxLocalBidirectionalStreams(10);
|
|
auto stream = conn.streamManager->createNextBidirectionalStream().value();
|
|
auto streamId = stream->id;
|
|
auto buf = folly::IOBuf::copyBuffer("A million miles away.");
|
|
writeStreamFrameHeader(
|
|
regularBuilder1,
|
|
streamId,
|
|
0,
|
|
buf->computeChainDataLength(),
|
|
buf->computeChainDataLength(),
|
|
true);
|
|
writeStreamFrameData(
|
|
regularBuilder1, buf->clone(), buf->computeChainDataLength());
|
|
auto packet1 = std::move(regularBuilder1).buildPacket();
|
|
ASSERT_EQ(1, packet1.packet.frames.size());
|
|
|
|
// Then we reset the stream
|
|
invokeStreamSendStateMachine(
|
|
conn,
|
|
*stream,
|
|
StreamEvents::SendReset(GenericApplicationErrorCode::UNKNOWN));
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
auto outstanding = makeDummyOutstandingPacket(packet1.packet, 1000);
|
|
EXPECT_FALSE(rebuilder.rebuildFromPacket(outstanding).hasValue());
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, FinOnlyStreamRebuild) {
|
|
ShortHeader shortHeader(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
QuicServerConnectionState conn;
|
|
conn.streamManager->setMaxLocalBidirectionalStreams(10);
|
|
auto stream = conn.streamManager->createNextBidirectionalStream().value();
|
|
auto streamId = stream->id;
|
|
|
|
// Write them with a regular builder
|
|
writeStreamFrameHeader(regularBuilder1, streamId, 0, 0, 0, true);
|
|
auto packet1 = std::move(regularBuilder1).buildPacket();
|
|
stream->retransmissionBuffer.emplace(
|
|
stream->retransmissionBuffer.begin(), nullptr, 0, true);
|
|
|
|
// rebuild a packet from the built out packet
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
auto outstanding = makeDummyOutstandingPacket(packet1.packet, 2000);
|
|
EXPECT_TRUE(rebuilder.rebuildFromPacket(outstanding).hasValue());
|
|
auto packet2 = std::move(regularBuilder2).buildPacket();
|
|
EXPECT_EQ(packet1.packet.frames.size(), packet2.packet.frames.size());
|
|
EXPECT_TRUE(
|
|
0 ==
|
|
memcmp(
|
|
packet1.packet.frames.data(),
|
|
packet2.packet.frames.data(),
|
|
packet1.packet.frames.size()));
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*packet1.header, *packet2.header));
|
|
// Once we start to use the correct ack delay value in AckFrames, this needs
|
|
// to be changed:
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*packet1.body, *packet2.body));
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, RebuildDataStreamAndEmptyCryptoStream) {
|
|
ShortHeader shortHeader(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
|
|
// Get a bunch frames
|
|
QuicServerConnectionState conn;
|
|
conn.streamManager->setMaxLocalBidirectionalStreams(10);
|
|
auto stream = conn.streamManager->createNextBidirectionalStream().value();
|
|
StreamId streamId = stream->id;
|
|
auto buf =
|
|
folly::IOBuf::copyBuffer("You can't deny you are looking for the sunset");
|
|
uint64_t cryptoOffset = 0;
|
|
auto cryptoBuf = folly::IOBuf::copyBuffer("NewSessionTicket");
|
|
|
|
// Write them with a regular builder
|
|
writeStreamFrameHeader(
|
|
regularBuilder1,
|
|
streamId,
|
|
0,
|
|
buf->computeChainDataLength(),
|
|
buf->computeChainDataLength(),
|
|
true);
|
|
writeStreamFrameData(
|
|
regularBuilder1, buf->clone(), buf->computeChainDataLength());
|
|
writeCryptoFrame(cryptoOffset, cryptoBuf->clone(), regularBuilder1);
|
|
auto packet1 = std::move(regularBuilder1).buildPacket();
|
|
ASSERT_EQ(2, packet1.packet.frames.size());
|
|
stream->retransmissionBuffer.emplace(
|
|
stream->retransmissionBuffer.begin(), buf->clone(), 0, true);
|
|
// Do not add the buf to crypto stream's retransmission buffer,
|
|
// imagine it was cleared
|
|
|
|
// rebuild a packet from the built out packet
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
auto outstanding = makeDummyOutstandingPacket(packet1.packet, 1000);
|
|
EXPECT_TRUE(rebuilder.rebuildFromPacket(outstanding).hasValue());
|
|
auto packet2 = std::move(regularBuilder2).buildPacket();
|
|
// rebuilder writes frames to regularBuilder2
|
|
EXPECT_EQ(packet1.packet.frames.size(), packet2.packet.frames.size() + 1);
|
|
for (const auto& frame : packet2.packet.frames) {
|
|
folly::variant_match(
|
|
frame,
|
|
[&buf, &streamId](const WriteStreamFrame& streamFrame) {
|
|
EXPECT_EQ(streamId, streamFrame.streamId);
|
|
EXPECT_EQ(0, streamFrame.offset);
|
|
EXPECT_EQ(buf->computeChainDataLength(), streamFrame.len);
|
|
EXPECT_EQ(true, streamFrame.fin);
|
|
},
|
|
[](const auto&) {
|
|
EXPECT_TRUE(false); /* should never happen*/
|
|
});
|
|
}
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*packet1.header, *packet2.header));
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, CannotRebuildEmptyCryptoStream) {
|
|
ShortHeader shortHeader(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
|
|
// Get a bunch frames
|
|
QuicServerConnectionState conn;
|
|
uint64_t cryptoOffset = 0;
|
|
auto cryptoBuf = folly::IOBuf::copyBuffer("NewSessionTicket");
|
|
|
|
// Write them with a regular builder
|
|
writeCryptoFrame(cryptoOffset, cryptoBuf->clone(), regularBuilder1);
|
|
auto packet1 = std::move(regularBuilder1).buildPacket();
|
|
ASSERT_EQ(1, packet1.packet.frames.size());
|
|
// Do not add the buf to crypto stream's retransmission buffer,
|
|
// imagine it was cleared
|
|
|
|
// rebuild a packet from the built out packet
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
auto outstanding = makeDummyOutstandingPacket(packet1.packet, 1000);
|
|
EXPECT_FALSE(rebuilder.rebuildFromPacket(outstanding).hasValue());
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, CannotRebuild) {
|
|
ShortHeader shortHeader(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
|
|
// Get a bunch frames
|
|
ConnectionCloseFrame connCloseFrame(
|
|
TransportErrorCode::FRAME_ENCODING_ERROR,
|
|
"The sun is in the sky.",
|
|
FrameType::ACK);
|
|
StreamsBlockedFrame maxStreamIdFrame(0x1024, true);
|
|
PingFrame pingFrame;
|
|
IntervalSet<PacketNum> ackBlocks;
|
|
ackBlocks.insert(10, 100);
|
|
ackBlocks.insert(200, 1000);
|
|
AckFrameMetaData ackMeta(ackBlocks, 0us, kDefaultAckDelayExponent);
|
|
QuicServerConnectionState conn;
|
|
conn.streamManager->setMaxLocalBidirectionalStreams(10);
|
|
auto stream = conn.streamManager->createNextBidirectionalStream().value();
|
|
auto streamId = stream->id;
|
|
auto buf =
|
|
folly::IOBuf::copyBuffer("You can't deny you are looking for the sunset");
|
|
|
|
// Write them with a regular builder
|
|
writeFrame(connCloseFrame, regularBuilder1);
|
|
writeFrame(maxStreamIdFrame, regularBuilder1);
|
|
writeFrame(pingFrame, regularBuilder1);
|
|
writeAckFrame(ackMeta, regularBuilder1);
|
|
writeStreamFrameHeader(
|
|
regularBuilder1,
|
|
streamId,
|
|
0,
|
|
buf->computeChainDataLength(),
|
|
buf->computeChainDataLength(),
|
|
true);
|
|
writeStreamFrameData(
|
|
regularBuilder1, buf->clone(), buf->computeChainDataLength());
|
|
auto packet1 = std::move(regularBuilder1).buildPacket();
|
|
ASSERT_EQ(5, packet1.packet.frames.size());
|
|
stream->retransmissionBuffer.emplace(
|
|
stream->retransmissionBuffer.begin(), buf->clone(), 0, true);
|
|
|
|
// new builder has a much smaller writable bytes limit
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
(packet1.header->computeChainDataLength() +
|
|
packet1.body->computeChainDataLength()) /
|
|
2,
|
|
shortHeader,
|
|
0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
auto outstanding = makeDummyOutstandingPacket(packet1.packet, 1000);
|
|
EXPECT_FALSE(rebuilder.rebuildFromPacket(outstanding).hasValue());
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, CloneCounter) {
|
|
ShortHeader shortHeader(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
PingFrame pingFrame;
|
|
writeFrame(pingFrame, regularBuilder);
|
|
auto packet = std::move(regularBuilder).buildPacket();
|
|
auto outstandingPacket = makeDummyOutstandingPacket(packet.packet, 1000);
|
|
QuicServerConnectionState conn;
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, shortHeader, 0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
rebuilder.rebuildFromPacket(outstandingPacket);
|
|
EXPECT_TRUE(outstandingPacket.associatedEvent.hasValue());
|
|
EXPECT_EQ(1, conn.outstandingClonedPacketsCount);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace quic
|