1 #include "quiche/http2/adapter/nghttp2_test_utils.h"
2
3 #include "quiche/http2/adapter/nghttp2_util.h"
4 #include "quiche/common/quiche_endian.h"
5
6 namespace http2 {
7 namespace adapter {
8 namespace test {
9
10 namespace {
11
12 // Custom gMock matcher, used to implement HasFrameHeader().
13 class FrameHeaderMatcher {
14 public:
FrameHeaderMatcher(int32_t streamid,uint8_t type,const testing::Matcher<int> flags)15 FrameHeaderMatcher(int32_t streamid, uint8_t type,
16 const testing::Matcher<int> flags)
17 : stream_id_(streamid), type_(type), flags_(flags) {}
18
Match(const nghttp2_frame_hd & frame,testing::MatchResultListener * listener) const19 bool Match(const nghttp2_frame_hd& frame,
20 testing::MatchResultListener* listener) const {
21 bool matched = true;
22 if (stream_id_ != frame.stream_id) {
23 *listener << "; expected stream " << stream_id_ << ", saw "
24 << frame.stream_id;
25 matched = false;
26 }
27 if (type_ != frame.type) {
28 *listener << "; expected frame type " << type_ << ", saw "
29 << static_cast<int>(frame.type);
30 matched = false;
31 }
32 if (!flags_.MatchAndExplain(frame.flags, listener)) {
33 matched = false;
34 }
35 return matched;
36 }
37
DescribeTo(std::ostream * os) const38 void DescribeTo(std::ostream* os) const {
39 *os << "contains a frame header with stream " << stream_id_ << ", type "
40 << type_ << ", ";
41 flags_.DescribeTo(os);
42 }
43
DescribeNegationTo(std::ostream * os) const44 void DescribeNegationTo(std::ostream* os) const {
45 *os << "does not contain a frame header with stream " << stream_id_
46 << ", type " << type_ << ", ";
47 flags_.DescribeNegationTo(os);
48 }
49
50 private:
51 const int32_t stream_id_;
52 const int type_;
53 const testing::Matcher<int> flags_;
54 };
55
56 class PointerToFrameHeaderMatcher
57 : public FrameHeaderMatcher,
58 public testing::MatcherInterface<const nghttp2_frame_hd*> {
59 public:
PointerToFrameHeaderMatcher(int32_t streamid,uint8_t type,const testing::Matcher<int> flags)60 PointerToFrameHeaderMatcher(int32_t streamid, uint8_t type,
61 const testing::Matcher<int> flags)
62 : FrameHeaderMatcher(streamid, type, flags) {}
63
MatchAndExplain(const nghttp2_frame_hd * frame,testing::MatchResultListener * listener) const64 bool MatchAndExplain(const nghttp2_frame_hd* frame,
65 testing::MatchResultListener* listener) const override {
66 return FrameHeaderMatcher::Match(*frame, listener);
67 }
68
DescribeTo(std::ostream * os) const69 void DescribeTo(std::ostream* os) const override {
70 FrameHeaderMatcher::DescribeTo(os);
71 }
72
DescribeNegationTo(std::ostream * os) const73 void DescribeNegationTo(std::ostream* os) const override {
74 FrameHeaderMatcher::DescribeNegationTo(os);
75 }
76 };
77
78 class ReferenceToFrameHeaderMatcher
79 : public FrameHeaderMatcher,
80 public testing::MatcherInterface<const nghttp2_frame_hd&> {
81 public:
ReferenceToFrameHeaderMatcher(int32_t streamid,uint8_t type,const testing::Matcher<int> flags)82 ReferenceToFrameHeaderMatcher(int32_t streamid, uint8_t type,
83 const testing::Matcher<int> flags)
84 : FrameHeaderMatcher(streamid, type, flags) {}
85
MatchAndExplain(const nghttp2_frame_hd & frame,testing::MatchResultListener * listener) const86 bool MatchAndExplain(const nghttp2_frame_hd& frame,
87 testing::MatchResultListener* listener) const override {
88 return FrameHeaderMatcher::Match(frame, listener);
89 }
90
DescribeTo(std::ostream * os) const91 void DescribeTo(std::ostream* os) const override {
92 FrameHeaderMatcher::DescribeTo(os);
93 }
94
DescribeNegationTo(std::ostream * os) const95 void DescribeNegationTo(std::ostream* os) const override {
96 FrameHeaderMatcher::DescribeNegationTo(os);
97 }
98 };
99
100 class DataMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
101 public:
DataMatcher(const testing::Matcher<uint32_t> stream_id,const testing::Matcher<size_t> length,const testing::Matcher<int> flags,const testing::Matcher<size_t> padding)102 DataMatcher(const testing::Matcher<uint32_t> stream_id,
103 const testing::Matcher<size_t> length,
104 const testing::Matcher<int> flags,
105 const testing::Matcher<size_t> padding)
106 : stream_id_(stream_id),
107 length_(length),
108 flags_(flags),
109 padding_(padding) {}
110
MatchAndExplain(const nghttp2_frame * frame,testing::MatchResultListener * listener) const111 bool MatchAndExplain(const nghttp2_frame* frame,
112 testing::MatchResultListener* listener) const override {
113 if (frame->hd.type != NGHTTP2_DATA) {
114 *listener << "; expected DATA frame, saw frame of type "
115 << static_cast<int>(frame->hd.type);
116 return false;
117 }
118 bool matched = true;
119 if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) {
120 matched = false;
121 }
122 if (!length_.MatchAndExplain(frame->hd.length, listener)) {
123 matched = false;
124 }
125 if (!flags_.MatchAndExplain(frame->hd.flags, listener)) {
126 matched = false;
127 }
128 if (!padding_.MatchAndExplain(frame->data.padlen, listener)) {
129 matched = false;
130 }
131 return matched;
132 }
133
DescribeTo(std::ostream * os) const134 void DescribeTo(std::ostream* os) const override {
135 *os << "contains a DATA frame, ";
136 stream_id_.DescribeTo(os);
137 length_.DescribeTo(os);
138 flags_.DescribeTo(os);
139 }
140
DescribeNegationTo(std::ostream * os) const141 void DescribeNegationTo(std::ostream* os) const override {
142 *os << "does not contain a DATA frame, ";
143 stream_id_.DescribeNegationTo(os);
144 length_.DescribeNegationTo(os);
145 flags_.DescribeNegationTo(os);
146 }
147
148 private:
149 const testing::Matcher<uint32_t> stream_id_;
150 const testing::Matcher<size_t> length_;
151 const testing::Matcher<int> flags_;
152 const testing::Matcher<size_t> padding_;
153 };
154
155 class HeadersMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
156 public:
HeadersMatcher(const testing::Matcher<uint32_t> stream_id,const testing::Matcher<int> flags,const testing::Matcher<int> category)157 HeadersMatcher(const testing::Matcher<uint32_t> stream_id,
158 const testing::Matcher<int> flags,
159 const testing::Matcher<int> category)
160 : stream_id_(stream_id), flags_(flags), category_(category) {}
161
MatchAndExplain(const nghttp2_frame * frame,testing::MatchResultListener * listener) const162 bool MatchAndExplain(const nghttp2_frame* frame,
163 testing::MatchResultListener* listener) const override {
164 if (frame->hd.type != NGHTTP2_HEADERS) {
165 *listener << "; expected HEADERS frame, saw frame of type "
166 << static_cast<int>(frame->hd.type);
167 return false;
168 }
169 bool matched = true;
170 if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) {
171 matched = false;
172 }
173 if (!flags_.MatchAndExplain(frame->hd.flags, listener)) {
174 matched = false;
175 }
176 if (!category_.MatchAndExplain(frame->headers.cat, listener)) {
177 matched = false;
178 }
179 return matched;
180 }
181
DescribeTo(std::ostream * os) const182 void DescribeTo(std::ostream* os) const override {
183 *os << "contains a HEADERS frame, ";
184 stream_id_.DescribeTo(os);
185 flags_.DescribeTo(os);
186 category_.DescribeTo(os);
187 }
188
DescribeNegationTo(std::ostream * os) const189 void DescribeNegationTo(std::ostream* os) const override {
190 *os << "does not contain a HEADERS frame, ";
191 stream_id_.DescribeNegationTo(os);
192 flags_.DescribeNegationTo(os);
193 category_.DescribeNegationTo(os);
194 }
195
196 private:
197 const testing::Matcher<uint32_t> stream_id_;
198 const testing::Matcher<int> flags_;
199 const testing::Matcher<int> category_;
200 };
201
202 class RstStreamMatcher
203 : public testing::MatcherInterface<const nghttp2_frame*> {
204 public:
RstStreamMatcher(const testing::Matcher<uint32_t> stream_id,const testing::Matcher<uint32_t> error_code)205 RstStreamMatcher(const testing::Matcher<uint32_t> stream_id,
206 const testing::Matcher<uint32_t> error_code)
207 : stream_id_(stream_id), error_code_(error_code) {}
208
MatchAndExplain(const nghttp2_frame * frame,testing::MatchResultListener * listener) const209 bool MatchAndExplain(const nghttp2_frame* frame,
210 testing::MatchResultListener* listener) const override {
211 if (frame->hd.type != NGHTTP2_RST_STREAM) {
212 *listener << "; expected RST_STREAM frame, saw frame of type "
213 << static_cast<int>(frame->hd.type);
214 return false;
215 }
216 bool matched = true;
217 if (!stream_id_.MatchAndExplain(frame->hd.stream_id, listener)) {
218 matched = false;
219 }
220 if (!error_code_.MatchAndExplain(frame->rst_stream.error_code, listener)) {
221 matched = false;
222 }
223 return matched;
224 }
225
DescribeTo(std::ostream * os) const226 void DescribeTo(std::ostream* os) const override {
227 *os << "contains a RST_STREAM frame, ";
228 stream_id_.DescribeTo(os);
229 error_code_.DescribeTo(os);
230 }
231
DescribeNegationTo(std::ostream * os) const232 void DescribeNegationTo(std::ostream* os) const override {
233 *os << "does not contain a RST_STREAM frame, ";
234 stream_id_.DescribeNegationTo(os);
235 error_code_.DescribeNegationTo(os);
236 }
237
238 private:
239 const testing::Matcher<uint32_t> stream_id_;
240 const testing::Matcher<uint32_t> error_code_;
241 };
242
243 class SettingsMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
244 public:
SettingsMatcher(const testing::Matcher<std::vector<Http2Setting>> values)245 SettingsMatcher(const testing::Matcher<std::vector<Http2Setting>> values)
246 : values_(values) {}
247
MatchAndExplain(const nghttp2_frame * frame,testing::MatchResultListener * listener) const248 bool MatchAndExplain(const nghttp2_frame* frame,
249 testing::MatchResultListener* listener) const override {
250 if (frame->hd.type != NGHTTP2_SETTINGS) {
251 *listener << "; expected SETTINGS frame, saw frame of type "
252 << static_cast<int>(frame->hd.type);
253 return false;
254 }
255 std::vector<Http2Setting> settings;
256 settings.reserve(frame->settings.niv);
257 for (size_t i = 0; i < frame->settings.niv; ++i) {
258 const auto& p = frame->settings.iv[i];
259 settings.push_back({static_cast<uint16_t>(p.settings_id), p.value});
260 }
261 return values_.MatchAndExplain(settings, listener);
262 }
263
DescribeTo(std::ostream * os) const264 void DescribeTo(std::ostream* os) const override {
265 *os << "contains a SETTINGS frame, ";
266 values_.DescribeTo(os);
267 }
268
DescribeNegationTo(std::ostream * os) const269 void DescribeNegationTo(std::ostream* os) const override {
270 *os << "does not contain a SETTINGS frame, ";
271 values_.DescribeNegationTo(os);
272 }
273
274 private:
275 const testing::Matcher<std::vector<Http2Setting>> values_;
276 };
277
278 class PingMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
279 public:
PingMatcher(const testing::Matcher<uint64_t> id,bool is_ack)280 PingMatcher(const testing::Matcher<uint64_t> id, bool is_ack)
281 : id_(id), is_ack_(is_ack) {}
282
MatchAndExplain(const nghttp2_frame * frame,testing::MatchResultListener * listener) const283 bool MatchAndExplain(const nghttp2_frame* frame,
284 testing::MatchResultListener* listener) const override {
285 if (frame->hd.type != NGHTTP2_PING) {
286 *listener << "; expected PING frame, saw frame of type "
287 << static_cast<int>(frame->hd.type);
288 return false;
289 }
290 bool matched = true;
291 bool frame_ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
292 if (is_ack_ != frame_ack) {
293 *listener << "; expected is_ack=" << is_ack_ << ", saw " << frame_ack;
294 matched = false;
295 }
296 uint64_t data;
297 std::memcpy(&data, frame->ping.opaque_data, sizeof(data));
298 data = quiche::QuicheEndian::HostToNet64(data);
299 if (!id_.MatchAndExplain(data, listener)) {
300 matched = false;
301 }
302 return matched;
303 }
304
DescribeTo(std::ostream * os) const305 void DescribeTo(std::ostream* os) const override {
306 *os << "contains a PING frame, ";
307 id_.DescribeTo(os);
308 }
309
DescribeNegationTo(std::ostream * os) const310 void DescribeNegationTo(std::ostream* os) const override {
311 *os << "does not contain a PING frame, ";
312 id_.DescribeNegationTo(os);
313 }
314
315 private:
316 const testing::Matcher<uint64_t> id_;
317 const bool is_ack_;
318 };
319
320 class GoAwayMatcher : public testing::MatcherInterface<const nghttp2_frame*> {
321 public:
GoAwayMatcher(const testing::Matcher<uint32_t> last_stream_id,const testing::Matcher<uint32_t> error_code,const testing::Matcher<absl::string_view> opaque_data)322 GoAwayMatcher(const testing::Matcher<uint32_t> last_stream_id,
323 const testing::Matcher<uint32_t> error_code,
324 const testing::Matcher<absl::string_view> opaque_data)
325 : last_stream_id_(last_stream_id),
326 error_code_(error_code),
327 opaque_data_(opaque_data) {}
328
MatchAndExplain(const nghttp2_frame * frame,testing::MatchResultListener * listener) const329 bool MatchAndExplain(const nghttp2_frame* frame,
330 testing::MatchResultListener* listener) const override {
331 if (frame->hd.type != NGHTTP2_GOAWAY) {
332 *listener << "; expected GOAWAY frame, saw frame of type "
333 << static_cast<int>(frame->hd.type);
334 return false;
335 }
336 bool matched = true;
337 if (!last_stream_id_.MatchAndExplain(frame->goaway.last_stream_id,
338 listener)) {
339 matched = false;
340 }
341 if (!error_code_.MatchAndExplain(frame->goaway.error_code, listener)) {
342 matched = false;
343 }
344 auto opaque_data =
345 ToStringView(frame->goaway.opaque_data, frame->goaway.opaque_data_len);
346 if (!opaque_data_.MatchAndExplain(opaque_data, listener)) {
347 matched = false;
348 }
349 return matched;
350 }
351
DescribeTo(std::ostream * os) const352 void DescribeTo(std::ostream* os) const override {
353 *os << "contains a GOAWAY frame, ";
354 last_stream_id_.DescribeTo(os);
355 error_code_.DescribeTo(os);
356 opaque_data_.DescribeTo(os);
357 }
358
DescribeNegationTo(std::ostream * os) const359 void DescribeNegationTo(std::ostream* os) const override {
360 *os << "does not contain a GOAWAY frame, ";
361 last_stream_id_.DescribeNegationTo(os);
362 error_code_.DescribeNegationTo(os);
363 opaque_data_.DescribeNegationTo(os);
364 }
365
366 private:
367 const testing::Matcher<uint32_t> last_stream_id_;
368 const testing::Matcher<uint32_t> error_code_;
369 const testing::Matcher<absl::string_view> opaque_data_;
370 };
371
372 class WindowUpdateMatcher
373 : public testing::MatcherInterface<const nghttp2_frame*> {
374 public:
WindowUpdateMatcher(const testing::Matcher<uint32_t> delta)375 WindowUpdateMatcher(const testing::Matcher<uint32_t> delta) : delta_(delta) {}
376
MatchAndExplain(const nghttp2_frame * frame,testing::MatchResultListener * listener) const377 bool MatchAndExplain(const nghttp2_frame* frame,
378 testing::MatchResultListener* listener) const override {
379 if (frame->hd.type != NGHTTP2_WINDOW_UPDATE) {
380 *listener << "; expected WINDOW_UPDATE frame, saw frame of type "
381 << static_cast<int>(frame->hd.type);
382 return false;
383 }
384 return delta_.MatchAndExplain(frame->window_update.window_size_increment,
385 listener);
386 }
387
DescribeTo(std::ostream * os) const388 void DescribeTo(std::ostream* os) const override {
389 *os << "contains a WINDOW_UPDATE frame, ";
390 delta_.DescribeTo(os);
391 }
392
DescribeNegationTo(std::ostream * os) const393 void DescribeNegationTo(std::ostream* os) const override {
394 *os << "does not contain a WINDOW_UPDATE frame, ";
395 delta_.DescribeNegationTo(os);
396 }
397
398 private:
399 const testing::Matcher<uint32_t> delta_;
400 };
401
402 } // namespace
403
HasFrameHeader(uint32_t streamid,uint8_t type,const testing::Matcher<int> flags)404 testing::Matcher<const nghttp2_frame_hd*> HasFrameHeader(
405 uint32_t streamid, uint8_t type, const testing::Matcher<int> flags) {
406 return MakeMatcher(new PointerToFrameHeaderMatcher(streamid, type, flags));
407 }
408
HasFrameHeaderRef(uint32_t streamid,uint8_t type,const testing::Matcher<int> flags)409 testing::Matcher<const nghttp2_frame_hd&> HasFrameHeaderRef(
410 uint32_t streamid, uint8_t type, const testing::Matcher<int> flags) {
411 return MakeMatcher(new ReferenceToFrameHeaderMatcher(streamid, type, flags));
412 }
413
IsData(const testing::Matcher<uint32_t> stream_id,const testing::Matcher<size_t> length,const testing::Matcher<int> flags,const testing::Matcher<size_t> padding)414 testing::Matcher<const nghttp2_frame*> IsData(
415 const testing::Matcher<uint32_t> stream_id,
416 const testing::Matcher<size_t> length, const testing::Matcher<int> flags,
417 const testing::Matcher<size_t> padding) {
418 return MakeMatcher(new DataMatcher(stream_id, length, flags, padding));
419 }
420
IsHeaders(const testing::Matcher<uint32_t> stream_id,const testing::Matcher<int> flags,const testing::Matcher<int> category)421 testing::Matcher<const nghttp2_frame*> IsHeaders(
422 const testing::Matcher<uint32_t> stream_id,
423 const testing::Matcher<int> flags, const testing::Matcher<int> category) {
424 return MakeMatcher(new HeadersMatcher(stream_id, flags, category));
425 }
426
IsRstStream(const testing::Matcher<uint32_t> stream_id,const testing::Matcher<uint32_t> error_code)427 testing::Matcher<const nghttp2_frame*> IsRstStream(
428 const testing::Matcher<uint32_t> stream_id,
429 const testing::Matcher<uint32_t> error_code) {
430 return MakeMatcher(new RstStreamMatcher(stream_id, error_code));
431 }
432
IsSettings(const testing::Matcher<std::vector<Http2Setting>> values)433 testing::Matcher<const nghttp2_frame*> IsSettings(
434 const testing::Matcher<std::vector<Http2Setting>> values) {
435 return MakeMatcher(new SettingsMatcher(values));
436 }
437
IsPing(const testing::Matcher<uint64_t> id)438 testing::Matcher<const nghttp2_frame*> IsPing(
439 const testing::Matcher<uint64_t> id) {
440 return MakeMatcher(new PingMatcher(id, false));
441 }
442
IsPingAck(const testing::Matcher<uint64_t> id)443 testing::Matcher<const nghttp2_frame*> IsPingAck(
444 const testing::Matcher<uint64_t> id) {
445 return MakeMatcher(new PingMatcher(id, true));
446 }
447
IsGoAway(const testing::Matcher<uint32_t> last_stream_id,const testing::Matcher<uint32_t> error_code,const testing::Matcher<absl::string_view> opaque_data)448 testing::Matcher<const nghttp2_frame*> IsGoAway(
449 const testing::Matcher<uint32_t> last_stream_id,
450 const testing::Matcher<uint32_t> error_code,
451 const testing::Matcher<absl::string_view> opaque_data) {
452 return MakeMatcher(
453 new GoAwayMatcher(last_stream_id, error_code, opaque_data));
454 }
455
IsWindowUpdate(const testing::Matcher<uint32_t> delta)456 testing::Matcher<const nghttp2_frame*> IsWindowUpdate(
457 const testing::Matcher<uint32_t> delta) {
458 return MakeMatcher(new WindowUpdateMatcher(delta));
459 }
460
461 } // namespace test
462 } // namespace adapter
463 } // namespace http2
464