bssl speed: better and more consistent pattern matching in `-filter`.

It now supports:

- All filters apply to either the catgeory, or the name, or both.
- Case, dashes and spaces are ignored.
- `bssl speed -filter filter1,filter2,...`: run the union of filters.
- `bssl speed -filter 'foo': run only tests named foo.
- `bssl speed -filter 'foo*': run all tests starting with foo.
- `bssl speed -filter '*foo': run all tests ending with foo.
- `bssl speed -filter '*foo*': run all tests with foo in the name.

Tested:
  `-filter '*128,*22*,sha1*,trusttoken'` matches:
  - AES-128 (just the block cipher, not the AEADs)
  - P-224 (both ECDH and ECDSA)
  - SHA-1
  - TrustToken (entire category)

Update-Note: parsing of `bssl speed -filter` changed. When the algorithm
name is fully specified, nothing changes; however substring matching now
needs to be explicitly requested via `bssl speed -filter '*substring*'`.
Also, this entire utility is likely soon going to get replaced by
something based on Google Benchmark in
Ic8d4cfd65065e61ae90c58bddb34463a5f398f14; we're making sure however
that all functionality added here will remain somehow possible using
`--benchmark_filter` expressions (but likely with extra syntax).

Change-Id: I766114ddb22b17638999b262c2d62cc86ae3e579
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/84707
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Rudolf Polzer <rpolzer@google.com>
Auto-Submit: Rudolf Polzer <rpolzer@google.com>
diff --git a/tool/speed.cc b/tool/speed.cc
index db23451..ddbfa85 100644
--- a/tool/speed.cc
+++ b/tool/speed.cc
@@ -16,6 +16,7 @@
 #include <functional>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <assert.h>
@@ -328,8 +329,87 @@
 }
 #endif
 
