1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-11-24 04:01:07 +03:00
Files
mvfst/quic/common/test/TestUtils.cpp
Joseph Beshay b45c82b884 ACK_EXTENDED frame support
Summary:
This introduces a new frame type for acks (ACK_EXTENDED) that can carry optional fields depending on the features supported by the peer. The currently supported features set will include ECN count fields, and Receive Timstamp fields. This enables a quic connection to report both ECN counts and receive timestamps, which is not possible otherwise because they use different frame types.

Support for the extended ack as well as the set of features that can be included in it is negotiated through a new transport parameter (extended_ack_supported = 0xff0a004). Its value indicates which features are supported by the local transport. The value is an integer which is evaluated against the following bitmasks:
```
  ECN_COUNTS = 0x01,
  RECEIVE_TIMESTAMPS = 0x02,
```

This diff introduces the transport parameter and negotiates the supported features between the peers of the connection. The parameter is cached in the psk cache so the client can remember the server config. It is also encoded inside the 0-rtt ticket so the server can reject it if its local config has changed.

The following diffs add reading and writing the frame itself.

The ACK_EXTENDED frame itself will have the following format
```
ACK_EXTENDED Frame {
  Type (i) = 0xB1
  // Fields of the existing ACK (type=0x02) frame:
  Largest Acknowledged (i),
  ACK Delay (i),
  ACK Range Count (i),
  First ACK Range (i),
  ACK Range (..) ...,
  Extended Ack Features (i),
  // Optional ECN counts (if bit 0 is set in Features)
  [ECN Counts (..)],
  // Optional Receive Timestamps (if bit 1 is set in Features)
  [Receive Timestamps (..)]
}

// Fields from the existing ACK_ECN frame
ECN Counts {
  ECT0 Count (i),
  ECT1 Count (i),
  ECN-CE Count (i),
}

// Fields from the existing ACK_RECEIVE_TIMESTAMPS frame
Receive Timestamps {
  Timestamp Range Count (i),
  Timestamp Ranges (..) ...,
}

Timestamp Range {
  Gap (i),
  Timestamp Delta Count (i),
  Timestamp Delta (i) ...,
}
```

Reviewed By: sharmafb

Differential Revision: D68931151

fbshipit-source-id: 44c8c83d2f434abca97c4e85f0fa7502736cddc1
2025-02-24 12:32:50 -08:00

