xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/http2/adapter/test_utils.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 #include "quiche/http2/adapter/test_utils.h"
2 
3 #include <optional>
4 #include <ostream>
5 
6 #include "absl/strings/str_format.h"
7 #include "quiche/http2/adapter/http2_visitor_interface.h"
8 #include "quiche/common/quiche_data_reader.h"
9 #include "quiche/spdy/core/hpack/hpack_encoder.h"
10 #include "quiche/spdy/core/spdy_protocol.h"
11 
12 namespace http2 {
13 namespace adapter {
14 namespace test {
15 namespace {
16 
17 using ConnectionError = Http2VisitorInterface::ConnectionError;
18 
19 }  // anonymous namespace
20 
TestDataFrameSource(Http2VisitorInterface & visitor,bool has_fin)21 TestDataFrameSource::TestDataFrameSource(Http2VisitorInterface& visitor,
22                                          bool has_fin)
23     : visitor_(visitor), has_fin_(has_fin) {}
24 
AppendPayload(absl::string_view payload)25 void TestDataFrameSource::AppendPayload(absl::string_view payload) {
26   QUICHE_CHECK(!end_data_);
27   if (!payload.empty()) {
28     payload_fragments_.push_back(std::string(payload));
29     current_fragment_ = payload_fragments_.front();
30   }
31 }
32 
EndData()33 void TestDataFrameSource::EndData() { end_data_ = true; }
34 
SelectPayloadLength(size_t max_length)35 std::pair<int64_t, bool> TestDataFrameSource::SelectPayloadLength(
36     size_t max_length) {
37   if (return_error_) {
38     return {DataFrameSource::kError, false};
39   }
40   // The stream is done if there's no more data, or if |max_length| is at least
41   // as large as the remaining data.
42   const bool end_data = end_data_ && (current_fragment_.empty() ||
43                                       (payload_fragments_.size() == 1 &&
44                                        max_length >= current_fragment_.size()));
45   const int64_t length = std::min(max_length, current_fragment_.size());
46   return {length, end_data};
47 }
48 
Send(absl::string_view frame_header,size_t payload_length)49 bool TestDataFrameSource::Send(absl::string_view frame_header,
50                                size_t payload_length) {
51   QUICHE_LOG_IF(DFATAL, payload_length > current_fragment_.size())
52       << "payload_length: " << payload_length
53       << " current_fragment_size: " << current_fragment_.size();
54   const std::string concatenated =
55       absl::StrCat(frame_header, current_fragment_.substr(0, payload_length));
56   const int64_t result = visitor_.OnReadyToSend(concatenated);
57   if (result < 0) {
58     // Write encountered error.
59     visitor_.OnConnectionError(ConnectionError::kSendError);
60     current_fragment_ = {};
61     payload_fragments_.clear();
62     return false;
63   } else if (result == 0) {
64     // Write blocked.
65     return false;
66   } else if (static_cast<size_t>(result) < concatenated.size()) {
67     // Probably need to handle this better within this test class.
68     QUICHE_LOG(DFATAL)
69         << "DATA frame not fully flushed. Connection will be corrupt!";
70     visitor_.OnConnectionError(ConnectionError::kSendError);
71     current_fragment_ = {};
72     payload_fragments_.clear();
73     return false;
74   }
75   if (payload_length > 0) {
76     current_fragment_.remove_prefix(payload_length);
77   }
78   if (current_fragment_.empty() && !payload_fragments_.empty()) {
79     payload_fragments_.erase(payload_fragments_.begin());
80     if (!payload_fragments_.empty()) {
81       current_fragment_ = payload_fragments_.front();
82     }
83   }
84   return true;
85 }
86 
EncodeHeaders(const spdy::Http2HeaderBlock & entries)87 std::string EncodeHeaders(const spdy::Http2HeaderBlock& entries) {
88   spdy::HpackEncoder encoder;
89   encoder.DisableCompression();
90   return encoder.EncodeHeaderBlock(entries);
91 }
92 
TestMetadataSource(const spdy::Http2HeaderBlock & entries)93 TestMetadataSource::TestMetadataSource(const spdy::Http2HeaderBlock& entries)
94     : encoded_entries_(EncodeHeaders(entries)) {
95   remaining_ = encoded_entries_;
96 }
97 
Pack(uint8_t * dest,size_t dest_len)98 std::pair<int64_t, bool> TestMetadataSource::Pack(uint8_t* dest,
99                                                   size_t dest_len) {
100   if (fail_when_packing_) {
101     return {-1, false};
102   }
103   const size_t copied = std::min(dest_len, remaining_.size());
104   std::memcpy(dest, remaining_.data(), copied);
105   remaining_.remove_prefix(copied);
106   return std::make_pair(copied, remaining_.empty());
107 }
108 
109 namespace {
110 
111 using TypeAndOptionalLength =
112     std::pair<spdy::SpdyFrameType, std::optional<size_t>>;
113 
operator <<(std::ostream & os,const std::vector<TypeAndOptionalLength> & types_and_lengths)114 std::ostream& operator<<(
115     std::ostream& os,
116     const std::vector<TypeAndOptionalLength>& types_and_lengths) {
117   for (const auto& type_and_length : types_and_lengths) {
118     os << "(" << spdy::FrameTypeToString(type_and_length.first) << ", "
119        << (type_and_length.second ? absl::StrCat(type_and_length.second.value())
120                                   : "<unspecified>")
121        << ") ";
122   }
123   return os;
124 }
125 
FrameTypeToString(uint8_t frame_type)126 std::string FrameTypeToString(uint8_t frame_type) {
127   if (spdy::IsDefinedFrameType(frame_type)) {
128     return spdy::FrameTypeToString(spdy::ParseFrameType(frame_type));
129   } else {
130     return absl::StrFormat("0x%x", static_cast<int>(frame_type));
131   }
132 }
133 
134 // Custom gMock matcher, used to implement EqualsFrames().
135 class SpdyControlFrameMatcher
136     : public testing::MatcherInterface<absl::string_view> {
137  public:
SpdyControlFrameMatcher(std::vector<TypeAndOptionalLength> types_and_lengths)138   explicit SpdyControlFrameMatcher(
139       std::vector<TypeAndOptionalLength> types_and_lengths)
140       : expected_types_and_lengths_(std::move(types_and_lengths)) {}
141 
MatchAndExplain(absl::string_view s,testing::MatchResultListener * listener) const142   bool MatchAndExplain(absl::string_view s,
143                        testing::MatchResultListener* listener) const override {
144     quiche::QuicheDataReader reader(s.data(), s.size());
145 
146     for (TypeAndOptionalLength expected : expected_types_and_lengths_) {
147       if (!MatchAndExplainOneFrame(expected.first, expected.second, &reader,
148                                    listener)) {
149         return false;
150       }
151     }
152     if (!reader.IsDoneReading()) {
153       *listener << "; " << reader.BytesRemaining() << " bytes left to read!";
154       return false;
155     }
156     return true;
157   }
158 
MatchAndExplainOneFrame(spdy::SpdyFrameType expected_type,std::optional<size_t> expected_length,quiche::QuicheDataReader * reader,testing::MatchResultListener * listener) const159   bool MatchAndExplainOneFrame(spdy::SpdyFrameType expected_type,
160                                std::optional<size_t> expected_length,
161                                quiche::QuicheDataReader* reader,
162                                testing::MatchResultListener* listener) const {
163     uint32_t payload_length;
164     if (!reader->ReadUInt24(&payload_length)) {
165       *listener << "; unable to read length field for expected_type "
166                 << FrameTypeToString(expected_type) << ". data too short!";
167       return false;
168     }
169 
170     if (expected_length && payload_length != expected_length.value()) {
171       *listener << "; actual length: " << payload_length
172                 << " but expected length: " << expected_length.value();
173       return false;
174     }
175 
176     uint8_t raw_type;
177     if (!reader->ReadUInt8(&raw_type)) {
178       *listener << "; unable to read type field for expected_type "
179                 << FrameTypeToString(expected_type) << ". data too short!";
180       return false;
181     }
182 
183     if (raw_type != static_cast<uint8_t>(expected_type)) {
184       *listener << "; actual type: " << FrameTypeToString(raw_type)
185                 << " but expected type: " << FrameTypeToString(expected_type);
186       return false;
187     }
188 
189     // Seek past flags (1B), stream ID (4B), and payload. Reach the next frame.
190     reader->Seek(5 + payload_length);
191     return true;
192   }
193 
DescribeTo(std::ostream * os) const194   void DescribeTo(std::ostream* os) const override {
195     *os << "Data contains frames of types in sequence "
196         << expected_types_and_lengths_;
197   }
198 
DescribeNegationTo(std::ostream * os) const199   void DescribeNegationTo(std::ostream* os) const override {
200     *os << "Data does not contain frames of types in sequence "
201         << expected_types_and_lengths_;
202   }
203 
204  private:
205   const std::vector<TypeAndOptionalLength> expected_types_and_lengths_;
206 };
207 
208 }  // namespace
209 
EqualsFrames(std::vector<std::pair<spdy::SpdyFrameType,std::optional<size_t>>> types_and_lengths)210 testing::Matcher<absl::string_view> EqualsFrames(
211     std::vector<std::pair<spdy::SpdyFrameType, std::optional<size_t>>>
212         types_and_lengths) {
213   return MakeMatcher(new SpdyControlFrameMatcher(std::move(types_and_lengths)));
214 }
215 
EqualsFrames(std::vector<spdy::SpdyFrameType> types)216 testing::Matcher<absl::string_view> EqualsFrames(
217     std::vector<spdy::SpdyFrameType> types) {
218   std::vector<std::pair<spdy::SpdyFrameType, std::optional<size_t>>>
219       types_and_lengths;
220   types_and_lengths.reserve(types.size());
221   for (spdy::SpdyFrameType type : types) {
222     types_and_lengths.push_back({type, std::nullopt});
223   }
224   return MakeMatcher(new SpdyControlFrameMatcher(std::move(types_and_lengths)));
225 }
226 
227 }  // namespace test
228 }  // namespace adapter
229 }  // namespace http2
230