Save all data from received IETF Connection Close frames

Save all data from received IETF connection close frames.
Also removes the QuicErrorCode that was prepended to the error details phrase. The phrase is the same on both the CONNECTION_CLOSE sender and receiver.

gfe-relnote: N/A rearranges existing code, new code is V99/IETF QUIC only.
PiperOrigin-RevId: 265442745
Change-Id: I1d487fc93f90863629e29b863b32c6ab9c6377e1
diff --git a/quic/core/frames/quic_connection_close_frame.cc b/quic/core/frames/quic_connection_close_frame.cc
index 3e37073..a7b3d53 100644
--- a/quic/core/frames/quic_connection_close_frame.cc
+++ b/quic/core/frames/quic_connection_close_frame.cc
@@ -22,7 +22,7 @@
     // Default close type ensures that existing, pre-V99 code works as expected.
     : close_type(GOOGLE_QUIC_CONNECTION_CLOSE),
       quic_error_code(error_code),
-      extracted_error_code(QUIC_IETF_GQUIC_ERROR_MISSING),
+      extracted_error_code(error_code),
       error_details(std::move(error_details)),
       transport_close_frame_type(0) {}
 
@@ -76,22 +76,4 @@
   return os;
 }
 
-std::ostream& operator<<(std::ostream& os, const QuicConnectionCloseType type) {
-  switch (type) {
-    case GOOGLE_QUIC_CONNECTION_CLOSE:
-      os << "GOOGLE_QUIC_CONNECTION_CLOSE";
-      break;
-    case IETF_QUIC_TRANSPORT_CONNECTION_CLOSE:
-      os << "IETF_QUIC_TRANSPORT_CONNECTION_CLOSE";
-      break;
-    case IETF_QUIC_APPLICATION_CONNECTION_CLOSE:
-      os << "IETF_QUIC_APPLICATION_CONNECTION_CLOSE";
-      break;
-    default:
-      os << "Unknown: " << static_cast<int>(type);
-      break;
-  }
-  return os;
-}
-
 }  // namespace quic