+static bool MatchesSelection(std::string_view pattern, std::string_view text) {
+  // Comparison rules:
+  // - Case insensitive.
+  // - Spaces/dashes don't matter.
+  // - Wildcards at start/end only.
+
+  if (pattern.empty()) {
+    return text.empty();
+  }
+
+  std::string pattern_str(pattern);
+  pattern_str.erase(std::remove_if(pattern_str.begin(), pattern_str.end(),
+                                   [](char c) { return c == ' ' || c == '-'; }),
+                    pattern_str.end());
+  std::transform(pattern_str.begin(), pattern_str.end(), pattern_str.begin(),
+                 OPENSSL_tolower);
+
+  std::string text_str(text);
+  text_str.erase(std::remove_if(text_str.begin(), text_str.end(),
+                                [](char c) { return c == ' ' || c == '-'; }),
+                 text_str.end());
+  std::transform(text_str.begin(), text_str.end(), text_str.begin(),
+                 OPENSSL_tolower);
+
+  if (pattern_str.size() > 1 && pattern_str.front() == '*') {
+    if (pattern_str.back() == '*') {
+      // Match anywhere.
+      return text_str.find(pattern_str.substr(1, pattern_str.size() - 2)) !=
+             std::string::npos;
+    } else {
+      // Match at end.
+      return text_str.size() >= pattern_str.size() - 1 &&
+             text_str.substr(text_str.size() - (pattern_str.size() - 1)) ==
+                 pattern_str.substr(1);
+    }
+  } else if (pattern_str.back() == '*') {
+    // Match at start. This is also hit for the "*" pattern and then matches
+    // all.
+    return text_str.size() >= pattern_str.size() - 1 &&
+           text_str.substr(0, pattern_str.size() - 1) ==
+               pattern_str.substr(0, pattern_str.size() - 1);
+  } else {
+    // Exact.
+    return text_str == pattern_str;
+  }
+}
+
+static bool IsSelected(std::string_view filter, std::string_view test,
+                       std::string_view category = "", bool defaulted = true) {
+  if (filter.empty()) {
+    return defaulted;
+  }
+  do {
+    size_t comma = filter.find(',');
+    std::string_view item;
+    if (comma == std::string::npos) {
+      item = filter;
+      filter = "";
+    } else {
+      item = filter.substr(0, comma);
+      filter = filter.substr(comma + 1);
+    }
+    // Support e.g. "AEAD".
+    if (!category.empty() && MatchesSelection(item, category)) {
+      return true;
+    }
+    // Support e.g. "AEAD AES-*".
+    if (!category.empty() &&
+        MatchesSelection(item, std::string(category) + std::string(test))) {
+      return true;
+    }
+    // Support e.g. "AES-128-GCM".
+    if (MatchesSelection(item, test)) {
+      return true;
+    }
+  } while (!filter.empty());
+  return false;
+}
+
 static bool SpeedRSA(const std::string &selected) {
-  if (!selected.empty() && selected.find("RSA") == std::string::npos) {
+  if (!IsSelected(selected, "RSA")) {
     return true;
   }
 
@@ -431,11 +511,6 @@
 }
 
 static bool SpeedRSAKeyGen(const std::string &selected) {
-  // Don't run this by default because it's so slow.
-  if (selected != "RSAKeyGen") {
-    return true;
-  }
-
   bssl::UniquePtr<BIGNUM> e(BN_new());
   if (!BN_set_word(e.get(), 65537)) {
     return false;
@@ -443,6 +518,14 @@
 
   const std::vector<int> kSizes = {2048, 3072, 4096};
   for (int size : kSizes) {
+    char size_str[15];
+    snprintf(size_str, sizeof(size_str), "RSAKeyGen-%d", size);
+    // Don't run this by default because it's so slow.
+    if (!IsSelected(selected, size_str, "RSAKeyGen",
+                    /*defaulted=*/false)) {
+      continue;
+    }
+
     const uint64_t start = time_now();
     uint64_t num_calls = 0;
     uint64_t us;
@@ -601,7 +684,7 @@
 
 static bool SpeedAEAD(const EVP_AEAD *aead, const std::string &name,
                       size_t ad_len, const std::string &selected) {
-  if (!selected.empty() && name.find(selected) == std::string::npos) {
+  if (!IsSelected(selected, name, "AEAD")) {
     return true;
   }
 
@@ -622,7 +705,7 @@
 
 static bool SpeedAESBlock(const std::string &name, unsigned bits,
                           const std::string &selected) {
-  if (!selected.empty() && name.find(selected) == std::string::npos) {
+  if (!IsSelected(selected, name, "Block")) {
     return true;
   }
 
@@ -719,7 +802,7 @@
 
 static bool SpeedHash(const EVP_MD *md, const std::string &name,
                       const std::string &selected) {
-  if (!selected.empty() && name.find(selected) == std::string::npos) {
+  if (!IsSelected(selected, name, "Hash")) {
     return true;
   }
 
@@ -753,7 +836,7 @@
 }
 
 static bool SpeedRandom(const std::string &selected) {
-  if (!selected.empty() && selected != "RNG") {
+  if (!IsSelected(selected, "RNG")) {
     return true;
   }
 
@@ -768,7 +851,7 @@
 
 static bool SpeedECDHCurve(const std::string &name, const EC_GROUP *group,
                            const std::string &selected) {
-  if (!selected.empty() && name.find(selected) == std::string::npos) {
+  if (!IsSelected(selected, name, "ECDH")) {
     return true;
   }
 
@@ -819,13 +902,13 @@
     return false;
   }
 
-  results.Print(name);
+  results.Print("ECDH " + name);
   return true;
 }
 
 static bool SpeedECDSACurve(const std::string &name, const EC_GROUP *group,
                             const std::string &selected) {
-  if (!selected.empty() && name.find(selected) == std::string::npos) {
+  if (!IsSelected(selected, name, "ECDSA")) {
     return true;
   }
 
@@ -852,7 +935,7 @@
     return false;
   }
 
-  results.Print(name + " signing");
+  results.Print("ECDSA " + name + " signing");
 
   uint8_t signature[kMaxSignature];
   unsigned sig_len;
@@ -868,27 +951,27 @@
     return false;
   }
 
-  results.Print(name + " verify");
+  results.Print("ECDSA " + name + " verify");
 
   return true;
 }
 
 static bool SpeedECDH(const std::string &selected) {
-  return SpeedECDHCurve("ECDH P-224", EC_group_p224(), selected) &&
-         SpeedECDHCurve("ECDH P-256", EC_group_p256(), selected) &&
-         SpeedECDHCurve("ECDH P-384", EC_group_p384(), selected) &&
-         SpeedECDHCurve("ECDH P-521", EC_group_p521(), selected);
+  return SpeedECDHCurve("P-224", EC_group_p224(), selected) &&
+         SpeedECDHCurve("P-256", EC_group_p256(), selected) &&
+         SpeedECDHCurve("P-384", EC_group_p384(), selected) &&
+         SpeedECDHCurve("P-521", EC_group_p521(), selected);
 }
 
 static bool SpeedECDSA(const std::string &selected) {
-  return SpeedECDSACurve("ECDSA P-224", EC_group_p224(), selected) &&
-         SpeedECDSACurve("ECDSA P-256", EC_group_p256(), selected) &&
-         SpeedECDSACurve("ECDSA P-384", EC_group_p384(), selected) &&
-         SpeedECDSACurve("ECDSA P-521", EC_group_p521(), selected);
+  return SpeedECDSACurve("P-224", EC_group_p224(), selected) &&
+         SpeedECDSACurve("P-256", EC_group_p256(), selected) &&
+         SpeedECDSACurve("P-384", EC_group_p384(), selected) &&
+         SpeedECDSACurve("P-521", EC_group_p521(), selected);
 }
 
 static bool Speed25519(const std::string &selected) {
-  if (!selected.empty() && selected.find("25519") == std::string::npos) {
+  if (!IsSelected(selected, "25519")) {
     return true;
   }
 
@@ -961,7 +1044,7 @@
 }
 
 static bool SpeedSPAKE2(const std::string &selected) {
-  if (!selected.empty() && selected.find("SPAKE2") == std::string::npos) {
+  if (!IsSelected(selected, "SPAKE2")) {
     return true;
   }
 
@@ -1007,7 +1090,7 @@
 }
 
 static bool SpeedScrypt(const std::string &selected) {
-  if (!selected.empty() && selected.find("scrypt") == std::string::npos) {
+  if (!IsSelected(selected, "scrypt")) {
     return true;
   }
 
@@ -1042,7 +1125,7 @@
 }
 
 static bool SpeedHRSS(const std::string &selected) {
-  if (!selected.empty() && selected != "HRSS") {
+  if (!IsSelected(selected, "HRSS")) {
     return true;
   }
 
@@ -1103,7 +1186,7 @@
 }
 
 static bool SpeedMLDSA(const std::string &selected) {
-  if (!selected.empty() && selected != "ML-DSA") {
+  if (!IsSelected(selected, "ML-DSA")) {
     return true;
   }
 
@@ -1196,7 +1279,7 @@
 }
 
 static bool SpeedMLKEM(const std::string &selected) {
-  if (!selected.empty() && selected != "ML-KEM-768") {
+  if (!IsSelected(selected, "ML-KEM-768", "ML-KEM")) {
     return true;
   }
 
@@ -1245,7 +1328,7 @@
 }
 
 static bool SpeedMLKEM1024(const std::string &selected) {
-  if (!selected.empty() && selected != "ML-KEM-1024") {
+  if (!IsSelected(selected, "ML-KEM-1024", "ML-KEM")) {
     return true;
   }
 
@@ -1299,7 +1382,11 @@
                    const uint8_t *, size_t),
           int Verify(const uint8_t *, size_t, const uint8_t *, const uint8_t *,
                      size_t, const uint8_t *, size_t)>
-static bool RunSLHDSA(std::string name) {
+static bool RunSLHDSA(std::string selected, std::string name) {
+  if (!IsSelected(selected, name, "SLH-DSA")) {
+    return true;
+  }
+
   TimeResults results;
   if (!TimeFunctionParallel(&results, []() -> bool {
         std::vector<uint8_t> public_key(PublicKeySize);
@@ -1347,24 +1434,20 @@
 }
 
 static bool SpeedSLHDSA(const std::string &selected) {
-  if (!selected.empty() && selected.find("SLH-DSA") == std::string::npos) {
-    return true;
-  }
-
   return RunSLHDSA<SLHDSA_SHA2_128S_PUBLIC_KEY_BYTES,
                    SLHDSA_SHA2_128S_PRIVATE_KEY_BYTES,
                    SLHDSA_SHA2_128S_SIGNATURE_BYTES,
                    SLHDSA_SHA2_128S_generate_key, SLHDSA_SHA2_128S_sign,
-                   SLHDSA_SHA2_128S_verify>("SLHDSA-SHA2-128s") &&
+                   SLHDSA_SHA2_128S_verify>(selected, "SHA2-128s") &&
          RunSLHDSA<SLHDSA_SHAKE_256F_PUBLIC_KEY_BYTES,
                    SLHDSA_SHAKE_256F_PRIVATE_KEY_BYTES,
                    SLHDSA_SHAKE_256F_SIGNATURE_BYTES,
                    SLHDSA_SHAKE_256F_generate_key, SLHDSA_SHAKE_256F_sign,
-                   SLHDSA_SHAKE_256F_verify>("SLHDSA-SHAKE-256f");
+                   SLHDSA_SHAKE_256F_verify>(selected, "SHAKE-256f");
 }
 
 static bool SpeedHashToCurve(const std::string &selected) {
-  if (!selected.empty() && selected.find("hashtocurve") == std::string::npos) {
+  if (!IsSelected(selected, "hashtocurve")) {
     return true;
   }
 
@@ -1413,7 +1496,7 @@
 }
 
 static bool SpeedBase64(const std::string &selected) {
-  if (!selected.empty() && selected.find("base64") == std::string::npos) {
+  if (!IsSelected(selected, "base64")) {
     return true;
   }
 
@@ -1455,7 +1538,7 @@
 }
 
 static bool SpeedSipHash(const std::string &selected) {
-  if (!selected.empty() && selected.find("siphash") == std::string::npos) {
+  if (!IsSelected(selected, "siphash", "Hash")) {
     return true;
   }
 
@@ -1485,7 +1568,7 @@
 
 static bool SpeedTrustToken(std::string name, const TRUST_TOKEN_METHOD *method,
                             size_t batchsize, const std::string &selected) {
-  if (!selected.empty() && selected.find("trusttoken") == std::string::npos) {
+  if (!IsSelected(selected, name, "TrustToken")) {
     return true;
   }
 
@@ -1501,7 +1584,7 @@
     fprintf(stderr, "TRUST_TOKEN_generate_key failed.\n");
     return false;
   }
-  results.Print(name + " generate_key");
+  results.Print("TrustToken-" + name + " generate_key");
 
   bssl::UniquePtr<TRUST_TOKEN_CLIENT> client(
       TRUST_TOKEN_CLIENT_new(method, batchsize));
@@ -1557,7 +1640,7 @@
     fprintf(stderr, "TRUST_TOKEN_CLIENT_begin_issuance failed.\n");
     return false;
   }
-  results.Print(name + " begin_issuance");
+  results.Print("TrustToken-" + name + " begin_issuance");
 
   uint8_t *issue_msg = NULL;
   size_t msg_len;
@@ -1587,7 +1670,7 @@
     fprintf(stderr, "TRUST_TOKEN_ISSUER_issue failed.\n");
     return false;
   }
-  results.Print(name + " issue");
+  results.Print("TrustToken-" + name + " issue");
 
   uint8_t *issue_resp = NULL;
   size_t resp_len, tokens_issued;
@@ -1615,7 +1698,7 @@
     fprintf(stderr, "TRUST_TOKEN_CLIENT_finish_issuance failed.\n");
     return false;
   }
-  results.Print(name + " finish_issuance");
+  results.Print("TrustToken-" + name + " finish_issuance");
 
   bssl::UniquePtr<STACK_OF(TRUST_TOKEN)> tokens(
       TRUST_TOKEN_CLIENT_finish_issuance(client.get(), &key_index, issue_resp,
@@ -1642,7 +1725,7 @@
     fprintf(stderr, "TRUST_TOKEN_CLIENT_begin_redemption failed.\n");
     return false;
   }
-  results.Print(name + " begin_redemption");
+  results.Print("TrustToken-" + name + " begin_redemption");
 
   uint8_t *redeem_msg = NULL;
   size_t redeem_msg_len;
@@ -1670,7 +1753,7 @@
     fprintf(stderr, "TRUST_TOKEN_ISSUER_redeem failed.\n");
     return false;
   }
-  results.Print(name + " redeem");
+  results.Print("TrustToken-" + name + " redeem");
 
   uint32_t public_value;
   uint8_t private_value;
@@ -1690,7 +1773,7 @@
 }
 
 static bool SpeedX509(const std::string &selected) {
-  if (!selected.empty() && selected.find("x509") == std::string::npos) {
+  if (!IsSelected(selected, "x509")) {
     return true;
   }
 
@@ -1738,13 +1821,13 @@
 
 #if defined(BORINGSSL_FIPS)
 static bool SpeedSelfTest(const std::string &selected) {
-  if (!selected.empty() && selected.find("self-test") == std::string::npos) {
+  if (!IsSelected(selected, "self-test")) {
     return true;
   }
 
   TimeResults results;
   if (!TimeFunction(&results, []() -> bool { return BORINGSSL_self_test(); })) {
-    fprintf(stderr, "BORINGSSL_self_test faileid.\n");
+    fprintf(stderr, "BORINGSSL_self_test failed.\n");
     ERR_print_errors_fp(stderr);
     return false;
   }
@@ -1758,7 +1841,10 @@
     {
         "-filter",
         kOptionalArgument,
-        "A filter on the speed tests to run",
+        "A comma separated list of patterns to filter the speed tests to run; "
+        "patterns can be string, prefix*, *suffix or *infix*; can match either "
+        "the category, or the test, or both concatenated; case, spaces and "
+        "dashes are ignored",
     },
     {
         "-timeout",
@@ -1930,18 +2016,18 @@
       !SpeedMLKEM1024(selected) ||    //
       !SpeedSLHDSA(selected) ||       //
       !SpeedHashToCurve(selected) ||  //
-      !SpeedTrustToken("TrustToken-Exp1-Batch1", TRUST_TOKEN_experiment_v1(), 1,
+      !SpeedTrustToken("Exp1-Batch1", TRUST_TOKEN_experiment_v1(), 1,
                        selected) ||
-      !SpeedTrustToken("TrustToken-Exp1-Batch10", TRUST_TOKEN_experiment_v1(),
+      !SpeedTrustToken("Exp1-Batch10", TRUST_TOKEN_experiment_v1(), 10,
+                       selected) ||
+      !SpeedTrustToken("Exp2VOPRF-Batch1", TRUST_TOKEN_experiment_v2_voprf(), 1,
+                       selected) ||
+      !SpeedTrustToken("Exp2VOPRF-Batch10", TRUST_TOKEN_experiment_v2_voprf(),
                        10, selected) ||
-      !SpeedTrustToken("TrustToken-Exp2VOPRF-Batch1",
-                       TRUST_TOKEN_experiment_v2_voprf(), 1, selected) ||
-      !SpeedTrustToken("TrustToken-Exp2VOPRF-Batch10",
-                       TRUST_TOKEN_experiment_v2_voprf(), 10, selected) ||
-      !SpeedTrustToken("TrustToken-Exp2PMB-Batch1",
-                       TRUST_TOKEN_experiment_v2_pmb(), 1, selected) ||
-      !SpeedTrustToken("TrustToken-Exp2PMB-Batch10",
-                       TRUST_TOKEN_experiment_v2_pmb(), 10, selected) ||
+      !SpeedTrustToken("Exp2PMB-Batch1", TRUST_TOKEN_experiment_v2_pmb(), 1,
+                       selected) ||
+      !SpeedTrustToken("Exp2PMB-Batch10", TRUST_TOKEN_experiment_v2_pmb(), 10,
+                       selected) ||
       !SpeedBase64(selected) ||   //
       !SpeedSipHash(selected) ||  //
       !SpeedX509(selected)) {