On the client side, donot send stream data when PTO fires before handshake gets confirmed. Credit to petr.matrix@gmail.com who reported this issue on proto-quic@chromium.org. PiperOrigin-RevId: 478562823
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index 0e71a7f..c37c86d 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -2497,16 +2497,18 @@ QuicUtils::IsCryptoStreamId(transport_version(), id)) { MaybeActivateLegacyVersionEncapsulation(); } - if (perspective_ == Perspective::IS_SERVER && - version().CanSendCoalescedPackets() && !IsHandshakeConfirmed()) { + if (version().CanSendCoalescedPackets() && !IsHandshakeConfirmed()) { if (in_on_retransmission_time_out_ && coalesced_packet_.NumberOfPackets() == 0u) { // PTO fires while handshake is not confirmed. Do not preempt handshake // data with stream data. QUIC_CODE_COUNT(quic_try_to_send_half_rtt_data_when_pto_fires); + QUIC_DVLOG(1) << ENDPOINT + << "Not PTOing stream data before handshake gets confirmed"; return QuicConsumedData(0, false); } - if (coalesced_packet_.ContainsPacketOfEncryptionLevel(ENCRYPTION_INITIAL) && + if (perspective_ == Perspective::IS_SERVER && + coalesced_packet_.ContainsPacketOfEncryptionLevel(ENCRYPTION_INITIAL) && coalesced_packet_.NumberOfPackets() == 1u) { // Handshake is not confirmed yet, if there is only an initial packet in // the coalescer, try to bundle an ENCRYPTION_HANDSHAKE packet before
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc index 4d9c91d..c6886c6 100644 --- a/quiche/quic/core/quic_connection_test.cc +++ b/quiche/quic/core/quic_connection_test.cc
@@ -9865,6 +9865,9 @@ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); connection_.SetFromConfig(config); EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet()); + EXPECT_CALL(visitor_, GetHandshakeState()) + .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); + connection_.OnHandshakeComplete(); QuicStreamId stream_id = 2; QuicPacketNumber last_packet; @@ -14670,6 +14673,80 @@ EXPECT_EQ(0x02020202u, writer_->final_bytes_of_last_packet()); } +TEST_P(QuicConnectionTest, ClientPtoSendZeroRttStreamData) { + if (!connection_.SupportsMultiplePacketNumberSpaces()) { + return; + } + use_tagging_decrypter(); + connection_.SetEncrypter(ENCRYPTION_INITIAL, + std::make_unique<TaggingEncrypter>(0x01)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL); + // Send CHLO. + connection_.SendCryptoStreamData(); + ASSERT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); + // Install 0-RTT keys. + connection_.SetEncrypter(ENCRYPTION_ZERO_RTT, + std::make_unique<TaggingEncrypter>(0x02)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_ZERO_RTT); + // Send 0-RTT stream data with congestion control blocked. + EXPECT_CALL(*send_algorithm_, CanSend(_)).WillRepeatedly(Return(false)); + connection_.SendStreamDataWithString(2, std::string(1500, 'a'), 0, NO_FIN); + + ASSERT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); + connection_.GetRetransmissionAlarm()->Fire(); + // Verify INITIAL packet get retransmitted. + EXPECT_EQ(0x01010101u, writer_->final_bytes_of_last_packet()); +} + +TEST_P(QuicConnectionTest, ClientPtoSendForwardSecureStreamData) { + if (!connection_.SupportsMultiplePacketNumberSpaces()) { + return; + } + if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { + EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); + } + EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); + use_tagging_decrypter(); + connection_.SetEncrypter(ENCRYPTION_INITIAL, + std::make_unique<TaggingEncrypter>(0x01)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL); + // Send CHLO. + connection_.SendCryptoDataWithString("foo", 0, ENCRYPTION_INITIAL); + // Receive server INITIAL + HANDSHAKE + QuicFrames frames1; + frames1.push_back(QuicFrame(&crypto_frame_)); + QuicAckFrame ack_frame1 = InitAckFrame(1); + frames1.push_back(QuicFrame(&ack_frame1)); + + QuicFrames frames2; + QuicCryptoFrame crypto_frame(ENCRYPTION_HANDSHAKE, 0, + absl::string_view(data1)); + frames2.push_back(QuicFrame(&crypto_frame)); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _)); + ProcessCoalescedPacket( + {{1, frames1, ENCRYPTION_INITIAL}, {2, frames2, ENCRYPTION_HANDSHAKE}}); + // Send Client finished. + connection_.SetEncrypter(ENCRYPTION_HANDSHAKE, + std::make_unique<TaggingEncrypter>(0x02)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_HANDSHAKE); + EXPECT_CALL(visitor_, OnHandshakePacketSent()).Times(1); + connection_.SendCryptoDataWithString("foo", 0, ENCRYPTION_HANDSHAKE); + + EXPECT_CALL(visitor_, GetHandshakeState()) + .WillRepeatedly(Return(HANDSHAKE_COMPLETE)); + connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE, + std::make_unique<TaggingEncrypter>(0x03)); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + // Send 1-RTT stream data with congestion control blocked. + EXPECT_CALL(*send_algorithm_, CanSend(_)).WillRepeatedly(Return(false)); + connection_.SendStreamDataWithString(2, std::string(1500, 'a'), 0, NO_FIN); + + ASSERT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); + connection_.GetRetransmissionAlarm()->Fire(); + // Verify HANDSHAKE packet get retransmitted. + EXPECT_EQ(0x02020202u, writer_->final_bytes_of_last_packet()); +} + TEST_P(QuicConnectionTest, SendingZeroRttPacketsDoesNotPostponePTO) { if (!connection_.SupportsMultiplePacketNumberSpaces()) { return;