Add --quic_versions to QuicToyServer and improve version parsing

This CL improves our version parsing code to allow using ALPN and parse a list, and uses that in a new --quic_versions flag on QuicToyServer. Note that ParseQuicVersionString is only used in tests and toy code.

gfe-relnote: n/a, test-only
PiperOrigin-RevId: 299245199
Change-Id: I33c0f73b6a094de0dba86b69bd598a24ee162872
diff --git a/quic/core/quic_versions.cc b/quic/core/quic_versions.cc
index a7450aa..b5fc585 100644
--- a/quic/core/quic_versions.cc
+++ b/quic/core/quic_versions.cc
@@ -263,7 +263,8 @@
   return UnsupportedQuicVersion();
 }
 
-ParsedQuicVersion ParseQuicVersionString(std::string version_string) {
+ParsedQuicVersion ParseQuicVersionString(
+    quiche::QuicheStringPiece version_string) {
   if (version_string.empty()) {
     return UnsupportedQuicVersion();
   }
@@ -271,12 +272,26 @@
   if (quiche::QuicheTextUtils::StringToInt(version_string,
                                            &quic_version_number) &&
       quic_version_number > 0) {
-    return ParsedQuicVersion(
-        PROTOCOL_QUIC_CRYPTO,
-        static_cast<QuicTransportVersion>(quic_version_number));
+    QuicTransportVersion transport_version =
+        static_cast<QuicTransportVersion>(quic_version_number);
+    bool transport_version_is_supported = false;
+    for (QuicTransportVersion transport_vers : SupportedTransportVersions()) {
+      if (transport_vers == transport_version) {
+        transport_version_is_supported = true;
+        break;
+      }
+    }
+    if (!transport_version_is_supported ||
+        !ParsedQuicVersionIsValid(PROTOCOL_QUIC_CRYPTO, transport_version)) {
+      return UnsupportedQuicVersion();
+    }
+    return ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, transport_version);
   }
   for (const ParsedQuicVersion& version : AllSupportedVersions()) {
-    if (version_string == ParsedQuicVersionToString(version)) {
+    if (version_string == ParsedQuicVersionToString(version) ||
+        version_string == AlpnForVersion(version) ||
+        (version.handshake_protocol == PROTOCOL_QUIC_CRYPTO &&
+         version_string == QuicVersionToString(version.transport_version))) {
       return version;
     }
   }
@@ -286,6 +301,25 @@
   return UnsupportedQuicVersion();
 }
 
+ParsedQuicVersionVector ParseQuicVersionVectorString(
+    quiche::QuicheStringPiece versions_string) {
+  ParsedQuicVersionVector versions;
+  std::vector<quiche::QuicheStringPiece> version_strings =
+      quiche::QuicheTextUtils::Split(versions_string, ',');
+  for (quiche::QuicheStringPiece version_string : version_strings) {
+    quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(
+        &version_string);
+    ParsedQuicVersion version = ParseQuicVersionString(version_string);
+    if (version.transport_version == QUIC_VERSION_UNSUPPORTED ||
+        std::find(versions.begin(), versions.end(), version) !=
+            versions.end()) {
+      continue;
+    }
+    versions.push_back(version);
+  }
+  return versions;
+}
+
 QuicTransportVersionVector AllSupportedTransportVersions() {
   constexpr auto supported_transport_versions = SupportedTransportVersions();
   QuicTransportVersionVector supported_versions(
diff --git a/quic/core/quic_versions.h b/quic/core/quic_versions.h
index bb539b1..e07e24a 100644
--- a/quic/core/quic_versions.h
+++ b/quic/core/quic_versions.h
@@ -21,6 +21,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_tag.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #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 {
 
@@ -399,10 +400,17 @@
 QUIC_EXPORT_PRIVATE ParsedQuicVersion
 ParseQuicVersionLabel(QuicVersionLabel version_label);
 
-// Parses a QUIC version string such as "Q043" or "T099".
-// Also supports parsing numbers such as "44".
+// Parses a QUIC version string such as "Q043" or "T050". Also supports parsing
+// ALPN such as "h3-25" or "h3-Q050". For PROTOCOL_QUIC_CRYPTO versions, also
+// supports parsing numbers such as "46".
 QUIC_EXPORT_PRIVATE ParsedQuicVersion
-ParseQuicVersionString(std::string version_string);
+ParseQuicVersionString(quiche::QuicheStringPiece version_string);
+
+// Parses a comma-separated list of QUIC version strings. Supports parsing by
+// label, ALPN and numbers for PROTOCOL_QUIC_CRYPTO. Skips unknown versions.
+// For example: "h3-25,Q050,46".
+QUIC_EXPORT_PRIVATE ParsedQuicVersionVector
+ParseQuicVersionVectorString(quiche::QuicheStringPiece versions_string);
 
 // Constructs a QuicVersionLabel from the provided ParsedQuicVersion.
 QUIC_EXPORT_PRIVATE QuicVersionLabel
diff --git a/quic/core/quic_versions_test.cc b/quic/core/quic_versions_test.cc
index 59d7b09..851619f 100644
--- a/quic/core/quic_versions_test.cc
+++ b/quic/core/quic_versions_test.cc
@@ -15,7 +15,9 @@
 namespace test {
 namespace {
 
-using testing::_;
+using ::testing::_;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
 
 class QuicVersionsTest : public QuicTest {
  protected:
@@ -152,19 +154,110 @@
   EXPECT_EQ(ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43),
             ParseQuicVersionString("Q043"));
   EXPECT_EQ(ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46),
+            ParseQuicVersionString("QUIC_VERSION_46"));
+  EXPECT_EQ(ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46),
+            ParseQuicVersionString("46"));
+  EXPECT_EQ(ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46),
             ParseQuicVersionString("Q046"));
   EXPECT_EQ(ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_48),
             ParseQuicVersionString("Q048"));
   EXPECT_EQ(ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_50),
             ParseQuicVersionString("Q050"));
