Allow setting connection options from command line in QuicToyClient

This CL also adds a convenience method that can parse a comma-separated list of QuicTags, with corresponding test. The additions in the shared code are only used in QuicToyClient, so this CL does not impact existing behavior in prod.

Test-tool-only change

PiperOrigin-RevId: 317685170
Change-Id: I83e7737a3e4e40446e6051bd3ec07dfbf81559e2
diff --git a/quic/core/quic_tag.cc b/quic/core/quic_tag.cc
index 270383d..b25c5b7 100644
--- a/quic/core/quic_tag.cc
+++ b/quic/core/quic_tag.cc
@@ -74,4 +74,32 @@
          tag_vector.end();
 }
 
+QuicTag ParseQuicTag(quiche::QuicheStringPiece tag_string) {
+  quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&tag_string);
+  std::string tag_bytes;
+  if (tag_string.length() == 8) {
+    tag_bytes = quiche::QuicheTextUtils::HexDecode(tag_string);
+    tag_string = tag_bytes;
+  }
+  QuicTag tag = 0;
+  // Iterate over every character from right to left.
+  for (auto it = tag_string.crbegin(); it != tag_string.crend(); ++it) {
+    // The cast here is required on platforms where char is signed.
+    unsigned char token_char = static_cast<unsigned char>(*it);
+    tag <<= 8;
+    tag |= token_char;
+  }
+  return tag;
+}
+
+QuicTagVector ParseQuicTagVector(quiche::QuicheStringPiece tags_string) {
+  QuicTagVector tag_vector;
+  std::vector<quiche::QuicheStringPiece> tag_strings =
+      quiche::QuicheTextUtils::Split(tags_string, ',');
+  for (quiche::QuicheStringPiece tag_string : tag_strings) {
+    tag_vector.push_back(ParseQuicTag(tag_string));
+  }
+  return tag_vector;
+}
+
 }  // namespace quic
diff --git a/quic/core/quic_tag.h b/quic/core/quic_tag.h
index 71fcdd5..7ba6cfc 100644
--- a/quic/core/quic_tag.h
+++ b/quic/core/quic_tag.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
 
 namespace quic {
 
@@ -48,6 +49,17 @@
 // treat it as a number if not.
 QUIC_EXPORT_PRIVATE std::string QuicTagToString(QuicTag tag);
 
+// Utility function that converts a string of the form "ABCD" to its
+// corresponding QuicTag. Note that tags that are less than four characters
+// long are right-padded with zeroes. Tags that contain non-ASCII characters
+// are represented as 8-character-long hexadecimal strings.
+QUIC_EXPORT_PRIVATE QuicTag ParseQuicTag(quiche::QuicheStringPiece tag_string);
+
+// Utility function that converts a string of the form "ABCD,EFGH" to a vector
+// of the form {kABCD,kEFGH}. Note the caveats on ParseQuicTag.
+QUIC_EXPORT_PRIVATE QuicTagVector
+ParseQuicTagVector(quiche::QuicheStringPiece tags_string);
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_QUIC_TAG_H_
diff --git a/quic/core/quic_tag_test.cc b/quic/core/quic_tag_test.cc
index 3d58133..3dc93ad 100644
--- a/quic/core/quic_tag_test.cc
+++ b/quic/core/quic_tag_test.cc
@@ -33,6 +33,35 @@
   EXPECT_EQ('D', bytes[3]);
 }
 
