mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-04-18 17:24:03 +03:00
Summary: This introduces a more generic typealias so that we can, for instance, write `BufHelpers::createCombined` instead of `folly::IOBuf::createCombined`. Reviewed By: jbeshay Differential Revision: D73127508 fbshipit-source-id: d585790904efc8e9f92d79cbf766bafe0e84a69f
784 lines
26 KiB
C++
784 lines
26 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/codec/QuicReadCodec.h>
|
|
|
|
#include <folly/io/Cursor.h>
|
|
#include <quic/codec/Decode.h>
|
|
#include <quic/codec/PacketNumber.h>
|
|
|
|
namespace {
|
|
quic::ConnectionId zeroConnId() {
|
|
std::vector<uint8_t> zeroData(quic::kDefaultConnectionIdSize, 0);
|
|
return quic::ConnectionId(zeroData);
|
|
}
|
|
} // namespace
|
|
|
|
namespace quic {
|
|
|
|
QuicReadCodec::QuicReadCodec(QuicNodeType nodeType) : nodeType_(nodeType) {}
|
|
|
|
Optional<VersionNegotiationPacket> QuicReadCodec::tryParsingVersionNegotiation(
|
|
BufQueue& queue) {
|
|
folly::io::Cursor cursor(queue.front());
|
|
if (!cursor.canAdvance(sizeof(uint8_t))) {
|
|
return none;
|
|
}
|
|
uint8_t initialByte = cursor.readBE<uint8_t>();
|
|
auto headerForm = getHeaderForm(initialByte);
|
|
if (headerForm != HeaderForm::Long) {
|
|
return none;
|
|
}
|
|
auto longHeaderInvariant = parseLongHeaderInvariant(initialByte, cursor);
|
|
if (!longHeaderInvariant) {
|
|
// if it is an invalid packet, it's definitely not a VN packet, so ignore
|
|
// it.
|
|
return none;
|
|
}
|
|
if (longHeaderInvariant->invariant.version !=
|
|
QuicVersion::VERSION_NEGOTIATION) {
|
|
return none;
|
|
}
|
|
return decodeVersionNegotiation(*longHeaderInvariant, cursor);
|
|
}
|
|
|
|
folly::Expected<ParsedLongHeader, TransportErrorCode> tryParseLongHeader(
|
|
folly::io::Cursor& cursor,
|
|
QuicNodeType nodeType) {
|
|
if (cursor.isAtEnd() || !cursor.canAdvance(sizeof(uint8_t))) {
|
|
return folly::makeUnexpected(TransportErrorCode::PROTOCOL_VIOLATION);
|
|
}
|
|
auto initialByte = cursor.readBE<uint8_t>();
|
|
auto longHeaderInvariant = parseLongHeaderInvariant(initialByte, cursor);
|
|
if (!longHeaderInvariant) {
|
|
VLOG(4) << "Dropping packet, failed to parse invariant";
|
|
// We've failed to parse the long header, so we have no idea where this
|
|
// packet ends. Clear the queue since no other data in this packet is
|
|
// parse-able.
|
|
return folly::makeUnexpected(longHeaderInvariant.error());
|
|
}
|
|
if (longHeaderInvariant->invariant.version ==
|
|
QuicVersion::VERSION_NEGOTIATION) {
|
|
// We shouldn't handle VN packets while parsing the long header.
|
|
// We assume here that they have been handled before calling this
|
|
// function.
|
|
// Since VN is not allowed to be coalesced with another packet
|
|
// type, we clear out the buffer to avoid anyone else parsing it.
|
|
return folly::makeUnexpected(TransportErrorCode::PROTOCOL_VIOLATION);
|
|
}
|
|
auto type = parseLongHeaderType(initialByte);
|
|
|
|
auto parsedLongHeader =
|
|
parseLongHeaderVariants(type, *longHeaderInvariant, cursor, nodeType);
|
|
if (!parsedLongHeader) {
|
|
VLOG(4) << "Dropping due to failed to parse header";
|
|
// We've failed to parse the long header, so we have no idea where this
|
|
// packet ends. Clear the queue since no other data in this packet is
|
|
// parse-able.
|
|
return folly::makeUnexpected(parsedLongHeader.error());
|
|
}
|
|
|
|
return std::move(parsedLongHeader.value());
|
|
}
|
|
|
|
static PacketDropReason getDecryptErrorReason(ProtectionType protectionType) {
|
|
switch (protectionType) {
|
|
case ProtectionType::Initial:
|
|
return PacketDropReason::DECRYPTION_ERROR_INITIAL;
|
|
case ProtectionType::Handshake:
|
|
return PacketDropReason::DECRYPTION_ERROR_HANDSHAKE;
|
|
case ProtectionType::ZeroRtt:
|
|
return PacketDropReason::DECRYPTION_ERROR_0RTT;
|
|
default:
|
|
return PacketDropReason::DECRYPTION_ERROR;
|
|
}
|
|
}
|
|
|
|
CodecResult QuicReadCodec::parseLongHeaderPacket(
|
|
BufQueue& queue,
|
|
const AckStates& ackStates) {
|
|
folly::io::Cursor cursor(queue.front());
|
|
const uint8_t initialByte = *cursor.peekBytes().data();
|
|
|
|
auto res = tryParseLongHeader(cursor, nodeType_);
|
|
if (res.hasError()) {
|
|
VLOG(4) << "Failed to parse long header " << connIdToHex();
|
|
queue.move();
|
|
return CodecResult(Nothing());
|
|
}
|
|
auto parsedLongHeader = std::move(res.value());
|
|
auto type = parsedLongHeader.header.getHeaderType();
|
|
|
|
// As soon as we have parsed out the long header we can split off any
|
|
// coalesced packets. We do this early since the spec mandates that decryption
|
|
// failure must not stop the processing of subsequent coalesced packets.
|
|
auto longHeader = std::move(parsedLongHeader.header);
|
|
|
|
if (type == LongHeader::Types::Retry) {
|
|
auto integrityTag = cursor.read<RetryPacket::IntegrityTagType>();
|
|
queue.move();
|
|
return RetryPacket(std::move(longHeader), integrityTag, initialByte);
|
|
}
|
|
|
|
uint64_t packetNumberOffset = cursor.getCurrentPosition();
|
|
size_t currentPacketLen =
|
|
packetNumberOffset + parsedLongHeader.packetLength.packetLength;
|
|
if (queue.chainLength() < currentPacketLen) {
|
|
// Packet appears truncated, there's no parse-able data left.
|
|
queue.move();
|
|
return CodecResult(Nothing());
|
|
}
|
|
auto currentPacketData = queue.splitAtMost(currentPacketLen);
|
|
cursor.reset(currentPacketData.get());
|
|
cursor.skip(packetNumberOffset);
|
|
// Sample starts after the max packet number size. This ensures that we
|
|
// have enough bytes to skip before we can start reading the sample.
|
|
if (!cursor.canAdvance(kMaxPacketNumEncodingSize)) {
|
|
VLOG(4) << "Dropping packet, not enough for packet number "
|
|
<< connIdToHex();
|
|
// Packet appears truncated, there's no parse-able data left.
|
|
queue.move();
|
|
return CodecResult(Nothing());
|
|
}
|
|
cursor.skip(kMaxPacketNumEncodingSize);
|
|
Sample sample;
|
|
if (!cursor.canAdvance(sample.size())) {
|
|
VLOG(4) << "Dropping packet, sample too small " << connIdToHex();
|
|
// Packet appears truncated, there's no parse-able data left.
|
|
queue.move();
|
|
return CodecResult(Nothing());
|
|
}
|
|
cursor.pull(sample.data(), sample.size());
|
|
const PacketNumberCipher* headerCipher{nullptr};
|
|
const Aead* cipher{nullptr};
|
|
auto protectionType = longHeader.getProtectionType();
|
|
switch (protectionType) {
|
|
case ProtectionType::Initial:
|
|
if (!initialHeaderCipher_) {
|
|
VLOG(4) << nodeToString(nodeType_)
|
|
<< " dropping initial packet after initial keys dropped"
|
|
<< connIdToHex();
|
|
return CodecResult(Nothing());
|
|
}
|
|
headerCipher = initialHeaderCipher_.get();
|
|
cipher = initialReadCipher_.get();
|
|
break;
|
|
case ProtectionType::Handshake:
|
|
headerCipher = handshakeHeaderCipher_.get();
|
|
cipher = handshakeReadCipher_.get();
|
|
break;
|
|
case ProtectionType::ZeroRtt:
|
|
if (handshakeDoneTime_) {
|
|
// TODO actually drop the 0-rtt keys in addition to dropping packets.
|
|
auto timeBetween = Clock::now() - *handshakeDoneTime_;
|
|
if (timeBetween > kTimeToRetainZeroRttKeys) {
|
|
VLOG(4) << nodeToString(nodeType_)
|
|
<< " dropping zero rtt packet for exceeding key timeout"
|
|
<< connIdToHex();
|
|
return CodecResult(Nothing());
|
|
}
|
|
}
|
|
headerCipher = zeroRttHeaderCipher_.get();
|
|
cipher = zeroRttReadCipher_.get();
|
|
break;
|
|
case ProtectionType::KeyPhaseZero:
|
|
case ProtectionType::KeyPhaseOne:
|
|
CHECK(false) << "one rtt protection type in long header";
|
|
}
|
|
if (!headerCipher || !cipher) {
|
|
return CodecResult(
|
|
CipherUnavailable(std::move(currentPacketData), protectionType));
|
|
}
|
|
|
|
PacketNum expectedNextPacketNum = 0;
|
|
Optional<PacketNum> largestRecvdPacketNum;
|
|
switch (longHeaderTypeToProtectionType(type)) {
|
|
case ProtectionType::Initial:
|
|
largestRecvdPacketNum = ackStates.initialAckState->largestRecvdPacketNum;
|
|
|
|
break;
|
|
case ProtectionType::Handshake:
|
|
largestRecvdPacketNum =
|
|
ackStates.handshakeAckState->largestRecvdPacketNum;
|
|
|
|
break;
|
|
case ProtectionType::ZeroRtt:
|
|
largestRecvdPacketNum = ackStates.appDataAckState.largestRecvdPacketNum;
|
|
|
|
break;
|
|
default:
|
|
folly::assume_unreachable();
|
|
}
|
|
if (largestRecvdPacketNum) {
|
|
expectedNextPacketNum = 1 + *largestRecvdPacketNum;
|
|
}
|
|
folly::MutableByteRange initialByteRange(
|
|
currentPacketData->writableData(), 1);
|
|
folly::MutableByteRange packetNumberByteRange(
|
|
currentPacketData->writableData() + packetNumberOffset,
|
|
kMaxPacketNumEncodingSize);
|
|
headerCipher->decryptLongHeader(
|
|
folly::range(sample), initialByteRange, packetNumberByteRange);
|
|
std::pair<PacketNum, size_t> packetNum = parsePacketNumber(
|
|
initialByteRange.data()[0], packetNumberByteRange, expectedNextPacketNum);
|
|
|
|
longHeader.setPacketNumber(packetNum.first);
|
|
BufQueue decryptQueue;
|
|
decryptQueue.append(std::move(currentPacketData));
|
|
size_t aadLen = packetNumberOffset + packetNum.second;
|
|
auto headerData = decryptQueue.splitAtMost(aadLen);
|
|
// parsing verifies that packetLength >= packet number length.
|
|
auto encryptedData = decryptQueue.splitAtMost(
|
|
parsedLongHeader.packetLength.packetLength - packetNum.second);
|
|
if (!encryptedData) {
|
|
// There should normally be some integrity tag at least in the data,
|
|
// however allowing the aead to process the data even if the tag is not
|
|
// present helps with writing tests.
|
|
encryptedData = BufHelpers::create(0);
|
|
}
|
|
|
|
Buf decrypted;
|
|
auto decryptAttempt = cipher->tryDecrypt(
|
|
std::move(encryptedData), headerData.get(), packetNum.first);
|
|
if (!decryptAttempt) {
|
|
VLOG(4) << "Unable to decrypt packet=" << packetNum.first
|
|
<< " packetNumLen=" << parsePacketNumberLength(initialByte)
|
|
<< " protectionType=" << toString(protectionType) << " "
|
|
<< connIdToHex();
|
|
return CodecResult(Nothing(getDecryptErrorReason(protectionType)));
|
|
}
|
|
decrypted = std::move(*decryptAttempt);
|
|
|
|
if (!decrypted) {
|
|
// TODO better way of handling this (tests break without this)
|
|
decrypted = BufHelpers::create(0);
|
|
}
|
|
|
|
auto packetRes =
|
|
decodeRegularPacket(std::move(longHeader), params_, std::move(decrypted));
|
|
|
|
if (!packetRes.hasValue()) {
|
|
return CodecResult(CodecError(std::move(packetRes.error())));
|
|
}
|
|
|
|
return CodecResult(std::move(*packetRes));
|
|
}
|
|
|
|
CodecResult QuicReadCodec::tryParseShortHeaderPacket(
|
|
Buf data,
|
|
const AckStates& ackStates,
|
|
size_t dstConnIdSize,
|
|
folly::io::Cursor& cursor) {
|
|
// TODO: allow other connid lengths from the state.
|
|
size_t packetNumberOffset = 1 + dstConnIdSize;
|
|
PacketNum expectedNextPacketNum =
|
|
ackStates.appDataAckState.largestRecvdPacketNum
|
|
? (1 + *ackStates.appDataAckState.largestRecvdPacketNum)
|
|
: 0;
|
|
size_t sampleOffset = packetNumberOffset + kMaxPacketNumEncodingSize;
|
|
Sample sample;
|
|
if (data->computeChainDataLength() < sampleOffset + sample.size()) {
|
|
VLOG(10) << "Dropping packet, too small for sample " << connIdToHex();
|
|
// There's not enough space for the short header packet
|
|
return CodecResult(Nothing());
|
|
}
|
|
|
|
folly::MutableByteRange initialByteRange(data->writableData(), 1);
|
|
folly::MutableByteRange packetNumberByteRange(
|
|
data->writableData() + packetNumberOffset, kMaxPacketNumEncodingSize);
|
|
folly::ByteRange sampleByteRange(
|
|
data->writableData() + sampleOffset, sample.size());
|
|
|
|
oneRttHeaderCipher_->decryptShortHeader(
|
|
sampleByteRange, initialByteRange, packetNumberByteRange);
|
|
std::pair<PacketNum, size_t> packetNum = parsePacketNumber(
|
|
initialByteRange.data()[0], packetNumberByteRange, expectedNextPacketNum);
|
|
auto shortHeader =
|
|
parseShortHeader(initialByteRange.data()[0], cursor, dstConnIdSize);
|
|
if (!shortHeader) {
|
|
VLOG(10) << "Dropping packet, cannot parse " << connIdToHex();
|
|
return CodecResult(Nothing());
|
|
}
|
|
shortHeader->setPacketNumber(packetNum.first);
|
|
bool peerKeyUpdateAttempt = false;
|
|
auto oneRttReadCipherToUse = [&]() -> Aead* {
|
|
if (shortHeader->getProtectionType() == currentOneRttReadPhase_) {
|
|
return currentOneRttReadCipher_.get();
|
|
} else {
|
|
// This is a packet from a different phase. It may be encrypted using the
|
|
// next key (new peer-initiated key update) or the previous key (out of
|
|
// order packet or pending locally-initiated key update).
|
|
|
|
if (!currentOneRttReadPhaseStartPacketNum_ ||
|
|
shortHeader->getPacketSequenceNum() <
|
|
currentOneRttReadPhaseStartPacketNum_.value()) {
|
|
// There is either a pending key update or this an out-of-order packet,
|
|
// attempt to use the previous cipher
|
|
if (previousOneRttReadCipher_) {
|
|
return previousOneRttReadCipher_.get();
|
|
} else {
|
|
// There is no previous packet. We can't decrypt this packet
|
|
VLOG(4)
|
|
<< nodeToString(nodeType_)
|
|
<< " cannot read packet using previous cipher. Cipher is not available";
|
|
return nullptr;
|
|
}
|
|
} else {
|
|
// This is a key update attempt
|
|
if (nextOneRttReadCipher_) {
|
|
peerKeyUpdateAttempt = true;
|
|
QUIC_STATS(statsCallback_, onKeyUpdateAttemptReceived);
|
|
return nextOneRttReadCipher_.get();
|
|
} else {
|
|
// The next cipher is not yet available. We can't decrypt this packet
|
|
VLOG(4)
|
|
<< nodeToString(nodeType_)
|
|
<< " unable to process key update. Next cipher is not yet available";
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
}();
|
|
|
|
if (oneRttReadCipherToUse == nullptr) {
|
|
return CodecResult(
|
|
CipherUnavailable(std::move(data), shortHeader->getProtectionType()));
|
|
}
|
|
|
|
// We know that the iobuf is not chained. This means that we can safely have
|
|
// a non-owning reference to the header without cloning the buffer. If we
|
|
// don't clone the buffer, the buffer will not show up as shared and we can
|
|
// decrypt in-place.
|
|
size_t aadLen = packetNumberOffset + packetNum.second;
|
|
folly::IOBuf headerData = BufHelpers::wrapBufferAsValue(data->data(), aadLen);
|
|
data->trimStart(aadLen);
|
|
|
|
Buf decrypted;
|
|
auto decryptAttempt = oneRttReadCipherToUse->tryDecrypt(
|
|
std::move(data), &headerData, packetNum.first);
|
|
if (!decryptAttempt) {
|
|
auto protectionType = shortHeader->getProtectionType();
|
|
VLOG(10) << "Unable to decrypt packet=" << packetNum.first
|
|
<< " protectionType=" << (int)protectionType << " "
|
|
<< connIdToHex();
|
|
return CodecResult(Nothing(PacketDropReason::DECRYPTION_ERROR));
|
|
}
|
|
decrypted = std::move(*decryptAttempt);
|
|
if (!decrypted) {
|
|
// TODO better way of handling this (tests break without this)
|
|
decrypted = BufHelpers::create(0);
|
|
}
|
|
|
|
if (peerKeyUpdateAttempt) {
|
|
// Peer initiated a key update and we've successfully decrypted a packet
|
|
// from the next phase. We should advance our oneRttCipher state.
|
|
currentOneRttReadPhase_ = shortHeader->getProtectionType();
|
|
currentOneRttReadPhaseStartPacketNum_.reset();
|
|
previousOneRttReadCipher_.reset(currentOneRttReadCipher_.release());
|
|
currentOneRttReadCipher_.reset(nextOneRttReadCipher_.release());
|
|
// nextOneRttReadCipher_ will be populated by the transport
|
|
}
|
|
|
|
if (!currentOneRttReadPhaseStartPacketNum_.has_value() &&
|
|
oneRttReadCipherToUse == currentOneRttReadCipher_.get()) {
|
|
// This is the first packet in the current phase. Record the packet
|
|
// number. This applies for both peer-initiated and self-initiated key
|
|
// updates.
|
|
currentOneRttReadPhaseStartPacketNum_ = shortHeader->getPacketSequenceNum();
|
|
QUIC_STATS(statsCallback_, onKeyUpdateAttemptSucceeded);
|
|
}
|
|
|
|
// TODO: Should we discard the previous cipher at some point? Keeping it
|
|
// around avoids the timing signals mentioned in the spec, but we could also
|
|
// drop it after 3 * PTO.
|
|
|
|
auto packetRes = decodeRegularPacket(
|
|
std::move(*shortHeader), params_, std::move(decrypted));
|
|
|
|
if (!packetRes.hasValue()) {
|
|
return CodecResult(CodecError(std::move(packetRes.error())));
|
|
}
|
|
return CodecResult(std::move(*packetRes));
|
|
}
|
|
|
|
CodecResult QuicReadCodec::parsePacket(
|
|
BufQueue& queue,
|
|
const AckStates& ackStates,
|
|
size_t dstConnIdSize) {
|
|
if (queue.empty()) {
|
|
return CodecResult(Nothing());
|
|
}
|
|
DCHECK(!queue.front()->isChained());
|
|
folly::io::Cursor cursor(queue.front());
|
|
if (!cursor.canAdvance(sizeof(uint8_t))) {
|
|
return CodecResult(Nothing());
|
|
}
|
|
uint8_t initialByte = cursor.readBE<uint8_t>();
|
|
auto headerForm = getHeaderForm(initialByte);
|
|
if (headerForm == HeaderForm::Long) {
|
|
return parseLongHeaderPacket(queue, ackStates);
|
|
}
|
|
// Missing 1-rtt header cipher is the only case we wouldn't consider reset
|
|
if (!currentOneRttReadCipher_ || !oneRttHeaderCipher_) {
|
|
VLOG(4) << nodeToString(nodeType_) << " cannot read key phase zero packet";
|
|
VLOG(20) << "cannot read data="
|
|
<< folly::hexlify(queue.front()->clone()->moveToFbString()) << " "
|
|
<< connIdToHex();
|
|
return CodecResult(
|
|
CipherUnavailable(queue.move(), ProtectionType::KeyPhaseZero));
|
|
}
|
|
|
|
auto data = queue.move();
|
|
Optional<StatelessResetToken> token;
|
|
if (nodeType_ == QuicNodeType::Client &&
|
|
initialByte & ShortHeader::kFixedBitMask) {
|
|
auto dataLength = data->length();
|
|
if (statelessResetToken_ && dataLength > sizeof(StatelessResetToken)) {
|
|
const uint8_t* tokenSource =
|
|
data->data() + (dataLength - sizeof(StatelessResetToken));
|
|
if (!cryptoEqual_) {
|
|
return CodecResult(CodecError(QuicError(
|
|
QuicErrorCode(LocalErrorCode::INTERNAL_ERROR),
|
|
"crypto constant time comparison function is not set.")));
|
|
}
|
|
// Only allocate & copy the token if it matches the token we have
|
|
if (cryptoEqual_(
|
|
folly::ByteRange(tokenSource, sizeof(StatelessResetToken)),
|
|
folly::ByteRange(
|
|
statelessResetToken_->data(), sizeof(StatelessResetToken)))) {
|
|
token = StatelessResetToken();
|
|
memcpy(token->data(), tokenSource, token->size());
|
|
return StatelessReset(*token);
|
|
}
|
|
}
|
|
}
|
|
return tryParseShortHeaderPacket(
|
|
std::move(data), ackStates, dstConnIdSize, cursor);
|
|
}
|
|
|
|
bool QuicReadCodec::canInitiateKeyUpdate() const {
|
|
if (!nextOneRttReadCipher_ || !currentOneRttReadPhaseStartPacketNum_) {
|
|
// We haven't received any packets in the current oneRtt phase yet.
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool QuicReadCodec::advanceOneRttReadPhase() {
|
|
if (!canInitiateKeyUpdate()) {
|
|
LOG(WARNING) << "Key update requested before the read codec can allow it";
|
|
return false;
|
|
}
|
|
previousOneRttReadCipher_.reset(currentOneRttReadCipher_.release());
|
|
currentOneRttReadCipher_.reset(nextOneRttReadCipher_.release());
|
|
currentOneRttReadPhase_ =
|
|
(currentOneRttReadPhase_ == ProtectionType::KeyPhaseZero)
|
|
? ProtectionType::KeyPhaseOne
|
|
: ProtectionType::KeyPhaseZero;
|
|
currentOneRttReadPhaseStartPacketNum_.reset();
|
|
return true;
|
|
}
|
|
|
|
const Aead* QuicReadCodec::getOneRttReadCipher() const {
|
|
return currentOneRttReadCipher_.get();
|
|
}
|
|
|
|
const Aead* QuicReadCodec::getZeroRttReadCipher() const {
|
|
return zeroRttReadCipher_.get();
|
|
}
|
|
|
|
const Aead* QuicReadCodec::getHandshakeReadCipher() const {
|
|
return handshakeReadCipher_.get();
|
|
}
|
|
|
|
const Optional<StatelessResetToken>& QuicReadCodec::getStatelessResetToken()
|
|
const {
|
|
return statelessResetToken_;
|
|
}
|
|
|
|
CodecParameters QuicReadCodec::getCodecParameters() const {
|
|
return params_;
|
|
}
|
|
|
|
void QuicReadCodec::setInitialReadCipher(
|
|
std::unique_ptr<Aead> initialReadCipher) {
|
|
initialReadCipher_ = std::move(initialReadCipher);
|
|
}
|
|
|
|
void QuicReadCodec::setOneRttReadCipher(
|
|
std::unique_ptr<Aead> oneRttReadCipher) {
|
|
currentOneRttReadCipher_ = std::move(oneRttReadCipher);
|
|
}
|
|
|
|
void QuicReadCodec::setNextOneRttReadCipher(
|
|
std::unique_ptr<Aead> oneRttReadCipher) {
|
|
nextOneRttReadCipher_ = std::move(oneRttReadCipher);
|
|
}
|
|
|
|
void QuicReadCodec::setZeroRttReadCipher(
|
|
std::unique_ptr<Aead> zeroRttReadCipher) {
|
|
CHECK(nodeType_ == QuicNodeType::Server)
|
|
<< "Setting zero rtt read cipher on client.";
|
|
zeroRttReadCipher_ = std::move(zeroRttReadCipher);
|
|
}
|
|
|
|
void QuicReadCodec::setHandshakeReadCipher(
|
|
std::unique_ptr<Aead> handshakeReadCipher) {
|
|
handshakeReadCipher_ = std::move(handshakeReadCipher);
|
|
}
|
|
|
|
void QuicReadCodec::setInitialHeaderCipher(
|
|
std::unique_ptr<PacketNumberCipher> initialHeaderCipher) {
|
|
initialHeaderCipher_ = std::move(initialHeaderCipher);
|
|
}
|
|
|
|
void QuicReadCodec::setOneRttHeaderCipher(
|
|
std::unique_ptr<PacketNumberCipher> oneRttHeaderCipher) {
|
|
oneRttHeaderCipher_ = std::move(oneRttHeaderCipher);
|
|
}
|
|
|
|
void QuicReadCodec::setZeroRttHeaderCipher(
|
|
std::unique_ptr<PacketNumberCipher> zeroRttHeaderCipher) {
|
|
zeroRttHeaderCipher_ = std::move(zeroRttHeaderCipher);
|
|
}
|
|
|
|
void QuicReadCodec::setHandshakeHeaderCipher(
|
|
std::unique_ptr<PacketNumberCipher> handshakeHeaderCipher) {
|
|
handshakeHeaderCipher_ = std::move(handshakeHeaderCipher);
|
|
}
|
|
|
|
void QuicReadCodec::setCodecParameters(CodecParameters params) {
|
|
params_ = std::move(params);
|
|
}
|
|
|
|
void QuicReadCodec::setClientConnectionId(ConnectionId connId) {
|
|
clientConnectionId_ = connId;
|
|
}
|
|
|
|
void QuicReadCodec::setServerConnectionId(ConnectionId connId) {
|
|
serverConnectionId_ = connId;
|
|
}
|
|
|
|
void QuicReadCodec::setStatelessResetToken(
|
|
StatelessResetToken statelessResetToken) {
|
|
statelessResetToken_ = std::move(statelessResetToken);
|
|
}
|
|
|
|
void QuicReadCodec::setCryptoEqual(
|
|
std::function<bool(folly::ByteRange, folly::ByteRange)> cryptoEqual) {
|
|
cryptoEqual_ = std::move(cryptoEqual);
|
|
}
|
|
|
|
void QuicReadCodec::setConnectionStatsCallback(
|
|
QuicTransportStatsCallback* callback) {
|
|
statsCallback_ = callback;
|
|
}
|
|
|
|
const ConnectionId& QuicReadCodec::getClientConnectionId() const {
|
|
return clientConnectionId_.value();
|
|
}
|
|
|
|
const ConnectionId& QuicReadCodec::getServerConnectionId() const {
|
|
return serverConnectionId_.value();
|
|
}
|
|
|
|
const Aead* QuicReadCodec::getInitialCipher() const {
|
|
return initialReadCipher_.get();
|
|
}
|
|
|
|
const PacketNumberCipher* QuicReadCodec::getInitialHeaderCipher() const {
|
|
return initialHeaderCipher_.get();
|
|
}
|
|
|
|
const PacketNumberCipher* QuicReadCodec::getOneRttHeaderCipher() const {
|
|
return oneRttHeaderCipher_.get();
|
|
}
|
|
|
|
const PacketNumberCipher* QuicReadCodec::getHandshakeHeaderCipher() const {
|
|
return handshakeHeaderCipher_.get();
|
|
}
|
|
|
|
const PacketNumberCipher* QuicReadCodec::getZeroRttHeaderCipher() const {
|
|
return zeroRttHeaderCipher_.get();
|
|
}
|
|
|
|
void QuicReadCodec::onHandshakeDone(TimePoint handshakeDoneTime) {
|
|
if (!handshakeDoneTime_) {
|
|
handshakeDoneTime_ = handshakeDoneTime;
|
|
}
|
|
}
|
|
|
|
Optional<TimePoint> QuicReadCodec::getHandshakeDoneTime() {
|
|
return handshakeDoneTime_;
|
|
}
|
|
|
|
ProtectionType QuicReadCodec::getCurrentOneRttReadPhase() const {
|
|
return currentOneRttReadPhase_;
|
|
}
|
|
|
|
std::string QuicReadCodec::connIdToHex() const {
|
|
static ConnectionId zeroConn = zeroConnId();
|
|
const auto& serverId = serverConnectionId_.value_or(zeroConn);
|
|
const auto& clientId = clientConnectionId_.value_or(zeroConn);
|
|
return folly::to<std::string>(
|
|
"server=", serverId.hex(), " ", "client=", clientId.hex());
|
|
}
|
|
|
|
CodecResult::CodecResult(RegularQuicPacket&& regularPacketIn)
|
|
: type_(CodecResult::Type::REGULAR_PACKET) {
|
|
new (&packet) RegularQuicPacket(std::move(regularPacketIn));
|
|
}
|
|
|
|
CodecResult::CodecResult(CipherUnavailable&& cipherUnavailableIn)
|
|
: type_(CodecResult::Type::CIPHER_UNAVAILABLE) {
|
|
new (&cipher) CipherUnavailable(std::move(cipherUnavailableIn));
|
|
}
|
|
|
|
CodecResult::CodecResult(StatelessReset&& statelessResetIn)
|
|
: type_(CodecResult::Type::STATELESS_RESET) {
|
|
new (&reset) StatelessReset(std::move(statelessResetIn));
|
|
}
|
|
|
|
CodecResult::CodecResult(RetryPacket&& retryPacketIn)
|
|
: type_(CodecResult::Type::RETRY) {
|
|
new (&retry) RetryPacket(std::move(retryPacketIn));
|
|
}
|
|
|
|
CodecResult::CodecResult(Nothing&& nothing)
|
|
: type_(CodecResult::Type::NOTHING) {
|
|
new (&none) Nothing(std::move(nothing));
|
|
}
|
|
|
|
CodecResult::CodecResult(CodecError&& codecErrorIn)
|
|
: type_(CodecResult::Type::CODEC_ERROR) {
|
|
new (&error) CodecError(std::move(codecErrorIn));
|
|
}
|
|
|
|
CodecError* CodecResult::codecError() {
|
|
if (type_ == CodecResult::Type::CODEC_ERROR) {
|
|
return &error;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void CodecResult::destroyCodecResult() {
|
|
switch (type_) {
|
|
case CodecResult::Type::REGULAR_PACKET:
|
|
packet.~RegularQuicPacket();
|
|
break;
|
|
case CodecResult::Type::RETRY:
|
|
retry.~RetryPacket();
|
|
break;
|
|
case CodecResult::Type::CIPHER_UNAVAILABLE:
|
|
cipher.~CipherUnavailable();
|
|
break;
|
|
case CodecResult::Type::STATELESS_RESET:
|
|
reset.~StatelessReset();
|
|
break;
|
|
case CodecResult::Type::NOTHING:
|
|
none.~Nothing();
|
|
break;
|
|
case CodecResult::Type::CODEC_ERROR:
|
|
error.~CodecError();
|
|
break;
|
|
}
|
|
}
|
|
|
|
CodecResult::~CodecResult() {
|
|
destroyCodecResult();
|
|
}
|
|
|
|
CodecResult::CodecResult(CodecResult&& other) noexcept {
|
|
switch (other.type_) {
|
|
case CodecResult::Type::REGULAR_PACKET:
|
|
new (&packet) RegularQuicPacket(std::move(other.packet));
|
|
break;
|
|
case CodecResult::Type::RETRY:
|
|
new (&retry) RetryPacket(std::move(other.retry));
|
|
break;
|
|
case CodecResult::Type::CIPHER_UNAVAILABLE:
|
|
new (&cipher) CipherUnavailable(std::move(other.cipher));
|
|
break;
|
|
case CodecResult::Type::STATELESS_RESET:
|
|
new (&reset) StatelessReset(std::move(other.reset));
|
|
break;
|
|
case CodecResult::Type::NOTHING:
|
|
new (&none) Nothing(std::move(other.none));
|
|
break;
|
|
case CodecResult::Type::CODEC_ERROR:
|
|
new (&error) CodecError(std::move(other.error));
|
|
break;
|
|
}
|
|
type_ = other.type_;
|
|
}
|
|
|
|
CodecResult& CodecResult::operator=(CodecResult&& other) noexcept {
|
|
destroyCodecResult();
|
|
switch (other.type_) {
|
|
case CodecResult::Type::REGULAR_PACKET:
|
|
new (&packet) RegularQuicPacket(std::move(other.packet));
|
|
break;
|
|
case CodecResult::Type::RETRY:
|
|
new (&retry) RetryPacket(std::move(other.retry));
|
|
break;
|
|
case CodecResult::Type::CIPHER_UNAVAILABLE:
|
|
new (&cipher) CipherUnavailable(std::move(other.cipher));
|
|
break;
|
|
case CodecResult::Type::STATELESS_RESET:
|
|
new (&reset) StatelessReset(std::move(other.reset));
|
|
break;
|
|
case CodecResult::Type::NOTHING:
|
|
new (&none) Nothing(std::move(other.none));
|
|
break;
|
|
case CodecResult::Type::CODEC_ERROR:
|
|
new (&error) CodecError(std::move(other.error));
|
|
break;
|
|
}
|
|
type_ = other.type_;
|
|
return *this;
|
|
}
|
|
|
|
CodecResult::Type CodecResult::type() {
|
|
return type_;
|
|
}
|
|
|
|
RegularQuicPacket* CodecResult::regularPacket() {
|
|
if (type_ == CodecResult::Type::REGULAR_PACKET) {
|
|
return &packet;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
CipherUnavailable* CodecResult::cipherUnavailable() {
|
|
if (type_ == CodecResult::Type::CIPHER_UNAVAILABLE) {
|
|
return &cipher;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
StatelessReset* CodecResult::statelessReset() {
|
|
if (type_ == CodecResult::Type::STATELESS_RESET) {
|
|
return &reset;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
RetryPacket* CodecResult::retryPacket() {
|
|
if (type_ == CodecResult::Type::RETRY) {
|
|
return &retry;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Nothing* CodecResult::nothing() {
|
|
if (type_ == CodecResult::Type::NOTHING) {
|
|
return &none;
|
|
}
|
|
return nullptr;
|
|
}
|
|
} // namespace quic
|