Support sender control of ack frequency step2: (1) add min_ack_delay transport parameters. (2) client send min_ack_delay by default. protected by gfe2_reloadable_flag_quic_record_received_min_ack_delay.
PiperOrigin-RevId: 321468850
Change-Id: I71a9b8ab272400a80a1af091f92e440172885dc5
diff --git a/quic/core/crypto/transport_parameters.cc b/quic/core/crypto/transport_parameters.cc
index 330a14d..d2a0edf 100644
--- a/quic/core/crypto/transport_parameters.cc
+++ b/quic/core/crypto/transport_parameters.cc
@@ -60,6 +60,8 @@
kGoogleQuicParam = 18257, // Used for non-standard Google-specific params.
kGoogleQuicVersion =
18258, // Used to transmit version and supported_versions.
+
+ kMinAckDelay = 0xde1a // An IETF QUIC extension.
};
namespace {
@@ -127,6 +129,8 @@
return "google";
case TransportParameters::kGoogleQuicVersion:
return "google-version";
+ case TransportParameters::kMinAckDelay:
+ return "min_ack_delay_us";
}
return "Unknown(" + quiche::QuicheTextUtils::Uint64ToString(param_id) + ")";
}
@@ -158,6 +162,7 @@
case TransportParameters::kGoogleSupportHandshakeDone:
case TransportParameters::kGoogleQuicParam:
case TransportParameters::kGoogleQuicVersion:
+ case TransportParameters::kMinAckDelay:
return true;
}
return false;
@@ -435,6 +440,7 @@
rv += initial_max_streams_uni.ToString(/*for_use_in_list=*/true);
rv += ack_delay_exponent.ToString(/*for_use_in_list=*/true);
rv += max_ack_delay.ToString(/*for_use_in_list=*/true);
+ rv += min_ack_delay_us.ToString(/*for_use_in_list=*/true);
if (disable_active_migration) {
rv += " " + TransportParameterIdToString(kDisableActiveMigration);
}
@@ -513,6 +519,10 @@
kDefaultMaxAckDelayTransportParam,
0,
kMaxMaxAckDelayTransportParam),
+ min_ack_delay_us(kMinAckDelay,
+ 0,
+ 0,
+ kMaxMaxAckDelayTransportParam * kNumMicrosPerMilli),
disable_active_migration(false),
active_connection_id_limit(kActiveConnectionIdLimit,
kDefaultActiveConnectionIdLimitTransportParam,
@@ -546,6 +556,7 @@
initial_max_streams_uni(other.initial_max_streams_uni),
ack_delay_exponent(other.ack_delay_exponent),
max_ack_delay(other.max_ack_delay),
+ min_ack_delay_us(other.min_ack_delay_us),
disable_active_migration(other.disable_active_migration),
active_connection_id_limit(other.active_connection_id_limit),
initial_source_connection_id(other.initial_source_connection_id),
@@ -587,6 +598,7 @@
rhs.initial_max_streams_uni.value() &&
ack_delay_exponent.value() == rhs.ack_delay_exponent.value() &&
max_ack_delay.value() == rhs.max_ack_delay.value() &&
+ min_ack_delay_us.value() == rhs.min_ack_delay_us.value() &&
disable_active_migration == rhs.disable_active_migration &&
active_connection_id_limit.value() ==
rhs.active_connection_id_limit.value() &&
@@ -691,7 +703,7 @@
initial_max_stream_data_uni.IsValid() &&
initial_max_streams_bidi.IsValid() && initial_max_streams_uni.IsValid() &&
ack_delay_exponent.IsValid() && max_ack_delay.IsValid() &&
- active_connection_id_limit.IsValid() &&
+ min_ack_delay_us.IsValid() && active_connection_id_limit.IsValid() &&
max_datagram_frame_size.IsValid() && initial_round_trip_time_us.IsValid();
if (!ok) {
*error_details = "Invalid transport parameters " + this->ToString();
@@ -749,6 +761,7 @@
kIntegerParameterLength + // initial_max_streams_uni
kIntegerParameterLength + // ack_delay_exponent
kIntegerParameterLength + // max_ack_delay
+ kIntegerParameterLength + // min_ack_delay_us
kTypeAndValueLength + // disable_active_migration
kPreferredAddressParameterLength + // preferred_address
kIntegerParameterLength + // active_connection_id_limit
@@ -854,6 +867,7 @@
!in.initial_max_streams_uni.Write(&writer, version) ||
!in.ack_delay_exponent.Write(&writer, version) ||
!in.max_ack_delay.Write(&writer, version) ||
+ !in.min_ack_delay_us.Write(&writer, version) ||
!in.active_connection_id_limit.Write(&writer, version) ||
!in.max_datagram_frame_size.Write(&writer, version) ||
!in.initial_round_trip_time_us.Write(&writer, version)) {
@@ -1389,6 +1403,10 @@
}
}
} break;
+ case TransportParameters::kMinAckDelay:
+ parse_success =
+ out->min_ack_delay_us.Read(&value_reader, error_details);
+ break;
default:
if (out->custom_parameters.find(param_id) !=
out->custom_parameters.end()) {
diff --git a/quic/core/crypto/transport_parameters.h b/quic/core/crypto/transport_parameters.h
index 093bb09..8c3b565 100644
--- a/quic/core/crypto/transport_parameters.h
+++ b/quic/core/crypto/transport_parameters.h
@@ -170,6 +170,10 @@
// delay sending acknowledgments.
IntegerParameter max_ack_delay;
+ // Minimum amount of time in microseconds by which the endpoint will
+ // delay sending acknowledgments. Used to enable sender control of ack delay.
+ IntegerParameter min_ack_delay_us;
+
// Indicates lack of support for connection migration.
bool disable_active_migration;
diff --git a/quic/core/crypto/transport_parameters_test.cc b/quic/core/crypto/transport_parameters_test.cc
index d47ffa3..84b50f3 100644
--- a/quic/core/crypto/transport_parameters_test.cc
+++ b/quic/core/crypto/transport_parameters_test.cc
@@ -259,6 +259,7 @@
orig_params.initial_max_streams_uni.set_value(kFakeInitialMaxStreamsUni);
orig_params.ack_delay_exponent.set_value(kAckDelayExponentForTest);
orig_params.max_ack_delay.set_value(kMaxAckDelayForTest);
+ orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest);
orig_params.disable_active_migration = kFakeDisableMigration;
orig_params.preferred_address = CreateFakePreferredAddress();
orig_params.active_connection_id_limit.set_value(
@@ -294,6 +295,7 @@
orig_params.initial_max_streams_uni.set_value(kFakeInitialMaxStreamsUni);
orig_params.ack_delay_exponent.set_value(kAckDelayExponentForTest);
orig_params.max_ack_delay.set_value(kMaxAckDelayForTest);
+ orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest);
orig_params.disable_active_migration = kFakeDisableMigration;
orig_params.active_connection_id_limit.set_value(
kActiveConnectionIdLimitForTest);
@@ -342,6 +344,7 @@
orig_params.initial_max_streams_uni.set_value(kFakeInitialMaxStreamsUni);
orig_params.ack_delay_exponent.set_value(kAckDelayExponentForTest);
orig_params.max_ack_delay.set_value(kMaxAckDelayForTest);
+ orig_params.min_ack_delay_us.set_value(kMinAckDelayUsForTest);
orig_params.disable_active_migration = kFakeDisableMigration;
orig_params.preferred_address = CreateFakePreferredAddress();
orig_params.active_connection_id_limit.set_value(
@@ -477,7 +480,7 @@
TEST_P(TransportParametersTest, ParseClientParams) {
// clang-format off
const uint8_t kClientParamsOld[] = {
- 0x00, 0x7A, // length of the parameters array that follows
+ 0x00, 0x80, // length of the parameters array that follows
// max_idle_timeout
0x00, 0x01, // parameter id
0x00, 0x02, // length
@@ -518,6 +521,10 @@
0x00, 0x0b, // parameter id
0x00, 0x01, // length
0x33, // value
+ // min_ack_delay_us
+ 0xde, 0x1a, // parameter id
+ 0x00, 0x02, // length
+ 0x43, 0xe8, // value
// disable_active_migration
0x00, 0x0c, // parameter id
0x00, 0x00, // length
@@ -592,6 +599,10 @@
0x0b, // parameter id
0x01, // length
0x33, // value
+ // min_ack_delay_us
+ 0x80, 0x00, 0xde, 0x1a, // parameter id
+ 0x02, // length
+ 0x43, 0xe8, // value
// disable_active_migration
0x0c, // parameter id
0x00, // length
@@ -661,6 +672,7 @@
new_params.initial_max_streams_uni.value());
EXPECT_EQ(kAckDelayExponentForTest, new_params.ack_delay_exponent.value());
EXPECT_EQ(kMaxAckDelayForTest, new_params.max_ack_delay.value());
+ EXPECT_EQ(kMinAckDelayUsForTest, new_params.min_ack_delay_us.value());
EXPECT_EQ(kFakeDisableMigration, new_params.disable_active_migration);
EXPECT_EQ(kActiveConnectionIdLimitForTest,
new_params.active_connection_id_limit.value());
@@ -843,7 +855,7 @@
TEST_P(TransportParametersTest, ParseServerParams) {
// clang-format off
const uint8_t kServerParamsOld[] = {
- 0x00, 0xd3, // length of parameters array that follows
+ 0x00, 0xd9, // length of parameters array that follows
// original_destination_connection_id
0x00, 0x00, // parameter id
0x00, 0x08, // length
@@ -893,6 +905,10 @@
0x00, 0x0b, // parameter id
0x00, 0x01, // length
0x33, // value
+ // min_ack_delay_us
+ 0xde, 0x1a, // parameter id
+ 0x00, 0x02, // length
+ 0x43, 0xe8, // value
// disable_active_migration
0x00, 0x0c, // parameter id
0x00, 0x00, // length
@@ -987,6 +1003,10 @@
0x0b, // parameter id
0x01, // length
0x33, // value
+ // min_ack_delay_us
+ 0x80, 0x00, 0xde, 0x1a, // parameter id
+ 0x02, // length
+ 0x43, 0xe8, // value
// disable_active_migration
0x0c, // parameter id
0x00, // length
@@ -1072,6 +1092,7 @@
new_params.initial_max_streams_uni.value());
EXPECT_EQ(kAckDelayExponentForTest, new_params.ack_delay_exponent.value());
EXPECT_EQ(kMaxAckDelayForTest, new_params.max_ack_delay.value());
+ EXPECT_EQ(kMinAckDelayUsForTest, new_params.min_ack_delay_us.value());
EXPECT_EQ(kFakeDisableMigration, new_params.disable_active_migration);
ASSERT_NE(nullptr, new_params.preferred_address.get());
EXPECT_EQ(CreateFakeV4SocketAddress(),
@@ -1297,6 +1318,7 @@
kFakeInitialMaxStreamsUni);
original_params_.ack_delay_exponent.set_value(kAckDelayExponentForTest);
original_params_.max_ack_delay.set_value(kMaxAckDelayForTest);
+ original_params_.min_ack_delay_us.set_value(kMinAckDelayUsForTest);
original_params_.disable_active_migration = kFakeDisableMigration;
original_params_.preferred_address = CreateFakePreferredAddress();
original_params_.active_connection_id_limit.set_value(
diff --git a/quic/core/quic_config.cc b/quic/core/quic_config.cc
index d7a9f00..0050649 100644
--- a/quic/core/quic_config.cc
+++ b/quic/core/quic_config.cc
@@ -452,6 +452,7 @@
alternate_server_address_ipv4_(kASAD, PRESENCE_OPTIONAL),
stateless_reset_token_(kSRST, PRESENCE_OPTIONAL),
max_ack_delay_ms_(kMAD, PRESENCE_OPTIONAL),
+ min_ack_delay_ms_(0, PRESENCE_OPTIONAL),
ack_delay_exponent_(kADE, PRESENCE_OPTIONAL),
max_udp_payload_size_(0, PRESENCE_OPTIONAL),
max_datagram_frame_size_(0, PRESENCE_OPTIONAL),
@@ -590,7 +591,7 @@
}
void QuicConfig::SetMaxAckDelayToSendMs(uint32_t max_ack_delay_ms) {
- return max_ack_delay_ms_.SetSendValue(max_ack_delay_ms);
+ max_ack_delay_ms_.SetSendValue(max_ack_delay_ms);
}
uint32_t QuicConfig::GetMaxAckDelayToSendMs() const {
@@ -605,6 +606,22 @@
return max_ack_delay_ms_.GetReceivedValue();
}
+void QuicConfig::SetMinAckDelayMs(uint32_t min_ack_delay_ms) {
+ min_ack_delay_ms_.SetSendValue(min_ack_delay_ms);
+}
+
+uint32_t QuicConfig::GetMinAckDelayToSendMs() const {
+ return min_ack_delay_ms_.GetSendValue();
+}
+
+bool QuicConfig::HasReceivedMinAckDelayMs() const {
+ return min_ack_delay_ms_.HasReceivedValue();
+}
+
+uint32_t QuicConfig::ReceivedMinAckDelayMs() const {
+ return min_ack_delay_ms_.GetReceivedValue();
+}
+
void QuicConfig::SetAckDelayExponentToSend(uint32_t exponent) {
ack_delay_exponent_.SetSendValue(exponent);
}
@@ -1166,6 +1183,10 @@
params->initial_max_streams_uni.set_value(
GetMaxUnidirectionalStreamsToSend());
params->max_ack_delay.set_value(GetMaxAckDelayToSendMs());
+ if (min_ack_delay_ms_.HasSendValue()) {
+ params->min_ack_delay_us.set_value(min_ack_delay_ms_.GetSendValue() *
+ kNumMicrosPerMilli);
+ }
params->ack_delay_exponent.set_value(GetAckDelayExponentToSend());
params->disable_active_migration =
connection_migration_disabled_.HasSendValue() &&
@@ -1310,6 +1331,17 @@
params.preferred_address->ipv4_socket_address);
}
}
+ if (GetQuicReloadableFlag(quic_record_received_min_ack_delay)) {
+ if (params.min_ack_delay_us.value() != 0) {
+ if (params.min_ack_delay_us.value() >
+ params.max_ack_delay.value() * kNumMicrosPerMilli) {
+ *error_details = "MinAckDelay is greater than MaxAckDelay.";
+ return IETF_QUIC_PROTOCOL_VIOLATION;
+ }
+ min_ack_delay_ms_.SetReceivedValue(params.min_ack_delay_us.value() /
+ kNumMicrosPerMilli);
+ }
+ }
}
if (params.disable_active_migration) {
diff --git a/quic/core/quic_config.h b/quic/core/quic_config.h
index 6f041b8..1c329dc 100644
--- a/quic/core/quic_config.h
+++ b/quic/core/quic_config.h
@@ -420,6 +420,14 @@
bool HasReceivedMaxAckDelayMs() const;
uint32_t ReceivedMaxAckDelayMs() const;
+ // Manage the IETF QUIC extension Min Ack Delay transport parameter.
+ // An endpoint uses min_ack_delay to advsertise its support for
+ // AckFrequencyFrame sent by peer.
+ void SetMinAckDelayMs(uint32_t min_ack_delay_ms);
+ uint32_t GetMinAckDelayToSendMs() const;
+ bool HasReceivedMinAckDelayMs() const;
+ uint32_t ReceivedMinAckDelayMs() const;
+
void SetAckDelayExponentToSend(uint32_t exponent);
uint32_t GetAckDelayExponentToSend() const;
bool HasReceivedAckDelayExponent() const;
@@ -596,6 +604,10 @@
// Uses the max_ack_delay transport parameter in IETF QUIC.
QuicFixedUint32 max_ack_delay_ms_;
+ // Minimum ack delay. Used to enable sender control of max_ack_delay.
+ // Uses the min_ack_delay transport parameter in IETF QUIC extension.
+ QuicFixedUint32 min_ack_delay_ms_;
+
// The sent exponent is the exponent that this node uses when serializing an
// ACK frame (and the peer should use when deserializing the frame);
// the received exponent is the value the peer uses to serialize frames and
diff --git a/quic/core/quic_config_test.cc b/quic/core/quic_config_test.cc
index 41dd2f8..6c1d829 100644
--- a/quic/core/quic_config_test.cc
+++ b/quic/core/quic_config_test.cc
@@ -8,6 +8,7 @@
#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h"
#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
+#include "net/third_party/quiche/src/quic/core/quic_constants.h"
#include "net/third_party/quiche/src/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/quic/core/quic_time.h"
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
@@ -428,6 +429,30 @@
config_.IdleNetworkTimeout());
}
+TEST_P(QuicConfigTest, RecievedInvalidMinAckDelayInTransportParameter) {
+ if (!version_.UsesTls()) {
+ // TransportParameters are only used for QUIC+TLS.
+ return;
+ }
+ SetQuicReloadableFlag(quic_record_received_min_ack_delay, true);
+ TransportParameters params;
+
+ params.max_ack_delay.set_value(25 /*ms*/);
+ params.min_ack_delay_us.set_value(25 * kNumMicrosPerMilli + 1);
+ std::string error_details = "foobar";
+ EXPECT_THAT(config_.ProcessTransportParameters(
+ params, SERVER, /* is_resumption = */ false, &error_details),
+ IsError(IETF_QUIC_PROTOCOL_VIOLATION));
+ EXPECT_EQ("MinAckDelay is greater than MaxAckDelay.", error_details);
+
+ params.max_ack_delay.set_value(25 /*ms*/);
+ params.min_ack_delay_us.set_value(25 * kNumMicrosPerMilli);
+ EXPECT_THAT(config_.ProcessTransportParameters(
+ params, SERVER, /* is_resumption = */ false, &error_details),
+ IsQuicNoError());
+ EXPECT_TRUE(error_details.empty());
+}
+
TEST_P(QuicConfigTest, FillTransportParams) {
if (!version_.UsesTls()) {
// TransportParameters are only used for QUIC+TLS.
@@ -446,6 +471,7 @@
config_.SetOriginalConnectionIdToSend(TestConnectionId(0x1111));
config_.SetInitialSourceConnectionIdToSend(TestConnectionId(0x2222));
config_.SetRetrySourceConnectionIdToSend(TestConnectionId(0x3333));
+ config_.SetMinAckDelayMs(kDefaultMinAckDelayTimeMs);
TransportParameters params;
config_.FillTransportParameters(¶ms);
@@ -475,6 +501,10 @@
ASSERT_TRUE(params.retry_source_connection_id.has_value());
EXPECT_EQ(TestConnectionId(0x3333),
params.retry_source_connection_id.value());
+
+ EXPECT_EQ(
+ static_cast<uint64_t>(kDefaultMinAckDelayTimeMs) * kNumMicrosPerMilli,
+ params.min_ack_delay_us.value());
}
TEST_P(QuicConfigTest, ProcessTransportParametersServer) {
@@ -495,6 +525,7 @@
params.initial_max_streams_bidi.set_value(kDefaultMaxStreamsPerConnection);
params.stateless_reset_token = CreateStatelessResetTokenForTest();
params.max_ack_delay.set_value(kMaxAckDelayForTest);
+ params.min_ack_delay_us.set_value(kMinAckDelayUsForTest);
params.ack_delay_exponent.set_value(kAckDelayExponentForTest);
params.active_connection_id_limit.set_value(kActiveConnectionIdLimitForTest);
params.original_destination_connection_id = TestConnectionId(0x1111);
@@ -541,6 +572,7 @@
EXPECT_FALSE(config_.HasReceivedStatelessResetToken());
EXPECT_FALSE(config_.HasReceivedMaxAckDelayMs());
EXPECT_FALSE(config_.HasReceivedAckDelayExponent());
+ EXPECT_FALSE(config_.HasReceivedMinAckDelayMs());
EXPECT_FALSE(config_.HasReceivedOriginalConnectionId());
EXPECT_FALSE(config_.HasReceivedInitialSourceConnectionId());
EXPECT_FALSE(config_.HasReceivedRetrySourceConnectionId());
@@ -597,9 +629,18 @@
EXPECT_TRUE(config_.PeerSupportsHandshakeDone());
ASSERT_TRUE(config_.HasReceivedStatelessResetToken());
+
ASSERT_TRUE(config_.HasReceivedMaxAckDelayMs());
EXPECT_EQ(config_.ReceivedMaxAckDelayMs(), kMaxAckDelayForTest);
+ if (GetQuicReloadableFlag(quic_record_received_min_ack_delay)) {
+ ASSERT_TRUE(config_.HasReceivedMinAckDelayMs());
+ EXPECT_EQ(config_.ReceivedMinAckDelayMs(),
+ kMinAckDelayUsForTest / kNumMicrosPerMilli);
+ } else {
+ ASSERT_FALSE(config_.HasReceivedMinAckDelayMs());
+ }
+
ASSERT_TRUE(config_.HasReceivedAckDelayExponent());
EXPECT_EQ(config_.ReceivedAckDelayExponent(), kAckDelayExponentForTest);
diff --git a/quic/core/quic_constants.h b/quic/core/quic_constants.h
index 51ad0f0..6da3151 100644
--- a/quic/core/quic_constants.h
+++ b/quic/core/quic_constants.h
@@ -123,6 +123,10 @@
// in low-bandwidth (< ~384 kbps), where an ack is sent per packet.
const int64_t kDefaultDelayedAckTimeMs = 25;
+// Default minimum delayed ack time, in ms (used only for sender control of ack
+// frequency).
+const uint32_t kDefaultMinAckDelayTimeMs = 1;
+
// Default shift of the ACK delay in the IETF QUIC ACK frame.
const uint32_t kDefaultAckDelayExponent = 3;
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index d63006c..0a9f872 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -120,11 +120,14 @@
connection_->SetSessionNotifier(this);
connection_->SetDataProducer(this);
connection_->SetFromConfig(config_);
- if (GetQuicReloadableFlag(quic_support_handshake_done_in_t050) &&
- version().UsesTls() && !version().HasHandshakeDone() &&
- perspective_ == Perspective::IS_CLIENT) {
- config_.SetSupportHandshakeDone();
+ if (perspective() == Perspective::IS_CLIENT && version().UsesTls()) {
+ config_.SetMinAckDelayMs(kDefaultMinAckDelayTimeMs);
+ if (GetQuicReloadableFlag(quic_support_handshake_done_in_t050) &&
+ !version().HasHandshakeDone()) {
+ config_.SetSupportHandshakeDone();
+ }
}
+
// On the server side, version negotiation has been done by the dispatcher,
// and the server session is created with the right version.
if (perspective() == Perspective::IS_SERVER) {
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 54c7bfb..63a2bb1 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -2207,6 +2207,14 @@
session_.OnStreamFrame(frame);
}
+TEST_P(QuicSessionTestClient, MinAckDelaySetOnTheClientQuicConfig) {
+ if (!VersionUsesHttp3(transport_version())) {
+ return;
+ }
+ ASSERT_EQ(session_.config()->GetMinAckDelayToSendMs(),
+ kDefaultMinAckDelayTimeMs);
+}
+
TEST_P(QuicSessionTestServer, ZombieStreams) {
TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
QuicStreamPeer::SetStreamBytesWritten(3, stream2);
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index 2b483b5..27799bb 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -63,8 +63,9 @@
enum : uint64_t {
kAckDelayExponentForTest = 10,
- kMaxAckDelayForTest = 51,
+ kMaxAckDelayForTest = 51, // ms
kActiveConnectionIdLimitForTest = 52,
+ kMinAckDelayUsForTest = 1000
};
// Create an arbitrary stateless reset token, same across multiple calls.