mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-09 20:42:44 +03:00
Summary: This replaces three separate functions for writing ACK, ACK_ECN, and ACK_RECEIVE_TIMESTAMPS with one function that can write all three. Previously, we have two paths: 1. Default path which writes the base ACK frame and optionally adds the ECN counts if the frame type is ACK_ECN. 2. If ACK_RECEIVE_TIMESTAMPS are supported, another path would take the ART config and write that frame with the ART fields. Path #2 does not support ECN marks. This change consolidates ACK writing into a single path which takes ART config and frame type. It decides which fields to include based upon the type and config passed. This change does not add any new frame types but it prepares for one that can carry both ECN counts and ART fields. Differential Revision: D68931147 fbshipit-source-id: 47b425b30f00b6c76574bc768d0ec249c60a0aa7
336 lines
13 KiB
C++
336 lines
13 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/QuicPacketRebuilder.h>
|
|
#include <quic/codec/QuicWriteCodec.h>
|
|
#include <quic/flowcontrol/QuicFlowController.h>
|
|
#include <quic/state/QuicStateFunctions.h>
|
|
#include <quic/state/QuicStreamFunctions.h>
|
|
#include <quic/state/SimpleFrameFunctions.h>
|
|
|
|
namespace quic {
|
|
|
|
PacketRebuilder::PacketRebuilder(
|
|
PacketBuilderInterface& regularBuilder,
|
|
QuicConnectionStateBase& conn)
|
|
: builder_(regularBuilder), conn_(conn) {}
|
|
|
|
uint64_t PacketRebuilder::getHeaderBytes() const {
|
|
return builder_.getHeaderBytes();
|
|
}
|
|
|
|
ClonedPacketIdentifier PacketRebuilder::cloneOutstandingPacket(
|
|
OutstandingPacketWrapper& packet) {
|
|
// Either the packet has never been cloned before, or it's
|
|
// maybeClonedPacketIdentifier is still in the
|
|
// outstandings.clonedPacketIdentifiers set.
|
|
DCHECK(
|
|
!packet.maybeClonedPacketIdentifier ||
|
|
conn_.outstandings.clonedPacketIdentifiers.count(
|
|
*packet.maybeClonedPacketIdentifier));
|
|
if (!packet.maybeClonedPacketIdentifier) {
|
|
auto packetNum = packet.packet.header.getPacketSequenceNum();
|
|
auto packetNumberSpace = packet.packet.header.getPacketNumberSpace();
|
|
ClonedPacketIdentifier event(packetNumberSpace, packetNum);
|
|
DCHECK(!conn_.outstandings.clonedPacketIdentifiers.count(event));
|
|
packet.maybeClonedPacketIdentifier = event;
|
|
conn_.outstandings.clonedPacketIdentifiers.insert(event);
|
|
++conn_.outstandings
|
|
.clonedPacketCount[packet.packet.header.getPacketNumberSpace()];
|
|
}
|
|
return *packet.maybeClonedPacketIdentifier;
|
|
}
|
|
|
|
Optional<ClonedPacketIdentifier> PacketRebuilder::rebuildFromPacket(
|
|
OutstandingPacketWrapper& packet) {
|
|
// TODO: if PMTU changes between the transmission of the original packet and
|
|
// now, then we cannot clone everything in the packet.
|
|
|
|
bool writeSuccess = false;
|
|
bool windowUpdateWritten = false;
|
|
bool shouldWriteWindowUpdate = false;
|
|
bool notPureAck = false;
|
|
bool shouldRebuildWriteAckFrame = false;
|
|
auto encryptionLevel =
|
|
protectionTypeToEncryptionLevel(packet.packet.header.getProtectionType());
|
|
// First check if there's an ACK in this packet. We do this because we need
|
|
// to know before we rebuild a stream frame whether there is an ACK in this
|
|
// packet. If there is an ACK, we have to always encode the stream frame's
|
|
// length. This forces the maybeClonedPacketIdentifier code to reconsider the
|
|
// packet for ACK processing. We should always be able to write an ACK since
|
|
// the min ACK frame size is 4, while 1500 MTU stream frame lengths are going
|
|
// to be 2 bytes maximum.
|
|
bool hasAckFrame = false;
|
|
for (const auto& frame : packet.packet.frames) {
|
|
if (frame.asWriteAckFrame()) {
|
|
hasAckFrame = true;
|
|
break;
|
|
}
|
|
}
|
|
for (auto iter = packet.packet.frames.cbegin();
|
|
iter != packet.packet.frames.cend();
|
|
iter++) {
|
|
bool lastFrame = iter == packet.packet.frames.cend() - 1;
|
|
const QuicWriteFrame& frame = *iter;
|
|
switch (frame.type()) {
|
|
case QuicWriteFrame::Type::WriteAckFrame: {
|
|
// We need to rebuild this WriteAckFrame with fresh AckStats
|
|
// which may make the packet larger. We keep track of this
|
|
// for now and rebuild the frame after the loop.
|
|
shouldRebuildWriteAckFrame = true;
|
|
continue;
|
|
}
|
|
case QuicWriteFrame::Type::WriteStreamFrame: {
|
|
const WriteStreamFrame& streamFrame = *frame.asWriteStreamFrame();
|
|
auto stream = conn_.streamManager->getStream(streamFrame.streamId);
|
|
if (stream && retransmittable(*stream)) {
|
|
auto streamData = cloneRetransmissionBuffer(streamFrame, stream);
|
|
auto bufferLen = streamData ? streamData->chainLength() : 0;
|
|
auto dataLen = writeStreamFrameHeader(
|
|
builder_,
|
|
streamFrame.streamId,
|
|
streamFrame.offset,
|
|
bufferLen,
|
|
bufferLen,
|
|
streamFrame.fin,
|
|
// It's safe to skip the length if it was the last frame in the
|
|
// original packet and there's no ACK frame. Since we put the ACK
|
|
// frame last we need to end the stream frame in that case.
|
|
lastFrame && bufferLen && !hasAckFrame,
|
|
streamFrame.streamGroupId);
|
|
bool ret = dataLen.has_value() && *dataLen == streamFrame.len;
|
|
if (ret) {
|
|
// Writing 0 byte for stream data is legit if the stream frame has
|
|
// FIN. That's checked in writeStreamFrameHeader.
|
|
CHECK(streamData || streamFrame.fin);
|
|
if (streamData) {
|
|
writeStreamFrameData(builder_, *streamData, *dataLen);
|
|
}
|
|
notPureAck = true;
|
|
writeSuccess = true;
|
|
break;
|
|
}
|
|
writeSuccess = false;
|
|
break;
|
|
}
|
|
// If a stream is already Closed, we should not clone and resend this
|
|
// stream data. But should we abort the cloning of this packet and
|
|
// move on to the next packet? I'm gonna err on the aggressive side
|
|
// for now and call it success.
|
|
writeSuccess = true;
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::WriteCryptoFrame: {
|
|
const WriteCryptoFrame& cryptoFrame = *frame.asWriteCryptoFrame();
|
|
auto stream = getCryptoStream(*conn_.cryptoState, encryptionLevel);
|
|
auto buf = cloneCryptoRetransmissionBuffer(cryptoFrame, *stream);
|
|
|
|
// No crypto data found to be cloned, just skip
|
|
if (!buf) {
|
|
writeSuccess = true;
|
|
break;
|
|
}
|
|
auto cryptoWriteResult =
|
|
writeCryptoFrame(cryptoFrame.offset, *buf, builder_);
|
|
bool ret = cryptoWriteResult.has_value() &&
|
|
cryptoWriteResult->offset == cryptoFrame.offset &&
|
|
cryptoWriteResult->len == cryptoFrame.len;
|
|
notPureAck |= ret;
|
|
writeSuccess = ret;
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::MaxDataFrame: {
|
|
shouldWriteWindowUpdate = true;
|
|
auto ret = 0 != writeFrame(generateMaxDataFrame(conn_), builder_);
|
|
windowUpdateWritten |= ret;
|
|
notPureAck |= ret;
|
|
writeSuccess = true;
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::MaxStreamDataFrame: {
|
|
const MaxStreamDataFrame& maxStreamDataFrame =
|
|
*frame.asMaxStreamDataFrame();
|
|
auto stream =
|
|
conn_.streamManager->getStream(maxStreamDataFrame.streamId);
|
|
if (!stream || !stream->shouldSendFlowControl()) {
|
|
writeSuccess = true;
|
|
break;
|
|
}
|
|
shouldWriteWindowUpdate = true;
|
|
auto ret =
|
|
0 != writeFrame(generateMaxStreamDataFrame(*stream), builder_);
|
|
windowUpdateWritten |= ret;
|
|
notPureAck |= ret;
|
|
writeSuccess = true;
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::PaddingFrame: {
|
|
const PaddingFrame& paddingFrame = *frame.asPaddingFrame();
|
|
writeSuccess = writeFrame(paddingFrame, builder_) != 0;
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::PingFrame: {
|
|
const PingFrame& pingFrame = *frame.asPingFrame();
|
|
writeSuccess = writeFrame(pingFrame, builder_) != 0;
|
|
notPureAck |= writeSuccess;
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::QuicSimpleFrame: {
|
|
const QuicSimpleFrame& simpleFrame = *frame.asQuicSimpleFrame();
|
|
auto updatedSimpleFrame =
|
|
updateSimpleFrameOnPacketClone(conn_, simpleFrame);
|
|
if (!updatedSimpleFrame) {
|
|
writeSuccess = true;
|
|
break;
|
|
}
|
|
bool ret =
|
|
writeSimpleFrame(std::move(*updatedSimpleFrame), builder_) != 0;
|
|
notPureAck |= ret;
|
|
writeSuccess = ret;
|
|
break;
|
|
}
|
|
case QuicWriteFrame::Type::DatagramFrame:
|
|
// Do not clone Datagram frames. If datagram frame is the only frame in
|
|
// the packet, notPureAck will be false, and the function will return
|
|
// none correctly.
|
|
writeSuccess = true;
|
|
break;
|
|
default: {
|
|
bool ret = writeFrame(QuicWriteFrame(frame), builder_) != 0;
|
|
notPureAck |= ret;
|
|
writeSuccess = ret;
|
|
break;
|
|
}
|
|
}
|
|
if (!writeSuccess) {
|
|
return none;
|
|
}
|
|
}
|
|
// If this packet had a WriteAckFrame, build a new one it with
|
|
// fresh AckState on best-effort basis. If writing
|
|
// that ACK fails, just ignore it and use the rest of the
|
|
// cloned packet.
|
|
if (shouldRebuildWriteAckFrame) {
|
|
auto& packetHeader = builder_.getPacketHeader();
|
|
uint64_t ackDelayExponent =
|
|
(packetHeader.getHeaderForm() == HeaderForm::Long)
|
|
? kDefaultAckDelayExponent
|
|
: conn_.transportSettings.ackDelayExponent;
|
|
const AckState& ackState_ = getAckState(
|
|
conn_,
|
|
protectionTypeToPacketNumberSpace(packetHeader.getProtectionType()));
|
|
auto ackingTime = Clock::now();
|
|
DCHECK(ackState_.largestRecvdPacketTime.hasValue())
|
|
<< "Missing received time for the largest acked packet";
|
|
auto receivedTime = *ackState_.largestRecvdPacketTime;
|
|
std::chrono::microseconds ackDelay =
|
|
(ackingTime > receivedTime
|
|
? std::chrono::duration_cast<std::chrono::microseconds>(
|
|
ackingTime - receivedTime)
|
|
: 0us);
|
|
|
|
WriteAckFrameMetaData meta = {
|
|
ackState_, /* ackState*/
|
|
ackDelay, /* ackDelay */
|
|
static_cast<uint8_t>(ackDelayExponent), /* ackDelayExponent */
|
|
conn_.connectionTime, /* connect timestamp */
|
|
};
|
|
|
|
Optional<WriteAckFrameResult> ackWriteResult;
|
|
|
|
uint64_t peerRequestedTimestampsCount =
|
|
conn_.maybePeerAckReceiveTimestampsConfig.has_value()
|
|
? conn_.maybePeerAckReceiveTimestampsConfig.value()
|
|
.maxReceiveTimestampsPerAck
|
|
: 0;
|
|
|
|
// Write the AckFrame ignoring the result. This is best-effort.
|
|
bool isAckReceiveTimestampsSupported =
|
|
conn_.transportSettings.maybeAckReceiveTimestampsConfigSentToPeer &&
|
|
conn_.maybePeerAckReceiveTimestampsConfig;
|
|
|
|
if (!isAckReceiveTimestampsSupported || !peerRequestedTimestampsCount) {
|
|
writeAckFrame(meta, builder_, FrameType::ACK);
|
|
} else {
|
|
writeAckFrame(
|
|
meta,
|
|
builder_,
|
|
FrameType::ACK_RECEIVE_TIMESTAMPS,
|
|
conn_.transportSettings.maybeAckReceiveTimestampsConfigSentToPeer
|
|
.value(),
|
|
peerRequestedTimestampsCount);
|
|
}
|
|
}
|
|
// We shouldn't clone if:
|
|
// (1) we only end up cloning only acks, ping, or paddings.
|
|
// (2) we should write window update, but didn't, and wrote nothing else.
|
|
if (!notPureAck ||
|
|
(shouldWriteWindowUpdate && !windowUpdateWritten && !writeSuccess)) {
|
|
return none;
|
|
}
|
|
|
|
if (encryptionLevel == EncryptionLevel::Initial) {
|
|
// Pad anything else that's left.
|
|
while (builder_.remainingSpaceInPkt() > 0) {
|
|
writeFrame(PaddingFrame(), builder_);
|
|
}
|
|
}
|
|
|
|
return cloneOutstandingPacket(packet);
|
|
}
|
|
|
|
const ChainedByteRangeHead* PacketRebuilder::cloneCryptoRetransmissionBuffer(
|
|
const WriteCryptoFrame& frame,
|
|
const QuicCryptoStream& stream) {
|
|
/**
|
|
* Crypto's StreamBuffer is removed from retransmissionBuffer in 2 cases.
|
|
* 1: Packet containing the buffer gets acked.
|
|
* 2: Packet containing the buffer is marked loss.
|
|
* They have to be covered by making sure we do not clone an already acked or
|
|
* lost packet.
|
|
*/
|
|
DCHECK(frame.len) << "WriteCryptoFrame cloning: frame is empty. " << conn_;
|
|
auto iter = stream.retransmissionBuffer.find(frame.offset);
|
|
|
|
// If the crypto stream is canceled somehow, just skip cloning this frame
|
|
if (iter == stream.retransmissionBuffer.end()) {
|
|
return nullptr;
|
|
}
|
|
DCHECK(iter->second->offset == frame.offset)
|
|
<< "WriteCryptoFrame cloning: offset mismatch. " << conn_;
|
|
DCHECK(iter->second->data.chainLength() == frame.len)
|
|
<< "WriteCryptoFrame cloning: Len mismatch. " << conn_;
|
|
return &(iter->second->data);
|
|
}
|
|
|
|
const ChainedByteRangeHead* PacketRebuilder::cloneRetransmissionBuffer(
|
|
const WriteStreamFrame& frame,
|
|
const QuicStreamState* stream) {
|
|
/**
|
|
* StreamBuffer is removed from retransmissionBuffer in 3 cases.
|
|
* 1: After send or receive RST.
|
|
* 2: Packet containing the buffer gets acked.
|
|
* 3: Packet containing the buffer is marked loss.
|
|
*
|
|
* Checking retransmittable() should cover first case. The latter three cases
|
|
* have to be covered by making sure we do not clone an already acked, lost or
|
|
* skipped packet.
|
|
*/
|
|
DCHECK(stream);
|
|
DCHECK(retransmittable(*stream));
|
|
auto iter = stream->retransmissionBuffer.find(frame.offset);
|
|
if (iter != stream->retransmissionBuffer.end()) {
|
|
DCHECK(!frame.len || !iter->second->data.empty())
|
|
<< "WriteStreamFrame cloning: frame is not empty but StreamBuffer has"
|
|
<< " empty data. " << conn_;
|
|
return frame.len ? &(iter->second->data) : nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace quic
|