| // Copyright (c) 2019 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include <iostream> | 
 | #include <memory> | 
 | #include <string> | 
 |  | 
 | #include "net/third_party/quiche/src/quic/core/quic_versions.h" | 
 | #include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h" | 
 | #include "net/third_party/quiche/src/quic/platform/api/quic_system_event_loop.h" | 
 | #include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h" | 
 | #include "net/third_party/quiche/src/quic/tools/quic_client.h" | 
 |  | 
 | DEFINE_QUIC_COMMAND_LINE_FLAG(std::string, | 
 |                               host, | 
 |                               "", | 
 |                               "The IP or hostname to connect to."); | 
 |  | 
 | DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to."); | 
 |  | 
 | DEFINE_QUIC_COMMAND_LINE_FLAG( | 
 |     int32_t, | 
 |     quic_ietf_draft, | 
 |     0, | 
 |     "QUIC IETF draft number to use over the wire, e.g. 18. " | 
 |     "By default this sets quic_version to T099. " | 
 |     "This also enables required internal QUIC flags."); | 
 |  | 
 | namespace quic { | 
 |  | 
 | enum class Feature { | 
 |   // A version negotiation response is elicited and acted on. | 
 |   kVersionNegotiation, | 
 |   // The handshake completes successfully. | 
 |   kHandshake, | 
 |   // Stream data is being exchanged and ACK'ed. | 
 |   kStreamData, | 
 |   // The connection close procedcure completes with a zero error code. | 
 |   kConnectionClose, | 
 |   // An H3 transaction succeeded. | 
 |   kHttp3, | 
 |   // TODO(nharper): Add Retry to list of tested features. | 
 | }; | 
 |  | 
 | char MatrixLetter(Feature f) { | 
 |   switch (f) { | 
 |     case Feature::kVersionNegotiation: | 
 |       return 'V'; | 
 |     case Feature::kHandshake: | 
 |       return 'H'; | 
 |     case Feature::kStreamData: | 
 |       return 'D'; | 
 |     case Feature::kConnectionClose: | 
 |       return 'C'; | 
 |     case Feature::kHttp3: | 
 |       return '3'; | 
 |   } | 
 | } | 
 |  | 
 | std::set<Feature> AttemptRequest(QuicSocketAddress addr, | 
 |                                  std::string authority, | 
 |                                  QuicServerId server_id, | 
 |                                  ParsedQuicVersionVector versions) { | 
 |   std::set<Feature> features; | 
 |   auto proof_verifier = QuicMakeUnique<FakeProofVerifier>(); | 
 |   QuicEpollServer epoll_server; | 
 |   auto client = QuicMakeUnique<QuicClient>( | 
 |       addr, server_id, versions, &epoll_server, std::move(proof_verifier)); | 
 |   if (!client->Initialize()) { | 
 |     return features; | 
 |   } | 
 |   if (!client->Connect()) { | 
 |     QuicErrorCode error = client->session()->error(); | 
 |     if (error == QUIC_INVALID_VERSION) { | 
 |       // QuicFramer::ProcessPacket returns RaiseError(QUIC_INVALID_VERSION) if | 
 |       // it receives a packet containing a version in the header that is not our | 
 |       // version. It might be possible that we didn't actually process a VN | 
 |       // packet here. | 
 |       features.insert(Feature::kVersionNegotiation); | 
 |       return features; | 
 |     } | 
 |     return features; | 
 |   } | 
 |   if (!client->session()->IsCryptoHandshakeConfirmed()) { | 
 |     return features; | 
 |   } | 
 |   features.insert(Feature::kHandshake); | 
 |  | 
 |   // Construct and send a request. | 
 |   spdy::SpdyHeaderBlock header_block; | 
 |   header_block[":method"] = "GET"; | 
 |   header_block[":scheme"] = "https"; | 
 |   header_block[":authority"] = authority; | 
 |   header_block[":path"] = "/"; | 
 |   client->set_store_response(true); | 
 |   client->SendRequest(header_block, "", /*fin=*/true); | 
 |  | 
 |   // TODO(nharper): After some period of time, time out and don't report | 
 |   // success. | 
 |   while (client->WaitForEvents()) { | 
 |   } | 
 |  | 
 |   if (!client->connected()) { | 
 |     return features; | 
 |   } | 
 |  | 
 |   if (client->latest_response_code() != -1) { | 
 |     features.insert(Feature::kHttp3); | 
 |   } | 
 |  | 
 |   // TODO(nharper): Properly check that we actually sent stream data and | 
 |   // received ACKs for it. | 
 |   features.insert(Feature::kStreamData); | 
 |   // TODO(nharper): Check that we sent/received (which one?) a CONNECTION_CLOSE | 
 |   // with error code 0. | 
 |   features.insert(Feature::kConnectionClose); | 
 |   return features; | 
 | } | 
 |  | 
 | std::set<Feature> ServerSupport(std::string host, | 
 |                                 int port, | 
 |                                 int32_t ietf_draft) { | 
 |   // Configure version list. | 
 |   QuicVersionInitializeSupportForIetfDraft(ietf_draft); | 
 |   ParsedQuicVersion version = | 
 |       ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99); | 
 |   ParsedQuicVersionVector versions = {version}; | 
 |   QuicEnableVersion(version); | 
 |  | 
 |   // Build the client, and try to connect. | 
 |   QuicSocketAddress addr = tools::LookupAddress(host, QuicStrCat(port)); | 
 |   QuicServerId server_id(host, port, false); | 
 |   std::string authority = QuicStrCat(host, ":", port); | 
 |  | 
 |   ParsedQuicVersionVector versions_with_negotiation = versions; | 
 |   versions_with_negotiation.insert(versions_with_negotiation.begin(), | 
 |                                    QuicVersionReservedForNegotiation()); | 
 |   auto supported_features = | 
 |       AttemptRequest(addr, authority, server_id, versions_with_negotiation); | 
 |   if (!supported_features.empty()) { | 
 |     supported_features.insert(Feature::kVersionNegotiation); | 
 |   } else { | 
 |     supported_features = AttemptRequest(addr, authority, server_id, versions); | 
 |   } | 
 |   return supported_features; | 
 | } | 
 |  | 
 | }  // namespace quic | 
 |  | 
 | int main(int argc, char* argv[]) { | 
 |   QuicSystemEventLoop event_loop("quic_client"); | 
 |   const char* usage = "Usage: quic_client_interop_test [options]"; | 
 |  | 
 |   std::vector<std::string> args = | 
 |       quic::QuicParseCommandLineFlags(usage, argc, argv); | 
 |   if (!args.empty()) { | 
 |     quic::QuicPrintCommandLineFlagHelp(usage); | 
 |     exit(1); | 
 |   } | 
 |   std::string host = GetQuicFlag(FLAGS_host); | 
 |   int port = GetQuicFlag(FLAGS_port); | 
 |   const int32_t quic_ietf_draft = GetQuicFlag(FLAGS_quic_ietf_draft); | 
 |   if (host.empty() || port == 0 || quic_ietf_draft == 0) { | 
 |     quic::QuicPrintCommandLineFlagHelp(usage); | 
 |     exit(1); | 
 |   } | 
 |  | 
 |   auto supported_features = quic::ServerSupport(host, port, quic_ietf_draft); | 
 |   std::cout << "Supported features: "; | 
 |   for (auto feature : supported_features) { | 
 |     std::cout << MatrixLetter(feature); | 
 |   } | 
 |   std::cout << std::endl; | 
 | } |