diff options
Diffstat (limited to 'chromium/net/third_party/quiche/src/quiche/http2/adapter/test_utils.cc')
-rw-r--r-- | chromium/net/third_party/quiche/src/quiche/http2/adapter/test_utils.cc | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/quiche/http2/adapter/test_utils.cc b/chromium/net/third_party/quiche/src/quiche/http2/adapter/test_utils.cc new file mode 100644 index 00000000000..5d6d427378c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/http2/adapter/test_utils.cc @@ -0,0 +1,224 @@ +#include "quiche/http2/adapter/test_utils.h" + +#include <ostream> + +#include "absl/strings/str_format.h" +#include "quiche/http2/adapter/http2_visitor_interface.h" +#include "quiche/common/quiche_data_reader.h" +#include "quiche/spdy/core/hpack/hpack_encoder.h" + +namespace http2 { +namespace adapter { +namespace test { +namespace { + +using ConnectionError = Http2VisitorInterface::ConnectionError; + +} // anonymous namespace + +TestDataFrameSource::TestDataFrameSource(Http2VisitorInterface& visitor, + bool has_fin) + : visitor_(visitor), has_fin_(has_fin) {} + +void TestDataFrameSource::AppendPayload(absl::string_view payload) { + QUICHE_CHECK(!end_data_); + if (!payload.empty()) { + payload_fragments_.push_back(std::string(payload)); + current_fragment_ = payload_fragments_.front(); + } +} + +void TestDataFrameSource::EndData() { end_data_ = true; } + +std::pair<int64_t, bool> TestDataFrameSource::SelectPayloadLength( + size_t max_length) { + if (return_error_) { + return {DataFrameSource::kError, false}; + } + // The stream is done if there's no more data, or if |max_length| is at least + // as large as the remaining data. + const bool end_data = end_data_ && (current_fragment_.empty() || + (payload_fragments_.size() == 1 && + max_length >= current_fragment_.size())); + const int64_t length = std::min(max_length, current_fragment_.size()); + return {length, end_data}; +} + +bool TestDataFrameSource::Send(absl::string_view frame_header, + size_t payload_length) { + QUICHE_LOG_IF(DFATAL, payload_length > current_fragment_.size()) + << "payload_length: " << payload_length + << " current_fragment_size: " << current_fragment_.size(); + const std::string concatenated = + absl::StrCat(frame_header, current_fragment_.substr(0, payload_length)); + const int64_t result = visitor_.OnReadyToSend(concatenated); + if (result < 0) { + // Write encountered error. + visitor_.OnConnectionError(ConnectionError::kSendError); + current_fragment_ = {}; + payload_fragments_.clear(); + return false; + } else if (result == 0) { + // Write blocked. + return false; + } else if (static_cast<const size_t>(result) < concatenated.size()) { + // Probably need to handle this better within this test class. + QUICHE_LOG(DFATAL) + << "DATA frame not fully flushed. Connection will be corrupt!"; + visitor_.OnConnectionError(ConnectionError::kSendError); + current_fragment_ = {}; + payload_fragments_.clear(); + return false; + } + if (payload_length > 0) { + current_fragment_.remove_prefix(payload_length); + } + if (current_fragment_.empty() && !payload_fragments_.empty()) { + payload_fragments_.erase(payload_fragments_.begin()); + if (!payload_fragments_.empty()) { + current_fragment_ = payload_fragments_.front(); + } + } + return true; +} + +std::string EncodeHeaders(const spdy::SpdyHeaderBlock& entries) { + spdy::HpackEncoder encoder; + encoder.DisableCompression(); + return encoder.EncodeHeaderBlock(entries); +} + +TestMetadataSource::TestMetadataSource(const spdy::SpdyHeaderBlock& entries) + : encoded_entries_(EncodeHeaders(entries)) { + remaining_ = encoded_entries_; +} + +std::pair<int64_t, bool> TestMetadataSource::Pack(uint8_t* dest, + size_t dest_len) { + const size_t copied = std::min(dest_len, remaining_.size()); + std::memcpy(dest, remaining_.data(), copied); + remaining_.remove_prefix(copied); + return std::make_pair(copied, remaining_.empty()); +} + +namespace { + +using TypeAndOptionalLength = + std::pair<spdy::SpdyFrameType, absl::optional<size_t>>; + +std::ostream& operator<<( + std::ostream& os, + const std::vector<TypeAndOptionalLength>& types_and_lengths) { + for (const auto& type_and_length : types_and_lengths) { + os << "(" << spdy::FrameTypeToString(type_and_length.first) << ", " + << (type_and_length.second ? absl::StrCat(type_and_length.second.value()) + : "<unspecified>") + << ") "; + } + return os; +} + +std::string FrameTypeToString(uint8_t frame_type) { + if (spdy::IsDefinedFrameType(frame_type)) { + return spdy::FrameTypeToString(spdy::ParseFrameType(frame_type)); + } else { + return absl::StrFormat("0x%x", static_cast<int>(frame_type)); + } +} + +// Custom gMock matcher, used to implement EqualsFrames(). +class SpdyControlFrameMatcher + : public testing::MatcherInterface<absl::string_view> { + public: + explicit SpdyControlFrameMatcher( + std::vector<TypeAndOptionalLength> types_and_lengths) + : expected_types_and_lengths_(std::move(types_and_lengths)) {} + + bool MatchAndExplain(absl::string_view s, + testing::MatchResultListener* listener) const override { + quiche::QuicheDataReader reader(s.data(), s.size()); + + for (TypeAndOptionalLength expected : expected_types_and_lengths_) { + if (!MatchAndExplainOneFrame(expected.first, expected.second, &reader, + listener)) { + return false; + } + } + if (!reader.IsDoneReading()) { + *listener << "; " << reader.BytesRemaining() << " bytes left to read!"; + return false; + } + return true; + } + + bool MatchAndExplainOneFrame(spdy::SpdyFrameType expected_type, + absl::optional<size_t> expected_length, + quiche::QuicheDataReader* reader, + testing::MatchResultListener* listener) const { + uint32_t payload_length; + if (!reader->ReadUInt24(&payload_length)) { + *listener << "; unable to read length field for expected_type " + << FrameTypeToString(expected_type) << ". data too short!"; + return false; + } + + if (expected_length && payload_length != expected_length.value()) { + *listener << "; actual length: " << payload_length + << " but expected length: " << expected_length.value(); + return false; + } + + uint8_t raw_type; + if (!reader->ReadUInt8(&raw_type)) { + *listener << "; unable to read type field for expected_type " + << FrameTypeToString(expected_type) << ". data too short!"; + return false; + } + + if (raw_type != static_cast<uint8_t>(expected_type)) { + *listener << "; actual type: " << FrameTypeToString(raw_type) + << " but expected type: " << FrameTypeToString(expected_type); + return false; + } + + // Seek past flags (1B), stream ID (4B), and payload. Reach the next frame. + reader->Seek(5 + payload_length); + return true; + } + + void DescribeTo(std::ostream* os) const override { + *os << "Data contains frames of types in sequence " + << expected_types_and_lengths_; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "Data does not contain frames of types in sequence " + << expected_types_and_lengths_; + } + + private: + const std::vector<TypeAndOptionalLength> expected_types_and_lengths_; +}; + +} // namespace + +testing::Matcher<absl::string_view> EqualsFrames( + std::vector<std::pair<spdy::SpdyFrameType, absl::optional<size_t>>> + types_and_lengths) { + return MakeMatcher(new SpdyControlFrameMatcher(std::move(types_and_lengths))); +} + +testing::Matcher<absl::string_view> EqualsFrames( + std::vector<spdy::SpdyFrameType> types) { + std::vector<std::pair<spdy::SpdyFrameType, absl::optional<size_t>>> + types_and_lengths; + types_and_lengths.reserve(types.size()); + for (spdy::SpdyFrameType type : types) { + types_and_lengths.push_back({type, absl::nullopt}); + } + return MakeMatcher(new SpdyControlFrameMatcher(std::move(types_and_lengths))); +} + +} // namespace test +} // namespace adapter +} // namespace http2 |