quic/dsr/backend/DSRPacketizer.h (132 lines of code) (raw):
/*
* 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.
*/
#pragma once
#include <fizz/crypto/aead/Aead.h>
#include <fizz/protocol/Factory.h>
#include <fizz/protocol/OpenSSLFactory.h>
#include <fizz/protocol/Protocol.h>
#include <fizz/record/Types.h>
#include <folly/Hash.h>
#include <folly/SocketAddress.h>
#include <folly/container/EvictingCacheMap.h>
#include <quic/api/IoBufQuicBatch.h>
#include <quic/codec/PacketNumberCipher.h>
#include <quic/codec/QuicConnectionId.h>
#include <quic/codec/QuicPacketBuilder.h>
#include <quic/codec/Types.h>
#include <quic/dsr/Types.h>
#include <quic/fizz/handshake/FizzBridge.h>
#include <quic/fizz/handshake/FizzCryptoFactory.h>
#include <quic/handshake/Aead.h>
namespace quic {
/**
* For now, each packetization request builds only one QUIC packet. I think
* it's easier to batch packetization requests than to make one request build
* multiple packets. But I'm open to discussion.
*
* What a Packetization request is supposed to include:
*
* All parems in CipherBuilder::buildCiphers():
* TrafficKey (key + iv), CipherSuite, packet protection key
*
* Then all the non-cipher params in DSRBackenderSender::sendQuicPacket:
* DCID, Client addr, Packet Number, Stream Id, Stream offset, Stream data
* length, Stream data EOF.
*
* Then the extra info to look up Cipher from CipherMap:
* SCID
*/
struct CipherKey {
ConnectionId scid;
ConnectionId dcid;
folly::SocketAddress clientAddress;
};
struct CipherKeyHash {
std::size_t operator()(const CipherKey& key) const {
return folly::hash::hash_combine(
ConnectionIdHash()(key.scid),
ConnectionIdHash()(key.dcid),
key.clientAddress.hash());
}
};
struct CipherKeyEq {
bool operator()(const CipherKey& first, const CipherKey& second) const {
return first.scid == second.scid && first.dcid == second.dcid &&
first.clientAddress == second.clientAddress;
}
};
struct CipherPair {
std::unique_ptr<Aead> aead;
std::unique_ptr<PacketNumberCipher> headerCipher;
};
// This is supposed to be per-thread. If two packetization requests for the
// same connection wind up on two threads, they both will have to build the
// same CipherPairs, and keep them in their own map.
using CipherMap =
folly::EvictingCacheMap<CipherKey, CipherPair, CipherKeyHash, CipherKeyEq>;
class CipherBuilder {
public:
CipherPair buildCiphers(
fizz::TrafficKey&& trafficKey,
fizz::CipherSuite cipherSuite,
std::unique_ptr<folly::IOBuf> packetProtectionKey) {
auto aead = FizzAead::wrap(deriveRecordAeadWithLabel(
*quicFizzCryptoFactory_.getFizzFactory(),
std::move(trafficKey),
cipherSuite));
auto headerCipher = quicFizzCryptoFactory_.makePacketNumberCipher(
fizz::CipherSuite::TLS_AES_128_GCM_SHA256);
headerCipher->setKey(packetProtectionKey->coalesce());
return {std::move(aead), std::move(headerCipher)};
}
private:
std::unique_ptr<fizz::Aead> deriveRecordAeadWithLabel(
const fizz::Factory& factory,
fizz::TrafficKey trafficKey,
fizz::CipherSuite cipher) {
auto aead = factory.makeAead(cipher);
aead->setKey(std::move(trafficKey));
return aead;
}
FizzCryptoFactory quicFizzCryptoFactory_;
};
// TODO: it needs to be able to get the cache data.
// TODO: We are not going to maintain any QUIC related states. So if the cache
// data can be just passed in, we can make this into a free function.
class QuicPacketizer {
public:
virtual ~QuicPacketizer() = default;
virtual std::unique_ptr<folly::IOBuf> sendQuicPacket(
ConnectionId dcid,
const folly::SocketAddress& clientAddr,
PacketNum packetNum,
const Aead& aead,
const PacketNumberCipher& headerCipher,
StreamId streamId,
size_t offset,
size_t length,
bool eof) = 0;
};
/**
* Write a single encrypted packet buffer into ioBufBatch. The source data is
* passed via buf. The first byte in buf is supposed to be matching the offset.
* Alternatively some sort of cache data provider can be passed to this function
* to let it fetch the correct bytes internally.
*/
bool writeSingleQuicPacket(
IOBufQuicBatch& ioBufBatch,
ConnectionId dcid,
PacketNum packetNum,
PacketNum largestAckedByPeer,
const Aead& aead,
const PacketNumberCipher& headerCipher,
StreamId streamId,
size_t offset,
size_t length,
bool eof,
Buf buf);
struct PacketizationRequest {
PacketizationRequest(
PacketNum packetNumIn,
PacketNum largestAckedPacketNumIn,
StreamId streamIdIn,
uint64_t offsetIn,
uint64_t lenIn,
bool finIn,
uint64_t payloadOffsetIn)
: packetNum(packetNumIn),
largestAckedPacketNum(largestAckedPacketNumIn),
streamId(streamIdIn),
offset(offsetIn),
len(lenIn),
fin(finIn),
payloadOffset(payloadOffsetIn) {}
PacketNum packetNum;
PacketNum largestAckedPacketNum;
// QUIC Stream info
StreamId streamId;
uint64_t offset;
uint64_t len;
bool fin;
// This is the offset of the buffer payload. It is different from the offset
// above which is the stream bytes offset.
uint64_t payloadOffset;
};
struct RequestGroup {
ConnectionId dcid;
ConnectionId scid;
folly::SocketAddress clientAddress;
const CipherPair* cipherPair{nullptr};
SmallVec<PacketizationRequest, 64, uint32_t> requests;
};
BufQuicBatchResult writePacketsGroup(
folly::AsyncUDPSocket& sock,
RequestGroup& reqGroup,
const std::function<Buf(const PacketizationRequest& req)>& bufProvider);
} // namespace quic