diff --git a/quic/core/frames/quic_connection_close_frame.h b/quic/core/frames/quic_connection_close_frame.h
index fd8baf1..b4f4fd7 100644
--- a/quic/core/frames/quic_connection_close_frame.h
+++ b/quic/core/frames/quic_connection_close_frame.h
@@ -14,16 +14,6 @@
 
 namespace quic {
 
-// There are three different forms of CONNECTION_CLOSE.
-typedef enum QuicConnectionCloseType {
-  GOOGLE_QUIC_CONNECTION_CLOSE = 0,
-  IETF_QUIC_TRANSPORT_CONNECTION_CLOSE = 1,
-  IETF_QUIC_APPLICATION_CONNECTION_CLOSE = 2
-} QuicConnectionCloseType;
-QUIC_EXPORT_PRIVATE std::ostream& operator<<(
-    std::ostream& os,
-    const QuicConnectionCloseType type);
-
 struct QUIC_EXPORT_PRIVATE QuicConnectionCloseFrame {
   QuicConnectionCloseFrame();
 
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 6d41a61..4e3b40b 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -1186,18 +1186,40 @@
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnConnectionCloseFrame(frame);
   }
-  QUIC_DLOG(INFO) << ENDPOINT << "Received ConnectionClose for connection: "
-                  << connection_id() << ", with error: "
-                  << QuicErrorCodeToString(frame.quic_error_code) << " ("
-                  << frame.error_details << ")";
-  if (frame.close_type == GOOGLE_QUIC_CONNECTION_CLOSE &&
-      frame.quic_error_code == QUIC_BAD_MULTIPATH_FLAG) {
+  switch (frame.close_type) {
+    case GOOGLE_QUIC_CONNECTION_CLOSE:
+      QUIC_DLOG(INFO) << ENDPOINT << "Received ConnectionClose for connection: "
+                      << connection_id() << ", with error: "
+                      << QuicErrorCodeToString(frame.extracted_error_code)
+                      << " (" << frame.error_details << ")";
+      break;
+    case IETF_QUIC_TRANSPORT_CONNECTION_CLOSE:
+      QUIC_DLOG(INFO) << ENDPOINT
+                      << "Received Transport ConnectionClose for connection: "
+                      << connection_id() << ", with error: "
+                      << QuicErrorCodeToString(frame.extracted_error_code)
+                      << " (" << frame.error_details << ")"
+                      << ", transport error code: "
+                      << frame.transport_error_code << ", error frame type: "
+                      << frame.transport_close_frame_type;
+      break;
+    case IETF_QUIC_APPLICATION_CONNECTION_CLOSE:
+      QUIC_DLOG(INFO) << ENDPOINT
+                      << "Received Application ConnectionClose for connection: "
+                      << connection_id() << ", with error: "
+                      << QuicErrorCodeToString(frame.extracted_error_code)
+                      << " (" << frame.error_details << ")"
+                      << ", application error code: "
+                      << frame.application_error_code;
+      break;
+  }
+
+  if (frame.extracted_error_code == QUIC_BAD_MULTIPATH_FLAG) {
     QUIC_LOG_FIRST_N(ERROR, 10) << "Unexpected QUIC_BAD_MULTIPATH_FLAG error."
                                 << " last_received_header: " << last_header_
                                 << " encryption_level: " << encryption_level_;
   }
-  TearDownLocalConnectionState(frame.quic_error_code, frame.error_details,
-                               ConnectionCloseSource::FROM_PEER);
+  TearDownLocalConnectionState(frame, ConnectionCloseSource::FROM_PEER);
   return connected_;
 }
 
@@ -2841,6 +2863,13 @@
     QuicErrorCode error,
     const std::string& error_details,
     ConnectionCloseSource source) {
+  QuicConnectionCloseFrame frame(error, error_details);
+  return TearDownLocalConnectionState(frame, source);
+}
+
+void QuicConnection::TearDownLocalConnectionState(
+    const QuicConnectionCloseFrame& frame,
+    ConnectionCloseSource source) {
   if (!connected_) {
     QUIC_DLOG(INFO) << "Connection is already closed.";
     return;
@@ -2850,9 +2879,6 @@
   FlushPackets();
   connected_ = false;
   DCHECK(visitor_ != nullptr);
-  // TODO(fkastenholz): When the IETF Transport Connection Close information
-  // gets plumbed in, expand this constructor to include that information.
-  QuicConnectionCloseFrame frame(error, error_details);
   visitor_->OnConnectionClosed(frame, source);
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnConnectionClosed(frame, source);
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 5572f5d..c20a3ef 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -972,6 +972,8 @@
   void TearDownLocalConnectionState(QuicErrorCode error,
                                     const std::string& details,
                                     ConnectionCloseSource source);
+  void TearDownLocalConnectionState(const QuicConnectionCloseFrame& frame,
+                                    ConnectionCloseSource source);
 
   // Writes the given packet to socket, encrypted with packet's
   // encryption_level. Returns true on successful write, and false if the writer
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 13a72d8..dbf3860 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -2030,13 +2030,8 @@
   TestConnectionCloseQuicErrorCode(QUIC_INTERNAL_ERROR);
   const std::vector<QuicConnectionCloseFrame>& connection_close_frames =
       writer_->connection_close_frames();
-  if (VersionHasIetfQuicFrames(version().transport_version)) {
-    EXPECT_EQ("1:Packet written out of order.",
-              connection_close_frames[0].error_details);
-  } else {
-    EXPECT_EQ("Packet written out of order.",
-              connection_close_frames[0].error_details);
-  }
+  EXPECT_EQ("Packet written out of order.",
+            connection_close_frames[0].error_details);
 }
 
 TEST_P(QuicConnectionTest, DiscardQueuedPacketsAfterConnectionClose) {
@@ -7020,11 +7015,27 @@
   header.packet_number = QuicPacketNumber(1);
   header.version_flag = false;
 
-  QuicConnectionCloseFrame qccf(QUIC_PEER_GOING_AWAY, "");
+  QuicErrorCode kQuicErrorCode = QUIC_PEER_GOING_AWAY;
+  // This QuicConnectionCloseFrame will default to being for a Google QUIC
+  // close. If doing IETF QUIC then set fields appropriately for CC/T or CC/A,
+  // depending on the mapping.
+  QuicConnectionCloseFrame qccf(kQuicErrorCode, "");
   if (VersionHasIetfQuicFrames(peer_framer_.transport_version())) {
-    // Default close-type is Google QUIC. If doing IETF QUIC then
-    // set close type to be IETF CC/T.
-    qccf.close_type = IETF_QUIC_TRANSPORT_CONNECTION_CLOSE;
+    QuicErrorCodeToIetfMapping mapping =
+        QuicErrorCodeToTransportErrorCode(kQuicErrorCode);
+    if (mapping.is_transport_close_) {
+      // Maps to a transport close
+      qccf.close_type = IETF_QUIC_TRANSPORT_CONNECTION_CLOSE;
+      qccf.transport_error_code = mapping.transport_error_code_;
+      // TODO(fkastenholz) need to change "0" to get the frame type currently
+      // being processed so that it can be inserted into the frame.
+      qccf.transport_close_frame_type = 0;
+    } else {
+      // Maps to an application close.
+      qccf.close_type = IETF_QUIC_APPLICATION_CONNECTION_CLOSE;
+      qccf.application_error_code = mapping.application_error_code_;
+    }
+    //    qccf.extracted_error_code = kQuicErrorCode;
   }
 
   QuicFrames frames;
@@ -7047,7 +7058,7 @@
       QuicReceivedPacket(buffer, encrypted_length, QuicTime::Zero(), false));
   EXPECT_EQ(1, connection_close_frame_count_);
   EXPECT_EQ(QUIC_PEER_GOING_AWAY,
-            saved_connection_close_frame_.quic_error_code);
+            saved_connection_close_frame_.extracted_error_code);
 }
 
 TEST_P(QuicConnectionTest, SelectMutualVersion) {
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index ffab835..c667e9b 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -5782,7 +5782,7 @@
   // The frame may have an extracted error code in it. Look for it and
   // extract it. If it's not present, MaybeExtract will return
   // QUIC_IETF_GQUIC_ERROR_MISSING.
-  frame->extracted_error_code = MaybeExtractQuicErrorCode(phrase);
+  MaybeExtractQuicErrorCode(frame);
   return true;
 }
 
