mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-09 20:42:44 +03:00
Summary: The retransmission buffer tracks stream frame data we have sent that is currently unacked. We keep this as a sorted `deque`. This isn't so bad for performance, but we can do better if we break ourselves of the requirement that it be sorted (removing a binary search on ACK). To do this we make the buffer a map of offset -> `StreamBuffer`. There were two places that were dependent on the sorted nature of the list. 1. For partial reliablity we call `shrinkBuffers` to remove all unacked buffers less than an offset. For this we now have to do it with a full traversal of the retransmission buffer instead of only having to do an O(offset) search. In the future we could make this better by only lazily deleting from the retransmission buffer on ACK or packet loss. 2. We used the start of the retransmission buffer to determine if a delivery callback could be fired for a given offset. We need some new state to track this. Instead of tracking unacked buffers, we now track acked ranges using the existing `IntervalSet`. This set should be small for the typical case, as we think most ACKs will come in order and just cause existing ranges to merge. Reviewed By: yangchi Differential Revision: D18609467 fbshipit-source-id: 13cd2164352f1183362be9f675c1bdc686426698
438 lines
18 KiB
C++
438 lines
18 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/StreamSendHandlers.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,
|
|
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 shortHeader1(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader1), 0 /* largestAcked */);
|
|
|
|
// Get a bunch frames
|
|
ConnectionCloseFrame connCloseFrame(
|
|
TransportErrorCode::FRAME_ENCODING_ERROR,
|
|
"The sun is in the sky.",
|
|
FrameType::ACK);
|
|
MaxStreamsFrame maxStreamsFrame(4321, true);
|
|
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");
|
|
|
|
PingFrame pingFrame{};
|
|
// Write them with a regular builder
|
|
writeFrame(connCloseFrame, regularBuilder1);
|
|
writeFrame(QuicSimpleFrame(maxStreamsFrame), regularBuilder1);
|
|
writeFrame(QuicSimpleFrame(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(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(buf->clone(), 0, true));
|
|
conn.cryptoState->oneRttStream.retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(cryptoBuf->clone(), 0, true));
|
|
|
|
// rebuild a packet from the built out packet
|
|
ShortHeader shortHeader2(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader2), 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) {
|
|
switch (frame.type()) {
|
|
case QuicWriteFrame::Type::ConnectionCloseFrame_E: {
|
|
const ConnectionCloseFrame& closeFrame =
|
|
*frame.asConnectionCloseFrame();
|
|
EXPECT_EQ(
|
|
TransportErrorCode::FRAME_ENCODING_ERROR, closeFrame.errorCode);
|
|
EXPECT_EQ("The sun is in the sky.", closeFrame.reasonPhrase);
|
|
EXPECT_EQ(FrameType::ACK, closeFrame.closingFrameType);
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::QuicSimpleFrame_E: {
|
|
const QuicSimpleFrame& simpleFrame = *frame.asQuicSimpleFrame();
|
|
switch (simpleFrame.type()) {
|
|
case QuicSimpleFrame::Type::MaxStreamsFrame_E: {
|
|
const MaxStreamsFrame* maxStreamFrame =
|
|
simpleFrame.asMaxStreamsFrame();
|
|
EXPECT_NE(maxStreamFrame, nullptr);
|
|
EXPECT_EQ(4321, maxStreamFrame->maxStreams);
|
|
break;
|
|
}
|
|
case QuicSimpleFrame::Type::PingFrame_E: {
|
|
const PingFrame* simplePingFrame = simpleFrame.asPingFrame();
|
|
EXPECT_NE(simplePingFrame, nullptr);
|
|
break;
|
|
}
|
|
default:
|
|
EXPECT_TRUE(false); /* fail if this happens */
|
|
}
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::WriteAckFrame_E: {
|
|
const WriteAckFrame& ack = *frame.asWriteAckFrame();
|
|
EXPECT_EQ(Interval<PacketNum>(10, 100), ack.ackBlocks.front());
|
|
EXPECT_EQ(Interval<PacketNum>(200, 1000), ack.ackBlocks.back());
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::WriteStreamFrame_E: {
|
|
const WriteStreamFrame& streamFrame = *frame.asWriteStreamFrame();
|
|
EXPECT_EQ(streamId, streamFrame.streamId);
|
|
EXPECT_EQ(0, streamFrame.offset);
|
|
EXPECT_EQ(buf->computeChainDataLength(), streamFrame.len);
|
|
EXPECT_EQ(true, streamFrame.fin);
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::WriteCryptoFrame_E: {
|
|
const WriteCryptoFrame& cryptoFrame = *frame.asWriteCryptoFrame();
|
|
EXPECT_EQ(cryptoFrame.offset, cryptoOffset);
|
|
EXPECT_EQ(cryptoFrame.len, cryptoBuf->computeChainDataLength());
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::MaxDataFrame_E: {
|
|
const MaxDataFrame& maxData = *frame.asMaxDataFrame();
|
|
EXPECT_EQ(expectedConnFlowControlValue, maxData.maximumData);
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::MaxStreamDataFrame_E: {
|
|
const MaxStreamDataFrame& maxStreamData = *frame.asMaxStreamDataFrame();
|
|
EXPECT_EQ(streamId, maxStreamData.streamId);
|
|
EXPECT_EQ(expectedStreamFlowControlValue, maxStreamData.maximumData);
|
|
break;
|
|
}
|
|
default:
|
|
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 shortHeader1(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader1), 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
|
|
sendRstSMHandler(*stream, GenericApplicationErrorCode::UNKNOWN);
|
|
ShortHeader shortHeader2(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader2), 0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
auto outstanding = makeDummyOutstandingPacket(packet1.packet, 1000);
|
|
EXPECT_FALSE(rebuilder.rebuildFromPacket(outstanding).hasValue());
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, FinOnlyStreamRebuild) {
|
|
ShortHeader shortHeader1(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader1), 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(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(nullptr, 0, true));
|
|
|
|
// rebuild a packet from the built out packet
|
|
ShortHeader shortHeader2(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader2), 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 shortHeader1(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader1), 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(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(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
|
|
ShortHeader shortHeader2(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader2), 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) {
|
|
const WriteStreamFrame* streamFrame = frame.asWriteStreamFrame();
|
|
if (!streamFrame) {
|
|
EXPECT_TRUE(false); /* should never happen*/
|
|
}
|
|
EXPECT_EQ(streamId, streamFrame->streamId);
|
|
EXPECT_EQ(0, streamFrame->offset);
|
|
EXPECT_EQ(buf->computeChainDataLength(), streamFrame->len);
|
|
EXPECT_EQ(true, streamFrame->fin);
|
|
}
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*packet1.header, *packet2.header));
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, CannotRebuildEmptyCryptoStream) {
|
|
ShortHeader shortHeader1(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader1), 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
|
|
ShortHeader shortHeader2(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader2), 0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
auto outstanding = makeDummyOutstandingPacket(packet1.packet, 1000);
|
|
EXPECT_FALSE(rebuilder.rebuildFromPacket(outstanding).hasValue());
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, CannotRebuild) {
|
|
ShortHeader shortHeader1(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder1(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader1), 0 /* largestAcked */);
|
|
|
|
// Get a bunch frames
|
|
ConnectionCloseFrame connCloseFrame(
|
|
TransportErrorCode::FRAME_ENCODING_ERROR,
|
|
"The sun is in the sky.",
|
|
FrameType::ACK);
|
|
StreamsBlockedFrame maxStreamIdFrame(0x1024, true);
|
|
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");
|
|
PingFrame pingFrame;
|
|
// Write them with a regular builder
|
|
writeFrame(connCloseFrame, regularBuilder1);
|
|
writeFrame(maxStreamIdFrame, regularBuilder1);
|
|
writeFrame(QuicSimpleFrame(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(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(buf->clone(), 0, true));
|
|
|
|
// new builder has a much smaller writable bytes limit
|
|
ShortHeader shortHeader2(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
(packet1.header->computeChainDataLength() +
|
|
packet1.body->computeChainDataLength()) /
|
|
2,
|
|
std::move(shortHeader2),
|
|
0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
auto outstanding = makeDummyOutstandingPacket(packet1.packet, 1000);
|
|
EXPECT_FALSE(rebuilder.rebuildFromPacket(outstanding).hasValue());
|
|
}
|
|
|
|
TEST_F(QuicPacketRebuilderTest, CloneCounter) {
|
|
ShortHeader shortHeader1(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader1), 0 /* largestAcked */);
|
|
PingFrame pingFrame;
|
|
writeFrame(QuicSimpleFrame(pingFrame), regularBuilder);
|
|
auto packet = std::move(regularBuilder).buildPacket();
|
|
auto outstandingPacket = makeDummyOutstandingPacket(packet.packet, 1000);
|
|
QuicServerConnectionState conn;
|
|
ShortHeader shortHeader2(
|
|
ProtectionType::KeyPhaseZero, getTestConnectionId(), 0);
|
|
RegularQuicPacketBuilder regularBuilder2(
|
|
kDefaultUDPSendPacketLen, std::move(shortHeader2), 0 /* largestAcked */);
|
|
PacketRebuilder rebuilder(regularBuilder2, conn);
|
|
rebuilder.rebuildFromPacket(outstandingPacket);
|
|
EXPECT_TRUE(outstandingPacket.associatedEvent.hasValue());
|
|
EXPECT_EQ(1, conn.outstandingClonedPacketsCount);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace quic
|