|  | // Copyright (c) 2021 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/core/http/capsule.h" | 
|  |  | 
|  | #include <cstddef> | 
|  | #include <deque> | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/strings/escaping.h" | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "quic/platform/api/quic_test.h" | 
|  | #include "quic/test_tools/quic_test_utils.h" | 
|  | #include "common/test_tools/quiche_test_utils.h" | 
|  |  | 
|  | using ::testing::_; | 
|  | using ::testing::InSequence; | 
|  | using ::testing::Return; | 
|  |  | 
|  | namespace quic { | 
|  | namespace test { | 
|  |  | 
|  | class CapsuleParserPeer { | 
|  | public: | 
|  | static std::string* buffered_data(CapsuleParser* capsule_parser) { | 
|  | return &capsule_parser->buffered_data_; | 
|  | } | 
|  | }; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr DatagramFormatType kFakeFormatType = | 
|  | static_cast<DatagramFormatType>(0x123456); | 
|  | constexpr ContextCloseCode kFakeCloseCode = | 
|  | static_cast<ContextCloseCode>(0x654321); | 
|  |  | 
|  | class MockCapsuleParserVisitor : public CapsuleParser::Visitor { | 
|  | public: | 
|  | MockCapsuleParserVisitor() { | 
|  | ON_CALL(*this, OnCapsule(_)).WillByDefault(Return(true)); | 
|  | } | 
|  | ~MockCapsuleParserVisitor() override = default; | 
|  | MOCK_METHOD(bool, OnCapsule, (const Capsule& capsule), (override)); | 
|  | MOCK_METHOD(void, OnCapsuleParseFailure, (const std::string& error_message), | 
|  | (override)); | 
|  | }; | 
|  |  | 
|  | class CapsuleTest : public QuicTest { | 
|  | public: | 
|  | CapsuleTest() : capsule_parser_(&visitor_) {} | 
|  |  | 
|  | void ValidateParserIsEmpty() { | 
|  | EXPECT_CALL(visitor_, OnCapsule(_)).Times(0); | 
|  | EXPECT_CALL(visitor_, OnCapsuleParseFailure(_)).Times(0); | 
|  | capsule_parser_.ErrorIfThereIsRemainingBufferedData(); | 
|  | EXPECT_TRUE(CapsuleParserPeer::buffered_data(&capsule_parser_)->empty()); | 
|  | } | 
|  |  | 
|  | void TestSerialization(const Capsule& capsule, | 
|  | const std::string& expected_bytes) { | 
|  | QuicBuffer serialized_capsule = | 
|  | SerializeCapsule(capsule, SimpleBufferAllocator::Get()); | 
|  | quiche::test::CompareCharArraysWithHexError( | 
|  | "Serialized capsule", serialized_capsule.data(), | 
|  | serialized_capsule.size(), expected_bytes.data(), | 
|  | expected_bytes.size()); | 
|  | } | 
|  |  | 
|  | ::testing::StrictMock<MockCapsuleParserVisitor> visitor_; | 
|  | CapsuleParser capsule_parser_; | 
|  | }; | 
|  |  | 
|  | TEST_F(CapsuleTest, LegacyDatagramCapsule) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "80ff37a0"          // LEGACY_DATAGRAM capsule type | 
|  | "08"                // capsule length | 
|  | "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload | 
|  | ); | 
|  | std::string datagram_payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
|  | Capsule expected_capsule = | 
|  | Capsule::LegacyDatagram(/*context_id=*/absl::nullopt, datagram_payload); | 
|  | { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | TestSerialization(expected_capsule, capsule_fragment); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, LegacyDatagramCapsuleWithContext) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "80ff37a0"          // LEGACY_DATAGRAM capsule type | 
|  | "09"                // capsule length | 
|  | "04"                // context ID | 
|  | "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload | 
|  | ); | 
|  | capsule_parser_.set_datagram_context_id_present(true); | 
|  | std::string datagram_payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
|  | Capsule expected_capsule = | 
|  | Capsule::LegacyDatagram(/*context_id=*/4, datagram_payload); | 
|  | { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | TestSerialization(expected_capsule, capsule_fragment); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, DatagramWithoutContextCapsule) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "80ff37a5"          // DATAGRAM_WITHOUT_CONTEXT capsule type | 
|  | "08"                // capsule length | 
|  | "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload | 
|  | ); | 
|  | std::string datagram_payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
|  | Capsule expected_capsule = Capsule::DatagramWithoutContext(datagram_payload); | 
|  | { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | TestSerialization(expected_capsule, capsule_fragment); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, DatagramWithContextCapsule) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "80ff37a4"          // DATAGRAM_WITH_CONTEXT capsule type | 
|  | "09"                // capsule length | 
|  | "04"                // context ID | 
|  | "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload | 
|  | ); | 
|  | std::string datagram_payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
|  | Capsule expected_capsule = | 
|  | Capsule::DatagramWithContext(/*context_id=*/4, datagram_payload); | 
|  | { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | TestSerialization(expected_capsule, capsule_fragment); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, RegisterContextCapsule) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "80ff37a1"          // REGISTER_DATAGRAM_CONTEXT capsule type | 
|  | "0d"                // capsule length | 
|  | "04"                // context ID | 
|  | "80123456"          // 0x123456 datagram format type | 
|  | "f1f2f3f4f5f6f7f8"  // format additional data | 
|  | ); | 
|  | std::string format_additional_data = | 
|  | absl::HexStringToBytes("f1f2f3f4f5f6f7f8"); | 
|  | Capsule expected_capsule = Capsule::RegisterDatagramContext( | 
|  | /*context_id=*/4, kFakeFormatType, format_additional_data); | 
|  | { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | TestSerialization(expected_capsule, capsule_fragment); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, RegisterNoContextCapsule) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "80ff37a2"          // REGISTER_DATAGRAM_NO_CONTEXT capsule type | 
|  | "0c"                // capsule length | 
|  | "80123456"          // 0x123456 datagram format type | 
|  | "f1f2f3f4f5f6f7f8"  // format additional data | 
|  | ); | 
|  | std::string format_additional_data = | 
|  | absl::HexStringToBytes("f1f2f3f4f5f6f7f8"); | 
|  | Capsule expected_capsule = Capsule::RegisterDatagramNoContext( | 
|  | kFakeFormatType, format_additional_data); | 
|  | { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | TestSerialization(expected_capsule, capsule_fragment); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, CloseContextCapsule) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "80ff37a3"  // CLOSE_DATAGRAM_CONTEXT capsule type | 
|  | "27"        // capsule length | 
|  | "04"        // context ID | 
|  | "80654321"  // 0x654321 close code | 
|  | ); | 
|  | std::string close_details = "All your contexts are belong to us"; | 
|  | capsule_fragment += close_details; | 
|  | Capsule expected_capsule = Capsule::CloseDatagramContext( | 
|  | /*context_id=*/4, kFakeCloseCode, close_details); | 
|  | { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | TestSerialization(expected_capsule, capsule_fragment); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, CloseWebTransportStreamCapsule) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "6843"        // CLOSE_WEBTRANSPORT_STREAM capsule type | 
|  | "09"          // capsule length | 
|  | "00001234"    // 0x1234 error code | 
|  | "68656c6c6f"  // "hello" error message | 
|  | ); | 
|  | Capsule expected_capsule = Capsule::CloseWebTransportSession( | 
|  | /*error_code=*/0x1234, /*error_message=*/"hello"); | 
|  | { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | TestSerialization(expected_capsule, capsule_fragment); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, UnknownCapsule) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "33"                // unknown capsule type of 0x33 | 
|  | "08"                // capsule length | 
|  | "a1a2a3a4a5a6a7a8"  // unknown capsule data | 
|  | ); | 
|  | std::string unknown_capsule_data = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
|  | Capsule expected_capsule = Capsule::Unknown(0x33, unknown_capsule_data); | 
|  | { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | TestSerialization(expected_capsule, capsule_fragment); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, TwoCapsules) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "80ff37a5"          // DATAGRAM_WITHOUT_CONTEXT capsule type | 
|  | "08"                // capsule length | 
|  | "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload | 
|  | "80ff37a5"          // DATAGRAM_WITHOUT_CONTEXT capsule type | 
|  | "08"                // capsule length | 
|  | "b1b2b3b4b5b6b7b8"  // HTTP Datagram payload | 
|  | ); | 
|  | std::string datagram_payload1 = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
|  | std::string datagram_payload2 = absl::HexStringToBytes("b1b2b3b4b5b6b7b8"); | 
|  | Capsule expected_capsule1 = | 
|  | Capsule::DatagramWithoutContext(datagram_payload1); | 
|  | Capsule expected_capsule2 = | 
|  | Capsule::DatagramWithoutContext(datagram_payload2); | 
|  | { | 
|  | InSequence s; | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule1)); | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule2)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, TwoCapsulesPartialReads) { | 
|  | std::string capsule_fragment1 = absl::HexStringToBytes( | 
|  | "80ff37a5"  // first capsule DATAGRAM_WITHOUT_CONTEXT capsule type | 
|  | "08"        // frist capsule length | 
|  | "a1a2a3a4"  // first half of HTTP Datagram payload of first capsule | 
|  | ); | 
|  | std::string capsule_fragment2 = absl::HexStringToBytes( | 
|  | "a5a6a7a8"  // second half of HTTP Datagram payload 1 | 
|  | "80ff37a5"  // second capsule DATAGRAM_WITHOUT_CONTEXT capsule type | 
|  | ); | 
|  | std::string capsule_fragment3 = absl::HexStringToBytes( | 
|  | "08"                // second capsule length | 
|  | "b1b2b3b4b5b6b7b8"  // HTTP Datagram payload of second capsule | 
|  | ); | 
|  | capsule_parser_.ErrorIfThereIsRemainingBufferedData(); | 
|  | std::string datagram_payload1 = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
|  | std::string datagram_payload2 = absl::HexStringToBytes("b1b2b3b4b5b6b7b8"); | 
|  | Capsule expected_capsule1 = | 
|  | Capsule::DatagramWithoutContext(datagram_payload1); | 
|  | Capsule expected_capsule2 = | 
|  | Capsule::DatagramWithoutContext(datagram_payload2); | 
|  | { | 
|  | InSequence s; | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule1)); | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule2)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment1)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment2)); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment3)); | 
|  | } | 
|  | ValidateParserIsEmpty(); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, TwoCapsulesOneByteAtATime) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "80ff37a5"          // DATAGRAM_WITHOUT_CONTEXT capsule type | 
|  | "08"                // capsule length | 
|  | "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload | 
|  | "80ff37a5"          // DATAGRAM_WITHOUT_CONTEXT capsule type | 
|  | "08"                // capsule length | 
|  | "b1b2b3b4b5b6b7b8"  // HTTP Datagram payload | 
|  | ); | 
|  | std::string datagram_payload1 = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
|  | std::string datagram_payload2 = absl::HexStringToBytes("b1b2b3b4b5b6b7b8"); | 
|  | Capsule expected_capsule1 = | 
|  | Capsule::DatagramWithoutContext(datagram_payload1); | 
|  | Capsule expected_capsule2 = | 
|  | Capsule::DatagramWithoutContext(datagram_payload2); | 
|  | for (size_t i = 0; i < capsule_fragment.size(); i++) { | 
|  | if (i < capsule_fragment.size() / 2 - 1) { | 
|  | EXPECT_CALL(visitor_, OnCapsule(_)).Times(0); | 
|  | ASSERT_TRUE( | 
|  | capsule_parser_.IngestCapsuleFragment(capsule_fragment.substr(i, 1))); | 
|  | } else if (i == capsule_fragment.size() / 2 - 1) { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule1)); | 
|  | ASSERT_TRUE( | 
|  | capsule_parser_.IngestCapsuleFragment(capsule_fragment.substr(i, 1))); | 
|  | EXPECT_TRUE(CapsuleParserPeer::buffered_data(&capsule_parser_)->empty()); | 
|  | } else if (i < capsule_fragment.size() - 1) { | 
|  | EXPECT_CALL(visitor_, OnCapsule(_)).Times(0); | 
|  | ASSERT_TRUE( | 
|  | capsule_parser_.IngestCapsuleFragment(capsule_fragment.substr(i, 1))); | 
|  | } else { | 
|  | EXPECT_CALL(visitor_, OnCapsule(expected_capsule2)); | 
|  | ASSERT_TRUE( | 
|  | capsule_parser_.IngestCapsuleFragment(capsule_fragment.substr(i, 1))); | 
|  | EXPECT_TRUE(CapsuleParserPeer::buffered_data(&capsule_parser_)->empty()); | 
|  | } | 
|  | } | 
|  | capsule_parser_.ErrorIfThereIsRemainingBufferedData(); | 
|  | EXPECT_TRUE(CapsuleParserPeer::buffered_data(&capsule_parser_)->empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, PartialCapsuleThenError) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "80ff37a5"  // DATAGRAM_WITHOUT_CONTEXT capsule type | 
|  | "08"        // capsule length | 
|  | "a1a2a3a4"  // first half of HTTP Datagram payload | 
|  | ); | 
|  | EXPECT_CALL(visitor_, OnCapsule(_)).Times(0); | 
|  | { | 
|  | EXPECT_CALL(visitor_, OnCapsuleParseFailure(_)).Times(0); | 
|  | ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  | { | 
|  | EXPECT_CALL(visitor_, | 
|  | OnCapsuleParseFailure( | 
|  | "Incomplete capsule left at the end of the stream")); | 
|  | capsule_parser_.ErrorIfThereIsRemainingBufferedData(); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(CapsuleTest, RejectOverlyLongCapsule) { | 
|  | std::string capsule_fragment = absl::HexStringToBytes( | 
|  | "33"        // unknown capsule type of 0x33 | 
|  | "80123456"  // capsule length | 
|  | ) + | 
|  | std::string(1111111, '?'); | 
|  | EXPECT_CALL(visitor_, OnCapsuleParseFailure( | 
|  | "Refusing to buffer too much capsule data")); | 
|  | EXPECT_FALSE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace test | 
|  | }  // namespace quic |