@@ -6766,14 +6766,25 @@
 // contains additional error information that narrows down the exact error.  If
 // the string is not found, or is not properly formed, it returns
 // ErrorCode::QUIC_IETF_GQUIC_ERROR_MISSING
-QuicErrorCode MaybeExtractQuicErrorCode(QuicStringPiece error_details) {
-  std::vector<QuicStringPiece> ed = QuicTextUtils::Split(error_details, ':');
+void MaybeExtractQuicErrorCode(QuicConnectionCloseFrame* frame) {
+  std::vector<QuicStringPiece> ed =
+      QuicTextUtils::Split(frame->error_details, ':');
   uint64_t extracted_error_code;
   if (ed.size() < 2 || !QuicTextUtils::IsAllDigits(ed[0]) ||
       !QuicTextUtils::StringToUint64(ed[0], &extracted_error_code)) {
-    return QUIC_IETF_GQUIC_ERROR_MISSING;
+    frame->extracted_error_code = QUIC_IETF_GQUIC_ERROR_MISSING;
+    return;
   }
-  return static_cast<QuicErrorCode>(extracted_error_code);
+  // Return the error code (numeric) and the error details string without the
+  // error code prefix. Note that Split returns everything up to, but not
+  // including, the split character, so the length of ed[0] is just the number
+  // of digits in the error number. In removing the prefix, 1 is added to the
+  // length to account for the :
+  QuicStringPiece x = QuicStringPiece(frame->error_details);
+  x.remove_prefix(ed[0].length() + 1);
+  frame->error_details = std::string(x);
+  frame->extracted_error_code =
+      static_cast<QuicErrorCode>(extracted_error_code);
 }
 
 #undef ENDPOINT  // undef for jumbo builds
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index c749f04..5cfba86 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -1103,11 +1103,13 @@
 // Look for and parse the error code from the "<quic_error_code>:" text that
 // may be present at the start of the CONNECTION_CLOSE error details string.
 // This text, inserted by the peer if it's using Google's QUIC implementation,
-// contains additional error information that narrows down the exact error. If
-// the string is not found, or is not properly formed, it returns
-// ErrorCode::QUIC_IETF_GQUIC_ERROR_MISSING
-QUIC_EXPORT_PRIVATE QuicErrorCode
-MaybeExtractQuicErrorCode(QuicStringPiece error_details);
+// contains additional error information that narrows down the exact error. The
+// extracted error code and (possibly updated) error_details string are returned
+// in |*frame|. If an error code is not found in the error details then the
+// extracted_error_code is set to QuicErrorCode::QUIC_IETF_GQUIC_ERROR_MISSING.
+// If there is an error code in the string then it is removed from the string.
+QUIC_EXPORT_PRIVATE void MaybeExtractQuicErrorCode(
+    QuicConnectionCloseFrame* frame);
 
 }  // namespace quic
 
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index 0200ed3..2c2e3fc 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -4297,15 +4297,18 @@
   EXPECT_EQ(0u, visitor_.stream_frames_.size());
   EXPECT_EQ(0x11u, static_cast<unsigned>(
                        visitor_.connection_close_frame_.quic_error_code));