+TEST_F(QuicTagTest, ParseQuicTag) {
+  QuicTag tag_abcd = MakeQuicTag('A', 'B', 'C', 'D');
+  EXPECT_EQ(ParseQuicTag("ABCD"), tag_abcd);
+  QuicTag tag_efgh = MakeQuicTag('E', 'F', 'G', 'H');
+  EXPECT_EQ(ParseQuicTag("EFGH"), tag_efgh);
+  QuicTag tag_ijk = MakeQuicTag('I', 'J', 'K', 0);
+  EXPECT_EQ(ParseQuicTag("IJK"), tag_ijk);
+  QuicTag tag_l = MakeQuicTag('L', 0, 0, 0);
+  EXPECT_EQ(ParseQuicTag("L"), tag_l);
+  QuicTag tag_hex = MakeQuicTag('M', 'N', 'O', 255);
+  EXPECT_EQ(ParseQuicTag("4d4e4fff"), tag_hex);
+  EXPECT_EQ(ParseQuicTag("4D4E4FFF"), tag_hex);
+  QuicTag tag_zero = 0;
+  EXPECT_EQ(ParseQuicTag(""), tag_zero);
+  QuicTagVector tag_vector;
+  tag_vector.push_back(tag_abcd);
+  EXPECT_EQ(ParseQuicTagVector("ABCD"), tag_vector);
+  tag_vector.push_back(tag_efgh);
+  EXPECT_EQ(ParseQuicTagVector("ABCD,EFGH"), tag_vector);
+  tag_vector.push_back(tag_ijk);
+  EXPECT_EQ(ParseQuicTagVector("ABCD,EFGH,IJK"), tag_vector);
+  tag_vector.push_back(tag_l);
+  EXPECT_EQ(ParseQuicTagVector("ABCD,EFGH,IJK,L"), tag_vector);
+  tag_vector.push_back(tag_hex);
+  EXPECT_EQ(ParseQuicTagVector("ABCD,EFGH,IJK,L,4d4e4fff"), tag_vector);
+  tag_vector.push_back(tag_zero);
+  EXPECT_EQ(ParseQuicTagVector("ABCD,EFGH,IJK,L,4d4e4fff,"), tag_vector);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/tools/quic_epoll_client_factory.cc b/quic/tools/quic_epoll_client_factory.cc
index 1751f41..38dc582 100644
--- a/quic/tools/quic_epoll_client_factory.cc
+++ b/quic/tools/quic_epoll_client_factory.cc
@@ -22,6 +22,7 @@
     std::string host_for_lookup,
     uint16_t port,
     ParsedQuicVersionVector versions,
+    const QuicConfig& config,
     std::unique_ptr<ProofVerifier> verifier) {
   QuicSocketAddress addr =
       tools::LookupAddress(host_for_lookup, quiche::QuicheStrCat(port));
@@ -30,8 +31,9 @@
     return nullptr;
   }
   QuicServerId server_id(host_for_handshake, port, false);
-  return std::make_unique<QuicClient>(addr, server_id, versions, &epoll_server_,
-                                      std::move(verifier));
+  return std::make_unique<QuicClient>(addr, server_id, versions, config,
+                                      &epoll_server_, std::move(verifier),
+                                      nullptr);
 }
 
 }  // namespace quic
diff --git a/quic/tools/quic_epoll_client_factory.h b/quic/tools/quic_epoll_client_factory.h
index 2ee26d9..392bd6c 100644
--- a/quic/tools/quic_epoll_client_factory.h
+++ b/quic/tools/quic_epoll_client_factory.h
@@ -18,6 +18,7 @@
       std::string host_for_lookup,
       uint16_t port,
       ParsedQuicVersionVector versions,
+      const QuicConfig& config,
       std::unique_ptr<ProofVerifier> verifier) override;
 
  private:
diff --git a/quic/tools/quic_toy_client.cc b/quic/tools/quic_toy_client.cc
index 0018f05..b42f967 100644
--- a/quic/tools/quic_toy_client.cc
+++ b/quic/tools/quic_toy_client.cc
@@ -109,6 +109,20 @@
     "versions are offered in the handshake. Also supports wire versions "
     "such as Q043 or T099.");
 
+DEFINE_QUIC_COMMAND_LINE_FLAG(
+    std::string,
+    connection_options,
+    "",
+    "Connection options as ASCII tags separated by commas, "
+    "e.g. \"ABCD,EFGH\"");
+
+DEFINE_QUIC_COMMAND_LINE_FLAG(
+    std::string,
+    client_connection_options,
+    "",
+    "Client connection options as ASCII tags separated by commas, "
+    "e.g. \"ABCD,EFGH\"");
+
 DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
                               quic_ietf_draft,
                               false,
@@ -232,9 +246,22 @@
     proof_verifier = quic::CreateDefaultProofVerifier(url.host());
   }
 
+  QuicConfig config;
+  std::string connection_options_string = GetQuicFlag(FLAGS_connection_options);
+  if (!connection_options_string.empty()) {
+    config.SetConnectionOptionsToSend(
+        ParseQuicTagVector(connection_options_string));
+  }
+  std::string client_connection_options_string =
+      GetQuicFlag(FLAGS_client_connection_options);
+  if (!client_connection_options_string.empty()) {
+    config.SetClientConnectionOptions(
+        ParseQuicTagVector(client_connection_options_string));
+  }
+
   // Build the client, and try to connect.
   std::unique_ptr<QuicSpdyClientBase> client = client_factory_->CreateClient(
-      url.host(), host, port, versions, std::move(proof_verifier));
+      url.host(), host, port, versions, config, std::move(proof_verifier));
 
   if (client == nullptr) {
     std::cerr << "Failed to create client." << std::endl;
diff --git a/quic/tools/quic_toy_client.h b/quic/tools/quic_toy_client.h
index 1a20122..d9d8eca 100644
--- a/quic/tools/quic_toy_client.h
+++ b/quic/tools/quic_toy_client.h
@@ -26,6 +26,7 @@
         std::string host_for_lookup,
         uint16_t port,
         ParsedQuicVersionVector versions,
+        const QuicConfig& config,
         std::unique_ptr<ProofVerifier> verifier) = 0;
   };