| // 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 "quic/quic_transport/quic_transport_server_session.h" |
| |
| #include <cstddef> |
| #include <memory> |
| #include <string> |
| |
| #include "absl/strings/string_view.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #include "quic/core/crypto/quic_compressed_certs_cache.h" |
| #include "quic/core/crypto/quic_crypto_server_config.h" |
| #include "quic/core/frames/quic_stream_frame.h" |
| #include "quic/core/quic_data_writer.h" |
| #include "quic/core/quic_versions.h" |
| #include "quic/platform/api/quic_test.h" |
| #include "quic/quic_transport/quic_transport_protocol.h" |
| #include "quic/test_tools/crypto_test_utils.h" |
| #include "quic/test_tools/quic_test_utils.h" |
| #include "quic/test_tools/quic_transport_test_tools.h" |
| #include "common/quiche_text_utils.h" |
| |
| namespace quic { |
| namespace test { |
| namespace { |
| |
| using testing::_; |
| using testing::AnyNumber; |
| using testing::DoAll; |
| using testing::HasSubstr; |
| using testing::Return; |
| using testing::SaveArg; |
| |
| constexpr char kTestOrigin[] = "https://test-origin.test"; |
| constexpr char kTestOriginClientIndication[] = |
| "\0\0" // key (0x0000, origin) |
| "\0\x18" // length |
| "https://test-origin.test" // value |
| "\0\x01" // key (0x0001, path) |
| "\0\x05" // length |
| "/test"; // value |
| const url::Origin GetTestOrigin() { |
| return url::Origin::Create(GURL(kTestOrigin)); |
| } |
| const std::string GetTestOriginClientIndication() { |
| return std::string(kTestOriginClientIndication, |
| sizeof(kTestOriginClientIndication) - 1); |
| } |
| |
| ParsedQuicVersionVector GetVersions() { |
| return {DefaultVersionForQuicTransport()}; |
| } |
| |
| class QuicTransportServerSessionTest : public QuicTest { |
| public: |
| QuicTransportServerSessionTest() |
| : connection_(&helper_, |
| &alarm_factory_, |
| Perspective::IS_SERVER, |
| GetVersions()), |
| crypto_config_(QuicCryptoServerConfig::TESTING, |
| QuicRandom::GetInstance(), |
| crypto_test_utils::ProofSourceForTesting(), |
| KeyExchangeSource::Default()), |
| compressed_certs_cache_( |
| QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) { |
| QuicEnableVersion(DefaultVersionForQuicTransport()); |
| connection_.AdvanceTime(QuicTime::Delta::FromSeconds(100000)); |
| crypto_test_utils::SetupCryptoServerConfigForTest( |
| helper_.GetClock(), helper_.GetRandomGenerator(), &crypto_config_); |
| session_ = std::make_unique<QuicTransportServerSession>( |
| &connection_, nullptr, DefaultQuicConfig(), GetVersions(), |
| &crypto_config_, &compressed_certs_cache_, &visitor_); |
| session_->Initialize(); |
| crypto_stream_ = session_->GetMutableCryptoStream(); |
| ON_CALL(visitor_, ProcessPath(_)) |
| .WillByDefault(DoAll(SaveArg<0>(&path_), Return(true))); |
| } |
| |
| void Connect() { |
| crypto_test_utils::FakeClientOptions options; |
| options.only_tls_versions = true; |
| crypto_test_utils::HandshakeWithFakeClient( |
| &helper_, &alarm_factory_, &connection_, crypto_stream_, |
| QuicServerId("test.example.com", 443), options, QuicTransportAlpn()); |
| } |
| |
| void ReceiveIndication(absl::string_view indication) { |
| QUIC_LOG(INFO) << "Receiving indication: " |
| << quiche::QuicheTextUtils::HexDump(indication); |
| constexpr size_t kChunkSize = 1024; |
| // Shard the indication, since some of the tests cause it to not fit into a |
| // single frame. |
| for (size_t i = 0; i < indication.size(); i += kChunkSize) { |
| QuicStreamFrame frame(ClientIndicationStream(), /*fin=*/false, i, |
| indication.substr(i, i + kChunkSize)); |
| session_->OnStreamFrame(frame); |
| } |
| session_->OnStreamFrame(QuicStreamFrame(ClientIndicationStream(), |
| /*fin=*/true, indication.size(), |
| absl::string_view())); |
| } |
| |
| void ReceiveIndicationWithPath(absl::string_view path) { |
| constexpr char kTestOriginClientIndicationPrefix[] = |
| "\0\0" // key (0x0000, origin) |
| "\0\x18" // length |
| "https://test-origin.test" // value |
| "\0\x01"; // key (0x0001, path) |
| std::string indication{kTestOriginClientIndicationPrefix, |
| sizeof(kTestOriginClientIndicationPrefix) - 1}; |
| indication.push_back(static_cast<char>(path.size() >> 8)); |
| indication.push_back(static_cast<char>(path.size() & 0xff)); |
| indication += std::string{path}; |
| ReceiveIndication(indication); |
| } |
| |
| protected: |
| MockAlarmFactory alarm_factory_; |
| MockQuicConnectionHelper helper_; |
| |
| PacketSavingConnection connection_; |
| QuicCryptoServerConfig crypto_config_; |
| std::unique_ptr<QuicTransportServerSession> session_; |
| QuicCompressedCertsCache compressed_certs_cache_; |
| testing::NiceMock<MockServerVisitor> visitor_; |
| QuicCryptoServerStreamBase* crypto_stream_; |
| GURL path_; |
| }; |
| |
| TEST_F(QuicTransportServerSessionTest, SuccessfulHandshake) { |
| Connect(); |
| |
| url::Origin origin; |
| EXPECT_CALL(visitor_, CheckOrigin(_)) |
| .WillOnce(DoAll(SaveArg<0>(&origin), Return(true))); |
| ReceiveIndication(GetTestOriginClientIndication()); |
| EXPECT_TRUE(session_->IsSessionReady()); |
| EXPECT_EQ(origin, GetTestOrigin()); |
| EXPECT_EQ(path_.path(), "/test"); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, PiecewiseClientIndication) { |
| Connect(); |
| size_t i = 0; |
| for (; i < sizeof(kTestOriginClientIndication) - 2; i++) { |
| QuicStreamFrame frame( |
| ClientIndicationStream(), false, i, |
| absl::string_view(&kTestOriginClientIndication[i], 1)); |
| session_->OnStreamFrame(frame); |
| } |
| |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); |
| QuicStreamFrame last_frame( |
| ClientIndicationStream(), true, i, |
| absl::string_view(&kTestOriginClientIndication[i], 1)); |
| session_->OnStreamFrame(last_frame); |
| EXPECT_TRUE(session_->IsSessionReady()); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, OriginRejected) { |
| Connect(); |
| EXPECT_CALL(connection_, |
| CloseConnection(_, HasSubstr("Origin check failed"), _)); |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(false)); |
| ReceiveIndication(GetTestOriginClientIndication()); |
| EXPECT_FALSE(session_->IsSessionReady()); |
| } |
| |
| std::string MakeUnknownField(absl::string_view payload) { |
| std::string buffer; |
| buffer.resize(payload.size() + 4); |
| QuicDataWriter writer(buffer.size(), &buffer[0]); |
| EXPECT_TRUE(writer.WriteUInt16(0xffff)); |
| EXPECT_TRUE(writer.WriteUInt16(payload.size())); |
| EXPECT_TRUE(writer.WriteStringPiece(payload)); |
| EXPECT_EQ(writer.remaining(), 0u); |
| return buffer; |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, SkipUnusedFields) { |
| Connect(); |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); |
| ReceiveIndication(GetTestOriginClientIndication() + |
| MakeUnknownField("foobar")); |
| EXPECT_TRUE(session_->IsSessionReady()); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, SkipLongUnusedFields) { |
| const size_t bytes = |
| ClientIndicationMaxSize() - GetTestOriginClientIndication().size() - 4; |
| Connect(); |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); |
| ReceiveIndication(GetTestOriginClientIndication() + |
| MakeUnknownField(std::string(bytes, 'a'))); |
| EXPECT_TRUE(session_->IsSessionReady()); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, ClientIndicationTooLong) { |
| Connect(); |
| EXPECT_CALL( |
| connection_, |
| CloseConnection(_, HasSubstr("Client indication size exceeds"), _)) |
| .Times(AnyNumber()); |
| ReceiveIndication(GetTestOriginClientIndication() + |
| MakeUnknownField(std::string(65534, 'a'))); |
| EXPECT_FALSE(session_->IsSessionReady()); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, NoOrigin) { |
| Connect(); |
| EXPECT_CALL(connection_, CloseConnection(_, HasSubstr("No origin"), _)); |
| ReceiveIndication(MakeUnknownField("foobar")); |
| EXPECT_FALSE(session_->IsSessionReady()); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, EmptyClientIndication) { |
| Connect(); |
| EXPECT_CALL(connection_, CloseConnection(_, HasSubstr("No origin"), _)); |
| ReceiveIndication(""); |
| EXPECT_FALSE(session_->IsSessionReady()); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, MalformedIndicationHeader) { |
| Connect(); |
| EXPECT_CALL(connection_, |
| CloseConnection(_, HasSubstr("Expected 16-bit key"), _)); |
| ReceiveIndication("\xff"); |
| EXPECT_FALSE(session_->IsSessionReady()); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, FieldTooShort) { |
| Connect(); |
| EXPECT_CALL( |
| connection_, |
| CloseConnection(_, HasSubstr("Failed to read value for key 257"), _)); |
| ReceiveIndication("\x01\x01\x01\x01"); |
| EXPECT_FALSE(session_->IsSessionReady()); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, InvalidOrigin) { |
| const std::string kEmptyOriginIndication(4, '\0'); |
| Connect(); |
| EXPECT_CALL( |
| connection_, |
| CloseConnection(_, HasSubstr("Unable to parse the specified origin"), _)); |
| ReceiveIndication(kEmptyOriginIndication); |
| EXPECT_FALSE(session_->IsSessionReady()); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, PathWithQuery) { |
| Connect(); |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); |
| ReceiveIndicationWithPath("/test?foo=bar"); |
| EXPECT_TRUE(session_->IsSessionReady()); |
| EXPECT_EQ(path_.path(), "/test"); |
| EXPECT_EQ(path_.query(), "foo=bar"); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, PathNormalization) { |
| Connect(); |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); |
| ReceiveIndicationWithPath("/foo/../bar"); |
| EXPECT_TRUE(session_->IsSessionReady()); |
| EXPECT_EQ(path_.path(), "/bar"); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, EmptyPath) { |
| Connect(); |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); |
| EXPECT_CALL(connection_, |
| CloseConnection(_, HasSubstr("Path must begin with a '/'"), _)); |
| ReceiveIndicationWithPath(""); |
| EXPECT_FALSE(session_->IsSessionReady()); |
| } |
| |
| TEST_F(QuicTransportServerSessionTest, UnprefixedPath) { |
| Connect(); |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); |
| EXPECT_CALL(connection_, |
| CloseConnection(_, HasSubstr("Path must begin with a '/'"), _)); |
| ReceiveIndicationWithPath("test"); |
| EXPECT_FALSE(session_->IsSessionReady()); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace quic |