Add support for the latency spin bit (RFC 9000 section 17.4) to Quiche, storing spin state on a per-path basis Protected by FLAGS_quic_reloadable_flag_quic_enable_spin_bit. PiperOrigin-RevId: 885955655
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h index 709670c..ed32c81 100755 --- a/quiche/common/quiche_feature_flags_list.h +++ b/quiche/common/quiche_feature_flags_list.h
@@ -38,6 +38,7 @@ QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_disable_resumption, true, true, "If true, disable resumption when receiving NRES connection option.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_mtu_discovery_at_server, false, false, "If true, QUIC will default enable MTU discovery at server, with a target of 1450 bytes.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_server_on_wire_ping, true, true, "If true, enable server retransmittable on wire PING.") +QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_spin_bit, false, false, "When true, enable the QUIC latency spin bit (RFC 9000 section 17.3) on 3/4 of QUIC connections.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_version_rfcv2, false, false, "When true, support RFC9369.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enforce_immediate_goaway, false, true, "If true, QUIC will support sending immediate GOAWAYS and will refuse streams above the limit.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enobufs_blocked, true, true, "If true, ENOBUFS socket errors are reported as socket blocked instead of socket failure.")
diff --git a/quiche/quic/core/quic_buffered_packet_store.h b/quiche/quic/core/quic_buffered_packet_store.h index 3e6a208..6d0b403 100644 --- a/quiche/quic/core/quic_buffered_packet_store.h +++ b/quiche/quic/core/quic_buffered_packet_store.h
@@ -389,6 +389,8 @@ return SEND_TO_WRITER; } + bool NextSpinBitToSend() override { return false; } + // QuicStreamFrameDataProducer WriteStreamDataResult WriteStreamData(QuicStreamId /*id*/, QuicStreamOffset offset,
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index 4286a92..18619fd 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -120,6 +120,12 @@ // but doesn't allow multiple RTTs of user delay in the hope of using ECN. const uint8_t kEcnPtoLimit = 2; +// Constant representing a 7/8 probability of enabling the spin bit for each +// direction of communication. Since the spin bit only works when both sides +// choose to enable it, this results in a 3/4 probability that any given +// connection will have the spin bit enabled, per guidance in RFC 9000. +const uint8_t kSpinDefaultProbability = 223; + // When the clearer goes out of scope, the coalesced packet gets cleared. class ScopedCoalescedPacketClearer { public: @@ -229,7 +235,8 @@ perspective_(perspective), owns_writer_(owns_writer), can_truncate_connection_ids_(perspective == Perspective::IS_SERVER), - store_one_dcid_(GetQuicReloadableFlag(quic_one_dcid)) { + store_one_dcid_(GetQuicReloadableFlag(quic_one_dcid)), + spin_bit_enabled_(false) { QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT || default_path_.self_address.IsInitialized()); @@ -1227,6 +1234,39 @@ } bool QuicConnection::OnPacketHeader(const QuicPacketHeader& header) { + if (spin_bit_enabled_ && header.form == IETF_QUIC_SHORT_HEADER_PACKET) { + QUIC_CODE_COUNT(quic_enable_spin_bit); + QuicPacketNumber largest_observed = + uber_received_packet_manager_.GetLargestObserved( + ENCRYPTION_FORWARD_SECURE); + if (!largest_observed.IsInitialized() || + header.packet_number > largest_observed) { + PathState* absl_nonnull path = &default_path_; + + if (perspective_ == Perspective::IS_CLIENT) { + if ((header.destination_connection_id == + alternative_path_.client_connection_id) && + (alternative_path_.client_connection_id != + default_path_.client_connection_id)) { + path = &alternative_path_; + } + } else { + if ((header.destination_connection_id == + alternative_path_.server_connection_id) && + (alternative_path_.server_connection_id != + default_path_.server_connection_id)) { + path = &alternative_path_; + } + } + + if (perspective_ == Perspective::IS_SERVER) { + path->next_spin_bit = header.spin_bit; + } else { + path->next_spin_bit = !header.spin_bit; + } + } + } + if (debug_visitor_ != nullptr) { debug_visitor_->OnPacketHeader(header, clock_->ApproximateNow(), last_received_packet_info_.decrypted_level); @@ -7248,6 +7288,7 @@ stateless_reset_token.reset(); ecn_marked_packet_acked = false; ecn_pto_count = 0; + next_spin_bit = false; } QuicConnection::PathState::PathState(PathState&& other) { @@ -7604,4 +7645,18 @@ #undef ENDPOINT // undef for jumbo builds +bool QuicConnection::ShouldEnableSpinBit() const { + // Whether this connection will use the spin bit depends on (1) the flag + // being enabled and, if so, (2) a coin flip to generate probabilistic + // participation. + if (!GetQuicReloadableFlag(quic_enable_spin_bit)) { + return false; + } + + QUIC_RELOADABLE_FLAG_COUNT(quic_enable_spin_bit); + uint8_t r; + random_generator_->RandBytes(&r, 1); + return (r < kSpinDefaultProbability); +} + } // namespace quic
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h index 4decc34..366b036 100644 --- a/quiche/quic/core/quic_connection.h +++ b/quiche/quic/core/quic_connection.h
@@ -815,6 +815,9 @@ QuicByteCount GetFlowControlSendWindowSize(QuicStreamId id) override { return visitor_->GetFlowControlSendWindowSize(id); } + bool NextSpinBitToSend() override { + return spin_bit_enabled_ && default_path_.next_spin_bit; + } QuicPacketBuffer GetPacketBuffer() override; void OnSerializedPacket(SerializedPacket packet) override; void OnUnrecoverableError(QuicErrorCode error, @@ -1518,6 +1521,10 @@ return framer_.process_reset_stream_at(); } + // Returns true if the spin bit should be enabled per the RFC 9000 spin + // participation guidance. + bool ShouldEnableSpinBit() const; + protected: // Calls cancel() on all the alarms owned by this connection. void CancelAllAlarms(); @@ -1660,6 +1667,8 @@ // How many total PTOs have fired since the connection started sending ECN // on this path, but before an ECN-marked packet has been acked. uint8_t ecn_pto_count = 0; + // The value of the spin bit for the next packet to be sent on this path. + bool next_spin_bit = false; }; using QueuedPacketList = std::list<SerializedPacket>; @@ -2568,6 +2577,8 @@ const bool quic_fix_timeouts_ : 1 = GetQuicReloadableFlag(quic_fix_timeouts); bool quic_close_on_idle_timeout_ : 1 = GetQuicReloadableFlag(quic_close_on_idle_timeout); + // True if spin bit is enabled for this connection. + bool spin_bit_enabled_ : 1 = ShouldEnableSpinBit(); }; } // namespace quic
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc index e7b0832..802923b 100644 --- a/quiche/quic/core/quic_connection_test.cc +++ b/quiche/quic/core/quic_connection_test.cc
@@ -26,6 +26,7 @@ #include "quiche/quic/core/frames/quic_path_response_frame.h" #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h" #include "quiche/quic/core/frames/quic_rst_stream_frame.h" +#include "quiche/quic/core/quic_buffered_packet_store.h" #include "quiche/quic/core/quic_connection_id.h" #include "quiche/quic/core/quic_constants.h" #include "quiche/quic/core/quic_error_codes.h" @@ -614,8 +615,8 @@ alarm_factory_(new TestAlarmFactory()), peer_framer_(SupportedVersions(version()), QuicTime::Zero(), Perspective::IS_SERVER, connection_id_.length()), - peer_creator_(connection_id_, &peer_framer_, - /*delegate=*/nullptr), + peer_creator_delegate_(&buffer_allocator_), + peer_creator_(connection_id_, &peer_framer_, &peer_creator_delegate_), writer_( new TestPacketWriter(version(), &clock_, Perspective::IS_CLIENT)), connection_(connection_id_, kSelfAddress, kPeerAddress, helper_.get(), @@ -749,6 +750,25 @@ } } + void ProcessPacketWithSpinBit(bool spin_bit, uint64_t pkn) { + QuicPacketHeader header = + ConstructPacketHeader(pkn, ENCRYPTION_FORWARD_SECURE); + header.spin_bit = spin_bit; + QuicFrames frames; + frames.push_back(QuicFrame(QuicPingFrame())); + std::unique_ptr<QuicPacket> packet(ConstructPacket(header, frames)); + char buffer[kMaxOutgoingPacketSize]; + size_t encrypted_length = peer_framer_.EncryptPayload( + ENCRYPTION_FORWARD_SECURE, header.packet_number, *packet, buffer, + kMaxOutgoingPacketSize); + connection_.ProcessUdpPacket( + kSelfAddress, kPeerAddress, + QuicReceivedPacket(buffer, encrypted_length, clock_.Now(), false)); + if (connection_.GetSendAlarm()->IsSet()) { + connection_.GetSendAlarm()->Fire(); + } + } + void ProcessReceivedPacket(const QuicSocketAddress& self_address, const QuicSocketAddress& peer_address, const QuicReceivedPacket& packet) { @@ -758,6 +778,26 @@ } } + void ProcessPacketWithSpinBitAndAddress( + bool spin_bit, uint64_t pkn, const QuicSocketAddress& peer_address) { + QuicPacketHeader header = + ConstructPacketHeader(pkn, ENCRYPTION_FORWARD_SECURE); + header.spin_bit = spin_bit; + QuicFrames frames; + frames.push_back(QuicFrame(QuicPingFrame())); + std::unique_ptr<QuicPacket> packet(ConstructPacket(header, frames)); + char buffer[kMaxOutgoingPacketSize]; + size_t encrypted_length = peer_framer_.EncryptPayload( + ENCRYPTION_FORWARD_SECURE, header.packet_number, *packet, buffer, + kMaxOutgoingPacketSize); + connection_.ProcessUdpPacket( + kSelfAddress, peer_address, + QuicReceivedPacket(buffer, encrypted_length, clock_.Now(), false)); + if (connection_.GetSendAlarm()->IsSet()) { + connection_.GetSendAlarm()->Fire(); + } + } + QuicFrame MakeCryptoFrame() const { if (VersionIsIetfQuic(connection_.transport_version())) { return QuicFrame(new QuicCryptoFrame(crypto_frame_)); @@ -1560,6 +1600,7 @@ std::unique_ptr<TestConnectionHelper> helper_; std::unique_ptr<TestAlarmFactory> alarm_factory_; QuicFramer peer_framer_; + PacketCollector peer_creator_delegate_; QuicPacketCreator peer_creator_; std::unique_ptr<TestPacketWriter> writer_; TestConnection connection_; @@ -18181,6 +18222,113 @@ ENCRYPTION_FORWARD_SECURE); } +TEST_P(QuicConnectionTest, DisabledSpinBit) { + const int iterations = 10; + + SetQuicReloadableFlag(quic_enable_spin_bit, false); + + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + EXPECT_CALL(*send_algorithm_, EnableECT1()).WillRepeatedly(Return(false)); + EXPECT_CALL(*send_algorithm_, EnableECT0()).WillRepeatedly(Return(false)); + + QuicConfig config; + connection_.SetFromConfig(config); + bool saw_enabled = false; + for (int i = 0; i < iterations; i++) { + saw_enabled |= QuicConnectionPeer::GetSpinBitEnabled(&connection_); + } + EXPECT_FALSE(saw_enabled); +} + +TEST_P(QuicConnectionTest, EnabledSpinBit) { + SetQuicReloadableFlag(quic_enable_spin_bit, true); + ON_CALL(random_generator_, RandBytes(_, 1)) + .WillByDefault([](void* data, size_t len) { + ASSERT_EQ(1u, len); + *static_cast<uint8_t*>(data) = 0x00; + }); + + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + EXPECT_CALL(*send_algorithm_, EnableECT1()).WillRepeatedly(Return(false)); + EXPECT_CALL(*send_algorithm_, EnableECT0()).WillRepeatedly(Return(false)); + + QuicConfig config; + connection_.SetFromConfig(config); + EXPECT_FALSE(QuicConnectionPeer::GetSpinBitEnabled(&connection_)); +} + +TEST_P(QuicConnectionTest, ClientSpinsSpinBit) { + SetQuicReloadableFlag(quic_enable_spin_bit, true); + set_perspective(Perspective::IS_CLIENT); + // Skip test for non-IETF versions, + if (!version().IsIetfQuic()) { + return; + } + QuicConfig config; + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + EXPECT_CALL(*send_algorithm_, EnableECT1()).WillOnce(Return(false)); + EXPECT_CALL(*send_algorithm_, EnableECT0()).WillOnce(Return(false)); + connection_.SetFromConfig(config); + QuicConnectionPeer::SetSpinBitEnabled(&connection_, true); + ASSERT_TRUE(QuicConnectionPeer::GetSpinBitEnabled(&connection_)); + + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + peer_framer_.SetEncrypter( + ENCRYPTION_FORWARD_SECURE, + std::make_unique<TaggingEncrypter>(ENCRYPTION_FORWARD_SECURE)); + connection_.OnHandshakeComplete(); + EXPECT_CALL(visitor_, GetHandshakeState()) + .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); + + const int kPacketCount = 4; + bool next_spin = false; + QuicStreamOffset offset = 0; + + for (int i = 0; i < kPacketCount; i++) { + ProcessPacketWithSpinBit(next_spin, i + 1); + connection_.SendStreamDataWithString(3, "foo", offset, NO_FIN); + EXPECT_EQ(!next_spin, writer_->last_packet_header().spin_bit); + next_spin = !next_spin; + offset += 3; + } +} + +TEST_P(QuicConnectionTest, ServerReflectsSpinBit) { + SetQuicReloadableFlag(quic_enable_spin_bit, true); + set_perspective(Perspective::IS_SERVER); + // Skip test for non-IETF versions, + if (!version().IsIetfQuic()) { + return; + } + QuicConfig config; + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + EXPECT_CALL(*send_algorithm_, EnableECT1()).WillOnce(Return(false)); + EXPECT_CALL(*send_algorithm_, EnableECT0()).WillOnce(Return(false)); + connection_.SetFromConfig(config); + QuicConnectionPeer::SetSpinBitEnabled(&connection_, true); + ASSERT_TRUE(QuicConnectionPeer::GetSpinBitEnabled(&connection_)); + + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + peer_framer_.SetEncrypter( + ENCRYPTION_FORWARD_SECURE, + std::make_unique<TaggingEncrypter>(ENCRYPTION_FORWARD_SECURE)); + connection_.OnHandshakeComplete(); + EXPECT_CALL(visitor_, GetHandshakeState()) + .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); + + const int kPacketCount = 4; + bool next_spin = false; + QuicStreamOffset offset = 0; + + for (int i = 0; i < kPacketCount; i++) { + ProcessPacketWithSpinBit(next_spin, i + 1); + connection_.SendStreamDataWithString(3, "foo", offset, NO_FIN); + EXPECT_EQ(next_spin, writer_->last_packet_header().spin_bit); + next_spin = !next_spin; + offset += 3; + } +} + } // namespace } // namespace test } // namespace quic
diff --git a/quiche/quic/core/quic_framer.cc b/quiche/quic/core/quic_framer.cc index 253c1b0..1be4f2a 100644 --- a/quiche/quic/core/quic_framer.cc +++ b/quiche/quic/core/quic_framer.cc
@@ -2006,8 +2006,10 @@ } else { type = static_cast<uint8_t>( FLAGS_FIXED_BIT | (current_key_phase_bit_ ? FLAGS_KEY_PHASE_BIT : 0) | + (header.spin_bit ? FLAGS_SPIN_BIT : 0) | PacketNumberLengthToOnWireValue(header.packet_number_length)); } + return writer->WriteUInt8(type); } @@ -2440,8 +2442,10 @@ header->packet_number_length = GetShortHeaderPacketNumberLength(header->type_byte); } + header->spin_bit = (header->type_byte & FLAGS_SPIN_BIT) > 0; return true; } + if (header->long_packet_type == RETRY) { if (!version().IsIetfQuic()) { set_detailed_error("RETRY not supported in this version.");
diff --git a/quiche/quic/core/quic_framer_test.cc b/quiche/quic/core/quic_framer_test.cc index 2885f04..86583b6 100644 --- a/quiche/quic/core/quic_framer_test.cc +++ b/quiche/quic/core/quic_framer_test.cc
@@ -15014,6 +15014,59 @@ } } +TEST_P(QuicFramerTest, SpinBit) { + if (!framer_.version().IsIetfQuic()) { + return; + } + SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE); + QuicFramerPeer::SetLargestPacketNumber(&framer_, kPacketNumber - 2); + + // clang-format off + unsigned char packet[] = { + // type (short header, 1 byte packet number, spin bit on) + 0x40 | FLAGS_SPIN_BIT, + // connection_id + 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, + // packet number + 0x78, + // padding + 0x00, 0x00, 0x00, + }; + // clang-format on + + QuicEncryptedPacket encrypted(AsChars(packet), ABSL_ARRAYSIZE(packet), false); + EXPECT_TRUE(framer_.ProcessPacket(encrypted)); + EXPECT_THAT(framer_.error(), IsQuicNoError()); + ASSERT_TRUE(visitor_.header_.get()); + EXPECT_EQ(FramerTestConnectionId(), + visitor_.header_->destination_connection_id); + EXPECT_FALSE(visitor_.header_->reset_flag); + EXPECT_FALSE(visitor_.header_->version_flag); + EXPECT_EQ(PACKET_1BYTE_PACKET_NUMBER, visitor_.header_->packet_number_length); + EXPECT_EQ(kPacketNumber, visitor_.header_->packet_number); + EXPECT_TRUE(visitor_.header_->spin_bit); +} + +TEST_P(QuicFramerTest, BuildPacketWithSpinBit) { + if (!framer_.version().IsIetfQuic()) { + return; + } + QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT); + QuicPacketHeader header; + header.destination_connection_id = FramerTestConnectionId(); + header.reset_flag = false; + header.version_flag = false; + header.packet_number_length = PACKET_1BYTE_PACKET_NUMBER; + header.packet_number = kPacketNumber; + header.spin_bit = true; + + QuicFrames frames = {QuicFrame(QuicPingFrame())}; + + std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames)); + ASSERT_TRUE(data != nullptr); + EXPECT_TRUE(data->data()[0] & FLAGS_SPIN_BIT); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quiche/quic/core/quic_packet_creator.cc b/quiche/quic/core/quic_packet_creator.cc index 45f78f6..137a4e4 100644 --- a/quiche/quic/core/quic_packet_creator.cc +++ b/quiche/quic/core/quic_packet_creator.cc
@@ -1895,6 +1895,9 @@ header->retry_token = GetRetryToken(); header->length_length = GetLengthLength(); header->remaining_packet_length = 0; + + header->spin_bit = delegate_->NextSpinBitToSend(); + if (!HasIetfLongHeader()) { return; }
diff --git a/quiche/quic/core/quic_packet_creator.h b/quiche/quic/core/quic_packet_creator.h index 8fff612..737e32b 100644 --- a/quiche/quic/core/quic_packet_creator.h +++ b/quiche/quic/core/quic_packet_creator.h
@@ -79,6 +79,9 @@ // serialized. virtual SerializedPacketFate GetSerializedPacketFate( bool is_mtu_discovery, EncryptionLevel encryption_level) = 0; + + // Return the spin bit value to send in the next short header packet. + virtual bool NextSpinBitToSend() = 0; }; // Interface which gets callbacks from the QuicPacketCreator at interesting
diff --git a/quiche/quic/core/quic_packet_creator_test.cc b/quiche/quic/core/quic_packet_creator_test.cc index 62e7c6d..f4c3262 100644 --- a/quiche/quic/core/quic_packet_creator_test.cc +++ b/quiche/quic/core/quic_packet_creator_test.cc
@@ -56,28 +56,36 @@ } // Run tests with combinations of {ParsedQuicVersion, -// ToggleVersionSerialization}. +// ToggleVersionSerialization, and spin bit}. struct TestParams { - TestParams(ParsedQuicVersion version, bool version_serialization) - : version(version), version_serialization(version_serialization) {} + TestParams(ParsedQuicVersion version, bool version_serialization, + bool spin_bit) + : version(version), + version_serialization(version_serialization), + spin_bit(spin_bit) {} ParsedQuicVersion version; bool version_serialization; + bool spin_bit; }; // Used by ::testing::PrintToStringParamName(). std::string PrintToString(const TestParams& p) { return absl::StrCat(ParsedQuicVersionToString(p.version), "_", - (p.version_serialization ? "Include" : "No"), "Version"); + (p.version_serialization ? "Include" : "No"), "Version", + "_", (p.spin_bit ? "Spin" : "NoSpin")); } // Constructs various test permutations. std::vector<TestParams> GetTestParams() { std::vector<TestParams> params; ParsedQuicVersionVector all_supported_versions = AllSupportedVersions(); - for (size_t i = 0; i < all_supported_versions.size(); ++i) { - params.push_back(TestParams(all_supported_versions[i], true)); - params.push_back(TestParams(all_supported_versions[i], false)); + for (const auto& version : all_supported_versions) { + for (bool version_serialization : {true, false}) { + for (bool spin_bit : {true, false}) { + params.push_back(TestParams(version, version_serialization, spin_bit)); + } + } } return params; } @@ -146,6 +154,8 @@ .WillRepeatedly(Return(QuicPacketBuffer())); EXPECT_CALL(delegate_, GetSerializedPacketFate(_, _)) .WillRepeatedly(Return(SEND_TO_WRITER)); + EXPECT_CALL(delegate_, NextSpinBitToSend()) + .WillRepeatedly(Return(GetParam().spin_bit)); creator_.SetEncrypter( ENCRYPTION_INITIAL, std::make_unique<TaggingEncrypter>(ENCRYPTION_INITIAL)); @@ -2558,6 +2568,7 @@ (override)); MOCK_METHOD(SerializedPacketFate, GetSerializedPacketFate, (bool, EncryptionLevel), (override)); + MOCK_METHOD(bool, NextSpinBitToSend, (), (override)); void SetCanWriteAnything() { EXPECT_CALL(*this, ShouldGeneratePacket(_, _)).WillRepeatedly(Return(true)); @@ -2691,6 +2702,7 @@ .WillRepeatedly(Return(QuicPacketBuffer())); EXPECT_CALL(delegate_, GetSerializedPacketFate(_, _)) .WillRepeatedly(Return(SEND_TO_WRITER)); + EXPECT_CALL(delegate_, NextSpinBitToSend()).WillRepeatedly(Return(false)); EXPECT_CALL(delegate_, GetFlowControlSendWindowSize(_)) .WillRepeatedly(Return(std::numeric_limits<QuicByteCount>::max())); creator_.SetEncrypter(
diff --git a/quiche/quic/core/quic_packets.cc b/quiche/quic/core/quic_packets.cc index 8064434..cfa2df2 100644 --- a/quiche/quic/core/quic_packets.cc +++ b/quiche/quic/core/quic_packets.cc
@@ -166,9 +166,10 @@ type_byte(0), destination_connection_id_included(CONNECTION_ID_PRESENT), source_connection_id_included(CONNECTION_ID_ABSENT), - reset_flag(false), version_flag(false), + reset_flag(false), has_possible_stateless_reset_token(false), + spin_bit(false), version(UnsupportedQuicVersion()), source_connection_id(EmptyQuicConnectionId()), remaining_packet_length(0), @@ -255,7 +256,8 @@ << absl::BytesToHexString( absl::string_view(header.nonce->data(), header.nonce->size())); } - os << ", packet_number: " << header.packet_number << " }\n"; + os << ", packet_number: " << header.packet_number + << ", spin_bit: " << header.spin_bit << " }\n"; return os; } @@ -604,6 +606,7 @@ form == other.form && long_packet_type == other.long_packet_type && possible_stateless_reset_token == other.possible_stateless_reset_token && + spin_bit == other.spin_bit && retry_token_length_length == other.retry_token_length_length && retry_token == other.retry_token && length_length == other.length_length &&
diff --git a/quiche/quic/core/quic_packets.h b/quiche/quic/core/quic_packets.h index a73184e..31b4e85 100644 --- a/quiche/quic/core/quic_packets.h +++ b/quiche/quic/core/quic_packets.h
@@ -134,15 +134,18 @@ uint8_t type_byte; QuicConnectionIdIncluded destination_connection_id_included; QuicConnectionIdIncluded source_connection_id_included; - // TODO(martinduke): Compress these into bitfields. - // This is only used for Google QUIC. - bool reset_flag; // For Google QUIC, version flag in packets from the server means version // negotiation packet. For IETF QUIC, version flag means long header. bool version_flag; + // --- bitfield-able bools in the first cacheline go here --- + // This is only used for Google QUIC. + bool reset_flag : 1; // Indicates whether |possible_stateless_reset_token| contains a valid value // parsed from the packet buffer. IETF QUIC only, always false for GQUIC. - bool has_possible_stateless_reset_token; + bool has_possible_stateless_reset_token : 1; + // Latency spin bit on the short packet header (RFC 9000 Section 17.4) + bool spin_bit : 1; + // -- end bitfield-able bools in the first cacheline -- // There are 8 bytes still available in the first cacheline. Start with long // header stuff.
diff --git a/quiche/quic/core/quic_types.h b/quiche/quic/core/quic_types.h index bb5e3e8..dcf16b5 100644 --- a/quiche/quic/core/quic_types.h +++ b/quiche/quic/core/quic_types.h
@@ -649,9 +649,10 @@ // bit to 0, allowing to distinguish Google QUIC packets from short header // packets. FLAGS_DEMULTIPLEXING_BIT = 1 << 3, - // Bits 4 and 5: Reserved bits for short header. + // Bits 4: Reserved bit for short header. FLAGS_SHORT_HEADER_RESERVED_1 = 1 << 4, - FLAGS_SHORT_HEADER_RESERVED_2 = 1 << 5, + // Bit 5: the spin bit. + FLAGS_SPIN_BIT = 1 << 5, // Bit 6: the 'QUIC' bit. FLAGS_FIXED_BIT = 1 << 6, // Bit 7: Indicates the header is long or short header.
diff --git a/quiche/quic/test_tools/quic_connection_peer.cc b/quiche/quic/test_tools/quic_connection_peer.cc index 1e549f9..818576e 100644 --- a/quiche/quic/test_tools/quic_connection_peer.cc +++ b/quiche/quic/test_tools/quic_connection_peer.cc
@@ -629,5 +629,16 @@ return connection->active_migration_disabled_; } +// static +void QuicConnectionPeer::SetSpinBitEnabled(QuicConnection* connection, + bool enabled) { + connection->spin_bit_enabled_ = enabled; +} + +// static +bool QuicConnectionPeer::GetSpinBitEnabled(QuicConnection* connection) { + return connection->spin_bit_enabled_; +} + } // namespace test } // namespace quic
diff --git a/quiche/quic/test_tools/quic_connection_peer.h b/quiche/quic/test_tools/quic_connection_peer.h index d7b9378..0016642 100644 --- a/quiche/quic/test_tools/quic_connection_peer.h +++ b/quiche/quic/test_tools/quic_connection_peer.h
@@ -258,6 +258,10 @@ static uint64_t GetPeerReorderingThreshold(QuicConnection* connection); static bool ConnectionMigrationDisabled(QuicConnection* connection); + + static void SetSpinBitEnabled(QuicConnection* connection, bool enabled); + + static bool GetSpinBitEnabled(QuicConnection* connection); }; } // namespace test
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h index c040b85..2e1149a 100644 --- a/quiche/quic/test_tools/quic_test_utils.h +++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -1433,6 +1433,7 @@ (override)); MOCK_METHOD(SerializedPacketFate, GetSerializedPacketFate, (bool, EncryptionLevel), (override)); + MOCK_METHOD(bool, NextSpinBitToSend, (), (override)); }; class MockSessionNotifier : public SessionNotifierInterface {