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
134 lines
4.7 KiB
C++
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
|