summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/payload_decoder_base_test_util.h
blob: 5193688fc6ff9ccf4eb2a0d467d2c9e3ba1b1082 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
// Copyright 2016 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.

#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_

// Base class for testing concrete payload decoder classes.

#include <stddef.h>

#include <string>

#include "absl/strings/string_view.h"
#include "quiche/http2/decoder/decode_buffer.h"
#include "quiche/http2/decoder/decode_status.h"
#include "quiche/http2/decoder/frame_decoder_state.h"
#include "quiche/http2/decoder/http2_frame_decoder_listener.h"
#include "quiche/http2/http2_constants.h"
#include "quiche/http2/http2_constants_test_util.h"
#include "quiche/http2/http2_structures.h"
#include "quiche/http2/platform/api/http2_logging.h"
#include "quiche/http2/test_tools/frame_parts.h"
#include "quiche/http2/tools/http2_frame_builder.h"
#include "quiche/http2/tools/random_decoder_test.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/platform/api/quiche_test_helpers.h"

namespace http2 {
namespace test {

// Base class for tests of payload decoders. Below this there is a templated
// sub-class that adds a bunch of type specific features.
class QUICHE_NO_EXPORT PayloadDecoderBaseTest : public RandomDecoderTest {
 protected:
  PayloadDecoderBaseTest();

  // Virtual functions to be implemented by the test classes for the individual
  // payload decoders...

  // Start decoding the payload.
  virtual DecodeStatus StartDecodingPayload(DecodeBuffer* db) = 0;

  // Resume decoding the payload.
  virtual DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) = 0;

  // In support of ensuring that we're really accessing and updating the
  // decoder, prepare the decoder by, for example, overwriting the decoder.
  virtual void PreparePayloadDecoder() = 0;

  // Get the listener to be inserted into the FrameDecoderState, ready for
  // listening (e.g. reset if it is a FramePartsCollector).
  virtual Http2FrameDecoderListener* PrepareListener() = 0;

  // Record a frame header for use on each call to StartDecoding.
  void set_frame_header(const Http2FrameHeader& header) {
    EXPECT_EQ(0, InvalidFlagMaskForFrameType(header.type) & header.flags);
    if (!frame_header_is_set_ || frame_header_ != header) {
      HTTP2_VLOG(2) << "set_frame_header: " << frame_header_;
    }
    frame_header_ = header;
    frame_header_is_set_ = true;
  }

  FrameDecoderState* mutable_state() { return frame_decoder_state_.get(); }

  // Randomize the payload decoder, sets the payload decoder's frame_header_,
  // then start decoding the payload. Called by RandomDecoderTest. This method
  // is final so that we can always perform certain actions when
  // RandomDecoderTest starts the decoding of a payload, such as randomizing the
  // the payload decoder, injecting the frame header and counting fast decoding
  // cases. Sub-classes must implement StartDecodingPayload to perform their
  // initial decoding of a frame's payload.
  DecodeStatus StartDecoding(DecodeBuffer* db) final;

  // Called by RandomDecoderTest. This method is final so that we can always
  // perform certain actions when RandomDecoderTest calls it, such as counting
  // slow decode cases. Sub-classes must implement ResumeDecodingPayload to
  // continue decoding the frame's payload, which must not all be in one buffer.
  DecodeStatus ResumeDecoding(DecodeBuffer* db) final;

  // Given the specified payload (without the common frame header), decode
  // it with several partitionings of the payload.
  ::testing::AssertionResult DecodePayloadAndValidateSeveralWays(
      absl::string_view payload,
      Validator validator);

  // TODO(jamessynge): Add helper method for verifying these are both non-zero,
  // and call the new method from tests that expect successful decoding.
  void ResetDecodeSpeedCounters() {
    fast_decode_count_ = 0;
    slow_decode_count_ = 0;
  }

  // Count of payloads that are full decoded by StartDecodingPayload, or that
  // an error was detected by StartDecodingPayload.
  size_t fast_decode_count_ = 0;

  // Count of payloads that require calling ResumeDecodingPayload in order to
  // decode them completely (or to detect an error during decoding).
  size_t slow_decode_count_ = 0;

