int fizzClientCommand()

in fizz/tool/FizzClientCommand.cpp [494:853]


int fizzClientCommand(const std::vector<std::string>& args) {
  std::string host = "localhost";
  uint16_t port = 8443;
  bool verify = false;
  std::string certPath;
  std::string keyPath;
  std::string keyPass;
  std::string caPath;
  std::string caFile;
  std::string pskSaveFile;
  std::string pskLoadFile;
  std::string keyLogFile;
  bool reconnect = false;
  std::string customSNI;
  std::vector<std::string> alpns;
  folly::Optional<std::vector<CertificateCompressionAlgorithm>> compAlgos;
  bool early = false;
  std::string proxyHost = "";
  uint16_t proxyPort = 0;
  std::vector<CipherSuite> ciphers {
    CipherSuite::TLS_AES_128_GCM_SHA256, CipherSuite::TLS_AES_256_GCM_SHA384,
#if FOLLY_OPENSSL_HAS_CHACHA
        CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
#endif
  };
  std::vector<SignatureScheme> sigSchemes{
      SignatureScheme::ecdsa_secp256r1_sha256,
      SignatureScheme::ecdsa_secp384r1_sha384,
      SignatureScheme::ecdsa_secp521r1_sha512,
      SignatureScheme::rsa_pss_sha256,
  };
  std::vector<NamedGroup> groups{
      NamedGroup::secp256r1,
      NamedGroup::x25519,
  };
  bool delegatedCredentials = false;
  bool ech = false;
  std::string echConfigsFile;
  bool uring = false;
  bool uringAsync = false;
  bool uringRegisterFds = false;
  int32_t uringCapacity = 128;
  int32_t uringMaxSubmit = 64;
  int32_t uringMaxGet = -1;

  // clang-format off
  FizzArgHandlerMap handlers = {
    {"-host", {true, [&host](const std::string& arg) { host = arg; }}},
    {"-port", {true, [&port](const std::string& arg) {
        port = portFromString(arg, false);
    }}},
    {"-connect", {true, [&host, &port](const std::string& arg) {
        std::tie(host, port) = hostPortFromString(arg);
     }}},
    {"-verify", {false, [&verify](const std::string&) { verify = true; }}},
    {"-cert", {true, [&certPath](const std::string& arg) { certPath = arg; }}},
    {"-key", {true, [&keyPath](const std::string& arg) { keyPath = arg; }}},
    {"-pass", {true, [&keyPass](const std::string& arg) { keyPass = arg; }}},
    {"-capath", {true, [&caPath](const std::string& arg) { caPath = arg; }}},
    {"-cafile", {true, [&caFile](const std::string& arg) { caFile = arg; }}},
    {"-psk_save", {true, [&pskSaveFile](const std::string& arg) {
      pskSaveFile = arg;
    }}},
    {"-psk_load", {true,[&pskLoadFile](const std::string& arg) {
      pskLoadFile = arg;
    }}},
    {"-keylog", {true,[&keyLogFile](const std::string& arg) {
      keyLogFile = arg;
    }}},
    {"-reconnect", {false, [&reconnect](const std::string&) {
        reconnect = true;
    }}},
    {"-servername", {true, [&customSNI](const std::string& arg) {
        customSNI = arg;
    }}},
    {"-alpn", {true, [&alpns](const std::string& arg) {
        alpns.clear();
        folly::split(",", arg, alpns);
    }}},
    {"-certcompression", {true, [&compAlgos](const std::string& arg) {
        try {
          compAlgos = splitParse<CertificateCompressionAlgorithm>(arg);
        } catch (const std::exception& e) {
          LOG(ERROR) << "Error parsing certificate compression algorithms: " << e.what();
          throw;
        }
    }}},
    {"-early", {false, [&early](const std::string&) { early = true; }}},
    {"-quiet", {false, [](const std::string&) {
        FLAGS_minloglevel = google::GLOG_ERROR;
    }}},
    {"-httpproxy", {true, [&proxyHost, &proxyPort] (const std::string& arg) {
        std::tie(proxyHost, proxyPort) = hostPortFromString(arg);
    }}},
    {"-ciphers", {true, [&ciphers](const std::string& arg) {
        ciphers = splitParse<CipherSuite>(arg);
    }}},
    {"-sigschemes", {true, [&sigSchemes](const std::string& arg) {
        sigSchemes = splitParse<SignatureScheme>(arg);
    }}},
    {"-curves", {true, [&groups](const std::string& arg) {
        groups = splitParse<NamedGroup>(arg);
    }}},
    {"-delegatedcred", {false, [&delegatedCredentials](const std::string&) {
        delegatedCredentials = true;
    }}},
    {"-ech", {false, [&ech](const std::string&) {
        ech = true;
    }}},
    {"-echconfigs", {true, [&echConfigsFile](const std::string& arg) {
        echConfigsFile = arg;
    }}}
#ifdef FIZZ_TOOL_ENABLE_IO_URING
    ,{"-io_uring", {false, [&uring](const std::string&) { uring = true; }}},
    {"-io_uring_async_recv", {false, [&uringAsync](const std::string&) {
        uringAsync = true;
    }}},
    {"-io_uring_register_fds", {false, [&uringRegisterFds](const std::string&) {
        uringRegisterFds = true;
    }}},
    {"-io_uring_capacity", {true, [&uringCapacity](const std::string& arg) {
        uringCapacity = folly::to<int32_t>(arg);
    }}},
    {"-io_uring_max_get", {true, [&uringMaxGet](const std::string& arg) {
        uringMaxGet = folly::to<int32_t>(arg);
    }}},
    {"-io_uring_max_submit", {true, [&uringMaxSubmit](const std::string& arg) {
        uringMaxSubmit = folly::to<int32_t>(arg);
    }}}
#endif
  };
  // clang-format on

  try {
    if (parseArguments(args, handlers, printUsage)) {
      // Parsing failed, return
      return 1;
    }
  } catch (const std::exception& e) {
    LOG(ERROR) << "Error: " << e.what();
    return 1;
  }

  // Sanity check input.
  if (certPath.empty() != keyPath.empty()) {
    LOG(ERROR) << "-cert and -key are both required when specified";
    return 1;
  }

  EventBase evb(folly::EventBase::Options().setBackendFactory([uring,
                                                               uringAsync,
                                                               uringRegisterFds,
                                                               uringCapacity,
                                                               uringMaxSubmit,
                                                               uringMaxGet] {
    return setupBackend(
        uring,
        uringAsync,
        uringRegisterFds,
        uringCapacity,
        uringMaxSubmit,
        uringMaxGet);
  }));

  auto clientContext = std::make_shared<FizzClientContext>();

  if (!alpns.empty()) {
    clientContext->setSupportedAlpns(std::move(alpns));
  }

  clientContext->setSupportedCiphers(ciphers);
  clientContext->setSupportedSigSchemes(sigSchemes);
  clientContext->setSupportedGroups(groups);
  clientContext->setDefaultShares(groups);

  clientContext->setSupportedVersions(
      {ProtocolVersion::tls_1_3, ProtocolVersion::tls_1_3_28});
  clientContext->setSendEarlyData(early);

  if (compAlgos) {
    auto mgr = std::make_shared<CertDecompressionManager>();
    std::vector<std::shared_ptr<CertificateDecompressor>> decompressors;
    for (const auto& algo : *compAlgos) {
      switch (algo) {
        case CertificateCompressionAlgorithm::zlib:
          decompressors.push_back(
              std::make_shared<ZlibCertificateDecompressor>());
          break;
#ifdef FIZZ_TOOL_ENABLE_BROTLI
        case CertificateCompressionAlgorithm::brotli:
          decompressors.push_back(
              std::make_shared<BrotliCertificateDecompressor>());
          break;
#endif
#ifdef FIZZ_TOOL_ENABLE_ZSTD
        case CertificateCompressionAlgorithm::zstd:
          decompressors.push_back(
              std::make_shared<ZstdCertificateDecompressor>());
          break;
#endif
        default:
          LOG(WARNING) << "Don't know what decompressor to use for "
                       << toString(algo) << ", ignoring...";
          break;
      }
    }
    mgr->setDecompressors(decompressors);
    clientContext->setCertDecompressionManager(std::move(mgr));
  }

  X509StoreUniquePtr connStore;
  X509StoreUniquePtr resumptionStore;
  if (verify) {
    // Initialize CA store first, if given.
    if (!caPath.empty() || !caFile.empty()) {
      connStore.reset(X509_STORE_new());
      auto caFilePtr = caFile.empty() ? nullptr : caFile.c_str();
      auto caPathPtr = caPath.empty() ? nullptr : caPath.c_str();

      if (X509_STORE_load_locations(connStore.get(), caFilePtr, caPathPtr) ==
          0) {
        LOG(ERROR) << "Failed to load CA certificates";
        return 1;
      }
      resumptionStore.reset(connStore.get());
      X509_STORE_up_ref(resumptionStore.get());
    }
  }

  auto makeVerifier = [](X509StoreUniquePtr storePtr)
      -> std::unique_ptr<StoreCertificateChain> {
    std::unique_ptr<CertificateVerifier> verifier;
    if (storePtr) {
      verifier = std::make_unique<DefaultCertificateVerifier>(
          VerificationContext::Client, std::move(storePtr));
    } else {
      verifier = std::make_unique<InsecureAcceptAnyCertificate>();
    }
    auto storeChainVerifier =
        std::make_unique<StoreCertificateChain>(std::move(verifier));

    return storeChainVerifier;
  };

  auto connVerifier = makeVerifier(std::move(connStore));
  auto resumptionVerifier = makeVerifier(std::move(resumptionStore));

  if (!certPath.empty()) {
    std::string certData;
    std::string keyData;
    if (!readFile(certPath.c_str(), certData)) {
      LOG(ERROR) << "Failed to read certificate";
      return 1;
    } else if (!readFile(keyPath.c_str(), keyData)) {
      LOG(ERROR) << "Failed to read private key";
      return 1;
    }

    std::unique_ptr<SelfCert> cert;
    if (!keyPass.empty()) {
      cert = CertUtils::makeSelfCert(certData, keyData, keyPass);
    } else {
      cert = CertUtils::makeSelfCert(certData, keyData);
    }
    clientContext->setClientCertificate(std::move(cert));
  }

  std::shared_ptr<ClientExtensions> extensions;
  if (delegatedCredentials) {
    clientContext->setFactory(
        std::make_shared<extensions::DelegatedCredentialFactory>());
    extensions =
        std::make_shared<extensions::DelegatedCredentialClientExtension>(
            clientContext->getSupportedSigSchemes());
  }

  folly::Optional<std::vector<ech::ECHConfig>> echConfigs = folly::none;

  if (ech) {
    // Use default ECH config values.
    echConfigs = getDefaultECHConfigs();
  }

  if (!echConfigsFile.empty()) {
    // Parse user set ECH configs.
    auto echConfigsJson = readECHConfigsJson(echConfigsFile);
    if (!echConfigsJson.has_value()) {
      LOG(ERROR) << "Unable to load ECH configs from json file";
      return 1;
    }
    auto gotECHConfigs = parseECHConfigs(echConfigsJson.value());
    if (!gotECHConfigs.has_value()) {
      LOG(ERROR)
          << "Unable to parse JSON file and make ECH config."
          << "Ensure the format matches what is expected."
          << "Rough example of format: {echconfigs: [${your ECH config here with all the fields..}]}"
          << "See FizzCommandCommonTest for a more concrete example.";
      return 1;
    }
    echConfigs = std::move(gotECHConfigs.value());
  }

  try {
    auto sni = customSNI.empty() ? host : customSNI;
    auto connectHost = proxyHost.empty() ? host : proxyHost;
    auto connectPort = proxyHost.empty() ? port : proxyPort;
    auto proxiedHost = proxyHost.empty()
        ? std::string()
        : folly::to<std::string>(host, ":", port);

    SocketAddress addr(connectHost, connectPort, true);
    Connection conn(
        &evb,
        clientContext,
        sni,
        std::move(connVerifier),
        reconnect,
        proxiedHost,
        extensions,
        std::move(echConfigs),
        uringAsync);
    Connection resumptionConn(
        &evb,
        clientContext,
        sni,
        std::move(resumptionVerifier),
        false,
        proxiedHost,
        extensions,
        folly::none,
        uringAsync);

    Connection* inputTarget = &conn;
    if (reconnect) {
      auto pskCache = std::make_shared<ResumptionPskCache>(
          &evb, [&conn, &resumptionConn, addr]() {
            conn.close();
            resumptionConn.connect(addr);
          });
      clientContext->setPskCache(pskCache);
      inputTarget = &resumptionConn;
    }
    if (!pskSaveFile.empty() || !pskLoadFile.empty()) {
      auto pskCache =
          std::make_shared<BasicPersistentPskCache>(pskSaveFile, pskLoadFile);
      clientContext->setPskCache(pskCache);
    }
    if (!keyLogFile.empty()) {
      conn.setKeyLogWriter(std::make_unique<KeyLogWriter>(keyLogFile));
    }
    TerminalInputHandler input(&evb, inputTarget);
    conn.connect(addr);
    evb.loop();
  } catch (const std::exception& e) {
    LOG(ERROR) << "Error: " << e.what();
    return 1;
  }

  return 0;
}