Plumb original_connection_id transport parameter

This transport parameter was already parsed in TransportParameters, but was not acted on. This CL plumbs the received value through QuicConfig to QuicConnection and fails the handshake if the value is not the expected one. This CL also improves debugging in various tests.

Client-only change

PiperOrigin-RevId: 310221632
Change-Id: I75b50926034f1ee5d913c7579e4f3a5e15e97b4a
diff --git a/quic/core/crypto/transport_parameters.cc b/quic/core/crypto/transport_parameters.cc
index 6c3f3c5..75f55ca 100644
--- a/quic/core/crypto/transport_parameters.cc
+++ b/quic/core/crypto/transport_parameters.cc
@@ -387,9 +387,9 @@
     rv += " supported_versions " +
           QuicVersionLabelVectorToString(supported_versions);
   }
-  if (!original_connection_id.IsEmpty()) {
+  if (original_connection_id.has_value()) {
     rv += " " + TransportParameterIdToString(kOriginalConnectionId) + " " +
-          original_connection_id.ToString();
+          original_connection_id.value().ToString();
   }
   rv += idle_timeout_milliseconds.ToString(/*for_use_in_list=*/true);
   if (!stateless_reset_token.empty()) {
@@ -429,7 +429,6 @@
 
 TransportParameters::TransportParameters()
     : version(0),
-      original_connection_id(EmptyQuicConnectionId()),
       idle_timeout_milliseconds(kIdleTimeout),
       max_packet_size(kMaxPacketSize,
                       kDefaultMaxPacketSizeTransportParam,
@@ -550,7 +549,7 @@
     return false;
   }
   if (perspective == Perspective::IS_CLIENT &&
-      !original_connection_id.IsEmpty()) {
+      original_connection_id.has_value()) {
     *error_details = "Client cannot send original connection ID";
     return false;
   }
@@ -638,17 +637,18 @@
   }
 
   // original_connection_id
-  if (!in.original_connection_id.IsEmpty()) {
+  if (in.original_connection_id.has_value()) {
     DCHECK_EQ(Perspective::IS_SERVER, in.perspective);
+    QuicConnectionId original_connection_id = in.original_connection_id.value();
     if (!WriteTransportParameterId(
             &writer, TransportParameters::kOriginalConnectionId, version) ||
         !WriteTransportParameterStringPiece(
             &writer,
-            quiche::QuicheStringPiece(in.original_connection_id.data(),
-                                      in.original_connection_id.length()),
+            quiche::QuicheStringPiece(original_connection_id.data(),
+                                      original_connection_id.length()),
             version)) {
       QUIC_BUG << "Failed to write original_connection_id "
-               << in.original_connection_id << " for " << in;
+               << in.original_connection_id.value() << " for " << in;
       return false;
     }
   }
@@ -900,7 +900,7 @@
     bool parse_success = true;
     switch (param_id) {
       case TransportParameters::kOriginalConnectionId: {
-        if (!out->original_connection_id.IsEmpty()) {
+        if (out->original_connection_id.has_value()) {
           *error_details = "Received a second original connection ID";
           return false;
         }
@@ -912,11 +912,13 @@
               connection_id_length);
           return false;
         }
-        if (!value_reader.ReadConnectionId(&out->original_connection_id,
+        QuicConnectionId original_connection_id;
+        if (!value_reader.ReadConnectionId(&original_connection_id,
                                            connection_id_length)) {
           *error_details = "Failed to read original connection ID";
           return false;
         }
+        out->original_connection_id = original_connection_id;
       } break;
       case TransportParameters::kIdleTimeout:
         parse_success =
diff --git a/quic/core/crypto/transport_parameters.h b/quic/core/crypto/transport_parameters.h
index 28dcc24..d4dd82f 100644
--- a/quic/core/crypto/transport_parameters.h
+++ b/quic/core/crypto/transport_parameters.h
@@ -15,6 +15,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/core/quic_versions.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_optional.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
 
 namespace quic {
@@ -131,7 +132,7 @@
 
   // The value of the Destination Connection ID field from the first
   // Initial packet sent by the client.
-  QuicConnectionId original_connection_id;
+  quiche::QuicheOptional<QuicConnectionId> original_connection_id;
 
   // Idle timeout expressed in milliseconds.
   IntegerParameter idle_timeout_milliseconds;
diff --git a/quic/core/crypto/transport_parameters_test.cc b/quic/core/crypto/transport_parameters_test.cc
index bfaeb1f..72654a7 100644
--- a/quic/core/crypto/transport_parameters_test.cc
+++ b/quic/core/crypto/transport_parameters_test.cc
@@ -7,6 +7,7 @@
 #include <cstring>
 #include <utility>
 
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/core/quic_versions.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
@@ -533,7 +534,7 @@
   EXPECT_EQ(Perspective::IS_CLIENT, new_params.perspective);
   EXPECT_EQ(kFakeVersionLabel, new_params.version);
   EXPECT_TRUE(new_params.supported_versions.empty());
-  EXPECT_EQ(EmptyQuicConnectionId(), new_params.original_connection_id);
+  EXPECT_FALSE(new_params.original_connection_id.has_value());
   EXPECT_EQ(kFakeIdleTimeoutMilliseconds,
             new_params.idle_timeout_milliseconds.value());
   EXPECT_TRUE(new_params.stateless_reset_token.empty());
@@ -895,8 +896,9 @@
   EXPECT_EQ(2u, new_params.supported_versions.size());
   EXPECT_EQ(kFakeVersionLabel, new_params.supported_versions[0]);
   EXPECT_EQ(kFakeVersionLabel2, new_params.supported_versions[1]);
+  ASSERT_TRUE(new_params.original_connection_id.has_value());
   EXPECT_EQ(CreateFakeOriginalConnectionId(),
-            new_params.original_connection_id);
+            new_params.original_connection_id.value());
   EXPECT_EQ(kFakeIdleTimeoutMilliseconds,
             new_params.idle_timeout_milliseconds.value());
   EXPECT_EQ(CreateFakeStatelessResetToken(), new_params.stateless_reset_token);
@@ -985,6 +987,59 @@
   EXPECT_EQ(error_details, "Received a second idle_timeout");
 }
 
+TEST_P(TransportParametersTest,
+       ParseServerParametersEmptyOriginalConnectionId) {
+  // clang-format off
+  const uint8_t kServerParamsEmptyOriginalConnectionIdOld[] = {
+      0x00, 0x1e,  // length of parameters array that follows
+      // original_connection_id
+      0x00, 0x00,  // parameter id
+      0x00, 0x00,  // length
+      // idle_timeout
+      0x00, 0x01,  // parameter id
+      0x00, 0x02,  // length
+      0x6e, 0xec,  // value
+      // stateless_reset_token
+      0x00, 0x02,  // parameter id
+      0x00, 0x10,  // length
+      0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+      0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+  };
+  const uint8_t kServerParamsEmptyOriginalConnectionId[] = {
+      // original_connection_id
+      0x00,  // parameter id
+      0x00,  // length
+      // idle_timeout
+      0x01,  // parameter id
+      0x02,  // length
+      0x6e, 0xec,  // value
+      // stateless_reset_token
+      0x02,  // parameter id
+      0x10,  // length
+      0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+      0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+  };
+  // clang-format on
+  const uint8_t* server_params =
+      reinterpret_cast<const uint8_t*>(kServerParamsEmptyOriginalConnectionId);
+  size_t server_params_length =
+      QUICHE_ARRAYSIZE(kServerParamsEmptyOriginalConnectionId);
+  if (!version_.HasVarIntTransportParams()) {
+    server_params = reinterpret_cast<const uint8_t*>(
+        kServerParamsEmptyOriginalConnectionIdOld);
+    server_params_length =
+        QUICHE_ARRAYSIZE(kServerParamsEmptyOriginalConnectionIdOld);
+  }
+  TransportParameters out_params;
+  std::string error_details;
+  ASSERT_TRUE(ParseTransportParameters(version_, Perspective::IS_SERVER,
+                                       server_params, server_params_length,
+                                       &out_params, &error_details))
+      << error_details;
+  ASSERT_TRUE(out_params.original_connection_id.has_value());
+  EXPECT_EQ(out_params.original_connection_id.value(), EmptyQuicConnectionId());
+}
+
 TEST_P(TransportParametersTest, CryptoHandshakeMessageRoundtrip) {
   TransportParameters orig_params;
   orig_params.perspective = Perspective::IS_CLIENT;
diff --git a/quic/core/quic_config.cc b/quic/core/quic_config.cc
index a0de6cb..0eaf9d6 100644
--- a/quic/core/quic_config.cc
+++ b/quic/core/quic_config.cc
@@ -12,6 +12,7 @@
 
 #include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h"
 #include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
 #include "net/third_party/quiche/src/quic/core/quic_constants.h"
 #include "net/third_party/quiche/src/quic/core/quic_socket_address_coder.h"
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
@@ -851,6 +852,23 @@
   return alternate_server_address_ipv4_.GetReceivedValue();
 }
 
+void QuicConfig::SetOriginalConnectionIdToSend(
+    const QuicConnectionId& original_connection_id) {
+  original_connection_id_to_send_ = original_connection_id;
+}
+
+bool QuicConfig::HasReceivedOriginalConnectionId() const {
+  return received_original_connection_id_.has_value();
+}
+
+QuicConnectionId QuicConfig::ReceivedOriginalConnectionId() const {
+  if (!HasReceivedOriginalConnectionId()) {
+    QUIC_BUG << "No received original connection ID";
+    return EmptyQuicConnectionId();
+  }
+  return received_original_connection_id_.value();
+}
+
 void QuicConfig::SetStatelessResetTokenToSend(
     QuicUint128 stateless_reset_token) {
   stateless_reset_token_.SetSendValue(stateless_reset_token);
@@ -1037,6 +1055,10 @@
 }
 
 bool QuicConfig::FillTransportParameters(TransportParameters* params) const {
+  if (original_connection_id_to_send_.has_value()) {
+    params->original_connection_id = original_connection_id_to_send_.value();
+  }
+
   params->idle_timeout_milliseconds.set_value(
       idle_timeout_to_send_.ToMilliseconds());
 
@@ -1109,6 +1131,10 @@
     HelloType hello_type,
     bool is_resumption,
     std::string* error_details) {
+  if (!is_resumption && params.original_connection_id.has_value()) {
+    received_original_connection_id_ = params.original_connection_id.value();
+  }
+
   if (params.idle_timeout_milliseconds.value() > 0 &&
       params.idle_timeout_milliseconds.value() <
           static_cast<uint64_t>(idle_timeout_to_send_.ToMilliseconds())) {
diff --git a/quic/core/quic_config.h b/quic/core/quic_config.h
index 091e5de..f2ca652 100644
--- a/quic/core/quic_config.h
+++ b/quic/core/quic_config.h
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "net/third_party/quiche/src/quic/core/crypto/transport_parameters.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
 #include "net/third_party/quiche/src/quic/core/quic_packets.h"
 #include "net/third_party/quiche/src/quic/core/quic_time.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
@@ -323,24 +324,17 @@
     return max_undecryptable_packets_;
   }
 
+  // Peer's connection id length, in bytes. Only used in Q043 and Q046.
   bool HasSetBytesForConnectionIdToSend() const;
-
-  // Sets the peer's connection id length, in bytes.
   void SetBytesForConnectionIdToSend(uint32_t bytes);
-
   bool HasReceivedBytesForConnectionId() const;
-
   uint32_t ReceivedBytesForConnectionId() const;
 
-  // Sets an estimated initial round trip time in us.
+  // Estimated initial round trip time in us.
   void SetInitialRoundTripTimeUsToSend(uint32_t rtt_us);
-
   bool HasReceivedInitialRoundTripTimeUs() const;
-
   uint32_t ReceivedInitialRoundTripTimeUs() const;
-
   bool HasInitialRoundTripTimeUsToSend() const;
-
   uint32_t GetInitialRoundTripTimeUsToSend() const;
 
   // Sets an initial stream flow control window size to transmit to the peer.
@@ -380,15 +374,12 @@
 
   // Sets an initial session flow control window size to transmit to the peer.
   void SetInitialSessionFlowControlWindowToSend(uint64_t window_bytes);
-
   uint64_t GetInitialSessionFlowControlWindowToSend() const;
-
   bool HasReceivedInitialSessionFlowControlWindowBytes() const;
-
   uint64_t ReceivedInitialSessionFlowControlWindowBytes() const;
 
+  // Disable connection migration.
   void SetDisableConnectionMigration();
-
   bool DisableConnectionMigration() const;
 
   // IPv6 alternate server address.
@@ -403,10 +394,15 @@
   bool HasReceivedIPv4AlternateServerAddress() const;
   const QuicSocketAddress& ReceivedIPv4AlternateServerAddress() const;
 
+  // Original connection ID.
+  void SetOriginalConnectionIdToSend(
+      const QuicConnectionId& original_connection_id);
+  bool HasReceivedOriginalConnectionId() const;
+  QuicConnectionId ReceivedOriginalConnectionId() const;
+
+  // Stateless reset token.
   void SetStatelessResetTokenToSend(QuicUint128 stateless_reset_token);
-
   bool HasReceivedStatelessResetToken() const;
-
   QuicUint128 ReceivedStatelessResetToken() const;
 
   // Manage the IETF QUIC Max ACK Delay transport parameter.
@@ -588,6 +584,11 @@
   // Uses the max_datagram_frame_size transport parameter in IETF QUIC.
   QuicFixedUint62 max_datagram_frame_size_;
 
+  // Sent by the server when it has previously sent a RETRY packet.
+  // Uses the original_connection_id transport parameter in IETF QUIC.
+  quiche::QuicheOptional<QuicConnectionId> original_connection_id_to_send_;
+  quiche::QuicheOptional<QuicConnectionId> received_original_connection_id_;
+
   // Custom transport parameters that can be sent and received in the TLS
   // handshake.
   TransportParameters::ParameterMap custom_transport_parameters_to_send_;
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 7ec7955..3595fb9 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -409,6 +409,39 @@
                        config.IdleNetworkTimeout());
     idle_timeout_connection_close_behavior_ =
         ConnectionCloseBehavior::SILENT_CLOSE;
+    if (original_connection_id_.has_value()) {
+      DCHECK_EQ(perspective_, Perspective::IS_CLIENT);
+      // We received a RETRY packet, validate that the |original_connection_id|
+      // from the config matches the one from the RETRY.
+      if (!config.HasReceivedOriginalConnectionId() ||
+          config.ReceivedOriginalConnectionId() !=
+              original_connection_id_.value()) {
+        std::string received_value;
+        if (config.HasReceivedOriginalConnectionId()) {
+          received_value = config.ReceivedOriginalConnectionId().ToString();
+        } else {
+          received_value = "none";
+        }
+        std::string error_details = quiche::QuicheStrCat(
+            "Bad original_connection_id: expected ",
+            original_connection_id_.value().ToString(), ", received ",
+            received_value, ", RETRY used ", server_connection_id_.ToString());
+        CloseConnection(IETF_QUIC_PROTOCOL_VIOLATION, error_details,
+                        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+        return;
+      }
+    } else {
+      // We did not receive a RETRY packet, make sure we did not receive the
+      // original_connection_id transport parameter.
+      if (config.HasReceivedOriginalConnectionId()) {
+        std::string error_details = quiche::QuicheStrCat(
+            "Bad original_connection_id: did not receive RETRY but received ",
+            config.ReceivedOriginalConnectionId().ToString());
+        CloseConnection(IETF_QUIC_PROTOCOL_VIOLATION, error_details,
+                        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+        return;
+      }
+    }
   } else {
     SetNetworkTimeouts(config.max_time_before_crypto_handshake(),
                        config.max_idle_time_before_crypto_handshake());
@@ -681,6 +714,8 @@
                   << server_connection_id_ << " with " << new_connection_id
                   << ", received token "
                   << quiche::QuicheTextUtils::HexEncode(retry_token);
+  DCHECK(!original_connection_id_.has_value());
+  original_connection_id_ = server_connection_id_;
   server_connection_id_ = new_connection_id;
   packet_creator_.SetServerConnectionId(server_connection_id_);
   packet_creator_.SetRetryToken(retry_token);
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index f516e52..a9e1d7b 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -49,6 +49,7 @@
 #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_optional.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_str_cat.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
 
@@ -1594,6 +1595,11 @@
   // vector to improve performance since it is expected to be very small.
   std::vector<QuicConnectionId> incoming_connection_ids_;
 
+  // When we receive a RETRY packet, we replace |server_connection_id_| with the
+  // value from the RETRY packet and save off the original value of
+  // |server_connection_id_| into |original_connection_id_| for validation.
+  quiche::QuicheOptional<QuicConnectionId> original_connection_id_;
+
   // Indicates whether received RETRY packets should be dropped.
   bool drop_incoming_retry_packets_;
 
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 6f803c3..edeff6d 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -382,7 +382,8 @@
           ENCRYPTION_FORWARD_SECURE,
           std::make_unique<NullDecrypter>(Perspective::IS_SERVER));
     }
-    EXPECT_TRUE(framer_.ProcessPacket(packet));
+    EXPECT_TRUE(framer_.ProcessPacket(packet))
+        << framer_.framer()->detailed_error();
     if (block_on_next_write_) {
       write_blocked_ = true;
       block_on_next_write_ = false;
@@ -1669,10 +1670,12 @@
         writer_->connection_close_frames();
     ASSERT_EQ(1u, connection_close_frames.size());
 
-    EXPECT_EQ(expected_code, connection_close_frames[0].quic_error_code);
+    EXPECT_THAT(connection_close_frames[0].quic_error_code,
+                IsError(expected_code));
 
     if (!VersionHasIetfQuicFrames(version().transport_version)) {
-      EXPECT_EQ(expected_code, connection_close_frames[0].wire_error_code);
+      EXPECT_THAT(connection_close_frames[0].wire_error_code,
+                  IsError(expected_code));
       EXPECT_EQ(GOOGLE_QUIC_CONNECTION_CLOSE,
                 connection_close_frames[0].close_type);
       return;
@@ -1714,6 +1717,10 @@
     EXPECT_TRUE(connection_.connected());
   }
 
+  void TestClientRetryHandling(bool invalid_retry_tag,
+                               bool missing_id_in_config,
+                               bool wrong_id_in_config);
+
   QuicConnectionId connection_id_;
   QuicFramer framer_;
 
@@ -1747,7 +1754,7 @@
 };
 
 // Run all end to end tests with all supported versions.
-INSTANTIATE_TEST_SUITE_P(SupportedVersion,
+INSTANTIATE_TEST_SUITE_P(QuicConnectionTests,
                          QuicConnectionTest,
                          ::testing::ValuesIn(GetTestParams()),
                          ::testing::PrintToStringParamName());
@@ -1758,7 +1765,7 @@
 // close, the second an application connection close.
 // The connection close codes for the two tests are manually chosen;
 // they are expected to always map to transport- and application-
-// closes, respectively. If that changes, mew codes should be chosen.
+// closes, respectively. If that changes, new codes should be chosen.
 TEST_P(QuicConnectionTest, CloseErrorCodeTestTransport) {
   EXPECT_TRUE(connection_.connected());
   EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
@@ -10306,22 +10313,45 @@
   EXPECT_EQ(0x01010101u, writer_->final_bytes_of_last_packet());
 }
 
-TEST_P(QuicConnectionTest, ClientParsesRetry) {
+void QuicConnectionTest::TestClientRetryHandling(bool invalid_retry_tag,
+                                                 bool missing_id_in_config,
+                                                 bool wrong_id_in_config) {
+  if (invalid_retry_tag) {
+    ASSERT_FALSE(missing_id_in_config);
+    ASSERT_FALSE(wrong_id_in_config);
+  } else {
+    ASSERT_FALSE(missing_id_in_config && wrong_id_in_config);
+  }
   if (!version().HasRetryIntegrityTag()) {
     return;
   }
-  if (version() !=
-      ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_IETF_DRAFT_25)) {
+
+  // These values come from draft-ietf-quic-tls Appendix A.4.
+  char retry_packet25[] = {
+      0xff, 0xff, 0x00, 0x00, 0x19, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a,
+      0x42, 0x62, 0xb5, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x1e, 0x5e, 0xc5, 0xb0,
+      0x14, 0xcb, 0xb1, 0xf0, 0xfd, 0x93, 0xdf, 0x40, 0x48, 0xc4, 0x46, 0xa6};
+  char retry_packet27[] = {
+      0xff, 0xff, 0x00, 0x00, 0x1b, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a,
+      0x42, 0x62, 0xb5, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0xa5, 0x23, 0xcb, 0x5b,
+      0xa5, 0x24, 0x69, 0x5f, 0x65, 0x69, 0xf2, 0x93, 0xa1, 0x35, 0x9d, 0x8e};
+
+  char* retry_packet;
+  size_t retry_packet_length;
+  if (version() ==
+      ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_IETF_DRAFT_27)) {
+    retry_packet = retry_packet27;
+    retry_packet_length = QUICHE_ARRAYSIZE(retry_packet27);
+  } else if (version() ==
+             ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_IETF_DRAFT_25)) {
+    retry_packet = retry_packet25;
+    retry_packet_length = QUICHE_ARRAYSIZE(retry_packet25);
+  } else {
     // TODO(dschinazi) generate retry packets for all versions once we have
     // server-side support for generating these programmatically.
     return;
   }
 
-  // These values come from draft-ietf-quic-tls Appendix A.4.
-  char retry_packet[] = {0xff, 0xff, 0x00, 0x00, 0x19, 0x00, 0x08, 0xf0, 0x67,
-                         0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, 0x74, 0x6f, 0x6b,
-                         0x65, 0x6e, 0x1e, 0x5e, 0xc5, 0xb0, 0x14, 0xcb, 0xb1,
-                         0xf0, 0xfd, 0x93, 0xdf, 0x40, 0x48, 0xc4, 0x46, 0xa6};
   char original_connection_id_bytes[] = {0x83, 0x94, 0xc8, 0xf0,
                                          0x3e, 0x51, 0x57, 0x08};
   char new_connection_id_bytes[] = {0xf0, 0x67, 0xa5, 0x50,
@@ -10337,43 +10367,110 @@
   std::string retry_token(retry_token_bytes,
                           QUICHE_ARRAYSIZE(retry_token_bytes));
 
-  {
-    TestConnection connection1(
-        original_connection_id, kPeerAddress, helper_.get(),
-        alarm_factory_.get(), writer_.get(), Perspective::IS_CLIENT, version());
-    connection1.set_visitor(&visitor_);
-
-    connection1.ProcessUdpPacket(
-        kSelfAddress, kPeerAddress,
-        QuicReceivedPacket(retry_packet, QUICHE_ARRAYSIZE(retry_packet),
-                           clock_.Now()));
-    EXPECT_TRUE(connection1.GetStats().retry_packet_processed);
-    EXPECT_EQ(connection1.connection_id(), new_connection_id);
-    EXPECT_EQ(QuicPacketCreatorPeer::GetRetryToken(
-                  QuicConnectionPeer::GetPacketCreator(&connection1)),
-              retry_token);
+  if (invalid_retry_tag) {
+    // Flip the last bit of the retry packet to prevent the integrity tag
+    // from validating correctly.
+    retry_packet[retry_packet_length - 1] ^= 1;
   }
 
-  // Now flip the last bit of the retry packet to prevent the integrity tag
-  // from validating correctly.
-  retry_packet[QUICHE_ARRAYSIZE(retry_packet) - 1] ^= 1;
+  QuicConnectionId config_original_connection_id = original_connection_id;
+  if (wrong_id_in_config) {
+    // Flip the first bit of the connection ID.
+    ASSERT_FALSE(config_original_connection_id.IsEmpty());
+    config_original_connection_id.mutable_data()[0] ^= 0x80;
+  }
 
-  {
-    TestConnection connection2(
-        original_connection_id, kPeerAddress, helper_.get(),
-        alarm_factory_.get(), writer_.get(), Perspective::IS_CLIENT, version());
-    connection2.set_visitor(&visitor_);
+  // Make sure the connection uses the connection ID from the test vectors,
+  QuicConnectionPeer::SetServerConnectionId(&connection_,
+                                            original_connection_id);
 
-    connection2.ProcessUdpPacket(
-        kSelfAddress, kPeerAddress,
-        QuicReceivedPacket(retry_packet, QUICHE_ARRAYSIZE(retry_packet),
-                           clock_.Now()));
-    EXPECT_FALSE(connection2.GetStats().retry_packet_processed);
-    EXPECT_EQ(connection2.connection_id(), original_connection_id);
+  // Process the RETRY packet.
+  connection_.ProcessUdpPacket(
+      kSelfAddress, kPeerAddress,
+      QuicReceivedPacket(retry_packet, retry_packet_length, clock_.Now()));
+
+  if (invalid_retry_tag) {
+    // Make sure we refuse to process a RETRY with invalid tag.
+    EXPECT_FALSE(connection_.GetStats().retry_packet_processed);
+    EXPECT_EQ(connection_.connection_id(), original_connection_id);
     EXPECT_TRUE(QuicPacketCreatorPeer::GetRetryToken(
-                    QuicConnectionPeer::GetPacketCreator(&connection2))
+                    QuicConnectionPeer::GetPacketCreator(&connection_))
                     .empty());
+    return;
   }
+
+  // Make sure we correctly parsed the RETRY.
+  EXPECT_TRUE(connection_.GetStats().retry_packet_processed);
+  EXPECT_EQ(connection_.connection_id(), new_connection_id);
+  EXPECT_EQ(QuicPacketCreatorPeer::GetRetryToken(
+                QuicConnectionPeer::GetPacketCreator(&connection_)),
+            retry_token);
+  // Make sure our fake framer has the new post-retry INITIAL keys.
+  writer_->framer()->framer()->SetInitialObfuscators(new_connection_id);
+
+  // Test validating the original_connection_id from the config.
+  QuicConfig received_config;
+  QuicConfigPeer::SetNegotiated(&received_config, true);
+  if (!missing_id_in_config) {
+    QuicConfigPeer::SetReceivedOriginalConnectionId(
+        &received_config, config_original_connection_id);
+  }
+  if (missing_id_in_config || wrong_id_in_config) {
+    EXPECT_CALL(visitor_,
+                OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF))
+        .Times(1);
+  } else {
+    EXPECT_CALL(visitor_,
+                OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF))
+        .Times(0);
+  }
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(AnyNumber());
+  connection_.SetFromConfig(received_config);
+  if (missing_id_in_config || wrong_id_in_config) {
+    EXPECT_FALSE(connection_.connected());
+    TestConnectionCloseQuicErrorCode(IETF_QUIC_PROTOCOL_VIOLATION);
+  } else {
+    EXPECT_TRUE(connection_.connected());
+  }
+}
+
+TEST_P(QuicConnectionTest, ClientParsesRetry) {
+  TestClientRetryHandling(/*invalid_retry_tag=*/false,
+                          /*missing_id_in_config=*/false,
+                          /*wrong_id_in_config=*/false);
+}
+
+TEST_P(QuicConnectionTest, ClientParsesInvalidRetry) {
+  TestClientRetryHandling(/*invalid_retry_tag=*/true,
+                          /*missing_id_in_config=*/false,
+                          /*wrong_id_in_config=*/false);
+}
+
+TEST_P(QuicConnectionTest, ClientParsesRetryMissingId) {
+  TestClientRetryHandling(/*invalid_retry_tag=*/false,
+                          /*missing_id_in_config=*/true,
+                          /*wrong_id_in_config=*/false);
+}
+
+TEST_P(QuicConnectionTest, ClientParsesRetryWrongId) {
+  TestClientRetryHandling(/*invalid_retry_tag=*/false,
+                          /*missing_id_in_config=*/false,
+                          /*wrong_id_in_config=*/true);
+}
+
+TEST_P(QuicConnectionTest, ClientReceivesOriginalConnectionIdWithoutRetry) {
+  // Make sure that receiving the original_connection_id transport parameter
+  // fails the handshake when no RETRY packet was received before it.
+  QuicConfig received_config;
+  QuicConfigPeer::SetNegotiated(&received_config, true);
+  QuicConfigPeer::SetReceivedOriginalConnectionId(&received_config,
+                                                  TestConnectionId(0x12345));
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(AnyNumber());
+  EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF))
+      .Times(1);
+  connection_.SetFromConfig(received_config);
+  EXPECT_FALSE(connection_.connected());
+  TestConnectionCloseQuicErrorCode(IETF_QUIC_PROTOCOL_VIOLATION);
 }
 
 // Regression test for http://crbug/1047977
diff --git a/quic/core/quic_types.cc b/quic/core/quic_types.cc
index 75144e3..36614cd 100644
--- a/quic/core/quic_types.cc
+++ b/quic/core/quic_types.cc
@@ -17,12 +17,56 @@
   return os;
 }
 
