Verify receipt of connection_close in quic_client_interop

This CL also changes QuicFramer::MaybeExtractQuicErrorCode to map NO_IETF_QUIC_ERROR to QUIC_NO_ERROR.

gfe-relnote: change mapping of NO_IETF_QUIC_ERROR, not flag-protected
PiperOrigin-RevId: 274704993
Change-Id: If3e7f98dd6a2435a477ee318b44fce78e6ab6e30
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index a759adb..2d4d4a5 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -6603,7 +6603,12 @@
   uint64_t extracted_error_code;
   if (ed.size() < 2 || !QuicTextUtils::IsAllDigits(ed[0]) ||
       !QuicTextUtils::StringToUint64(ed[0], &extracted_error_code)) {
-    frame->extracted_error_code = QUIC_IETF_GQUIC_ERROR_MISSING;
+    if (frame->close_type == IETF_QUIC_TRANSPORT_CONNECTION_CLOSE &&
+        frame->transport_error_code == NO_IETF_QUIC_ERROR) {
+      frame->extracted_error_code = QUIC_NO_ERROR;
+    } else {
+      frame->extracted_error_code = QUIC_IETF_GQUIC_ERROR_MISSING;
+    }
     return;
   }
   // Return the error code (numeric) and the error details string without the
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc
index 8b99730..02775ca 100644
--- a/quic/test_tools/quic_connection_peer.cc
+++ b/quic/test_tools/quic_connection_peer.cc
@@ -357,5 +357,12 @@
   connection->address_validated_ = true;
 }
 
+// static
+void QuicConnectionPeer::SendConnectionClosePacket(QuicConnection* connection,
+                                                   QuicErrorCode error,
+                                                   const std::string& details) {
+  connection->SendConnectionClosePacket(error, details);
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h
index 5b97ae6..c5c972f 100644
--- a/quic/test_tools/quic_connection_peer.h
+++ b/quic/test_tools/quic_connection_peer.h
@@ -139,6 +139,10 @@
                                   PacketHeaderFormat format);
   static void AddBytesReceived(QuicConnection* connection, size_t length);
   static void SetAddressValidated(QuicConnection* connection);
+
+  static void SendConnectionClosePacket(QuicConnection* connection,
+                                        QuicErrorCode error,
+                                        const std::string& details);
 };
 
 }  // namespace test
diff --git a/quic/tools/quic_client_interop_test_bin.cc b/quic/tools/quic_client_interop_test_bin.cc
index 20a4699..2511141 100644
--- a/quic/tools/quic_client_interop_test_bin.cc
+++ b/quic/tools/quic_client_interop_test_bin.cc
@@ -11,6 +11,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_versions.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_system_event_loop.h"
+#include "net/quic/platform/impl/quic_epoll_clock.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
 #include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h"
 #include "net/third_party/quiche/src/quic/tools/quic_client.h"
@@ -34,10 +35,10 @@
   kStreamData,
   // The connection close procedcure completes with a zero error code.
   kConnectionClose,
-  // An H3 transaction succeeded.
-  kHttp3,
   // A RETRY packet was successfully processed.
   kRetry,
+  // An H3 transaction succeeded.
+  kHttp3,
 };
 
 char MatrixLetter(Feature f) {
@@ -64,6 +65,7 @@
   std::set<Feature> features;
   auto proof_verifier = std::make_unique<FakeProofVerifier>();
   QuicEpollServer epoll_server;
+  QuicEpollClock epoll_clock(&epoll_server);
   auto client = std::make_unique<QuicClient>(
       addr, server_id, versions, &epoll_server, std::move(proof_verifier));
   if (!client->Initialize()) {
@@ -95,9 +97,13 @@
   client->set_store_response(true);
   client->SendRequest(header_block, "", /*fin=*/true);
 
-  // TODO(nharper): After some period of time, time out and don't report
-  // success.
+  const QuicTime request_start_time = epoll_clock.Now();
+  static const auto request_timeout = QuicTime::Delta::FromSeconds(20);
   while (client->WaitForEvents()) {
+    if (epoll_clock.Now() - request_start_time >= request_timeout) {
+      QUIC_LOG(ERROR) << "Timed out waiting for HTTP response";
+      return features;
+    }
   }
 
   QuicConnection* connection = client->session()->connection();
@@ -125,9 +131,28 @@
     features.insert(Feature::kHttp3);
   }
 
-  // TODO(nharper): Check that we sent/received (which one?) a CONNECTION_CLOSE
-  // with error code 0.
-  features.insert(Feature::kConnectionClose);
+  if (connection != nullptr && connection->connected()) {
+    test::QuicConnectionPeer::SendConnectionClosePacket(
+        connection, QUIC_NO_ERROR, "Graceful close");
+    const QuicTime close_start_time = epoll_clock.Now();
+    static const auto close_timeout = QuicTime::Delta::FromSeconds(10);
+    while (client->connected()) {
+      client->epoll_network_helper()->RunEventLoop();
+      if (epoll_clock.Now() - close_start_time >= close_timeout) {
+        QUIC_LOG(ERROR) << "Timed out waiting for connection close";
+        return features;
+      }
+    }
+    const QuicErrorCode received_error = client->session()->error();
+    if (received_error == QUIC_NO_ERROR ||
+        received_error == QUIC_PUBLIC_RESET) {
+      features.insert(Feature::kConnectionClose);
+    } else {
+      QUIC_LOG(ERROR) << "Received error " << client->session()->error() << " "
+                      << client->session()->error_details();
+    }
+  }
+
   return features;
 }