 private:
  bool frame_header_is_set_ = false;
  Http2FrameHeader frame_header_;
  std::unique_ptr<FrameDecoderState> frame_decoder_state_;
};

// Base class for payload decoders of type Decoder, with corresponding test
// peer of type DecoderPeer, and using class Listener as the implementation
// of Http2FrameDecoderListenerInterface to be used during decoding.
// Typically Listener is a sub-class of FramePartsCollector.
// SupportedFrameType is set to false only for UnknownPayloadDecoder.
template <class Decoder, class DecoderPeer, class Listener,
          bool SupportedFrameType = true>
class QUICHE_NO_EXPORT AbstractPayloadDecoderTest
    : public PayloadDecoderBaseTest {
 protected:
  // An ApproveSize function returns true to approve decoding the specified
  // size of payload, else false to skip that size. Typically used for negative
  // tests; for example, decoding a SETTINGS frame at all sizes except for
  // multiples of 6.
  typedef std::function<bool(size_t size)> ApproveSize;

  AbstractPayloadDecoderTest() {}

  // These tests are in setup rather than the constructor for two reasons:
  // 1) Constructors are not allowed to fail, so gUnit documents that EXPECT_*
  //    and ASSERT_* are not allowed in constructors, and should instead be in
  //    SetUp if they are needed before the body of the test is executed.
  // 2) To allow the sub-class constructor to make any desired modifications to
  //    the DecoderPeer before these tests are executed; in particular,
  //    UnknownPayloadDecoderPeer has not got a fixed frame type, but it is
  //    instead set during the test's constructor.
  void SetUp() override {
    PayloadDecoderBaseTest::SetUp();

    // Confirm that DecoderPeer et al returns sensible values. Using auto as the
    // variable type so that no (narrowing) conversions take place that hide
    // problems; i.e. if someone changes KnownFlagsMaskForFrameType so that it
    // doesn't return a uint8, and has bits above the low-order 8 bits set, this
    // bit of paranoia should detect the problem before we get too far.
    auto frame_type = DecoderPeer::FrameType();
    if (SupportedFrameType) {
      EXPECT_TRUE(IsSupportedHttp2FrameType(frame_type)) << frame_type;
    } else {
      EXPECT_FALSE(IsSupportedHttp2FrameType(frame_type)) << frame_type;
    }

    auto known_flags = KnownFlagsMaskForFrameType(frame_type);
    EXPECT_EQ(known_flags, known_flags & 0xff);

    auto flags_to_avoid = DecoderPeer::FlagsAffectingPayloadDecoding();
    EXPECT_EQ(flags_to_avoid, flags_to_avoid & known_flags);
  }

  void PreparePayloadDecoder() override {
    payload_decoder_ = std::make_unique<Decoder>();
  }

  Http2FrameDecoderListener* PrepareListener() override {
    listener_.Reset();
    return &listener_;
  }

  // Returns random flags, but only those valid for the frame type, yet not
  // those that the DecoderPeer says will affect the decoding of the payload
  // (e.g. the PRIORTY flag on a HEADERS frame or PADDED on DATA frames).
  uint8_t RandFlags() {
    return Random().Rand8() &
           KnownFlagsMaskForFrameType(DecoderPeer::FrameType()) &
           ~DecoderPeer::FlagsAffectingPayloadDecoding();
  }

  // Start decoding the payload.
  DecodeStatus StartDecodingPayload(DecodeBuffer* db) override {
    HTTP2_DVLOG(2) << "StartDecodingPayload, db->Remaining=" << db->Remaining();
    return payload_decoder_->StartDecodingPayload(mutable_state(), db);
  }

  // Resume decoding the payload.
  DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) override {
    HTTP2_DVLOG(2) << "ResumeDecodingPayload, db->Remaining="
                   << db->Remaining();
    return payload_decoder_->ResumeDecodingPayload(mutable_state(), db);
  }