-std::ostream& operator<<(std::ostream& os, const Perspective& s) {
-  if (s == Perspective::IS_SERVER) {
-    os << "IS_SERVER";
-  } else {
-    os << "IS_CLIENT";
+std::string PerspectiveToString(Perspective perspective) {
+  if (perspective == Perspective::IS_SERVER) {
+    return "IS_SERVER";
   }
+  if (perspective == Perspective::IS_CLIENT) {
+    return "IS_CLIENT";
+  }
+  return quiche::QuicheStrCat("Unknown(", static_cast<int>(perspective), ")");
+}
+
+std::ostream& operator<<(std::ostream& os, const Perspective& perspective) {
+  os << PerspectiveToString(perspective);
+  return os;
+}
+
+std::string ConnectionCloseSourceToString(
+    ConnectionCloseSource connection_close_source) {
+  if (connection_close_source == ConnectionCloseSource::FROM_PEER) {
+    return "FROM_PEER";
+  }
+  if (connection_close_source == ConnectionCloseSource::FROM_SELF) {
+    return "FROM_SELF";
+  }
+  return quiche::QuicheStrCat("Unknown(",
+                              static_cast<int>(connection_close_source), ")");
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const ConnectionCloseSource& connection_close_source) {
+  os << ConnectionCloseSourceToString(connection_close_source);
+  return os;
+}
+
+std::string ConnectionCloseBehaviorToString(
+    ConnectionCloseBehavior connection_close_behavior) {
+  if (connection_close_behavior == ConnectionCloseBehavior::SILENT_CLOSE) {
+    return "SILENT_CLOSE";
+  }
+  if (connection_close_behavior ==
+      ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET) {
+    return "SEND_CONNECTION_CLOSE_PACKET";
+  }
+  return quiche::QuicheStrCat("Unknown(",
+                              static_cast<int>(connection_close_behavior), ")");
+}
+
+std::ostream& operator<<(
+    std::ostream& os,
+    const ConnectionCloseBehavior& connection_close_behavior) {
+  os << ConnectionCloseBehaviorToString(connection_close_behavior);
   return os;
 }
 
diff --git a/quic/core/quic_types.h b/quic/core/quic_types.h
index a3d43b8..8a73361 100644
--- a/quic/core/quic_types.h
+++ b/quic/core/quic_types.h
@@ -182,18 +182,32 @@
 enum IsHandshake : uint8_t { NOT_HANDSHAKE, IS_HANDSHAKE };
 
 enum class Perspective : uint8_t { IS_SERVER, IS_CLIENT };
+
+QUIC_EXPORT_PRIVATE std::string PerspectiveToString(Perspective perspective);
 QUIC_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
-                                             const Perspective& s);
+                                             const Perspective& perspective);
 
 // Describes whether a ConnectionClose was originated by the peer.
 enum class ConnectionCloseSource { FROM_PEER, FROM_SELF };
 
