|  | // 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_TEST_TOOLS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_ | 
|  | #define QUICHE_HTTP2_TEST_TOOLS_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_structures.h" | 
|  | #include "quiche/http2/test_tools/frame_parts.h" | 
|  | #include "quiche/http2/test_tools/http2_constants_test_util.h" | 
|  | #include "quiche/http2/test_tools/http2_frame_builder.h" | 
|  | #include "quiche/http2/test_tools/random_decoder_test_base.h" | 
|  | #include "quiche/http2/test_tools/verify_macros.h" | 
|  | #include "quiche/common/platform/api/quiche_export.h" | 
|  | #include "quiche/common/platform/api/quiche_logging.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) { | 
|  | QUICHE_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 { | 
|  | QUICHE_DVLOG(2) << "StartDecodingPayload, db->Remaining=" | 
|  | << db->Remaining(); | 
|  | return payload_decoder_->StartDecodingPayload(mutable_state(), db); | 
|  | } | 
|  |  | 
|  | // Resume decoding the payload. | 
|  | DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) override { | 
|  | QUICHE_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 { | 
|  | HTTP2_VERIFY_FALSE(listener_.IsInProgress()); | 
|  | HTTP2_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 { | 
|  | QUICHE_DVLOG(2) << "VerifyDetectsFrameSizeError validator; status=" | 
|  | << status << "; input.Remaining=" << input.Remaining(); | 
|  | HTTP2_VERIFY_EQ(DecodeStatus::kDecodeError, status); | 
|  | HTTP2_VERIFY_FALSE(listener_.IsInProgress()); | 
|  | HTTP2_VERIFY_EQ(1u, listener_.size()); | 
|  | const FrameParts* frame = listener_.frame(0); | 
|  | HTTP2_VERIFY_EQ(header, frame->GetFrameHeader()); | 
|  | HTTP2_VERIFY_TRUE(frame->GetHasFrameSizeError()); | 
|  | // Verify did not get OnPaddingTooLong, as we should only ever produce | 
|  | // one of these two errors for a single frame. | 
|  | HTTP2_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(); | 
|  | HTTP2_VERIFY_EQ(required_flags, | 
|  | required_flags & KnownFlagsMaskForFrameType(frame_type)); | 
|  | HTTP2_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 { | 
|  | HTTP2_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; | 
|  | } | 
|  | QUICHE_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); | 
|  | HTTP2_VERIFY_SUCCESS( | 
|  | VerifyDetectsFrameSizeError(fb.buffer(), header, nullptr)); | 
|  | validated = true; | 
|  | } | 
|  | HTTP2_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); | 
|  | HTTP2_VERIFY_EQ(0, known_flags & Http2FrameFlag::PADDED); | 
|  | HTTP2_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 QUICHE_NO_EXPORT 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()) { | 
|  | QUICHE_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 { | 
|  | HTTP2_VERIFY_EQ(DecodeStatus::kDecodeError, status); | 
|  | HTTP2_VERIFY_FALSE(listener.IsInProgress()); | 
|  | HTTP2_VERIFY_EQ(1u, listener.size()); | 
|  | const FrameParts* frame = listener.frame(0); | 
|  | HTTP2_VERIFY_EQ(header, frame->GetFrameHeader()); | 
|  | HTTP2_VERIFY_TRUE(frame->GetOptMissingLength()); | 
|  | HTTP2_VERIFY_EQ(expected_missing_length, | 
|  | frame->GetOptMissingLength().value()); | 
|  | // Verify did not get OnFrameSizeError. | 
|  | HTTP2_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()); | 
|  | QUICHE_VLOG(1) << "fb.size=" << fb.size(); | 
|  | // Pick a random length for the payload that is shorter than neccesary. | 
|  | payload_length = Random().Uniform(fb.size()); | 
|  | } | 
|  |  | 
|  | QUICHE_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; | 
|  | QUICHE_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_TEST_TOOLS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_ |