mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-04-18 17:24:03 +03:00
Summary: The logic for deciding which ACK type to write was duplicated in QuicPacketScheduler and QuicPacketRebuilder. This refactors the logic out into a separate QuicAckScheduler so it can be tested for correction and reused in both places. Reviewed By: sharmafb Differential Revision: D69933311 fbshipit-source-id: e4f45688a5d258dd2a57f9f7844407f3efad5f49
294 lines
11 KiB
C++
294 lines
11 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/api/QuicAckScheduler.h>
|
|
#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();
|
|
const AckState& ackState = getAckState(
|
|
conn_,
|
|
protectionTypeToPacketNumberSpace(packetHeader.getProtectionType()));
|
|
AckScheduler ackScheduler(conn_, ackState);
|
|
ackScheduler.writeNextAcks(builder_);
|
|
}
|
|
// 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
|