-  // For this test, all versions have the QuicErrorCode tag
-  EXPECT_EQ("17767:because I can",
-            visitor_.connection_close_frame_.error_details);
+
   if (VersionHasIetfQuicFrames(framer_.transport_version())) {
     EXPECT_EQ(0x1234u,
               visitor_.connection_close_frame_.transport_close_frame_type);
     EXPECT_EQ(17767u, visitor_.connection_close_frame_.extracted_error_code);
+    EXPECT_EQ("because I can", visitor_.connection_close_frame_.error_details);
   } else {
     EXPECT_EQ(0x11u, visitor_.connection_close_frame_.extracted_error_code);
+    // Error code is not prepended in GQUIC, so it is not removed and should
+    // remain in the reason phrase.
+    EXPECT_EQ("17767:because I can",
+              visitor_.connection_close_frame_.error_details);
   }
 
   ASSERT_EQ(0u, visitor_.ack_frames_.size());
@@ -4429,8 +4432,7 @@
             visitor_.connection_close_frame_.close_type);
   EXPECT_EQ(17767u, visitor_.connection_close_frame_.extracted_error_code);
   EXPECT_EQ(0x11u, visitor_.connection_close_frame_.quic_error_code);
-  EXPECT_EQ("17767:because I can",
-            visitor_.connection_close_frame_.error_details);
+  EXPECT_EQ("because I can", visitor_.connection_close_frame_.error_details);
 
   ASSERT_EQ(0u, visitor_.ack_frames_.size());
 
@@ -13618,34 +13620,81 @@
   if (VersionHasIetfQuicFrames(framer_.transport_version())) {
     return;
   }
+  QuicConnectionCloseFrame frame;
+
+  frame.error_details = "this has no error code info in it";
+  MaybeExtractQuicErrorCode(&frame);
+  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING, frame.extracted_error_code);
+  EXPECT_EQ("this has no error code info in it", frame.error_details);
+
+  frame.error_details = "1234this does not have the colon in it";
+  MaybeExtractQuicErrorCode(&frame);
+  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING, frame.extracted_error_code);
+  EXPECT_EQ("1234this does not have the colon in it", frame.error_details);
+
+  frame.error_details = "1a234:this has a colon, but a malformed error number";
+  MaybeExtractQuicErrorCode(&frame);
+  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING, frame.extracted_error_code);
+  EXPECT_EQ("1a234:this has a colon, but a malformed error number",
+            frame.error_details);
+
+  frame.error_details = "1234:this is good";
+  MaybeExtractQuicErrorCode(&frame);
+  EXPECT_EQ(1234u, frame.extracted_error_code);
+  EXPECT_EQ("this is good", frame.error_details);
+
+  frame.error_details =
+      "1234 :this is not good, space between last digit and colon";
+  MaybeExtractQuicErrorCode(&frame);
+  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING, frame.extracted_error_code);
+  EXPECT_EQ("1234 :this is not good, space between last digit and colon",
+            frame.error_details);
+
+  frame.error_details = "123456789";
+  MaybeExtractQuicErrorCode(&frame);
   EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
