|  | // Copyright (c) 2016 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 "quiche/quic/core/chlo_extractor.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | #include "absl/base/macros.h" | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "quiche/quic/core/quic_framer.h" | 
|  | #include "quiche/quic/core/quic_utils.h" | 
|  | #include "quiche/quic/platform/api/quic_test.h" | 
|  | #include "quiche/quic/test_tools/crypto_test_utils.h" | 
|  | #include "quiche/quic/test_tools/first_flight.h" | 
|  | #include "quiche/quic/test_tools/quic_test_utils.h" | 
|  |  | 
|  | namespace quic { | 
|  | namespace test { | 
|  | namespace { | 
|  |  | 
|  | class TestDelegate : public ChloExtractor::Delegate { | 
|  | public: | 
|  | TestDelegate() = default; | 
|  | ~TestDelegate() override = default; | 
|  |  | 
|  | // ChloExtractor::Delegate implementation | 
|  | void OnChlo(QuicTransportVersion version, QuicConnectionId connection_id, | 
|  | const CryptoHandshakeMessage& chlo) override { | 
|  | version_ = version; | 
|  | connection_id_ = connection_id; | 
|  | chlo_ = chlo.DebugString(); | 
|  | absl::string_view alpn_value; | 
|  | if (chlo.GetStringPiece(kALPN, &alpn_value)) { | 
|  | alpn_ = std::string(alpn_value); | 
|  | } | 
|  | } | 
|  |  | 
|  | QuicConnectionId connection_id() const { return connection_id_; } | 
|  | QuicTransportVersion transport_version() const { return version_; } | 
|  | const std::string& chlo() const { return chlo_; } | 
|  | const std::string& alpn() const { return alpn_; } | 
|  |  | 
|  | private: | 
|  | QuicConnectionId connection_id_; | 
|  | QuicTransportVersion version_; | 
|  | std::string chlo_; | 
|  | std::string alpn_; | 
|  | }; | 
|  |  | 
|  | class ChloExtractorTest : public QuicTestWithParam<ParsedQuicVersion> { | 
|  | public: | 
|  | ChloExtractorTest() : version_(GetParam()) {} | 
|  |  | 
|  | void MakePacket(absl::string_view data, bool munge_offset, | 
|  | bool munge_stream_id) { | 
|  | QuicPacketHeader header; | 
|  | header.destination_connection_id = TestConnectionId(); | 
|  | header.destination_connection_id_included = CONNECTION_ID_PRESENT; | 
|  | header.version_flag = true; | 
|  | header.version = version_; | 
|  | header.reset_flag = false; | 
|  | header.packet_number_length = PACKET_4BYTE_PACKET_NUMBER; | 
|  | header.packet_number = QuicPacketNumber(1); | 
|  | if (version_.HasLongHeaderLengths()) { | 
|  | header.retry_token_length_length = | 
|  | quiche::VARIABLE_LENGTH_INTEGER_LENGTH_1; | 
|  | header.length_length = quiche::VARIABLE_LENGTH_INTEGER_LENGTH_2; | 
|  | } | 
|  | QuicFrames frames; | 
|  | size_t offset = 0; | 
|  | if (munge_offset) { | 
|  | offset++; | 
|  | } | 
|  | QuicFramer framer(SupportedVersions(version_), QuicTime::Zero(), | 
|  | Perspective::IS_CLIENT, kQuicDefaultConnectionIdLength); | 
|  | framer.SetInitialObfuscators(TestConnectionId()); | 
|  | if (!version_.UsesCryptoFrames() || munge_stream_id) { | 
|  | QuicStreamId stream_id = | 
|  | QuicUtils::GetCryptoStreamId(version_.transport_version); | 
|  | if (munge_stream_id) { | 
|  | stream_id++; | 
|  | } | 
|  | frames.push_back( | 
|  | QuicFrame(QuicStreamFrame(stream_id, false, offset, data))); | 
|  | } else { | 
|  | frames.push_back( | 
|  | QuicFrame(new QuicCryptoFrame(ENCRYPTION_INITIAL, offset, data))); | 
|  | } | 
|  | std::unique_ptr<QuicPacket> packet( | 
|  | BuildUnsizedDataPacket(&framer, header, frames)); | 
|  | EXPECT_TRUE(packet != nullptr); | 
|  | size_t encrypted_length = | 
|  | framer.EncryptPayload(ENCRYPTION_INITIAL, header.packet_number, *packet, | 
|  | buffer_, ABSL_ARRAYSIZE(buffer_)); | 
|  | ASSERT_NE(0u, encrypted_length); | 
|  | packet_ = std::make_unique<QuicEncryptedPacket>(buffer_, encrypted_length); | 
|  | EXPECT_TRUE(packet_ != nullptr); | 
|  | DeleteFrames(&frames); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | ParsedQuicVersion version_; | 
|  | TestDelegate delegate_; | 
|  | std::unique_ptr<QuicEncryptedPacket> packet_; | 
|  | char buffer_[kMaxOutgoingPacketSize]; | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | ChloExtractorTests, ChloExtractorTest, | 
|  | ::testing::ValuesIn(AllSupportedVersionsWithQuicCrypto()), | 
|  | ::testing::PrintToStringParamName()); | 
|  |  | 
|  | TEST_P(ChloExtractorTest, FindsValidChlo) { | 
|  | CryptoHandshakeMessage client_hello; | 
|  | client_hello.set_tag(kCHLO); | 
|  |  | 
|  | std::string client_hello_str(client_hello.GetSerialized().AsStringPiece()); | 
|  |  | 
|  | MakePacket(client_hello_str, /*munge_offset=*/false, | 
|  | /*munge_stream_id=*/false); | 
|  | EXPECT_TRUE(ChloExtractor::Extract(*packet_, version_, {}, &delegate_, | 
|  | kQuicDefaultConnectionIdLength)); | 
|  | EXPECT_EQ(version_.transport_version, delegate_.transport_version()); | 
|  | EXPECT_EQ(TestConnectionId(), delegate_.connection_id()); | 
|  | EXPECT_EQ(client_hello.DebugString(), delegate_.chlo()); | 
|  | } | 
|  |  | 
|  | TEST_P(ChloExtractorTest, DoesNotFindValidChloOnWrongStream) { | 
|  | if (version_.UsesCryptoFrames()) { | 
|  | // When crypto frames are in use we do not use stream frames. | 
|  | return; | 
|  | } | 
|  | CryptoHandshakeMessage client_hello; | 
|  | client_hello.set_tag(kCHLO); | 
|  |  | 
|  | std::string client_hello_str(client_hello.GetSerialized().AsStringPiece()); | 
|  | MakePacket(client_hello_str, | 
|  | /*munge_offset=*/false, /*munge_stream_id=*/true); | 
|  | EXPECT_FALSE(ChloExtractor::Extract(*packet_, version_, {}, &delegate_, | 
|  | kQuicDefaultConnectionIdLength)); | 
|  | } | 
|  |  | 
|  | TEST_P(ChloExtractorTest, DoesNotFindValidChloOnWrongOffset) { | 
|  | CryptoHandshakeMessage client_hello; | 
|  | client_hello.set_tag(kCHLO); | 
|  |  | 
|  | std::string client_hello_str(client_hello.GetSerialized().AsStringPiece()); | 
|  | MakePacket(client_hello_str, /*munge_offset=*/true, | 
|  | /*munge_stream_id=*/false); | 
|  | EXPECT_FALSE(ChloExtractor::Extract(*packet_, version_, {}, &delegate_, | 
|  | kQuicDefaultConnectionIdLength)); | 
|  | } | 
|  |  | 
|  | TEST_P(ChloExtractorTest, DoesNotFindInvalidChlo) { | 
|  | MakePacket("foo", /*munge_offset=*/false, | 
|  | /*munge_stream_id=*/false); | 
|  | EXPECT_FALSE(ChloExtractor::Extract(*packet_, version_, {}, &delegate_, | 
|  | kQuicDefaultConnectionIdLength)); | 
|  | } | 
|  |  | 
|  | TEST_P(ChloExtractorTest, FirstFlight) { | 
|  | std::vector<std::unique_ptr<QuicReceivedPacket>> packets = | 
|  | GetFirstFlightOfPackets(version_); | 
|  | ASSERT_EQ(packets.size(), 1u); | 
|  | EXPECT_TRUE(ChloExtractor::Extract(*packets[0], version_, {}, &delegate_, | 
|  | kQuicDefaultConnectionIdLength)); | 
|  | EXPECT_EQ(version_.transport_version, delegate_.transport_version()); | 
|  | EXPECT_EQ(TestConnectionId(), delegate_.connection_id()); | 
|  | EXPECT_EQ(AlpnForVersion(version_), delegate_.alpn()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace test | 
|  | }  // namespace quic |