+  EXPECT_EQ(ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_50),
+            ParseQuicVersionString("50"));
+  EXPECT_EQ(ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_50),
+            ParseQuicVersionString("h3-Q050"));
 
   EXPECT_EQ(UnsupportedQuicVersion(), ParseQuicVersionString(""));
   EXPECT_EQ(UnsupportedQuicVersion(), ParseQuicVersionString("Q 46"));
   EXPECT_EQ(UnsupportedQuicVersion(), ParseQuicVersionString("Q046 "));
+  EXPECT_EQ(UnsupportedQuicVersion(), ParseQuicVersionString("99"));
+  EXPECT_EQ(UnsupportedQuicVersion(), ParseQuicVersionString("70"));
 
-  // Test a TLS version:
   EXPECT_EQ(ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_50),
             ParseQuicVersionString("T050"));
+  EXPECT_EQ(ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_50),
+            ParseQuicVersionString("h3-T050"));
+  EXPECT_EQ(ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_IETF_DRAFT_27),
+            ParseQuicVersionString("ff00001b"));
+  EXPECT_EQ(ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_IETF_DRAFT_27),
+            ParseQuicVersionString("h3-27"));
+  EXPECT_EQ(ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_IETF_DRAFT_25),
+            ParseQuicVersionString("ff000019"));
+  EXPECT_EQ(ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_IETF_DRAFT_25),
+            ParseQuicVersionString("h3-25"));
+}
+
+TEST_F(QuicVersionsTest, ParseQuicVersionVectorString) {
+  ParsedQuicVersion version_q046(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46);
+  ParsedQuicVersion version_q050(PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_50);
+  ParsedQuicVersion version_t050(PROTOCOL_TLS1_3, QUIC_VERSION_50);
+  ParsedQuicVersion version_draft_25(PROTOCOL_TLS1_3,
+                                     QUIC_VERSION_IETF_DRAFT_25);
+  ParsedQuicVersion version_draft_27(PROTOCOL_TLS1_3,
+                                     QUIC_VERSION_IETF_DRAFT_27);
+
+  EXPECT_THAT(ParseQuicVersionVectorString(""), IsEmpty());
+
+  EXPECT_THAT(ParseQuicVersionVectorString("QUIC_VERSION_50"),
+              ElementsAre(version_q050));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-Q050"),
+              ElementsAre(version_q050));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-T050"),
+              ElementsAre(version_t050));
+
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-25, h3-27"),
+              ElementsAre(version_draft_25, version_draft_27));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-25,h3-27"),
+              ElementsAre(version_draft_25, version_draft_27));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-25,h3-27,h3-25"),
+              ElementsAre(version_draft_25, version_draft_27));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-25,h3-27, h3-25"),
+              ElementsAre(version_draft_25, version_draft_27));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-27,h3-25"),
+              ElementsAre(version_draft_27, version_draft_25));
+
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-27,50"),
+              ElementsAre(version_draft_27, version_q050));
+
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-Q050, h3-T050"),
+              ElementsAre(version_q050, version_t050));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-T050, h3-Q050"),
+              ElementsAre(version_t050, version_q050));
+  EXPECT_THAT(ParseQuicVersionVectorString("QUIC_VERSION_50,h3-T050"),
+              ElementsAre(version_q050, version_t050));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-T050,QUIC_VERSION_50"),
+              ElementsAre(version_t050, version_q050));
+  EXPECT_THAT(ParseQuicVersionVectorString("QUIC_VERSION_50, h3-T050"),
+              ElementsAre(version_q050, version_t050));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-T050, QUIC_VERSION_50"),
+              ElementsAre(version_t050, version_q050));
+
+  EXPECT_THAT(ParseQuicVersionVectorString("QUIC_VERSION_50,QUIC_VERSION_46"),
+              ElementsAre(version_q050, version_q046));
+  EXPECT_THAT(ParseQuicVersionVectorString("QUIC_VERSION_46,QUIC_VERSION_50"),
+              ElementsAre(version_q046, version_q050));
+
+  // Regression test for https://crbug.com/1044952.
+  EXPECT_THAT(ParseQuicVersionVectorString("QUIC_VERSION_50, QUIC_VERSION_50"),
+              ElementsAre(version_q050));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-Q050, h3-Q050"),
+              ElementsAre(version_q050));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-T050, h3-T050"),
+              ElementsAre(version_t050));
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-Q050, QUIC_VERSION_50"),
+              ElementsAre(version_q050));
+  EXPECT_THAT(ParseQuicVersionVectorString(
+                  "QUIC_VERSION_50, h3-Q050, QUIC_VERSION_50, h3-Q050"),
+              ElementsAre(version_q050));
+  EXPECT_THAT(ParseQuicVersionVectorString("QUIC_VERSION_50, h3-T050, h3-Q050"),
+              ElementsAre(version_q050, version_t050));
+
+  EXPECT_THAT(ParseQuicVersionVectorString("99"), IsEmpty());
+  EXPECT_THAT(ParseQuicVersionVectorString("70"), IsEmpty());
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-01"), IsEmpty());
+  EXPECT_THAT(ParseQuicVersionVectorString("h3-01,h3-25"),
+              ElementsAre(version_draft_25));
 }
 
 TEST_F(QuicVersionsTest, CreateQuicVersionLabel) {
diff --git a/quic/tools/quic_toy_client.cc b/quic/tools/quic_toy_client.cc
index 8a9532d..63a0671 100644
--- a/quic/tools/quic_toy_client.cc
+++ b/quic/tools/quic_toy_client.cc
@@ -184,30 +184,29 @@
 
   quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions();
 
-  std::string quic_version_string = GetQuicFlag(FLAGS_quic_version);
   if (GetQuicFlag(FLAGS_quic_ietf_draft)) {
     quic::QuicVersionInitializeSupportForIetfDraft();
     versions = {};
     for (const ParsedQuicVersion& version : AllSupportedVersions()) {
-      // Find the first version that supports IETF QUIC.
       if (version.HasIetfQuicFrames() &&
           version.handshake_protocol == quic::PROTOCOL_TLS1_3) {
-        versions = {version};
-        break;
+        versions.push_back(version);
       }
     }
-    CHECK_EQ(versions.size(), 1u);
-    quic::QuicEnableVersion(versions[0]);
+  }
 
-  } else if (!quic_version_string.empty()) {
-    quic::ParsedQuicVersion parsed_quic_version =
-        quic::ParseQuicVersionString(quic_version_string);
-    if (parsed_quic_version.transport_version ==
-        quic::QUIC_VERSION_UNSUPPORTED) {
-      return 1;
-    }
-    versions = {parsed_quic_version};
-    quic::QuicEnableVersion(parsed_quic_version);
+  std::string quic_version_string = GetQuicFlag(FLAGS_quic_version);
+  if (!quic_version_string.empty()) {
+    versions = quic::ParseQuicVersionVectorString(quic_version_string);
+  }
+
+  if (versions.empty()) {
+    std::cerr << "No known version selected." << std::endl;
+    return 1;
+  }
+
+  for (const quic::ParsedQuicVersion& version : versions) {
+    quic::QuicEnableVersion(version);
   }
 
   if (GetQuicFlag(FLAGS_force_version_negotiation)) {
diff --git a/quic/tools/quic_toy_server.cc b/quic/tools/quic_toy_server.cc
index a23e7d9..20ee151 100644
--- a/quic/tools/quic_toy_server.cc
+++ b/quic/tools/quic_toy_server.cc
@@ -36,8 +36,15 @@
 DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
                               quic_ietf_draft,
                               false,
-                              "Use the IETF draft version. This also enables "
-                              "required internal QUIC flags.");
+                              "Only enable IETF draft versions. This also "
+                              "enables required internal QUIC flags.");
+
+DEFINE_QUIC_COMMAND_LINE_FLAG(
+    std::string,
+    quic_versions,
+    "",
+    "QUIC versions to enable, e.g. \"h3-25,h3-27\". If not set, then all "
+    "available versions are enabled.");
 
 namespace quic {
 
@@ -73,6 +80,13 @@
   } else {
     supported_versions = AllSupportedVersions();
   }
+  std::string versions_string = GetQuicFlag(FLAGS_quic_versions);
+  if (!versions_string.empty()) {
+    supported_versions = ParseQuicVersionVectorString(versions_string);
+  }
+  if (supported_versions.empty()) {
+    return 1;
+  }
   for (const auto& version : supported_versions) {
     QuicEnableVersion(version);
   }