| #include "spdy/core/metadata_extension.h" |
| |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/functional/bind_front.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "common/platform/api/quiche_test.h" |
| #include "spdy/core/array_output_buffer.h" |
| #include "spdy/core/mock_spdy_framer_visitor.h" |
| #include "spdy/core/spdy_framer.h" |
| #include "spdy/core/spdy_header_block.h" |
| #include "spdy/core/spdy_no_op_visitor.h" |
| #include "spdy/core/spdy_protocol.h" |
| |
| namespace spdy { |
| namespace test { |
| namespace { |
| |
| using ::absl::bind_front; |
| using ::spdy::SpdyFramer; |
| using ::spdy::SpdyHeaderBlock; |
| using ::spdy::test::MockSpdyFramerVisitor; |
| using ::testing::_; |
| using ::testing::ElementsAre; |
| using ::testing::IsEmpty; |
| |
| const size_t kBufferSize = 64 * 1024; |
| char kBuffer[kBufferSize]; |
| |
| class MetadataExtensionTest : public QuicheTest { |
| protected: |
| MetadataExtensionTest() : test_buffer_(kBuffer, kBufferSize) {} |
| |
| void SetUp() override { |
| extension_ = absl::make_unique<MetadataVisitor>( |
| bind_front(&MetadataExtensionTest::OnCompletePayload, this), |
| bind_front(&MetadataExtensionTest::OnMetadataSupport, this)); |
| } |
| |
| void OnCompletePayload(spdy::SpdyStreamId stream_id, |
| MetadataVisitor::MetadataPayload payload) { |
| ++received_count_; |
| received_payload_map_.insert(std::make_pair(stream_id, std::move(payload))); |
| } |
| |
| void OnMetadataSupport(bool peer_supports_metadata) { |
| EXPECT_EQ(peer_supports_metadata, extension_->PeerSupportsMetadata()); |
| received_metadata_support_.push_back(peer_supports_metadata); |
| } |
| |
| MetadataSerializer::MetadataPayload PayloadForData(absl::string_view data) { |
| SpdyHeaderBlock block; |
| block["example-payload"] = data; |
| return block; |
| } |
| |
| std::unique_ptr<MetadataVisitor> extension_; |
| absl::flat_hash_map<spdy::SpdyStreamId, SpdyHeaderBlock> |
| received_payload_map_; |
| std::vector<bool> received_metadata_support_; |
| size_t received_count_ = 0; |
| spdy::ArrayOutputBuffer test_buffer_; |
| }; |
| |
| // This test verifies that the MetadataVisitor is initialized to a state where |
| // it believes the peer does not support metadata. |
| TEST_F(MetadataExtensionTest, MetadataNotSupported) { |
| EXPECT_FALSE(extension_->PeerSupportsMetadata()); |
| EXPECT_THAT(received_metadata_support_, IsEmpty()); |
| } |
| |
| // This test verifies that upon receiving a specific setting, the extension |
| // realizes that the peer supports metadata. |
| TEST_F(MetadataExtensionTest, MetadataSupported) { |
| EXPECT_FALSE(extension_->PeerSupportsMetadata()); |
| // 3 is not an appropriate value for the metadata extension key. |
| extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 3); |
| EXPECT_FALSE(extension_->PeerSupportsMetadata()); |
| extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); |
| ASSERT_TRUE(extension_->PeerSupportsMetadata()); |
| extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 0); |
| EXPECT_FALSE(extension_->PeerSupportsMetadata()); |
| EXPECT_THAT(received_metadata_support_, ElementsAre(true, false)); |
| } |
| |
| TEST_F(MetadataExtensionTest, MetadataIgnoredWithoutExtension) { |
| const char kData[] = "some payload"; |
| SpdyHeaderBlock payload = PayloadForData(kData); |
| |
| extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); |
| ASSERT_TRUE(extension_->PeerSupportsMetadata()); |
| |
| MetadataSerializer serializer; |
| auto sequence = serializer.FrameSequenceForPayload(3, std::move(payload)); |
| ASSERT_TRUE(sequence != nullptr); |
| |
| http2::Http2DecoderAdapter deframer; |
| ::testing::StrictMock<MockSpdyFramerVisitor> visitor; |
| deframer.set_visitor(&visitor); |
| |
| EXPECT_CALL(visitor, |
| OnCommonHeader(3, _, MetadataVisitor::kMetadataFrameType, _)); |
| // The Return(true) should not be necessary. http://b/36023792 |
| EXPECT_CALL(visitor, OnUnknownFrame(3, MetadataVisitor::kMetadataFrameType)) |
| .WillOnce(::testing::Return(true)); |
| |
| SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); |
| auto frame = sequence->Next(); |
| ASSERT_TRUE(frame != nullptr); |
| while (frame != nullptr) { |
| const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_); |
| ASSERT_GT(frame_size, 0u); |
| ASSERT_FALSE(deframer.HasError()); |
| ASSERT_EQ(frame_size, test_buffer_.Size()); |
| EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size)); |
| test_buffer_.Reset(); |
| frame = sequence->Next(); |
| } |
| EXPECT_FALSE(deframer.HasError()); |
| EXPECT_THAT(received_metadata_support_, ElementsAre(true)); |
| } |
| |
| // This test verifies that the METADATA frame emitted by a MetadataExtension |
| // can be parsed by another SpdyFramer with a MetadataVisitor. |
| TEST_F(MetadataExtensionTest, MetadataPayloadEndToEnd) { |
| SpdyHeaderBlock block1; |
| block1["foo"] = "Some metadata value."; |
| SpdyHeaderBlock block2; |
| block2["bar"] = |
| "The color taupe truly represents a triumph of the human spirit over " |
| "adversity."; |
| block2["baz"] = |
| "Or perhaps it represents abject surrender to the implacable and " |
| "incomprehensible forces of the universe."; |
| const absl::string_view binary_payload{"binary\0payload", 14}; |
| block2["qux"] = binary_payload; |
| EXPECT_EQ(binary_payload, block2["qux"]); |
| for (const SpdyHeaderBlock& payload_block : |
| {std::move(block1), std::move(block2)}) { |
| extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); |
| ASSERT_TRUE(extension_->PeerSupportsMetadata()); |
| |
| MetadataSerializer serializer; |
| auto sequence = |
| serializer.FrameSequenceForPayload(3, payload_block.Clone()); |
| ASSERT_TRUE(sequence != nullptr); |
| |
| http2::Http2DecoderAdapter deframer; |
| ::spdy::SpdyNoOpVisitor visitor; |
| deframer.set_visitor(&visitor); |
| deframer.set_extension_visitor(extension_.get()); |
| SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); |
| auto frame = sequence->Next(); |
| ASSERT_TRUE(frame != nullptr); |
| while (frame != nullptr) { |
| const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_); |
| ASSERT_GT(frame_size, 0u); |
| ASSERT_FALSE(deframer.HasError()); |
| ASSERT_EQ(frame_size, test_buffer_.Size()); |
| EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size)); |
| test_buffer_.Reset(); |
| frame = sequence->Next(); |
| } |
| EXPECT_EQ(1u, received_count_); |
| auto it = received_payload_map_.find(3); |
| ASSERT_TRUE(it != received_payload_map_.end()); |
| EXPECT_EQ(payload_block, it->second); |
| |
| received_count_ = 0; |
| received_payload_map_.clear(); |
| } |
| } |
| |
| // This test verifies that METADATA frames for two different streams can be |
| // interleaved and still successfully parsed by another SpdyFramer with a |
| // MetadataVisitor. |
| TEST_F(MetadataExtensionTest, MetadataPayloadInterleaved) { |
| const std::string kData1 = std::string(65 * 1024, 'a'); |
| const std::string kData2 = std::string(65 * 1024, 'b'); |
| const SpdyHeaderBlock payload1 = PayloadForData(kData1); |
| const SpdyHeaderBlock payload2 = PayloadForData(kData2); |
| |
| extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); |
| ASSERT_TRUE(extension_->PeerSupportsMetadata()); |
| |
| MetadataSerializer serializer; |
| auto sequence1 = serializer.FrameSequenceForPayload(3, payload1.Clone()); |
| ASSERT_TRUE(sequence1 != nullptr); |
| |
| auto sequence2 = serializer.FrameSequenceForPayload(5, payload2.Clone()); |
| ASSERT_TRUE(sequence2 != nullptr); |
| |
| http2::Http2DecoderAdapter deframer; |
| ::spdy::SpdyNoOpVisitor visitor; |
| deframer.set_visitor(&visitor); |
| deframer.set_extension_visitor(extension_.get()); |
| |
| SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); |
| auto frame1 = sequence1->Next(); |
| ASSERT_TRUE(frame1 != nullptr); |
| auto frame2 = sequence2->Next(); |
| ASSERT_TRUE(frame2 != nullptr); |
| while (frame1 != nullptr || frame2 != nullptr) { |
| for (auto frame : {frame1.get(), frame2.get()}) { |
| if (frame != nullptr) { |
| const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_); |
| ASSERT_GT(frame_size, 0u); |
| ASSERT_FALSE(deframer.HasError()); |
| ASSERT_EQ(frame_size, test_buffer_.Size()); |
| EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size)); |
| test_buffer_.Reset(); |
| } |
| } |
| frame1 = sequence1->Next(); |
| frame2 = sequence2->Next(); |
| } |
| EXPECT_EQ(2u, received_count_); |
| auto it = received_payload_map_.find(3); |
| ASSERT_TRUE(it != received_payload_map_.end()); |
| EXPECT_EQ(payload1, it->second); |
| |
| it = received_payload_map_.find(5); |
| ASSERT_TRUE(it != received_payload_map_.end()); |
| EXPECT_EQ(payload2, it->second); |
| } |
| |
| } // anonymous namespace |
| } // namespace test |
| } // namespace spdy |