-            MaybeExtractQuicErrorCode("this has no error code info in it"));
-  EXPECT_EQ(
-      QUIC_IETF_GQUIC_ERROR_MISSING,
-      MaybeExtractQuicErrorCode("1234this does not have the colon in it"));
-  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
-            MaybeExtractQuicErrorCode(
-                "1a234:this has a colon, but a malformed error number"));
-  EXPECT_EQ(1234u, MaybeExtractQuicErrorCode("1234:this is good"));
-  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
-            MaybeExtractQuicErrorCode(
-                "1234 :this is not good, space between last digit and colon"));
-  EXPECT_EQ(
-      QUIC_IETF_GQUIC_ERROR_MISSING,
-      MaybeExtractQuicErrorCode("123456789"));  // Not good, all numbers, no :
-  EXPECT_EQ(1234u, MaybeExtractQuicErrorCode("1234:"));  // corner case.
+            frame.extracted_error_code);  // Not good, all numbers, no :
+  EXPECT_EQ("123456789", frame.error_details);
+
+  frame.error_details = "1234:";
+  MaybeExtractQuicErrorCode(&frame);
   EXPECT_EQ(1234u,
-            MaybeExtractQuicErrorCode("1234:5678"));  // another corner case.
+            frame.extracted_error_code);  // corner case.
+  EXPECT_EQ("", frame.error_details);
+
+  frame.error_details = "1234:5678";
+  MaybeExtractQuicErrorCode(&frame);
+  EXPECT_EQ(1234u,
+            frame.extracted_error_code);  // another corner case.
+  EXPECT_EQ("5678", frame.error_details);
+
+  frame.error_details = "12345 6789:";
+  MaybeExtractQuicErrorCode(&frame);
   EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
-            MaybeExtractQuicErrorCode("12345 6789:"));  // Not good
-  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
-            MaybeExtractQuicErrorCode(":no numbers, is not good"));
-  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
-            MaybeExtractQuicErrorCode("qwer:also no numbers, is not good"));
-  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING,
-            MaybeExtractQuicErrorCode(
-                " 1234:this is not good, space before first digit"));
-  EXPECT_EQ(1234u, MaybeExtractQuicErrorCode("1234:"));  // this is good
+            frame.extracted_error_code);  // Not good
+  EXPECT_EQ("12345 6789:", frame.error_details);
+
+  frame.error_details = ":no numbers, is not good";
+  MaybeExtractQuicErrorCode(&frame);
+  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING, frame.extracted_error_code);
+  EXPECT_EQ(":no numbers, is not good", frame.error_details);
+
+  frame.error_details = "qwer:also no numbers, is not good";
+  MaybeExtractQuicErrorCode(&frame);
+  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING, frame.extracted_error_code);
+  EXPECT_EQ("qwer:also no numbers, is not good", frame.error_details);
+
+  frame.error_details = " 1234:this is not good, space before first digit";
+  MaybeExtractQuicErrorCode(&frame);
+  EXPECT_EQ(QUIC_IETF_GQUIC_ERROR_MISSING, frame.extracted_error_code);
+  EXPECT_EQ(" 1234:this is not good, space before first digit",
+            frame.error_details);
+
+  frame.error_details = "1234:";
+  MaybeExtractQuicErrorCode(&frame);
+  EXPECT_EQ(1234u,
+            frame.extracted_error_code);  // this is good
+  EXPECT_EQ("", frame.error_details);
 }
 
 }  // namespace
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 15fcf24..fc4ec23 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -69,7 +69,7 @@
       num_outgoing_static_streams_(0),
       num_incoming_static_streams_(0),
       num_locally_closed_incoming_streams_highest_offset_(0),
-      error_(QUIC_NO_ERROR),
+      on_closed_frame_(QUIC_NO_ERROR, ""),
       flow_controller_(
           this,
           QuicUtils::GetInvalidStreamId(connection->transport_version()),
@@ -397,8 +397,9 @@
     RecordConnectionCloseAtServer(frame.quic_error_code, source);
   }
 
