1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-04-18 17:24:03 +03:00
mvfst/quic/api/QuicAckScheduler.cpp
Joseph Beshay c6d8f76e67 Refactor AckScheduler to use it in both PacketScheduler and PacketRebuilder
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
2025-02-24 12:32:50 -08:00

134 lines
4.7 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>
namespace quic {
bool hasAcksToSchedule(const AckState& ackState) {
Optional<PacketNum> largestAckSend = largestAckToSend(ackState);
if (!largestAckSend) {
return false;
}
if (!ackState.largestAckScheduled) {
// Never scheduled an ack, we need to send
return true;
}
return *largestAckSend > *(ackState.largestAckScheduled);
}
Optional<PacketNum> largestAckToSend(const AckState& ackState) {
if (ackState.acks.empty()) {
return none;
}
return ackState.acks.back().end;
}
AckScheduler::AckScheduler(
const QuicConnectionStateBase& conn,
const AckState& ackState)
: conn_(conn), ackState_(ackState) {}
Optional<PacketNum> AckScheduler::writeNextAcks(
PacketBuilderInterface& builder) {
// Use default ack delay for long headers. Usually long headers are sent
// before crypto negotiation, so the peer might not know about the ack delay
// exponent yet, so we use the default.
uint8_t ackDelayExponentToUse =
builder.getPacketHeader().getHeaderForm() == HeaderForm::Long
? kDefaultAckDelayExponent
: conn_.transportSettings.ackDelayExponent;
auto largestAckedPacketNum = *largestAckToSend(ackState_);
auto ackingTime = Clock::now();
DCHECK(ackState_.largestRecvdPacketTime.hasValue())
<< "Missing received time for the largest acked packet";
// assuming that we're going to ack the largest received with highest pri
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>(ackDelayExponentToUse), /* ackDelayExponent */
conn_.connectionTime, /* connect timestamp */
};
Optional<WriteAckFrameResult> ackWriteResult;
bool isAckReceiveTimestampsSupported =
conn_.transportSettings.maybeAckReceiveTimestampsConfigSentToPeer &&
conn_.maybePeerAckReceiveTimestampsConfig;
uint64_t peerRequestedTimestampsCount =
conn_.maybePeerAckReceiveTimestampsConfig.has_value()
? conn_.maybePeerAckReceiveTimestampsConfig.value()
.maxReceiveTimestampsPerAck
: 0;
uint64_t extendedAckSupportedAndEnabled =
conn_.peerAdvertisedExtendedAckFeatures &
conn_.transportSettings.enableExtendedAckFeatures;
// Disable the ECN fields if we are not reading them
if (!conn_.transportSettings.readEcnOnIngress) {
extendedAckSupportedAndEnabled &= ~static_cast<ExtendedAckFeatureMaskType>(
ExtendedAckFeatureMask::ECN_COUNTS);
}
// Disable the receive timestamps fields if we have not regoatiated receive
// timestamps support
if (!isAckReceiveTimestampsSupported || (peerRequestedTimestampsCount == 0)) {
extendedAckSupportedAndEnabled &= ~static_cast<ExtendedAckFeatureMaskType>(
ExtendedAckFeatureMask::RECEIVE_TIMESTAMPS);
}
if (extendedAckSupportedAndEnabled > 0) {
// The peer supports extended ACKs and we have them enabled.
ackWriteResult = writeAckFrame(
meta,
builder,
FrameType::ACK_EXTENDED,
conn_.transportSettings.maybeAckReceiveTimestampsConfigSentToPeer
.value_or(AckReceiveTimestampsConfig()),
peerRequestedTimestampsCount,
extendedAckSupportedAndEnabled);
} else if (
conn_.transportSettings.readEcnOnIngress &&
(meta.ackState.ecnECT0CountReceived ||
meta.ackState.ecnECT1CountReceived ||
meta.ackState.ecnCECountReceived)) {
// We have to report ECN counts, but we can't use the extended ACK
// frame. In this case, we give ACK_ECN precedence over
// ACK_RECEIVE_TIMESTAMPS.
ackWriteResult = writeAckFrame(meta, builder, FrameType::ACK_ECN);
} else if (
isAckReceiveTimestampsSupported && (peerRequestedTimestampsCount > 0)) {
// Use ACK_RECEIVE_TIMESTAMPS if its enabled on both endpoints AND the
// peer requests at least 1 timestamp
ackWriteResult = writeAckFrame(
meta,
builder,
FrameType::ACK_RECEIVE_TIMESTAMPS,
conn_.transportSettings.maybeAckReceiveTimestampsConfigSentToPeer
.value(),
peerRequestedTimestampsCount);
} else {
ackWriteResult = writeAckFrame(meta, builder, FrameType::ACK);
}
if (!ackWriteResult) {
return none;
}
return largestAckedPacketNum;
}
bool AckScheduler::hasPendingAcks() const {
return hasAcksToSchedule(ackState_);
}
} // namespace quic