Rewrite bssl_shim command-line parser.

The command-line parser is slightly showing its age: first, it is hard
to add new integral types, such as uint16_t, which is getting in the way
of fixing some of the -Wformat-signedness errors. Second, the parameter
extraction logic and skipping logic is duplicated in every type.

While I'm here, use a binary search to look up the flag, since we have
rather a lot of them. With more C++ template tricks, we could avoid the
std::function, but that seemed more trouble than was worth it,
especially since, prior to C++17, it's a little hard to convince
template argument deduction to infer one of the parameters.

Change-Id: I208f89d46371b31fc8b44487725296bcd9d7c8e7
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/50769
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 16cb971..1fc2218 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -15,15 +15,22 @@
 #include "test_config.h"
 
 #include <assert.h>
+#include <ctype.h>
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
+#include <algorithm>
+#include <functional>
+#include <limits>
 #include <memory>
+#include <type_traits>
 
 #include <openssl/base64.h>
 #include <openssl/hpke.h>
 #include <openssl/rand.h>
+#include <openssl/span.h>
 #include <openssl/ssl.h>
 
 #include "../../crypto/internal.h"
@@ -34,235 +41,88 @@
 
 namespace {
 
-template <typename T>
 struct Flag {
-  const char *flag;
-  T TestConfig::*member;
+  const char *name;
+  bool has_param;
+  // If |has_param| is false, |param| will be nullptr.
+  std::function<bool(TestConfig *config, const char *param)> set_param;
 };
 
-// FindField looks for the flag in |flags| that matches |flag|. If one is found,
-// it returns a pointer to the corresponding field in |config|. Otherwise, it
-// returns NULL.
-template<typename T, size_t N>
-T *FindField(TestConfig *config, const Flag<T> (&flags)[N], const char *flag) {
-  for (size_t i = 0; i < N; i++) {
-    if (strcmp(flag, flags[i].flag) == 0) {
-      return &(config->*(flags[i].member));
-    }
-  }
-  return NULL;
+Flag BoolFlag(const char *name, bool TestConfig::*field) {
+  return Flag{name, false, [=](TestConfig *config, const char *) -> bool {
+                config->*field = true;
+                return true;
+              }};
 }
 
-const Flag<bool> kBoolFlags[] = {
-    {"-server", &TestConfig::is_server},
-    {"-dtls", &TestConfig::is_dtls},
-    {"-quic", &TestConfig::is_quic},
-    {"-fallback-scsv", &TestConfig::fallback_scsv},
-    {"-enable-ech-grease", &TestConfig::enable_ech_grease},
-    {"-expect-ech-accept", &TestConfig::expect_ech_accept},
-    {"-expect-no-ech-name-override", &TestConfig::expect_no_ech_name_override},
-    {"-expect-no-ech-retry-configs", &TestConfig::expect_no_ech_retry_configs},
-    {"-require-any-client-certificate",
-     &TestConfig::require_any_client_certificate},
-    {"-false-start", &TestConfig::false_start},
-    {"-async", &TestConfig::async},
-    {"-write-different-record-sizes",
-     &TestConfig::write_different_record_sizes},
-    {"-cbc-record-splitting", &TestConfig::cbc_record_splitting},
-    {"-partial-write", &TestConfig::partial_write},
-    {"-no-tls13", &TestConfig::no_tls13},
-    {"-no-tls12", &TestConfig::no_tls12},
-    {"-no-tls11", &TestConfig::no_tls11},
-    {"-no-tls1", &TestConfig::no_tls1},
-    {"-no-ticket", &TestConfig::no_ticket},
-    {"-enable-channel-id", &TestConfig::enable_channel_id},
-    {"-shim-writes-first", &TestConfig::shim_writes_first},
-    {"-expect-session-miss", &TestConfig::expect_session_miss},
-    {"-decline-alpn", &TestConfig::decline_alpn},
-    {"-reject-alpn", &TestConfig::reject_alpn},
-    {"-select-empty-alpn", &TestConfig::select_empty_alpn},
-    {"-defer-alps", &TestConfig::defer_alps},
-    {"-expect-extended-master-secret",
-     &TestConfig::expect_extended_master_secret},
-    {"-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling},
-    {"-enable-signed-cert-timestamps",
-     &TestConfig::enable_signed_cert_timestamps},
-    {"-implicit-handshake", &TestConfig::implicit_handshake},
-    {"-use-early-callback", &TestConfig::use_early_callback},
-    {"-fail-early-callback", &TestConfig::fail_early_callback},
-    {"-install-ddos-callback", &TestConfig::install_ddos_callback},
-    {"-fail-ddos-callback", &TestConfig::fail_ddos_callback},
-    {"-fail-cert-callback", &TestConfig::fail_cert_callback},
-    {"-handshake-never-done", &TestConfig::handshake_never_done},
-    {"-use-export-context", &TestConfig::use_export_context},
-    {"-tls-unique", &TestConfig::tls_unique},
-    {"-expect-ticket-renewal", &TestConfig::expect_ticket_renewal},
-    {"-expect-no-session", &TestConfig::expect_no_session},
-    {"-expect-ticket-supports-early-data",
-     &TestConfig::expect_ticket_supports_early_data},
-    {"-use-ticket-callback", &TestConfig::use_ticket_callback},
-    {"-renew-ticket", &TestConfig::renew_ticket},
-    {"-enable-early-data", &TestConfig::enable_early_data},
-    {"-check-close-notify", &TestConfig::check_close_notify},
-    {"-shim-shuts-down", &TestConfig::shim_shuts_down},
-    {"-verify-fail", &TestConfig::verify_fail},
-    {"-verify-peer", &TestConfig::verify_peer},
-    {"-verify-peer-if-no-obc", &TestConfig::verify_peer_if_no_obc},
-    {"-expect-verify-result", &TestConfig::expect_verify_result},
-    {"-renegotiate-once", &TestConfig::renegotiate_once},
-    {"-renegotiate-freely", &TestConfig::renegotiate_freely},
-    {"-renegotiate-ignore", &TestConfig::renegotiate_ignore},
-    {"-renegotiate-explicit", &TestConfig::renegotiate_explicit},
-    {"-forbid-renegotiation-after-handshake",
-     &TestConfig::forbid_renegotiation_after_handshake},
-    {"-use-old-client-cert-callback",
-     &TestConfig::use_old_client_cert_callback},
-    {"-send-alert", &TestConfig::send_alert},
-    {"-peek-then-read", &TestConfig::peek_then_read},
-    {"-enable-grease", &TestConfig::enable_grease},
-    {"-permute-extensions", &TestConfig::permute_extensions},
-    {"-use-exporter-between-reads", &TestConfig::use_exporter_between_reads},
-    {"-retain-only-sha256-client-cert",
-     &TestConfig::retain_only_sha256_client_cert},
-    {"-expect-sha256-client-cert", &TestConfig::expect_sha256_client_cert},
-    {"-read-with-unfinished-write", &TestConfig::read_with_unfinished_write},
-    {"-expect-secure-renegotiation", &TestConfig::expect_secure_renegotiation},
-    {"-expect-no-secure-renegotiation",
-     &TestConfig::expect_no_secure_renegotiation},
-    {"-expect-session-id", &TestConfig::expect_session_id},
-    {"-expect-no-session-id", &TestConfig::expect_no_session_id},
-    {"-expect-accept-early-data", &TestConfig::expect_accept_early_data},
-    {"-expect-reject-early-data", &TestConfig::expect_reject_early_data},
-    {"-expect-no-offer-early-data", &TestConfig::expect_no_offer_early_data},
-    {"-no-op-extra-handshake", &TestConfig::no_op_extra_handshake},
-    {"-handshake-twice", &TestConfig::handshake_twice},
-    {"-allow-unknown-alpn-protos", &TestConfig::allow_unknown_alpn_protos},
-    {"-use-custom-verify-callback", &TestConfig::use_custom_verify_callback},
-    {"-allow-false-start-without-alpn",
-     &TestConfig::allow_false_start_without_alpn},
-    {"-handoff", &TestConfig::handoff},
-    {"-handshake-hints", &TestConfig::handshake_hints},
-    {"-allow-hint-mismatch", &TestConfig::allow_hint_mismatch},
-    {"-use-ocsp-callback", &TestConfig::use_ocsp_callback},
-    {"-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback},
-    {"-decline-ocsp-callback", &TestConfig::decline_ocsp_callback},
-    {"-fail-ocsp-callback", &TestConfig::fail_ocsp_callback},
-    {"-install-cert-compression-algs",
-     &TestConfig::install_cert_compression_algs},
-    {"-is-handshaker-supported", &TestConfig::is_handshaker_supported},
-    {"-handshaker-resume", &TestConfig::handshaker_resume},
-    {"-reverify-on-resume", &TestConfig::reverify_on_resume},
-    {"-enforce-rsa-key-usage", &TestConfig::enforce_rsa_key_usage},
-    {"-jdk11-workaround", &TestConfig::jdk11_workaround},
-    {"-server-preference", &TestConfig::server_preference},
-    {"-export-traffic-secrets", &TestConfig::export_traffic_secrets},
-    {"-key-update", &TestConfig::key_update},
-    {"-expect-delegated-credential-used",
-     &TestConfig::expect_delegated_credential_used},
-    {"-expect-hrr", &TestConfig::expect_hrr},
-    {"-expect-no-hrr", &TestConfig::expect_no_hrr},
-    {"-wait-for-debugger", &TestConfig::wait_for_debugger},
-};
+template <typename T>
+bool StringToInt(T *out, const char *str) {
+  static_assert(std::is_integral<T>::value, "not an integral type");
+  static_assert(sizeof(T) <= sizeof(long long), "type too large for long long");
 
-const Flag<std::string> kStringFlags[] = {
-    {"-write-settings", &TestConfig::write_settings},
-    {"-key-file", &TestConfig::key_file},
-    {"-cert-file", &TestConfig::cert_file},
-    {"-expect-server-name", &TestConfig::expect_server_name},
-    {"-expect-ech-name-override", &TestConfig::expect_ech_name_override},
-    {"-advertise-npn", &TestConfig::advertise_npn},
-    {"-expect-next-proto", &TestConfig::expect_next_proto},
-    {"-select-next-proto", &TestConfig::select_next_proto},
-    {"-send-channel-id", &TestConfig::send_channel_id},
-    {"-host-name", &TestConfig::host_name},
-    {"-advertise-alpn", &TestConfig::advertise_alpn},
-    {"-expect-alpn", &TestConfig::expect_alpn},
-    {"-expect-late-alpn", &TestConfig::expect_late_alpn},
-    {"-expect-advertised-alpn", &TestConfig::expect_advertised_alpn},
-    {"-select-alpn", &TestConfig::select_alpn},
-    {"-psk", &TestConfig::psk},
-    {"-psk-identity", &TestConfig::psk_identity},
-    {"-srtp-profiles", &TestConfig::srtp_profiles},
-    {"-cipher", &TestConfig::cipher},
-    {"-export-label", &TestConfig::export_label},
-    {"-export-context", &TestConfig::export_context},
-    {"-expect-peer-cert-file", &TestConfig::expect_peer_cert_file},
-    {"-use-client-ca-list", &TestConfig::use_client_ca_list},
-    {"-expect-client-ca-list", &TestConfig::expect_client_ca_list},
-    {"-expect-msg-callback", &TestConfig::expect_msg_callback},
-    {"-handshaker-path", &TestConfig::handshaker_path},
-    {"-delegated-credential", &TestConfig::delegated_credential},
-    {"-expect-early-data-reason", &TestConfig::expect_early_data_reason},
-    {"-quic-early-data-context", &TestConfig::quic_early_data_context},
-};
+  // |strtoull| allows leading '-' with wraparound. Additionally, both
+  // functions accept empty strings and leading whitespace.
+  if (!isdigit(static_cast<unsigned char>(*str)) &&
+      (!std::is_signed<T>::value || *str != '-')) {
+    return false;
+  }
+
+  errno = 0;
+  char *end;
+  if (std::is_signed<T>::value) {
+    long long value = strtoll(str, &end, 10);
+    if (value < std::numeric_limits<T>::min() ||
+        value > std::numeric_limits<T>::max()) {
+      return false;
+    }
+    *out = static_cast<T>(value);
+  } else {
+    unsigned long long value = strtoull(str, &end, 10);
+    if (value > std::numeric_limits<T>::max()) {
+      return false;
+    }
+    *out = static_cast<T>(value);
+  }
+
+  // Check for overflow and that the whole input was consumed.
+  return errno != ERANGE && *end == '\0';
+}
+
+template <typename T>
+Flag IntFlag(const char *name, T TestConfig::*field) {
+  return Flag{name, true, [=](TestConfig *config, const char *param) -> bool {
+                return StringToInt(&(config->*field), param);
+              }};
+}
+
+template <typename T>
+Flag IntVectorFlag(const char *name, std::vector<T> TestConfig::*field) {
+  return Flag{name, true, [=](TestConfig *config, const char *param) -> bool {
+                T value;
+                if (!StringToInt(&value, param)) {
+                  return false;
+                }
+                (config->*field).push_back(value);
+                return true;
+              }};
+}
+
+Flag StringFlag(const char *name, std::string TestConfig::*field) {
+  return Flag{name, true, [=](TestConfig *config, const char *param) -> bool {
+                config->*field = param;
+                return true;
+              }};
+}
 
 // TODO(davidben): When we can depend on C++17 or Abseil, switch this to
 // std::optional or absl::optional.
-const Flag<std::unique_ptr<std::string>> kOptionalStringFlags[] = {
-    {"-expect-peer-application-settings",
-     &TestConfig::expect_peer_application_settings},
-};
-
-const Flag<std::string> kBase64Flags[] = {
-    {"-expect-ech-retry-configs", &TestConfig::expect_ech_retry_configs},
-    {"-ech-config-list", &TestConfig::ech_config_list},
-    {"-expect-certificate-types", &TestConfig::expect_certificate_types},
-    {"-expect-channel-id", &TestConfig::expect_channel_id},
-    {"-expect-ocsp-response", &TestConfig::expect_ocsp_response},
-    {"-expect-signed-cert-timestamps",
-     &TestConfig::expect_signed_cert_timestamps},
-    {"-ocsp-response", &TestConfig::ocsp_response},
-    {"-signed-cert-timestamps", &TestConfig::signed_cert_timestamps},
-    {"-ticket-key", &TestConfig::ticket_key},
-    {"-quic-transport-params", &TestConfig::quic_transport_params},
-    {"-expect-quic-transport-params",
-     &TestConfig::expect_quic_transport_params},
-};
-
-const Flag<int> kIntFlags[] = {
-    {"-port", &TestConfig::port},
-    {"-resume-count", &TestConfig::resume_count},
-    {"-min-version", &TestConfig::min_version},
-    {"-max-version", &TestConfig::max_version},
-    {"-expect-version", &TestConfig::expect_version},
-    {"-mtu", &TestConfig::mtu},
-    {"-export-keying-material", &TestConfig::export_keying_material},
-    {"-expect-total-renegotiations", &TestConfig::expect_total_renegotiations},
-    {"-expect-peer-signature-algorithm",
-     &TestConfig::expect_peer_signature_algorithm},
-    {"-expect-curve-id", &TestConfig::expect_curve_id},
-    {"-initial-timeout-duration-ms", &TestConfig::initial_timeout_duration_ms},
-    {"-max-cert-list", &TestConfig::max_cert_list},
-    {"-expect-cipher-aes", &TestConfig::expect_cipher_aes},
-    {"-expect-cipher-no-aes", &TestConfig::expect_cipher_no_aes},
-    {"-expect-cipher", &TestConfig::expect_cipher},
-    {"-resumption-delay", &TestConfig::resumption_delay},
-    {"-max-send-fragment", &TestConfig::max_send_fragment},
-    {"-read-size", &TestConfig::read_size},
-    {"-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew},
-    {"-quic-use-legacy-codepoint", &TestConfig::quic_use_legacy_codepoint},
-    {"-install-one-cert-compression-alg",
-     &TestConfig::install_one_cert_compression_alg},
-    {"-early-write-after-message", &TestConfig::early_write_after_message},
-};
-
-const Flag<std::vector<int>> kIntVectorFlags[] = {
-    {"-signing-prefs", &TestConfig::signing_prefs},
-    {"-verify-prefs", &TestConfig::verify_prefs},
-    {"-expect-peer-verify-pref", &TestConfig::expect_peer_verify_prefs},
-    {"-curves", &TestConfig::curves},
-    {"-ech-is-retry-config", &TestConfig::ech_is_retry_config},
-};
-
-const Flag<std::vector<std::string>> kBase64VectorFlags[] = {
-    {"-ech-server-config", &TestConfig::ech_server_configs},
-    {"-ech-server-key", &TestConfig::ech_server_keys},
-};
-
-const Flag<std::vector<std::pair<std::string, std::string>>>
-    kStringPairVectorFlags[] = {
-        {"-application-settings", &TestConfig::application_settings},
-};
+Flag OptionalStringFlag(const char *name,
+                        std::unique_ptr<std::string> TestConfig::*field) {
+  return Flag{name, true, [=](TestConfig *config, const char *param) -> bool {
+                (config->*field).reset(new std::string(param));
+                return true;
+              }};
+}
 
 bool DecodeBase64(std::string *out, const std::string &in) {
   size_t len;
@@ -281,134 +141,268 @@
   return true;
 }
 
-bool ParseFlag(const char *flag, int argc, char **argv, int *i,
-               bool skip, TestConfig *out_config) {
-  bool *bool_field = FindField(out_config, kBoolFlags, flag);
-  if (bool_field != NULL) {
-    if (!skip) {
-      *bool_field = true;
-    }
-    return true;
+Flag Base64Flag(const char *name, std::string TestConfig::*field) {
+  return Flag{name, true, [=](TestConfig *config, const char *param) -> bool {
+                return DecodeBase64(&(config->*field), param);
+              }};
+}
+
+Flag Base64VectorFlag(const char *name,
+                      std::vector<std::string> TestConfig::*field) {
+  return Flag{name, true, [=](TestConfig *config, const char *param) -> bool {
+                std::string value;
+                if (!DecodeBase64(&value, param)) {
+                  return false;
+                }
+                (config->*field).push_back(std::move(value));
+                return true;
+              }};
+}
+
+Flag StringPairVectorFlag(
+    const char *name,
+    std::vector<std::pair<std::string, std::string>> TestConfig::*field) {
+  return Flag{name, true, [=](TestConfig *config, const char *param) -> bool {
+                const char *comma = strchr(param, ',');
+                if (!comma) {
+                  return false;
+                }
+                (config->*field)
+                    .push_back(std::make_pair(std::string(param, comma - param),
+                                              std::string(comma + 1)));
+                return true;
+              }};
+}
+
+std::vector<Flag> SortedFlags() {
+  // TODO(davidben): Reorder these to match the struct.
+  std::vector<Flag> flags = {
+      BoolFlag("-server", &TestConfig::is_server),
+      BoolFlag("-dtls", &TestConfig::is_dtls),
+      BoolFlag("-quic", &TestConfig::is_quic),
+      BoolFlag("-fallback-scsv", &TestConfig::fallback_scsv),
+      BoolFlag("-enable-ech-grease", &TestConfig::enable_ech_grease),
+      BoolFlag("-expect-ech-accept", &TestConfig::expect_ech_accept),
+      BoolFlag("-expect-no-ech-name-override",
+               &TestConfig::expect_no_ech_name_override),
+      BoolFlag("-expect-no-ech-retry-configs",
+               &TestConfig::expect_no_ech_retry_configs),
+      BoolFlag("-require-any-client-certificate",
+               &TestConfig::require_any_client_certificate),
+      BoolFlag("-false-start", &TestConfig::false_start),
+      BoolFlag("-async", &TestConfig::async),
+      BoolFlag("-write-different-record-sizes",
+               &TestConfig::write_different_record_sizes),
+      BoolFlag("-cbc-record-splitting", &TestConfig::cbc_record_splitting),
+      BoolFlag("-partial-write", &TestConfig::partial_write),
+      BoolFlag("-no-tls13", &TestConfig::no_tls13),
+      BoolFlag("-no-tls12", &TestConfig::no_tls12),
+      BoolFlag("-no-tls11", &TestConfig::no_tls11),
+      BoolFlag("-no-tls1", &TestConfig::no_tls1),
+      BoolFlag("-no-ticket", &TestConfig::no_ticket),
+      BoolFlag("-enable-channel-id", &TestConfig::enable_channel_id),
+      BoolFlag("-shim-writes-first", &TestConfig::shim_writes_first),
+      BoolFlag("-expect-session-miss", &TestConfig::expect_session_miss),
+      BoolFlag("-decline-alpn", &TestConfig::decline_alpn),
+      BoolFlag("-reject-alpn", &TestConfig::reject_alpn),
+      BoolFlag("-select-empty-alpn", &TestConfig::select_empty_alpn),
+      BoolFlag("-defer-alps", &TestConfig::defer_alps),
+      BoolFlag("-expect-extended-master-secret",
+               &TestConfig::expect_extended_master_secret),
+      BoolFlag("-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling),
+      BoolFlag("-enable-signed-cert-timestamps",
+               &TestConfig::enable_signed_cert_timestamps),
+      BoolFlag("-implicit-handshake", &TestConfig::implicit_handshake),
+      BoolFlag("-use-early-callback", &TestConfig::use_early_callback),
+      BoolFlag("-fail-early-callback", &TestConfig::fail_early_callback),
+      BoolFlag("-install-ddos-callback", &TestConfig::install_ddos_callback),
+      BoolFlag("-fail-ddos-callback", &TestConfig::fail_ddos_callback),
+      BoolFlag("-fail-cert-callback", &TestConfig::fail_cert_callback),
+      BoolFlag("-handshake-never-done", &TestConfig::handshake_never_done),
+      BoolFlag("-use-export-context", &TestConfig::use_export_context),
+      BoolFlag("-tls-unique", &TestConfig::tls_unique),
+      BoolFlag("-expect-ticket-renewal", &TestConfig::expect_ticket_renewal),
+      BoolFlag("-expect-no-session", &TestConfig::expect_no_session),
+      BoolFlag("-expect-ticket-supports-early-data",
+               &TestConfig::expect_ticket_supports_early_data),
+      BoolFlag("-use-ticket-callback", &TestConfig::use_ticket_callback),
+      BoolFlag("-renew-ticket", &TestConfig::renew_ticket),
+      BoolFlag("-enable-early-data", &TestConfig::enable_early_data),
+      BoolFlag("-check-close-notify", &TestConfig::check_close_notify),
+      BoolFlag("-shim-shuts-down", &TestConfig::shim_shuts_down),
+      BoolFlag("-verify-fail", &TestConfig::verify_fail),
+      BoolFlag("-verify-peer", &TestConfig::verify_peer),
+      BoolFlag("-verify-peer-if-no-obc", &TestConfig::verify_peer_if_no_obc),
+      BoolFlag("-expect-verify-result", &TestConfig::expect_verify_result),
+      BoolFlag("-renegotiate-once", &TestConfig::renegotiate_once),
+      BoolFlag("-renegotiate-freely", &TestConfig::renegotiate_freely),
+      BoolFlag("-renegotiate-ignore", &TestConfig::renegotiate_ignore),
+      BoolFlag("-renegotiate-explicit", &TestConfig::renegotiate_explicit),
+      BoolFlag("-forbid-renegotiation-after-handshake",
+               &TestConfig::forbid_renegotiation_after_handshake),
+      BoolFlag("-use-old-client-cert-callback",
+               &TestConfig::use_old_client_cert_callback),
+      BoolFlag("-send-alert", &TestConfig::send_alert),
+      BoolFlag("-peek-then-read", &TestConfig::peek_then_read),
+      BoolFlag("-enable-grease", &TestConfig::enable_grease),
+      BoolFlag("-permute-extensions", &TestConfig::permute_extensions),
+      BoolFlag("-use-exporter-between-reads",
+               &TestConfig::use_exporter_between_reads),
+      BoolFlag("-retain-only-sha256-client-cert",
+               &TestConfig::retain_only_sha256_client_cert),
+      BoolFlag("-expect-sha256-client-cert",
+               &TestConfig::expect_sha256_client_cert),
+      BoolFlag("-read-with-unfinished-write",
+               &TestConfig::read_with_unfinished_write),
+      BoolFlag("-expect-secure-renegotiation",
+               &TestConfig::expect_secure_renegotiation),
+      BoolFlag("-expect-no-secure-renegotiation",
+               &TestConfig::expect_no_secure_renegotiation),
+      BoolFlag("-expect-session-id", &TestConfig::expect_session_id),
+      BoolFlag("-expect-no-session-id", &TestConfig::expect_no_session_id),
+      BoolFlag("-expect-accept-early-data",
+               &TestConfig::expect_accept_early_data),
+      BoolFlag("-expect-reject-early-data",
+               &TestConfig::expect_reject_early_data),
+      BoolFlag("-expect-no-offer-early-data",
+               &TestConfig::expect_no_offer_early_data),
+      BoolFlag("-no-op-extra-handshake", &TestConfig::no_op_extra_handshake),
+      BoolFlag("-handshake-twice", &TestConfig::handshake_twice),
+      BoolFlag("-allow-unknown-alpn-protos",
+               &TestConfig::allow_unknown_alpn_protos),
+      BoolFlag("-use-custom-verify-callback",
+               &TestConfig::use_custom_verify_callback),
+      BoolFlag("-allow-false-start-without-alpn",
+               &TestConfig::allow_false_start_without_alpn),
+      BoolFlag("-handoff", &TestConfig::handoff),
+      BoolFlag("-handshake-hints", &TestConfig::handshake_hints),
+      BoolFlag("-allow-hint-mismatch", &TestConfig::allow_hint_mismatch),
+      BoolFlag("-use-ocsp-callback", &TestConfig::use_ocsp_callback),
+      BoolFlag("-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback),
+      BoolFlag("-decline-ocsp-callback", &TestConfig::decline_ocsp_callback),
+      BoolFlag("-fail-ocsp-callback", &TestConfig::fail_ocsp_callback),
+      BoolFlag("-install-cert-compression-algs",
+               &TestConfig::install_cert_compression_algs),
+      BoolFlag("-is-handshaker-supported",
+               &TestConfig::is_handshaker_supported),
+      BoolFlag("-handshaker-resume", &TestConfig::handshaker_resume),
+      BoolFlag("-reverify-on-resume", &TestConfig::reverify_on_resume),
+      BoolFlag("-enforce-rsa-key-usage", &TestConfig::enforce_rsa_key_usage),
+      BoolFlag("-jdk11-workaround", &TestConfig::jdk11_workaround),
+      BoolFlag("-server-preference", &TestConfig::server_preference),
+      BoolFlag("-export-traffic-secrets", &TestConfig::export_traffic_secrets),
+      BoolFlag("-key-update", &TestConfig::key_update),
+      BoolFlag("-expect-delegated-credential-used",
+               &TestConfig::expect_delegated_credential_used),
+      BoolFlag("-expect-hrr", &TestConfig::expect_hrr),
+      BoolFlag("-expect-no-hrr", &TestConfig::expect_no_hrr),
+      BoolFlag("-wait-for-debugger", &TestConfig::wait_for_debugger),
+      StringFlag("-write-settings", &TestConfig::write_settings),
+      StringFlag("-key-file", &TestConfig::key_file),
+      StringFlag("-cert-file", &TestConfig::cert_file),
+      StringFlag("-expect-server-name", &TestConfig::expect_server_name),
+      StringFlag("-expect-ech-name-override",
+                 &TestConfig::expect_ech_name_override),
+      StringFlag("-advertise-npn", &TestConfig::advertise_npn),
+      StringFlag("-expect-next-proto", &TestConfig::expect_next_proto),
+      StringFlag("-select-next-proto", &TestConfig::select_next_proto),
+      StringFlag("-send-channel-id", &TestConfig::send_channel_id),
+      StringFlag("-host-name", &TestConfig::host_name),
+      StringFlag("-advertise-alpn", &TestConfig::advertise_alpn),
+      StringFlag("-expect-alpn", &TestConfig::expect_alpn),
+      StringFlag("-expect-late-alpn", &TestConfig::expect_late_alpn),
+      StringFlag("-expect-advertised-alpn",
+                 &TestConfig::expect_advertised_alpn),
+      StringFlag("-select-alpn", &TestConfig::select_alpn),
+      StringFlag("-psk", &TestConfig::psk),
+      StringFlag("-psk-identity", &TestConfig::psk_identity),
+      StringFlag("-srtp-profiles", &TestConfig::srtp_profiles),
+      StringFlag("-cipher", &TestConfig::cipher),
+      StringFlag("-export-label", &TestConfig::export_label),
+      StringFlag("-export-context", &TestConfig::export_context),
+      StringFlag("-expect-peer-cert-file", &TestConfig::expect_peer_cert_file),
+      StringFlag("-use-client-ca-list", &TestConfig::use_client_ca_list),
+      StringFlag("-expect-client-ca-list", &TestConfig::expect_client_ca_list),
+      StringFlag("-expect-msg-callback", &TestConfig::expect_msg_callback),
+      StringFlag("-handshaker-path", &TestConfig::handshaker_path),
+      StringFlag("-delegated-credential", &TestConfig::delegated_credential),
+      StringFlag("-expect-early-data-reason",
+                 &TestConfig::expect_early_data_reason),
+      StringFlag("-quic-early-data-context",
+                 &TestConfig::quic_early_data_context),
+      OptionalStringFlag("-expect-peer-application-settings",
+                         &TestConfig::expect_peer_application_settings),
+      Base64Flag("-expect-ech-retry-configs",
+                 &TestConfig::expect_ech_retry_configs),
+      Base64Flag("-ech-config-list", &TestConfig::ech_config_list),
+      Base64Flag("-expect-certificate-types",
+                 &TestConfig::expect_certificate_types),
+      Base64Flag("-expect-channel-id", &TestConfig::expect_channel_id),
+      Base64Flag("-expect-ocsp-response", &TestConfig::expect_ocsp_response),
+      Base64Flag("-expect-signed-cert-timestamps",
+                 &TestConfig::expect_signed_cert_timestamps),
+      Base64Flag("-ocsp-response", &TestConfig::ocsp_response),
+      Base64Flag("-signed-cert-timestamps",
+                 &TestConfig::signed_cert_timestamps),
+      Base64Flag("-ticket-key", &TestConfig::ticket_key),
+      Base64Flag("-quic-transport-params", &TestConfig::quic_transport_params),
+      Base64Flag("-expect-quic-transport-params",
+                 &TestConfig::expect_quic_transport_params),
+      IntFlag("-port", &TestConfig::port),
+      IntFlag("-resume-count", &TestConfig::resume_count),
+      IntFlag("-min-version", &TestConfig::min_version),
+      IntFlag("-max-version", &TestConfig::max_version),
+      IntFlag("-expect-version", &TestConfig::expect_version),
+      IntFlag("-mtu", &TestConfig::mtu),
+      IntFlag("-export-keying-material", &TestConfig::export_keying_material),
+      IntFlag("-expect-total-renegotiations",
+              &TestConfig::expect_total_renegotiations),
+      IntFlag("-expect-peer-signature-algorithm",
+              &TestConfig::expect_peer_signature_algorithm),
+      IntFlag("-expect-curve-id", &TestConfig::expect_curve_id),
+      IntFlag("-initial-timeout-duration-ms",
+              &TestConfig::initial_timeout_duration_ms),
+      IntFlag("-max-cert-list", &TestConfig::max_cert_list),
+      IntFlag("-expect-cipher-aes", &TestConfig::expect_cipher_aes),
+      IntFlag("-expect-cipher-no-aes", &TestConfig::expect_cipher_no_aes),
+      IntFlag("-expect-cipher", &TestConfig::expect_cipher),
+      IntFlag("-resumption-delay", &TestConfig::resumption_delay),
+      IntFlag("-max-send-fragment", &TestConfig::max_send_fragment),
+      IntFlag("-read-size", &TestConfig::read_size),
+      IntFlag("-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew),
+      IntFlag("-quic-use-legacy-codepoint",
+              &TestConfig::quic_use_legacy_codepoint),
+      IntFlag("-install-one-cert-compression-alg",
+              &TestConfig::install_one_cert_compression_alg),
+      IntFlag("-early-write-after-message",
+              &TestConfig::early_write_after_message),
+      IntVectorFlag("-signing-prefs", &TestConfig::signing_prefs),
+      IntVectorFlag("-verify-prefs", &TestConfig::verify_prefs),
+      IntVectorFlag("-expect-peer-verify-pref",
+                    &TestConfig::expect_peer_verify_prefs),
+      IntVectorFlag("-curves", &TestConfig::curves),
+      IntVectorFlag("-ech-is-retry-config", &TestConfig::ech_is_retry_config),
+      Base64VectorFlag("-ech-server-config", &TestConfig::ech_server_configs),
+      Base64VectorFlag("-ech-server-key", &TestConfig::ech_server_keys),
+      StringPairVectorFlag("-application-settings",
+                           &TestConfig::application_settings),
+  };
+  std::sort(flags.begin(), flags.end(), [](const Flag &a, const Flag &b) {
+    return strcmp(a.name, b.name) < 0;
+  });
+  return flags;
+}
+
+const Flag *FindFlag(const char *name) {
+  static const std::vector<Flag> kSortedFlags = SortedFlags();
+  auto iter = std::lower_bound(kSortedFlags.begin(), kSortedFlags.end(), name,
+                               [](const Flag &flag, const char *key) {
+                                 return strcmp(flag.name, key) < 0;
+                               });
+  if (iter == kSortedFlags.end() || strcmp(iter->name, name) != 0) {
+    return nullptr;
   }
-
-  std::string *string_field = FindField(out_config, kStringFlags, flag);
-  if (string_field != NULL) {
-    *i = *i + 1;
-    if (*i >= argc) {
-      fprintf(stderr, "Missing parameter.\n");
-      return false;
-    }
-    if (!skip) {
-      string_field->assign(argv[*i]);
-    }
-    return true;
-  }
-
-  std::unique_ptr<std::string> *optional_string_field =
-      FindField(out_config, kOptionalStringFlags, flag);
-  if (optional_string_field != NULL) {
-    *i = *i + 1;
-    if (*i >= argc) {
-      fprintf(stderr, "Missing parameter.\n");
-      return false;
-    }
-    if (!skip) {
-      optional_string_field->reset(new std::string(argv[*i]));
-    }
-    return true;
-  }
-
-  std::string *base64_field = FindField(out_config, kBase64Flags, flag);
-  if (base64_field != NULL) {
-    *i = *i + 1;
-    if (*i >= argc) {
-      fprintf(stderr, "Missing parameter.\n");
-      return false;
-    }
-    std::string value;
-    if (!DecodeBase64(&value, argv[*i])) {
-      return false;
-    }
-    if (!skip) {
-      *base64_field = std::move(value);
-    }
-    return true;
-  }
-
-  int *int_field = FindField(out_config, kIntFlags, flag);
-  if (int_field) {
-    *i = *i + 1;
-    if (*i >= argc) {
-      fprintf(stderr, "Missing parameter.\n");
-      return false;
-    }
-    if (!skip) {
-      *int_field = atoi(argv[*i]);
-    }
-    return true;
-  }
-
-  std::vector<int> *int_vector_field =
-      FindField(out_config, kIntVectorFlags, flag);
-  if (int_vector_field) {
-    *i = *i + 1;
-    if (*i >= argc) {
-      fprintf(stderr, "Missing parameter.\n");
-      return false;
-    }
-
-    // Each instance of the flag adds to the list.
-    if (!skip) {
-      int_vector_field->push_back(atoi(argv[*i]));
-    }
-    return true;
-  }
-
-  std::vector<std::string> *base64_vector_field =
-      FindField(out_config, kBase64VectorFlags, flag);
-  if (base64_vector_field) {
-    *i = *i + 1;
-    if (*i >= argc) {
-      fprintf(stderr, "Missing parameter.\n");
-      return false;
-    }
-    std::string value;
-    if (!DecodeBase64(&value, argv[*i])) {
-      return false;
-    }
-    // Each instance of the flag adds to the list.
-    if (!skip) {
-      base64_vector_field->push_back(std::move(value));
-    }
-    return true;
-  }
-
-  std::vector<std::pair<std::string, std::string>> *string_pair_vector_field =
-      FindField(out_config, kStringPairVectorFlags, flag);
-  if (string_pair_vector_field) {
-    *i = *i + 1;
-    if (*i >= argc) {
-      fprintf(stderr, "Missing parameter.\n");
-      return false;
-    }
-    const char *comma = strchr(argv[*i], ',');
-    if (!comma) {
-      fprintf(
-          stderr,
-          "Parameter should be a comma-separated triple composed of two base64 "
-          "strings followed by \"true\" or \"false\".\n");
-      return false;
-    }
-    // Each instance of the flag adds to the list.
-    if (!skip) {
-      string_pair_vector_field->push_back(std::make_pair(
-          std::string(argv[*i], comma - argv[*i]), std::string(comma + 1)));
-    }
-    return true;
-  }
-
-  fprintf(stderr, "Unknown argument: %s.\n", flag);
-  return false;
+  return &*iter;
 }
 
 // RemovePrefix checks if |*str| begins with |prefix| + "-". If so, it advances
@@ -433,15 +427,15 @@
   out_initial->argv = out_resume->argv = out_retry->argv = argv;
   for (int i = 0; i < argc; i++) {
     bool skip = false;
-    const char *flag = argv[i];
+    const char *name = argv[i];
 
     // -on-shim and -on-handshaker prefixes enable flags only on the shim or
     // handshaker.
-    if (RemovePrefix(&flag, "-on-shim")) {
+    if (RemovePrefix(&name, "-on-shim")) {
       if (!is_shim) {
         skip = true;
       }
-    } else if (RemovePrefix(&flag, "-on-handshaker")) {
+    } else if (RemovePrefix(&name, "-on-handshaker")) {
       if (is_shim) {
         skip = true;
       }
@@ -449,26 +443,45 @@
 
     // The following prefixes allow different configurations for each of the
     // initial, resumption, and 0-RTT retry handshakes.
-    if (RemovePrefix(&flag, "-on-initial")) {
-      if (!ParseFlag(flag, argc, argv, &i, skip, out_initial)) {
+    TestConfig *out = nullptr;
+    if (RemovePrefix(&name, "-on-initial")) {
+      out = out_initial;
+    } else if (RemovePrefix(&name, "-on-resume")) {
+      out = out_resume;
+    } else if (RemovePrefix(&name, "-on-retry")) {
+      out = out_retry;
+    }
+
+    const Flag *flag = FindFlag(name);
+    if (flag == nullptr) {
+      fprintf(stderr, "Unrecognized flag: %s\n", name);
+      return false;
+    }
+
+    const char *param = nullptr;
+    if (flag->has_param) {
+      if (i >= argc) {
+        fprintf(stderr, "Missing parameter for %s\n", name);
         return false;
       }
-    } else if (RemovePrefix(&flag, "-on-resume")) {
-      if (!ParseFlag(flag, argc, argv, &i, skip, out_resume)) {
-        return false;
-      }
-    } else if (RemovePrefix(&flag, "-on-retry")) {
-      if (!ParseFlag(flag, argc, argv, &i, skip, out_retry)) {
-        return false;
-      }
-    } else {
-      // Unprefixed flags apply to all three.
-      int i_init = i;
-      int i_resume = i;
-      if (!ParseFlag(flag, argc, argv, &i_init, skip, out_initial) ||
-          !ParseFlag(flag, argc, argv, &i_resume, skip, out_resume) ||
-          !ParseFlag(flag, argc, argv, &i, skip, out_retry)) {
-        return false;
+      i++;
+      param = argv[i];
+    }
+
+    if (!skip) {
+      if (out != nullptr) {
+        if (!flag->set_param(out, param)) {
+          fprintf(stderr, "Invalid parameter for %s: %s\n", name, param);
+          return false;
+        }
+      } else {
+        // Unprefixed flags apply to all three.
+        if (!flag->set_param(out_initial, param) ||
+            !flag->set_param(out_resume, param) ||
+            !flag->set_param(out_retry, param)) {
+          fprintf(stderr, "Invalid parameter for %s: %s\n", name, param);
+          return false;
+        }
       }
     }
   }