-  if (error_ == QUIC_NO_ERROR) {
-    error_ = frame.quic_error_code;
+  if (on_closed_frame_.extracted_error_code == QUIC_NO_ERROR) {
+    // Save all of the connection close information
+    on_closed_frame_ = frame;
   }
 
   // Copy all non static streams in a new map for the ease of deleting.
@@ -429,8 +430,8 @@
 
   if (visitor_) {
     visitor_->OnConnectionClosed(connection_->connection_id(),
-                                 frame.quic_error_code, frame.error_details,
-                                 source);
+                                 frame.extracted_error_code,
+                                 frame.error_details, source);
   }
 }
 
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 8216bbd..f57ae0f 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -343,7 +343,20 @@
 
   bool goaway_received() const { return goaway_received_; }
 
-  QuicErrorCode error() const { return error_; }
+  // Returns the Google QUIC error code
+  QuicErrorCode error() const { return on_closed_frame_.extracted_error_code; }
+  uint64_t transport_close_frame_type() const {
+    return on_closed_frame_.transport_close_frame_type;
+  }
+  QuicConnectionCloseType close_type() const {
+    return on_closed_frame_.close_type;
+  }
+  QuicIetfTransportErrorCodes transport_error_code() const {
+    return on_closed_frame_.transport_error_code;
+  }
+  uint16_t application_error_code() const {
+    return on_closed_frame_.application_error_code;
+  }
 
   Perspective perspective() const { return connection_->perspective(); }
 
@@ -519,7 +532,10 @@
   void set_largest_peer_created_stream_id(
       QuicStreamId largest_peer_created_stream_id);
 
-  void set_error(QuicErrorCode error) { error_ = error; }
+  void set_error(QuicErrorCode error) {
+    on_closed_frame_.extracted_error_code = error;
+  }
+
   QuicWriteBlockedList* write_blocked_streams() {
     return &write_blocked_streams_;
   }
@@ -687,8 +703,8 @@
   // locally_closed_streams_highest_offset_.
   size_t num_locally_closed_incoming_streams_highest_offset_;
 
-  // The latched error with which the connection was closed.
-  QuicErrorCode error_;
+  // Received information for a connection close.
+  QuicConnectionCloseFrame on_closed_frame_;
 
   // Used for connection-level flow control.
   QuicFlowController flow_controller_;
diff --git a/quic/core/quic_types.cc b/quic/core/quic_types.cc
index a8b8db9..a7453e8 100644
--- a/quic/core/quic_types.cc
+++ b/quic/core/quic_types.cc
@@ -454,4 +454,19 @@
   return os;
 }
 
+std::string QuicConnectionCloseTypeString(QuicConnectionCloseType type) {
+  switch (type) {
+    RETURN_STRING_LITERAL(GOOGLE_QUIC_CONNECTION_CLOSE);
+    RETURN_STRING_LITERAL(IETF_QUIC_TRANSPORT_CONNECTION_CLOSE);
+    RETURN_STRING_LITERAL(IETF_QUIC_APPLICATION_CONNECTION_CLOSE);
+    default:
+      return QuicStrCat("Unknown: ", static_cast<int>(type));
+      break;
+  }
+}
+std::ostream& operator<<(std::ostream& os, const QuicConnectionCloseType type) {
+  os << QuicConnectionCloseTypeString(type);
+  return os;
+}
+
 }  // namespace quic
diff --git a/quic/core/quic_types.h b/quic/core/quic_types.h
index cb90505..2d89392 100644
--- a/quic/core/quic_types.h
+++ b/quic/core/quic_types.h
@@ -646,6 +646,20 @@
   PACKETS_ACKED_IN_WRONG_PACKET_NUMBER_SPACE,
 };
 
+// There are three different forms of CONNECTION_CLOSE.
+typedef enum QuicConnectionCloseType {
+  GOOGLE_QUIC_CONNECTION_CLOSE = 0,
+  IETF_QUIC_TRANSPORT_CONNECTION_CLOSE = 1,
+  IETF_QUIC_APPLICATION_CONNECTION_CLOSE = 2
+} QuicConnectionCloseType;
+
+QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+    std::ostream& os,
+    const QuicConnectionCloseType type);
+
+QUIC_EXPORT_PRIVATE std::string QuicConnectionCloseTypeString(
+    QuicConnectionCloseType type);
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_QUIC_TYPES_H_