855 lines
27 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/common/test/TestUtils.h>
#include <fizz/backend/openssl/certificate/OpenSSLSelfCertImpl.h>
#include <fizz/crypto/test/TestUtil.h>
#include <fizz/protocol/clock/test/Mocks.h>
#include <fizz/protocol/test/Mocks.h>
#include <quic/api/QuicTransportFunctions.h>
#include <quic/codec/DefaultConnectionIdAlgo.h>
#include <quic/codec/QuicConnectionId.h>
#include <quic/fizz/handshake/QuicFizzFactory.h>
#include <quic/fizz/server/handshake/AppToken.h>
#include <quic/handshake/test/Mocks.h>
#include <quic/server/handshake/StatelessResetGenerator.h>
#include <quic/state/AckEvent.h>
#include <quic/state/LossState.h>
#include <quic/state/OutstandingPacket.h>
#include <quic/state/stream/StreamSendHandlers.h>
using namespace testing;
namespace quic::test {
std::function<MockClock::time_point()> MockClock::mockNow;
const RegularQuicWritePacket& writeQuicPacket(
QuicServerConnectionState& conn,
ConnectionId srcConnId,
ConnectionId dstConnId,
quic::test::MockAsyncUDPSocket& sock,
QuicStreamState& stream,
const folly::IOBuf& data,
bool eof) {
auto version = conn.version.value_or(*conn.originalVersion);
auto aead = createNoOpAead();
auto headerCipher = createNoOpHeaderCipher();
writeDataToQuicStream(stream, data.clone(), eof);
writeQuicDataToSocket(
sock,
conn,
srcConnId,
dstConnId,
*aead,
*headerCipher,
version,
conn.transportSettings.writeConnectionDataPacketsLimit);
CHECK(
conn.outstandings.packets.rend() !=
getLastOutstandingPacket(conn, PacketNumberSpace::AppData));
return getLastOutstandingPacket(conn, PacketNumberSpace::AppData)->packet;
}
PacketNum rstStreamAndSendPacket(
QuicServerConnectionState& conn,
QuicAsyncUDPSocket& sock,
QuicStreamState& stream,
ApplicationErrorCode errorCode) {
auto aead = createNoOpAead();
auto headerCipher = createNoOpHeaderCipher();
auto version = conn.version.value_or(*conn.originalVersion);
sendRstSMHandler(stream, errorCode);
writeQuicDataToSocket(
sock,
conn,
*conn.clientConnectionId,
*conn.serverConnectionId,
*aead,
*headerCipher,
version,
conn.transportSettings.writeConnectionDataPacketsLimit);
for (const auto& packet : conn.outstandings.packets) {
for (const auto& frame : packet.packet.frames) {
auto rstFrame = frame.asRstStreamFrame();
if (!rstFrame) {
continue;
}
if (rstFrame->streamId == stream.id) {
return packet.packet.header.getPacketSequenceNum();
}
}
}
CHECK(false) << "no packet with reset stream";
// some compilers are weird.
return 0;
}
void writeStreamFrameData(
PacketBuilderInterface& builder,
Buf writeBuffer,
uint64_t dataLen) {
ChainedByteRangeHead dataHead(writeBuffer);
writeStreamFrameData(builder, dataHead, dataLen);
}
RegularQuicPacketBuilder::Packet createAckPacket(
QuicConnectionStateBase& dstConn,
PacketNum pn,
AckBlocks& acks,
PacketNumberSpace pnSpace,
const Aead* aead,
std::chrono::microseconds ackDelay) {
auto builder = AckPacketBuilder()
.setDstConn(&dstConn)
.setPacketNumberSpace(pnSpace)
.setAckPacketNum(pn)
.setAckBlocks(acks)
.setAckDelay(ackDelay);
if (aead) {
builder.setAead(aead);
}
return std::move(builder).build();
}
static std::shared_ptr<fizz::SelfCert> readCert() {
auto certificate = fizz::test::getCert(fizz::test::kP256Certificate);
auto privKey = fizz::test::getPrivateKey(fizz::test::kP256Key);
std::vector<folly::ssl::X509UniquePtr> certs;
certs.emplace_back(std::move(certificate));
return std::make_shared<
fizz::openssl::OpenSSLSelfCertImpl<fizz::openssl::KeyType::P256>>(
std::move(privKey), std::move(certs));
}
std::shared_ptr<fizz::client::FizzClientContext> createClientCtx() {
auto clientCtx = std::make_shared<fizz::client::FizzClientContext>();
clientCtx->setClock(std::make_shared<NiceMock<fizz::test::MockClock>>());
clientCtx->setSupportedAlpns({"quic_test"});
return clientCtx;
}
std::shared_ptr<fizz::server::FizzServerContext> createServerCtx() {
auto cert = readCert();
auto certManager = std::make_unique<fizz::server::CertManager>();
certManager->addCertAndSetDefault(std::move(cert));
auto serverCtx = std::make_shared<fizz::server::FizzServerContext>();
serverCtx->setFactory(std::make_shared<QuicFizzFactory>());
serverCtx->setCertManager(std::move(certManager));
serverCtx->setOmitEarlyRecordLayer(true);
serverCtx->setClock(std::make_shared<NiceMock<fizz::test::MockClock>>());
serverCtx->setSupportedAlpns({"quic_test"});
return serverCtx;
}
class AcceptingTicketCipher : public fizz::server::TicketCipher {
public:
~AcceptingTicketCipher() override = default;
folly::SemiFuture<
Optional<std::pair<std::unique_ptr<folly::IOBuf>, std::chrono::seconds>>>
encrypt(fizz::server::ResumptionState) const override {
// Fake handshake, no need todo anything here.
return std::make_pair(folly::IOBuf::create(0), 2s);
}
void setPsk(const QuicCachedPsk& cachedPsk) {
cachedPsk_ = cachedPsk;
}
fizz::server::ResumptionState createResumptionState() const {
fizz::server::ResumptionState resState;
resState.version = cachedPsk_.cachedPsk.version;
resState.cipher = cachedPsk_.cachedPsk.cipher;
resState.resumptionSecret =
folly::IOBuf::copyBuffer(cachedPsk_.cachedPsk.secret);
resState.serverCert = cachedPsk_.cachedPsk.serverCert;
resState.alpn = cachedPsk_.cachedPsk.alpn;
resState.ticketAgeAdd = 0;
resState.ticketIssueTime = std::chrono::system_clock::time_point();
resState.handshakeTime = std::chrono::system_clock::time_point();
AppToken appToken;
appToken.transportParams = createTicketTransportParameters(
kDefaultIdleTimeout.count(),
kDefaultUDPReadBufferSize,
kDefaultConnectionFlowControlWindow,
kDefaultStreamFlowControlWindow,
kDefaultStreamFlowControlWindow,
kDefaultStreamFlowControlWindow,
kDefaultMaxStreamsBidirectional,
kDefaultMaxStreamsUnidirectional,
0 /*extendedAckSupport*/);
appToken.version = QuicVersion::MVFST;
resState.appToken = encodeAppToken(appToken);
return resState;
}
folly::SemiFuture<
std::pair<fizz::PskType, Optional<fizz::server::ResumptionState>>>
decrypt(std::unique_ptr<folly::IOBuf>) const override {
return std::make_pair(fizz::PskType::Resumption, createResumptionState());
}
private:
QuicCachedPsk cachedPsk_;
};
void setupZeroRttOnServerCtx(
fizz::server::FizzServerContext& serverCtx,
const QuicCachedPsk& cachedPsk) {
serverCtx.setEarlyDataSettings(
true,
fizz::server::ClockSkewTolerance{-100000ms, 100000ms},
std::make_shared<fizz::server::AllowAllReplayReplayCache>());
auto ticketCipher = std::make_shared<AcceptingTicketCipher>();
ticketCipher->setPsk(cachedPsk);
serverCtx.setTicketCipher(ticketCipher);
}
QuicCachedPsk setupZeroRttOnClientCtx(
fizz::client::FizzClientContext& clientCtx,
std::string hostname) {
clientCtx.setSendEarlyData(true);
QuicCachedPsk quicCachedPsk;
auto& psk = quicCachedPsk.cachedPsk;
psk.psk = std::string("psk");
psk.secret = std::string("secret");
psk.type = fizz::PskType::Resumption;
psk.version = clientCtx.getSupportedVersions()[0];
psk.cipher = clientCtx.getSupportedCiphers()[0];
psk.group = clientCtx.getSupportedGroups()[0];
auto mockCert = std::make_shared<NiceMock<fizz::test::MockCert>>();
ON_CALL(*mockCert, getIdentity()).WillByDefault(Return(hostname));
psk.serverCert = mockCert;
psk.alpn = clientCtx.getSupportedAlpns()[0];
psk.ticketAgeAdd = 1;
psk.ticketIssueTime = std::chrono::system_clock::time_point();
psk.ticketExpirationTime =
std::chrono::system_clock::time_point(std::chrono::minutes(100));
psk.ticketHandshakeTime = std::chrono::system_clock::time_point();
psk.maxEarlyDataSize = 2;
quicCachedPsk.transportParams.idleTimeout = kDefaultIdleTimeout.count();
quicCachedPsk.transportParams.maxRecvPacketSize = kDefaultUDPReadBufferSize;
quicCachedPsk.transportParams.initialMaxData =
kDefaultConnectionFlowControlWindow;
quicCachedPsk.transportParams.initialMaxStreamDataBidiLocal =
kDefaultStreamFlowControlWindow;
quicCachedPsk.transportParams.initialMaxStreamDataBidiRemote =
kDefaultStreamFlowControlWindow;
quicCachedPsk.transportParams.initialMaxStreamDataUni =
kDefaultStreamFlowControlWindow;
quicCachedPsk.transportParams.initialMaxStreamsBidi =
kDefaultMaxStreamsBidirectional;
quicCachedPsk.transportParams.initialMaxStreamsUni =
kDefaultMaxStreamsUnidirectional;
return quicCachedPsk;
}
void setupCtxWithTestCert(fizz::server::FizzServerContext& ctx) {
auto cert = readCert();
auto certManager = std::make_unique<fizz::server::CertManager>();
certManager->addCertAndSetDefault(std::move(cert));
ctx.setCertManager(std::move(certManager));
}
std::unique_ptr<MockAead> createNoOpAead(uint64_t cipherOverhead) {
return createNoOpAeadImpl<MockAead>(cipherOverhead);
}
std::unique_ptr<MockPacketNumberCipher> createNoOpHeaderCipher() {
auto headerCipher = std::make_unique<NiceMock<MockPacketNumberCipher>>();
ON_CALL(*headerCipher, mask(_)).WillByDefault(Return(HeaderProtectionMask{}));
ON_CALL(*headerCipher, keyLength()).WillByDefault(Return(16));
return headerCipher;
}
RegularQuicPacketBuilder::Packet createStreamPacket(
ConnectionId srcConnId,
ConnectionId dstConnId,
PacketNum packetNum,
StreamId streamId,
folly::IOBuf& data,
uint8_t cipherOverhead,
PacketNum largestAcked,
Optional<std::pair<LongHeader::Types, QuicVersion>> longHeaderOverride,
bool eof,
Optional<ProtectionType> shortHeaderOverride,
uint64_t offset,
uint64_t packetSizeLimit) {
std::unique_ptr<RegularQuicPacketBuilder> builder;
if (longHeaderOverride) {
LongHeader header(
longHeaderOverride->first,
srcConnId,
dstConnId,
packetNum,
longHeaderOverride->second);
builder.reset(new RegularQuicPacketBuilder(
packetSizeLimit, std::move(header), largestAcked));
} else {
ProtectionType protectionType = ProtectionType::KeyPhaseZero;
if (shortHeaderOverride) {
protectionType = *shortHeaderOverride;
}
ShortHeader header(protectionType, dstConnId, packetNum);
builder.reset(new RegularQuicPacketBuilder(
packetSizeLimit, std::move(header), largestAcked));
}
builder->encodePacketHeader();
builder->accountForCipherOverhead(cipherOverhead);
auto dataLen = *writeStreamFrameHeader(
*builder,
streamId,
offset,
data.computeChainDataLength(),
data.computeChainDataLength(),
eof,
none /* skipLenHint */);
auto dataBuf = data.clone();
writeStreamFrameData(
*builder,
std::move(dataBuf),
std::min(folly::to<size_t>(dataLen), data.computeChainDataLength()));
return std::move(*builder).buildPacket();
}
RegularQuicPacketBuilder::Packet createInitialCryptoPacket(
ConnectionId srcConnId,
ConnectionId dstConnId,
PacketNum packetNum,
QuicVersion version,
ChainedByteRangeHead& data,
const Aead& aead,
PacketNum largestAcked,
uint64_t offset,
std::string token,
const BuilderProvider& builderProvider) {
LongHeader header(
LongHeader::Types::Initial,
srcConnId,
dstConnId,
packetNum,
version,
std::move(token));
LongHeader copyHeader(header);
PacketBuilderInterface* builder = nullptr;
if (builderProvider) {
builder = builderProvider(std::move(header), largestAcked);
}
RegularQuicPacketBuilder fallbackBuilder(
kDefaultUDPSendPacketLen, std::move(copyHeader), largestAcked);
if (!builder) {
builder = &fallbackBuilder;
}
builder->encodePacketHeader();
builder->accountForCipherOverhead(aead.getCipherOverhead());
auto res = writeCryptoFrame(offset, data, *builder);
CHECK(res.hasValue()) << "failed to write crypto frame";
return std::move(*builder).buildPacket();
}
RegularQuicPacketBuilder::Packet createCryptoPacket(
ConnectionId srcConnId,
ConnectionId dstConnId,
PacketNum packetNum,
QuicVersion version,
ProtectionType protectionType,
ChainedByteRangeHead& data,
const Aead& aead,
PacketNum largestAcked,
uint64_t offset,
uint64_t packetSizeLimit) {
Optional<PacketHeader> header;
switch (protectionType) {
case ProtectionType::Initial:
header = LongHeader(
LongHeader::Types::Initial, srcConnId, dstConnId, packetNum, version);
break;
case ProtectionType::Handshake:
header = LongHeader(
LongHeader::Types::Handshake,
srcConnId,
dstConnId,
packetNum,
version);
break;
case ProtectionType::ZeroRtt:
header = LongHeader(
LongHeader::Types::ZeroRtt, srcConnId, dstConnId, packetNum, version);
break;
case ProtectionType::KeyPhaseOne:
case ProtectionType::KeyPhaseZero:
header = ShortHeader(protectionType, dstConnId, packetNum);
break;
}
RegularQuicPacketBuilder builder(
packetSizeLimit, std::move(*header), largestAcked);
builder.encodePacketHeader();
builder.accountForCipherOverhead(aead.getCipherOverhead());
auto res = writeCryptoFrame(offset, data, builder);
CHECK(res.hasValue()) << "failed to write crypto frame";
return std::move(builder).buildPacket();
}
Buf packetToBuf(const RegularQuicPacketBuilder::Packet& packet) {
auto packetBuf = packet.header.clone();
if (!packet.body.empty()) {
packetBuf->prependChain(packet.body.clone());
}
return packetBuf;
}
ReceivedUdpPacket packetToReceivedUdpPacket(
const RegularQuicPacketBuilder::Packet& writePacket) {
ReceivedUdpPacket packet(packetToBuf(writePacket));
return packet;
}
Buf packetToBufCleartext(
RegularQuicPacketBuilder::Packet& packet,
const Aead& cleartextCipher,
const PacketNumberCipher& headerCipher,
PacketNum packetNum) {
VLOG(10) << __func__ << " packet header: "
<< folly::hexlify(packet.header.clone()->moveToFbString());
auto packetBuf = packet.header.clone();
Buf body;
if (!packet.body.empty()) {
packet.body.coalesce();
body = packet.body.clone();
} else {
body = folly::IOBuf::create(0);
}
auto headerForm = packet.packet.header.getHeaderForm();
packet.header.coalesce();
auto tagLen = cleartextCipher.getCipherOverhead();
if (body->tailroom() < tagLen) {
body->prependChain(folly::IOBuf::create(tagLen));
}
body->coalesce();
auto encryptedBody = cleartextCipher.inplaceEncrypt(
std::move(body), &packet.header, packetNum);
encryptedBody->coalesce();
encryptPacketHeader(
headerForm,
packet.header.writableData(),
packet.header.length(),
encryptedBody->data(),
encryptedBody->length(),
headerCipher);
packetBuf->prependChain(std::move(encryptedBody));
return packetBuf;
}
Buf packetToBufCleartext(
RegularQuicPacketBuilder::Packet&& packet,
const Aead& cleartextCipher,
const PacketNumberCipher& headerCipher,
PacketNum packetNum) {
return packetToBufCleartext(packet, cleartextCipher, headerCipher, packetNum);
}
uint64_t computeExpectedDelay(
std::chrono::microseconds ackDelay,
uint8_t ackDelayExponent) {
uint64_t divide = uint64_t(ackDelay.count()) >> ackDelayExponent;
return divide << ackDelayExponent;
}
ConnectionId getTestConnectionId(uint32_t hostId, ConnectionIdVersion version) {
ServerConnectionIdParams params(version, hostId, 0, 0);
DefaultConnectionIdAlgo connIdAlgo;
auto connId = *connIdAlgo.encodeConnectionId(params);
// Clear random part of CID, some existing tests expect same CID value
// when repeatedly calling with the same hostId.
if (version == ConnectionIdVersion::V1) {
connId.data()[3] = 3;
connId.data()[4] = 4;
connId.data()[5] = 5;
connId.data()[6] = 6;
connId.data()[7] = 7;
} else if (version == ConnectionIdVersion::V2) {
connId.data()[0] &= 0xC0;
connId.data()[5] = 5;
connId.data()[6] = 6;
connId.data()[7] = 7;
} else {
CHECK(false) << "Unsupported CID version";
}
return connId;
}
ProtectionType encryptionLevelToProtectionType(
fizz::EncryptionLevel encryptionLevel) {
switch (encryptionLevel) {
case fizz::EncryptionLevel::Plaintext:
return ProtectionType::Initial;
case fizz::EncryptionLevel::Handshake:
// TODO: change this in draft-14
return ProtectionType::Initial;
case fizz::EncryptionLevel::EarlyData:
return ProtectionType::ZeroRtt;
case fizz::EncryptionLevel::AppTraffic:
return ProtectionType::KeyPhaseZero;
}
folly::assume_unreachable();
}
void updateAckState(
QuicConnectionStateBase& conn,
PacketNumberSpace pnSpace,
PacketNum packetNum,
bool pkHasRetransmittableData,
bool pkHasCryptoData,
TimePoint receiveTimePoint) {
ReceivedUdpPacket packet;
packet.timings.receiveTimePoint = receiveTimePoint;
uint64_t distance =
addPacketToAckState(conn, getAckState(conn, pnSpace), packetNum, packet);
updateAckSendStateOnRecvPacket(
conn,
getAckState(conn, pnSpace),
distance,
pkHasRetransmittableData,
pkHasCryptoData);
}
std::unique_ptr<folly::IOBuf> buildRandomInputData(size_t length) {
auto buf = folly::IOBuf::create(length);
buf->append(length);
folly::Random::secureRandom(buf->writableData(), buf->length());
return buf;
}
void addAckStatesWithCurrentTimestamps(
AckState& ackState,
PacketNum start,
PacketNum end) {
ackState.acks.insert(start, end);
ackState.largestRecvdPacketTime = Clock::now();
}
OutstandingPacketWrapper makeTestingWritePacket(
PacketNum desiredPacketSeqNum,
size_t desiredSize,
uint64_t totalBytesSent,
TimePoint sentTime /* = Clock::now() */,
uint64_t inflightBytes /* = 0 */,
uint64_t writeCount /* = 0 */) {
LongHeader longHeader(
LongHeader::Types::ZeroRtt,
getTestConnectionId(1),
getTestConnectionId(),
desiredPacketSeqNum,
QuicVersion::MVFST);
RegularQuicWritePacket packet(std::move(longHeader));
return OutstandingPacketWrapper(
packet,
sentTime,
desiredSize,
0,
totalBytesSent,
inflightBytes,
LossState(),
writeCount,
OutstandingPacketMetadata::DetailsPerStream());
}
CongestionController::AckEvent makeAck(
PacketNum seq,
uint64_t ackedSize,
TimePoint ackedTime,
TimePoint sentTime) {
CHECK(sentTime < ackedTime);
RegularQuicWritePacket packet(
ShortHeader(ProtectionType::KeyPhaseZero, getTestConnectionId(), seq));
auto ack = AckEvent::Builder()
.setAckTime(ackedTime)
.setAdjustedAckTime(ackedTime)
.setAckDelay(0us)
.setPacketNumberSpace(PacketNumberSpace::AppData)
.setLargestAckedPacket(seq)
.build();
ack.ackedBytes = ackedSize;
ack.largestNewlyAckedPacket = seq;
OutstandingPacketMetadata opm(
sentTime,
ackedSize /* encodedSize */,
ackedSize /* encodedBodySize */,
0 /* totalBytesSent */,
0 /* inflightBytes */,
LossState() /* lossState */,
0 /* writeCount */,
OutstandingPacketMetadata::DetailsPerStream());
ack.ackedPackets.emplace_back(
CongestionController::AckEvent::AckPacket::Builder()
.setPacketNum(seq)
.setNonDsrPacketSequenceNumber(seq)
.setOutstandingPacketMetadata(opm)
.setDetailsPerStream(AckEvent::AckPacket::DetailsPerStream())
.build());
ack.largestNewlyAckedPacketSentTime = sentTime;
return ack;
}
BufQueue bufToQueue(Buf buf) {
BufQueue queue;
buf->coalesce();
queue.append(std::move(buf));
return queue;
}
StatelessResetToken generateStatelessResetToken() {
StatelessResetSecret secret;
folly::Random::secureRandom(secret.data(), secret.size());
folly::SocketAddress address("1.2.3.4", 8080);
StatelessResetGenerator generator(secret, address.getFullyQualified());
return generator.generateToken(ConnectionId({0x14, 0x35, 0x22, 0x11}));
}
std::array<uint8_t, kStatelessResetTokenSecretLength> getRandSecret() {
std::array<uint8_t, kStatelessResetTokenSecretLength> secret;
folly::Random::secureRandom(secret.data(), secret.size());
return secret;
}
RegularQuicWritePacket createNewPacket(
PacketNum packetNum,
PacketNumberSpace pnSpace) {
switch (pnSpace) {
case PacketNumberSpace::Initial:
return RegularQuicWritePacket(LongHeader(
LongHeader::Types::Initial,
getTestConnectionId(1),
getTestConnectionId(2),
packetNum,
QuicVersion::MVFST));
case PacketNumberSpace::Handshake:
return RegularQuicWritePacket(LongHeader(
LongHeader::Types::Handshake,
getTestConnectionId(0),
getTestConnectionId(4),
packetNum,
QuicVersion::MVFST));
case PacketNumberSpace::AppData:
return RegularQuicWritePacket(ShortHeader(
ProtectionType::KeyPhaseOne, getTestConnectionId(), packetNum));
}
folly::assume_unreachable();
}
std::vector<QuicVersion> versionList(
std::initializer_list<QuicVersionType> types) {
std::vector<QuicVersion> versions;
for (auto type : types) {
versions.push_back(static_cast<QuicVersion>(type));
}
return versions;
}
RegularQuicWritePacket createRegularQuicWritePacket(
StreamId streamId,
uint64_t offset,
uint64_t len,
bool fin) {
auto regularWritePacket = createNewPacket(10, PacketNumberSpace::Initial);
WriteStreamFrame frame(streamId, offset, len, fin);
regularWritePacket.frames.emplace_back(frame);
return regularWritePacket;
}
VersionNegotiationPacket createVersionNegotiationPacket() {
auto versions = {QuicVersion::VERSION_NEGOTIATION, QuicVersion::MVFST};
auto packet = VersionNegotiationPacketBuilder(
getTestConnectionId(0), getTestConnectionId(1), versions)
.buildPacket()
.first;
return packet;
}
RegularQuicWritePacket createPacketWithAckFrames() {
RegularQuicWritePacket packet =
createNewPacket(100, PacketNumberSpace::Initial);
WriteAckFrame ackFrame;
ackFrame.ackDelay = 111us;
ackFrame.ackBlocks.emplace_back(900, 1000);
ackFrame.ackBlocks.emplace_back(500, 700);
packet.frames.emplace_back(std::move(ackFrame));
return packet;
}
RegularQuicWritePacket createPacketWithPaddingFrames() {
RegularQuicWritePacket packet =
createNewPacket(100, PacketNumberSpace::Initial);
PaddingFrame paddingFrame{20};
packet.frames.emplace_back(paddingFrame);
return packet;
}
std::vector<int> getQLogEventIndices(
QLogEventType type,
const std::shared_ptr<FileQLogger>& q) {
std::vector<int> indices;
for (uint64_t i = 0; i < q->logs.size(); ++i) {
if (q->logs[i]->eventType == type) {
indices.push_back(i);
}
}
return indices;
}
bool matchError(QuicError errorCode, LocalErrorCode error) {
return errorCode.code.type() == QuicErrorCode::Type::LocalErrorCode &&
*errorCode.code.asLocalErrorCode() == error;
}
bool matchError(QuicError errorCode, TransportErrorCode error) {
return errorCode.code.type() == QuicErrorCode::Type::TransportErrorCode &&
*errorCode.code.asTransportErrorCode() == error;
}
bool matchError(QuicError errorCode, ApplicationErrorCode error) {
return errorCode.code.type() == QuicErrorCode::Type::ApplicationErrorCode &&
*errorCode.code.asApplicationErrorCode() == error;
}
CongestionController::AckEvent::AckPacket makeAckPacketFromOutstandingPacket(
OutstandingPacketWrapper outstandingPacket) {
return CongestionController::AckEvent::AckPacket::Builder()
.setPacketNum(outstandingPacket.packet.header.getPacketSequenceNum())
.setNonDsrPacketSequenceNumber(
outstandingPacket.packet.header.getPacketSequenceNum())
.setOutstandingPacketMetadata(outstandingPacket.metadata)
.setLastAckedPacketInfo(
outstandingPacket.lastAckedPacketInfo
? &*outstandingPacket.lastAckedPacketInfo
: nullptr)
.setAppLimited(outstandingPacket.isAppLimited)
.setDetailsPerStream(
CongestionController::AckEvent::AckPacket::DetailsPerStream())
.build();
}
void overridePacketWithToken(
PacketBuilderInterface::Packet& packet,
const StatelessResetToken& token) {
overridePacketWithToken(packet.body, token);
}
void overridePacketWithToken(
folly::IOBuf& bodyBuf,
const StatelessResetToken& token) {
bodyBuf.coalesce();
CHECK(bodyBuf.length() > sizeof(StatelessResetToken));
memcpy(
bodyBuf.writableData() + bodyBuf.length() - sizeof(StatelessResetToken),
token.data(),
token.size());
}
bool writableContains(QuicStreamManager& streamManager, StreamId streamId) {
return streamManager.writeQueue().count(streamId) > 0 ||
streamManager.controlWriteQueue().count(streamId) > 0;
}
std::unique_ptr<PacketNumberCipher>
FizzCryptoTestFactory::makePacketNumberCipher(fizz::CipherSuite) const {
return std::move(packetNumberCipher_);
}
std::unique_ptr<PacketNumberCipher>
FizzCryptoTestFactory::makePacketNumberCipher(folly::ByteRange secret) const {
return _makePacketNumberCipher(secret);
}
void FizzCryptoTestFactory::setMockPacketNumberCipher(
std::unique_ptr<PacketNumberCipher> packetNumberCipher) {
packetNumberCipher_ = std::move(packetNumberCipher);
}
void FizzCryptoTestFactory::setDefault() {
ON_CALL(*this, _makePacketNumberCipher(_))
.WillByDefault(Invoke([&](folly::ByteRange secret) {
return FizzCryptoFactory::makePacketNumberCipher(secret);
}));
}
void TestPacketBatchWriter::reset() {
bufNum_ = 0;
bufSize_ = 0;
}
bool TestPacketBatchWriter::append(
std::unique_ptr<folly::IOBuf>&& /*unused*/,
size_t size,
const folly::SocketAddress& /*unused*/,
QuicAsyncUDPSocket* /*unused*/) {
bufNum_++;
bufSize_ += size;
return ((maxBufs_ < 0) || (bufNum_ >= maxBufs_));
}
ssize_t TestPacketBatchWriter::write(
QuicAsyncUDPSocket& /*unused*/,
const folly::SocketAddress& /*unused*/) {
return bufSize_;
}
TrafficKey getQuicTestKey() {
TrafficKey testKey;
testKey.key = folly::IOBuf::copyBuffer(
folly::unhexlify("000102030405060708090A0B0C0D0E0F"));
testKey.iv =
folly::IOBuf::copyBuffer(folly::unhexlify("000102030405060708090A0B"));
return testKey;
}
std::unique_ptr<folly::IOBuf> getProtectionKey() {
FizzCryptoFactory factory;
auto secret = getRandSecret();
auto pnCipher =
factory.makePacketNumberCipher(fizz::CipherSuite::TLS_AES_128_GCM_SHA256);
auto deriver = factory.getFizzFactory()->makeKeyDeriver(
fizz::CipherSuite::TLS_AES_128_GCM_SHA256);
return deriver->expandLabel(
folly::range(secret),
kQuicPNLabel,
folly::IOBuf::create(0),
pnCipher->keyLength());
}
size_t getTotalIovecLen(const struct iovec* vec, size_t iovec_len) {
uint32_t result = 0;
for (uint32_t i = 0; i < iovec_len; i++) {
result += vec[i].iov_len;
}
return result;
}
Buf copyChain(Buf&& input) {
folly::IOBuf* current = input.get();
Buf headCopy = folly::IOBuf::copyBuffer(current->data(), current->length());
current = current->next();
while (current != input.get()) {
Buf currCopy = folly::IOBuf::copyBuffer(current->data(), current->length());
headCopy->appendToChain(std::move(currCopy));
current = current->next();
}
return headCopy;
}
} // namespace quic::test