+QUIC_EXPORT_PRIVATE std::string ConnectionCloseSourceToString(
+    ConnectionCloseSource connection_close_source);
+QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+    std::ostream& os,
+    const ConnectionCloseSource& connection_close_source);
+
 // Should a connection be closed silently or not.
 enum class ConnectionCloseBehavior {
   SILENT_CLOSE,
   SEND_CONNECTION_CLOSE_PACKET
 };
 
+QUIC_EXPORT_PRIVATE std::string ConnectionCloseBehaviorToString(
+    ConnectionCloseBehavior connection_close_behavior);
+QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+    std::ostream& os,
+    const ConnectionCloseBehavior& connection_close_behavior);
+
 enum QuicFrameType : uint8_t {
   // Regular frame types. The values set here cannot change without the
   // introduction of a new QUIC version.
diff --git a/quic/test_tools/quic_config_peer.cc b/quic/test_tools/quic_config_peer.cc
index 2f20e95..f8f3f42 100644
--- a/quic/test_tools/quic_config_peer.cc
+++ b/quic/test_tools/quic_config_peer.cc
@@ -5,6 +5,7 @@
 #include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
 
 #include "net/third_party/quiche/src/quic/core/quic_config.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
 
 namespace quic {
 namespace test {
@@ -102,5 +103,12 @@
   config->negotiated_ = negotiated;
 }
 
+// static
+void QuicConfigPeer::SetReceivedOriginalConnectionId(
+    QuicConfig* config,
+    const QuicConnectionId& original_connection_id) {
+  config->received_original_connection_id_ = original_connection_id;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_config_peer.h b/quic/test_tools/quic_config_peer.h
index 64d4d1e..94f30b4 100644
--- a/quic/test_tools/quic_config_peer.h
+++ b/quic/test_tools/quic_config_peer.h
@@ -6,6 +6,7 @@
 #define QUICHE_QUIC_TEST_TOOLS_QUIC_CONFIG_PEER_H_
 
 #include "net/third_party/quiche/src/quic/core/quic_config.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
 #include "net/third_party/quiche/src/quic/core/quic_packets.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_uint128.h"
 
@@ -60,6 +61,10 @@
                                        uint32_t max_packet_size);
 
   static void SetNegotiated(QuicConfig* config, bool negotiated);
+
+  static void SetReceivedOriginalConnectionId(
+      QuicConfig* config,
+      const QuicConnectionId& original_connection_id);
 };
 
 }  // namespace test
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc
index 518e6a1..09dff83 100644
--- a/quic/test_tools/quic_connection_peer.cc
+++ b/quic/test_tools/quic_connection_peer.cc
@@ -375,5 +375,13 @@
   return connection->idle_network_detector_.alarm_.get();
 }
 
