Add method to get reason why 0-RTT was accepted or rejected in QUIC

This provides additional detail compared to
QuicCryptoClientStreamBase::EarlyDataAccepted to indicate why early data was
rejected (if it was). This change only adds client-side logging information
- it doesn't change the behavior of a client.
PiperOrigin-RevId: 330774692
Change-Id: I9f59ee5a4a64a5617b5ff9de5aba34156b88538f
diff --git a/quic/core/quic_crypto_client_handshaker.cc b/quic/core/quic_crypto_client_handshaker.cc
index 7276937..b60b026 100644
--- a/quic/core/quic_crypto_client_handshaker.cc
+++ b/quic/core/quic_crypto_client_handshaker.cc
@@ -128,6 +128,10 @@
   return num_client_hellos_ == 1;
 }
 
+ssl_early_data_reason_t QuicCryptoClientHandshaker::EarlyDataReason() const {
+  return early_data_reason_;
+}
+
 bool QuicCryptoClientHandshaker::ReceivedInchoateReject() const {
   QUIC_BUG_IF(!one_rtt_keys_available_);
   return num_client_hellos_ >= 3;
@@ -290,9 +294,16 @@
   // inchoate or subsequent hello.
   session()->config()->ToHandshakeMessage(&out, session()->transport_version());
 
-  if (!cached->IsComplete(session()->connection()->clock()->WallNow()) ||
-      session()->config()->HasClientRequestedIndependentOption(
-          kQNZR, session()->perspective())) {
+  bool fill_inchoate_client_hello = false;
+  if (!cached->IsComplete(session()->connection()->clock()->WallNow())) {
+    early_data_reason_ = ssl_early_data_no_session_offered;
+    fill_inchoate_client_hello = true;
+  } else if (session()->config()->HasClientRequestedIndependentOption(
+                 kQNZR, session()->perspective())) {
+    early_data_reason_ = ssl_early_data_disabled;
+    fill_inchoate_client_hello = true;
+  }
+  if (fill_inchoate_client_hello) {
     crypto_config_->FillInchoateClientHello(
         server_id_, session()->supported_versions().front(), cached,
         session()->connection()->random_generator(),
@@ -356,6 +367,9 @@
       /*latch_once_used=*/true);
   encryption_established_ = true;
   delegate_->SetDefaultEncryptionLevel(ENCRYPTION_ZERO_RTT);
+  if (early_data_reason_ == ssl_early_data_unknown && num_client_hellos_ > 1) {
+    early_data_reason_ = ssl_early_data_peer_declined;
+  }
 }
 
 void QuicCryptoClientHandshaker::DoReceiveREJ(
@@ -531,6 +545,9 @@
                                   "unencrypted SHLO message");
     return;
   }
+  if (num_client_hellos_ == 1) {
+    early_data_reason_ = ssl_early_data_accepted;
+  }
 
   std::string error_details;
   QuicErrorCode error = crypto_config_->ProcessServerHello(
diff --git a/quic/core/quic_crypto_client_handshaker.h b/quic/core/quic_crypto_client_handshaker.h
index 90f011d..605318e 100644
--- a/quic/core/quic_crypto_client_handshaker.h
+++ b/quic/core/quic_crypto_client_handshaker.h
@@ -40,6 +40,7 @@
   int num_sent_client_hellos() const override;
   bool IsResumption() const override;
   bool EarlyDataAccepted() const override;
+  ssl_early_data_reason_t EarlyDataReason() const override;
   bool ReceivedInchoateReject() const override;
   int num_scup_messages_received() const override;
   std::string chlo_hash() const override;
@@ -153,6 +154,8 @@
   // connection has sent.
   int num_client_hellos_;
 
+  ssl_early_data_reason_t early_data_reason_ = ssl_early_data_unknown;
+
   QuicCryptoClientConfig* const crypto_config_;
 
   // SHA-256 hash of the most recently sent CHLO.
diff --git a/quic/core/quic_crypto_client_stream.cc b/quic/core/quic_crypto_client_stream.cc
index 62a261d..67a9a11 100644
--- a/quic/core/quic_crypto_client_stream.cc
+++ b/quic/core/quic_crypto_client_stream.cc
@@ -71,6 +71,10 @@
   return handshaker_->EarlyDataAccepted();
 }
 
+ssl_early_data_reason_t QuicCryptoClientStream::EarlyDataReason() const {
+  return handshaker_->EarlyDataReason();
+}
+
 bool QuicCryptoClientStream::ReceivedInchoateReject() const {
   return handshaker_->ReceivedInchoateReject();
 }
diff --git a/quic/core/quic_crypto_client_stream.h b/quic/core/quic_crypto_client_stream.h
index be99fb2..4100a50 100644
--- a/quic/core/quic_crypto_client_stream.h
+++ b/quic/core/quic_crypto_client_stream.h
@@ -9,6 +9,7 @@
 #include <memory>
 #include <string>
 
+#include "third_party/boringssl/src/include/openssl/ssl.h"
 #include "net/third_party/quiche/src/quic/core/crypto/proof_verifier.h"
 #include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.h"
 #include "net/third_party/quiche/src/quic/core/quic_config.h"
@@ -53,6 +54,10 @@
   // Returns true if early data (0-RTT) was accepted in the connection.
   virtual bool EarlyDataAccepted() const = 0;
 
+  // Returns the ssl_early_data_reason_t describing why 0-RTT was accepted or
+  // rejected.
+  virtual ssl_early_data_reason_t EarlyDataReason() const = 0;
+
   // Returns true if the client received an inchoate REJ during the handshake,
   // extending the handshake by one round trip. This only applies for QUIC
   // crypto handshakes. The equivalent feature in IETF QUIC is a Retry packet,
@@ -116,6 +121,10 @@
     // Returns true if early data (0-RTT) was accepted in the connection.
     virtual bool EarlyDataAccepted() const = 0;
 
+    // Returns the ssl_early_data_reason_t describing why 0-RTT was accepted or
+    // rejected.
+    virtual ssl_early_data_reason_t EarlyDataReason() const = 0;
+
     // Returns true if the client received an inchoate REJ during the handshake,
     // extending the handshake by one round trip. This only applies for QUIC
     // crypto handshakes. The equivalent feature in IETF QUIC is a Retry packet,
@@ -203,6 +212,7 @@
   int num_sent_client_hellos() const override;
   bool IsResumption() const override;
   bool EarlyDataAccepted() const override;
+  ssl_early_data_reason_t EarlyDataReason() const override;
   bool ReceivedInchoateReject() const override;
 
   int num_scup_messages_received() const override;
diff --git a/quic/core/quic_crypto_client_stream_test.cc b/quic/core/quic_crypto_client_stream_test.cc
index 7f6d777..52791b3 100644
--- a/quic/core/quic_crypto_client_stream_test.cc
+++ b/quic/core/quic_crypto_client_stream_test.cc
@@ -107,6 +107,7 @@
   EXPECT_TRUE(stream()->encryption_established());
   EXPECT_TRUE(stream()->one_rtt_keys_available());
   EXPECT_FALSE(stream()->IsResumption());
+  EXPECT_EQ(stream()->EarlyDataReason(), ssl_early_data_no_session_offered);
 }
 
 TEST_F(QuicCryptoClientStreamTest, MessageAfterHandshake) {
@@ -179,6 +180,7 @@
   // Check that a client hello was sent.
   ASSERT_EQ(1u, connection_->encrypted_packets_.size());
   EXPECT_EQ(ENCRYPTION_INITIAL, connection_->encryption_level());
+  EXPECT_EQ(stream()->EarlyDataReason(), ssl_early_data_disabled);
 }
 
 TEST_F(QuicCryptoClientStreamTest, ClockSkew) {
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc
index 46bf285..fa19005 100644
--- a/quic/core/tls_client_handshaker.cc
+++ b/quic/core/tls_client_handshaker.cc
@@ -295,6 +295,10 @@
   return SSL_early_data_accepted(ssl()) == 1;
 }
 