  // Decode one frame's payload and confirm that the listener recorded the
  // expected FrameParts instance, and only FrameParts instance. The payload
  // will be decoded several times with different partitionings of the payload,
  // and after each the validator will be called.
  AssertionResult DecodePayloadAndValidateSeveralWays(
      absl::string_view payload,
      const FrameParts& expected) {
    auto validator = [&expected, this]() -> AssertionResult {
      VERIFY_FALSE(listener_.IsInProgress());
      VERIFY_EQ(1u, listener_.size());
      return expected.VerifyEquals(*listener_.frame(0));
    };
    return PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
        payload, ValidateDoneAndEmpty(validator));
  }

  // Decode one frame's payload, expecting that the final status will be
  // kDecodeError, and that OnFrameSizeError will have been called on the
  // listener. The payload will be decoded several times with different
  // partitionings of the payload. The type WrappedValidator is either
  // RandomDecoderTest::Validator, RandomDecoderTest::NoArgValidator or
  // std::nullptr_t (not extra validation).
  template <typename WrappedValidator>
  ::testing::AssertionResult VerifyDetectsFrameSizeError(
      absl::string_view payload,
      const Http2FrameHeader& header,
      WrappedValidator wrapped_validator) {
    set_frame_header(header);
    // If wrapped_validator is not a RandomDecoderTest::Validator, make it so.
    Validator validator = ToValidator(wrapped_validator);
    // And wrap that validator in another which will check that we've reached
    // the expected state of kDecodeError with OnFrameSizeError having been
    // called by the payload decoder.
    validator = [header, validator, this](
                    const DecodeBuffer& input,
                    DecodeStatus status) -> ::testing::AssertionResult {
      HTTP2_DVLOG(2) << "VerifyDetectsFrameSizeError validator; status="
                     << status << "; input.Remaining=" << input.Remaining();
      VERIFY_EQ(DecodeStatus::kDecodeError, status);
      VERIFY_FALSE(listener_.IsInProgress());
      VERIFY_EQ(1u, listener_.size());
      const FrameParts* frame = listener_.frame(0);
      VERIFY_EQ(header, frame->GetFrameHeader());
      VERIFY_TRUE(frame->GetHasFrameSizeError());
      // Verify did not get OnPaddingTooLong, as we should only ever produce
      // one of these two errors for a single frame.
      VERIFY_FALSE(frame->GetOptMissingLength());
      return validator(input, status);
    };
    return PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
        payload, validator);
  }

  // Confirm that we get OnFrameSizeError when trying to decode unpadded_payload
  // at all sizes from zero to unpadded_payload.size(), except those sizes not
  // approved by approve_size.
  // If total_pad_length is greater than zero, then that amount of padding
  // is added to the payload (including the Pad Length field).
  // The flags will be required_flags, PADDED if total_pad_length > 0, and some
  // randomly selected flag bits not excluded by FlagsAffectingPayloadDecoding.
  ::testing::AssertionResult VerifyDetectsMultipleFrameSizeErrors(
      uint8_t required_flags,
      absl::string_view unpadded_payload,
      ApproveSize approve_size,
      int total_pad_length) {
    // required_flags should come from those that are defined for the frame
    // type AND are those that affect the decoding of the payload (otherwise,
    // the flag shouldn't be required).
    Http2FrameType frame_type = DecoderPeer::FrameType();
    VERIFY_EQ(required_flags,
              required_flags & KnownFlagsMaskForFrameType(frame_type));
    VERIFY_EQ(required_flags,
              required_flags & DecoderPeer::FlagsAffectingPayloadDecoding());

    if (0 !=
        (Http2FrameFlag::PADDED & KnownFlagsMaskForFrameType(frame_type))) {
      // Frame type supports padding.
      if (total_pad_length == 0) {
        required_flags &= ~Http2FrameFlag::PADDED;
      } else {
        required_flags |= Http2FrameFlag::PADDED;
      }
    } else {
      VERIFY_EQ(0, total_pad_length);
    }

    bool validated = false;
    for (size_t real_payload_size = 0;
         real_payload_size <= unpadded_payload.size(); ++real_payload_size) {
      if (approve_size != nullptr && !approve_size(real_payload_size)) {
        continue;
      }
      HTTP2_VLOG(1) << "real_payload_size=" << real_payload_size;
      uint8_t flags = required_flags | RandFlags();
      Http2FrameBuilder fb;
      if (total_pad_length > 0) {
        // total_pad_length_ includes the size of the Pad Length field, and thus
        // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
        fb.AppendUInt8(total_pad_length - 1);
      }
      // Append a subset of the unpadded_payload, which the decoder should
      // determine is not a valid amount.
      fb.Append(unpadded_payload.substr(0, real_payload_size));
      if (total_pad_length > 0) {
        fb.AppendZeroes(total_pad_length - 1);
      }
      // We choose a random stream id because the payload decoders aren't
      // checking stream ids.
      uint32_t stream_id = RandStreamId();
      Http2FrameHeader header(fb.size(), frame_type, flags, stream_id);
      VERIFY_SUCCESS(VerifyDetectsFrameSizeError(fb.buffer(), header, nullptr));
      validated = true;
    }
    VERIFY_TRUE(validated);
    return ::testing::AssertionSuccess();
  }

  // As above, but for frames without padding.
  ::testing::AssertionResult VerifyDetectsFrameSizeError(
      uint8_t required_flags,
      absl::string_view unpadded_payload,
      const ApproveSize& approve_size) {
    Http2FrameType frame_type = DecoderPeer::FrameType();
    uint8_t known_flags = KnownFlagsMaskForFrameType(frame_type);
    VERIFY_EQ(0, known_flags & Http2FrameFlag::PADDED);
    VERIFY_EQ(0, required_flags & Http2FrameFlag::PADDED);
    return VerifyDetectsMultipleFrameSizeErrors(
        required_flags, unpadded_payload, approve_size, 0);
  }

  Listener listener_;
  std::unique_ptr<Decoder> payload_decoder_;
};

