| // 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 "quiche/common/capsule.h" | 
 |  | 
 | #include <cstddef> | 
 | #include <deque> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "absl/strings/escaping.h" | 
 | #include "absl/strings/string_view.h" | 
 | #include "quiche/common/platform/api/quiche_test.h" | 
 | #include "quiche/common/quiche_ip_address.h" | 
 | #include "quiche/common/simple_buffer_allocator.h" | 
 | #include "quiche/common/test_tools/quiche_test_utils.h" | 
 |  | 
 | using ::testing::_; | 
 | using ::testing::InSequence; | 
 | using ::testing::Return; | 
 |  | 
 | namespace quiche { | 
 | namespace test { | 
 |  | 
 | class CapsuleParserPeer { | 
 |  public: | 
 |   static std::string* buffered_data(CapsuleParser* capsule_parser) { | 
 |     return &capsule_parser->buffered_data_; | 
 |   } | 
 | }; | 
 |  | 
 | namespace { | 
 |  | 
 | 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, (absl::string_view error_message), | 
 |               (override)); | 
 | }; | 
 |  | 
 | class CapsuleTest : public QuicheTest { | 
 |  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) { | 
 |     quiche::QuicheBuffer 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, DatagramCapsule) { | 
 |   std::string capsule_fragment = absl::HexStringToBytes( | 
 |       "00"                // DATAGRAM capsule type | 
 |       "08"                // capsule length | 
 |       "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload | 
 |   ); | 
 |   std::string datagram_payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
 |   Capsule expected_capsule = Capsule::Datagram(datagram_payload); | 
 |   { | 
 |     EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
 |     ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
 |   } | 
 |   ValidateParserIsEmpty(); | 
 |   TestSerialization(expected_capsule, capsule_fragment); | 
 | } | 
 |  | 
 | 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(datagram_payload); | 
 |   { | 
 |     EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
 |     ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
 |   } | 
 |   ValidateParserIsEmpty(); | 
 |   TestSerialization(expected_capsule, capsule_fragment); | 
 | } | 
 |  | 
 | TEST_F(CapsuleTest, LegacyDatagramWithoutContextCapsule) { | 
 |   std::string capsule_fragment = absl::HexStringToBytes( | 
 |       "80ff37a5"          // LEGACY_DATAGRAM_WITHOUT_CONTEXT capsule type | 
 |       "08"                // capsule length | 
 |       "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload | 
 |   ); | 
 |   std::string datagram_payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
 |   Capsule expected_capsule = | 
 |       Capsule::LegacyDatagramWithoutContext(datagram_payload); | 
 |   { | 
 |     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, AddressAssignCapsule) { | 
 |   std::string capsule_fragment = absl::HexStringToBytes( | 
 |       "9ECA6A00"  // ADDRESS_ASSIGN capsule type | 
 |       "1A"        // capsule length = 26 | 
 |       // first assigned address | 
 |       "00"        // request ID = 0 | 
 |       "04"        // IP version = 4 | 
 |       "C000022A"  // 192.0.2.42 | 
 |       "1F"        // prefix length = 31 | 
 |       // second assigned address | 
 |       "01"                                // request ID = 1 | 
 |       "06"                                // IP version = 6 | 
 |       "20010db8123456780000000000000000"  // 2001:db8:1234:5678:: | 
 |       "40"                                // prefix length = 64 | 
 |   ); | 
 |   Capsule expected_capsule = Capsule::AddressAssign(); | 
 |   quiche::QuicheIpAddress ip_address1; | 
 |   ip_address1.FromString("192.0.2.42"); | 
 |   PrefixWithId assigned_address1; | 
 |   assigned_address1.request_id = 0; | 
 |   assigned_address1.ip_prefix = | 
 |       quiche::QuicheIpPrefix(ip_address1, /*prefix_length=*/31); | 
 |   expected_capsule.address_assign_capsule().assigned_addresses.push_back( | 
 |       assigned_address1); | 
 |   quiche::QuicheIpAddress ip_address2; | 
 |   ip_address2.FromString("2001:db8:1234:5678::"); | 
 |   PrefixWithId assigned_address2; | 
 |   assigned_address2.request_id = 1; | 
 |   assigned_address2.ip_prefix = | 
 |       quiche::QuicheIpPrefix(ip_address2, /*prefix_length=*/64); | 
 |   expected_capsule.address_assign_capsule().assigned_addresses.push_back( | 
 |       assigned_address2); | 
 |   { | 
 |     EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
 |     ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
 |   } | 
 |   ValidateParserIsEmpty(); | 
 |   TestSerialization(expected_capsule, capsule_fragment); | 
 | } | 
 |  | 
 | TEST_F(CapsuleTest, AddressRequestCapsule) { | 
 |   std::string capsule_fragment = absl::HexStringToBytes( | 
 |       "9ECA6A01"  // ADDRESS_REQUEST capsule type | 
 |       "1A"        // capsule length = 26 | 
 |       // first requested address | 
 |       "00"        // request ID = 0 | 
 |       "04"        // IP version = 4 | 
 |       "C000022A"  // 192.0.2.42 | 
 |       "1F"        // prefix length = 31 | 
 |       // second requested address | 
 |       "01"                                // request ID = 1 | 
 |       "06"                                // IP version = 6 | 
 |       "20010db8123456780000000000000000"  // 2001:db8:1234:5678:: | 
 |       "40"                                // prefix length = 64 | 
 |   ); | 
 |   Capsule expected_capsule = Capsule::AddressRequest(); | 
 |   quiche::QuicheIpAddress ip_address1; | 
 |   ip_address1.FromString("192.0.2.42"); | 
 |   PrefixWithId requested_address1; | 
 |   requested_address1.request_id = 0; | 
 |   requested_address1.ip_prefix = | 
 |       quiche::QuicheIpPrefix(ip_address1, /*prefix_length=*/31); | 
 |   expected_capsule.address_request_capsule().requested_addresses.push_back( | 
 |       requested_address1); | 
 |   quiche::QuicheIpAddress ip_address2; | 
 |   ip_address2.FromString("2001:db8:1234:5678::"); | 
 |   PrefixWithId requested_address2; | 
 |   requested_address2.request_id = 1; | 
 |   requested_address2.ip_prefix = | 
 |       quiche::QuicheIpPrefix(ip_address2, /*prefix_length=*/64); | 
 |   expected_capsule.address_request_capsule().requested_addresses.push_back( | 
 |       requested_address2); | 
 |   { | 
 |     EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); | 
 |     ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); | 
 |   } | 
 |   ValidateParserIsEmpty(); | 
 |   TestSerialization(expected_capsule, capsule_fragment); | 
 | } | 
 |  | 
 | TEST_F(CapsuleTest, RouteAdvertisementCapsule) { | 
 |   std::string capsule_fragment = absl::HexStringToBytes( | 
 |       "9ECA6A02"  // ROUTE_ADVERTISEMENT capsule type | 
 |       "2C"        // capsule length = 44 | 
 |       // first IP address range | 
 |       "04"        // IP version = 4 | 
 |       "C0000218"  // 192.0.2.24 | 
 |       "C000022A"  // 192.0.2.42 | 
 |       "00"        // ip protocol = 0 | 
 |       // second IP address range | 
 |       "06"                                // IP version = 6 | 
 |       "00000000000000000000000000000000"  // :: | 
 |       "ffffffffffffffffffffffffffffffff"  // all ones IPv6 address | 
 |       "01"                                // ip protocol = 1 (ICMP) | 
 |   ); | 
 |   Capsule expected_capsule = Capsule::RouteAdvertisement(); | 
 |   IpAddressRange ip_address_range1; | 
 |   ip_address_range1.start_ip_address.FromString("192.0.2.24"); | 
 |   ip_address_range1.end_ip_address.FromString("192.0.2.42"); | 
 |   ip_address_range1.ip_protocol = 0; | 
 |   expected_capsule.route_advertisement_capsule().ip_address_ranges.push_back( | 
 |       ip_address_range1); | 
 |   IpAddressRange ip_address_range2; | 
 |   ip_address_range2.start_ip_address.FromString("::"); | 
 |   ip_address_range2.end_ip_address.FromString( | 
 |       "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); | 
 |   ip_address_range2.ip_protocol = 1; | 
 |   expected_capsule.route_advertisement_capsule().ip_address_ranges.push_back( | 
 |       ip_address_range2); | 
 |   { | 
 |     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( | 
 |       "17"                // unknown capsule type of 0x17 | 
 |       "08"                // capsule length | 
 |       "a1a2a3a4a5a6a7a8"  // unknown capsule data | 
 |   ); | 
 |   std::string unknown_capsule_data = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); | 
 |   Capsule expected_capsule = Capsule::Unknown(0x17, 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( | 
 |       "00"                // DATAGRAM capsule type | 
 |       "08"                // capsule length | 
 |       "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload | 
 |       "00"                // DATAGRAM 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::Datagram(datagram_payload1); | 
 |   Capsule expected_capsule2 = Capsule::Datagram(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( | 
 |       "00"        // first capsule DATAGRAM capsule type | 
 |       "08"        // first 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 | 
 |       "00"        // second capsule DATAGRAM 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::Datagram(datagram_payload1); | 
 |   Capsule expected_capsule2 = Capsule::Datagram(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( | 
 |       "00"                // DATAGRAM capsule type | 
 |       "08"                // capsule length | 
 |       "a1a2a3a4a5a6a7a8"  // HTTP Datagram payload | 
 |       "00"                // DATAGRAM 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::Datagram(datagram_payload1); | 
 |   Capsule expected_capsule2 = Capsule::Datagram(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( | 
 |       "00"        // DATAGRAM 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( | 
 |                                      "17"        // unknown capsule type of 0x17 | 
 |                                      "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 quiche |