+ssl_early_data_reason_t TlsClientHandshaker::EarlyDataReason() const {
+  return SSL_get_early_data_reason(ssl());
+}
+
 bool TlsClientHandshaker::ReceivedInchoateReject() const {
   QUIC_BUG_IF(!one_rtt_keys_available_);
   // REJ messages are a QUIC crypto feature, so TLS always returns false.
diff --git a/quic/core/tls_client_handshaker.h b/quic/core/tls_client_handshaker.h
index bf05ca8..2a09fb8 100644
--- a/quic/core/tls_client_handshaker.h
+++ b/quic/core/tls_client_handshaker.h
@@ -46,6 +46,7 @@
   int num_sent_client_hellos() const override;
   bool IsResumption() const override;
   bool EarlyDataAccepted() const override;
+  ssl_early_data_reason_t EarlyDataReason() const override;
   bool ReceivedInchoateReject() const override;
   int num_scup_messages_received() const override;
   std::string chlo_hash() const override;
diff --git a/quic/core/tls_client_handshaker_test.cc b/quic/core/tls_client_handshaker_test.cc
index a0a6ea7..a4bd87e 100644
--- a/quic/core/tls_client_handshaker_test.cc
+++ b/quic/core/tls_client_handshaker_test.cc
@@ -376,6 +376,8 @@
   EXPECT_TRUE(stream()->one_rtt_keys_available());
   EXPECT_FALSE(stream()->IsResumption());
   EXPECT_FALSE(stream()->EarlyDataAccepted());
+  EXPECT_EQ(stream()->EarlyDataReason(),
+            ssl_early_data_unsupported_for_session);
 }
 
 TEST_P(TlsClientHandshakerTest, ZeroRttResumption) {
@@ -413,6 +415,7 @@
   EXPECT_TRUE(stream()->one_rtt_keys_available());
   EXPECT_TRUE(stream()->IsResumption());
   EXPECT_TRUE(stream()->EarlyDataAccepted());
+  EXPECT_EQ(stream()->EarlyDataReason(), ssl_early_data_accepted);
 }
 
 TEST_P(TlsClientHandshakerTest, ZeroRttRejection) {
@@ -461,6 +464,7 @@
   EXPECT_TRUE(stream()->one_rtt_keys_available());
   EXPECT_TRUE(stream()->IsResumption());
   EXPECT_FALSE(stream()->EarlyDataAccepted());
+  EXPECT_EQ(stream()->EarlyDataReason(), ssl_early_data_peer_declined);
 }
 
 TEST_P(TlsClientHandshakerTest, ZeroRttAndResumptionRejection) {
@@ -509,6 +513,7 @@
   EXPECT_TRUE(stream()->one_rtt_keys_available());
   EXPECT_FALSE(stream()->IsResumption());
   EXPECT_FALSE(stream()->EarlyDataAccepted());
+  EXPECT_EQ(stream()->EarlyDataReason(), ssl_early_data_session_not_resumed);
 }
 
 TEST_P(TlsClientHandshakerTest, ClientSendsNoSNI) {
@@ -606,6 +611,7 @@
   EXPECT_TRUE(stream()->encryption_established());
   EXPECT_TRUE(stream()->one_rtt_keys_available());
   EXPECT_FALSE(stream()->EarlyDataAccepted());
+  EXPECT_EQ(stream()->EarlyDataReason(), ssl_early_data_alpn_mismatch);
 }
 
 TEST_P(TlsClientHandshakerTest, InvalidSNI) {