// A base class for tests parameterized by the total number of bytes of
// padding, including the Pad Length field (i.e. a total_pad_length of 0
// means unpadded as there is then no room for the Pad Length field).
// The frame type must support padding.
template <class Decoder, class DecoderPeer, class Listener>
class AbstractPaddablePayloadDecoderTest
    : public AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener>,
      public ::testing::WithParamInterface<int> {
  typedef AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener> Base;

 protected:
  using Base::listener_;
  using Base::Random;
  using Base::RandStreamId;
  using Base::set_frame_header;
  typedef typename Base::Validator Validator;

  AbstractPaddablePayloadDecoderTest() : total_pad_length_(GetParam()) {
    HTTP2_LOG(INFO) << "total_pad_length_ = " << total_pad_length_;
  }

  // Note that total_pad_length_ includes the size of the Pad Length field,
  // and thus ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
  bool IsPadded() const { return total_pad_length_ > 0; }

  // Value of the Pad Length field. Only call if IsPadded.
  size_t pad_length() const {
    EXPECT_TRUE(IsPadded());
    return total_pad_length_ - 1;
  }

  // Clear the frame builder and add the Pad Length field if appropriate.
  void Reset() {
    frame_builder_ = Http2FrameBuilder();
    if (IsPadded()) {
      frame_builder_.AppendUInt8(pad_length());
    }
  }

  void MaybeAppendTrailingPadding() {
    if (IsPadded()) {
      frame_builder_.AppendZeroes(pad_length());
    }
  }

  uint8_t RandFlags() {
    uint8_t flags = Base::RandFlags();
    if (IsPadded()) {
      flags |= Http2FrameFlag::PADDED;
    } else {
      flags &= ~Http2FrameFlag::PADDED;
    }
    return flags;
  }

  // Verify that we get OnPaddingTooLong when decoding payload, and that the
  // amount of missing padding is as specified. header.IsPadded must be true,
  // and the payload must be empty or the PadLength field must be too large.
  ::testing::AssertionResult VerifyDetectsPaddingTooLong(
      absl::string_view payload,
      const Http2FrameHeader& header,
      size_t expected_missing_length) {
    set_frame_header(header);
    auto& listener = listener_;
    Validator validator =
        [header, expected_missing_length, &listener](
            const DecodeBuffer&,
            DecodeStatus status) -> ::testing::AssertionResult {
      VERIFY_EQ(DecodeStatus::kDecodeError, status);
      VERIFY_FALSE(listener.IsInProgress());
      VERIFY_EQ(1u, listener.size());
      const FrameParts* frame = listener.frame(0);
      VERIFY_EQ(header, frame->GetFrameHeader());
      VERIFY_TRUE(frame->GetOptMissingLength());
      VERIFY_EQ(expected_missing_length, frame->GetOptMissingLength().value());
      // Verify did not get OnFrameSizeError.
      VERIFY_FALSE(frame->GetHasFrameSizeError());
      return ::testing::AssertionSuccess();
    };
    return PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
        payload, validator);
  }

  // Verifies that we get OnPaddingTooLong for a padded frame payload whose
  // (randomly selected) payload length is less than total_pad_length_.
  // Flags will be selected at random, except PADDED will be set and
  // flags_to_avoid will not be set. The stream id is selected at random.
  ::testing::AssertionResult VerifyDetectsPaddingTooLong() {
    uint8_t flags = RandFlags() | Http2FrameFlag::PADDED;

    // Create an all padding payload for total_pad_length_.
    int payload_length = 0;
    Http2FrameBuilder fb;
    if (IsPadded()) {
      fb.AppendUInt8(pad_length());
      fb.AppendZeroes(pad_length());
      HTTP2_VLOG(1) << "fb.size=" << fb.size();
      // Pick a random length for the payload that is shorter than neccesary.
      payload_length = Random().Uniform(fb.size());
    }

    HTTP2_VLOG(1) << "payload_length=" << payload_length;
    std::string payload = fb.buffer().substr(0, payload_length);

    // The missing length is the amount we cut off the end, unless
    // payload_length is zero, in which case the decoder knows only that 1
    // byte, the Pad Length field, is missing.
    size_t missing_length =
        payload_length == 0 ? 1 : fb.size() - payload_length;
    HTTP2_VLOG(1) << "missing_length=" << missing_length;

    const Http2FrameHeader header(payload_length, DecoderPeer::FrameType(),
                                  flags, RandStreamId());
    return VerifyDetectsPaddingTooLong(payload, header, missing_length);
  }

  // total_pad_length_ includes the size of the Pad Length field, and thus
  // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
  const size_t total_pad_length_;
  Http2FrameBuilder frame_builder_;
};

}  // namespace test
}  // namespace http2

#endif  // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_