+// static
+void QuicConnectionPeer::SetServerConnectionId(
+    QuicConnection* connection,
+    const QuicConnectionId& server_connection_id) {
+  connection->server_connection_id_ = server_connection_id;
+  connection->InstallInitialCrypters(server_connection_id);
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h
index 4126503..b66a8a2 100644
--- a/quic/test_tools/quic_connection_peer.h
+++ b/quic/test_tools/quic_connection_peer.h
@@ -144,6 +144,10 @@
   static QuicTime GetBlackholeDetectionDeadline(QuicConnection* connection);
 
   static QuicAlarm* GetIdleNetworkDetectorAlarm(QuicConnection* connection);
+
+  static void SetServerConnectionId(
+      QuicConnection* connection,
+      const QuicConnectionId& server_connection_id);
 };
 
 }  // namespace test
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index 9c81412..cec38a1 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -1630,12 +1630,12 @@
 
 // A GMock matcher that prints expected and actual QuicErrorCode strings
 // upon failure.  Example usage:
-// EXPECT_THAT(stream_->connection_error()), IsError(QUIC_INTERNAL_ERROR));
+// EXPECT_THAT(stream_->connection_error(), IsError(QUIC_INTERNAL_ERROR));
 MATCHER_P(IsError,
           expected,
           quiche::QuicheStrCat(negation ? "isn't equal to " : "is equal to ",
                                QuicErrorCodeToString(expected))) {
-  *result_listener << QuicErrorCodeToString(arg);
+  *result_listener << QuicErrorCodeToString(static_cast<QuicErrorCode>(arg));
   return arg == expected;
 }