| // 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. | |
| #include "quiche/http2/decoder/http2_frame_decoder.h" | |
| #include "quiche/http2/decoder/decode_status.h" | |
| #include "quiche/http2/hpack/varint/hpack_varint_decoder.h" | |
| #include "quiche/http2/http2_constants.h" | |
| #include "quiche/common/platform/api/quiche_bug_tracker.h" | |
| #include "quiche/common/platform/api/quiche_logging.h" | |
| namespace http2 { | |
| std::ostream& operator<<(std::ostream& out, Http2FrameDecoder::State v) { | |
| switch (v) { | |
| case Http2FrameDecoder::State::kStartDecodingHeader: | |
| return out << "kStartDecodingHeader"; | |
| case Http2FrameDecoder::State::kResumeDecodingHeader: | |
| return out << "kResumeDecodingHeader"; | |
| case Http2FrameDecoder::State::kResumeDecodingPayload: | |
| return out << "kResumeDecodingPayload"; | |
| case Http2FrameDecoder::State::kDiscardPayload: | |
| return out << "kDiscardPayload"; | |
| } | |
| // Since the value doesn't come over the wire, only a programming bug should | |
| // result in reaching this point. | |
| int unknown = static_cast<int>(v); | |
| QUICHE_BUG(http2_bug_155_1) << "Http2FrameDecoder::State " << unknown; | |
| return out << "Http2FrameDecoder::State(" << unknown << ")"; | |
| } | |
| Http2FrameDecoder::Http2FrameDecoder(Http2FrameDecoderListener* listener) | |
| : state_(State::kStartDecodingHeader), | |
| maximum_payload_size_(Http2SettingsInfo::DefaultMaxFrameSize()) { | |
| set_listener(listener); | |
| } | |
| void Http2FrameDecoder::set_listener(Http2FrameDecoderListener* listener) { | |
| if (listener == nullptr) { | |
| listener = &no_op_listener_; | |
| } | |
| frame_decoder_state_.set_listener(listener); | |
| } | |
| Http2FrameDecoderListener* Http2FrameDecoder::listener() const { | |
| return frame_decoder_state_.listener(); | |
| } | |
| DecodeStatus Http2FrameDecoder::DecodeFrame(DecodeBuffer* db) { | |
| QUICHE_DVLOG(2) << "Http2FrameDecoder::DecodeFrame state=" << state_; | |
| switch (state_) { | |
| case State::kStartDecodingHeader: | |
| if (frame_decoder_state_.StartDecodingFrameHeader(db)) { | |
| return StartDecodingPayload(db); | |
| } | |
| state_ = State::kResumeDecodingHeader; | |
| return DecodeStatus::kDecodeInProgress; | |
| case State::kResumeDecodingHeader: | |
| if (frame_decoder_state_.ResumeDecodingFrameHeader(db)) { | |
| return StartDecodingPayload(db); | |
| } | |
| return DecodeStatus::kDecodeInProgress; | |
| case State::kResumeDecodingPayload: | |
| return ResumeDecodingPayload(db); | |
| case State::kDiscardPayload: | |
| return DiscardPayload(db); | |
| } | |
| QUICHE_NOTREACHED(); | |
| return DecodeStatus::kDecodeError; | |
| } | |
| size_t Http2FrameDecoder::remaining_payload() const { | |
| return frame_decoder_state_.remaining_payload(); | |
| } | |
| uint32_t Http2FrameDecoder::remaining_padding() const { | |
| return frame_decoder_state_.remaining_padding(); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingPayload(DecodeBuffer* db) { | |
| const Http2FrameHeader& header = frame_header(); | |
| // TODO(jamessynge): Remove OnFrameHeader once done with supporting | |
| // SpdyFramer's exact states. | |
| if (!listener()->OnFrameHeader(header)) { | |
| QUICHE_DVLOG(2) | |
| << "OnFrameHeader rejected the frame, will discard; header: " << header; | |
| state_ = State::kDiscardPayload; | |
| frame_decoder_state_.InitializeRemainders(); | |
| return DecodeStatus::kDecodeError; | |
| } | |
| if (header.payload_length > maximum_payload_size_) { | |
| QUICHE_DVLOG(2) << "Payload length is greater than allowed: " | |
| << header.payload_length << " > " << maximum_payload_size_ | |
| << "\n header: " << header; | |
| state_ = State::kDiscardPayload; | |
| frame_decoder_state_.InitializeRemainders(); | |
| listener()->OnFrameSizeError(header); | |
| return DecodeStatus::kDecodeError; | |
| } | |
| // The decode buffer can extend across many frames. Make sure that the | |
| // buffer we pass to the start method that is specific to the frame type | |
| // does not exend beyond this frame. | |
| DecodeBufferSubset subset(db, header.payload_length); | |
| DecodeStatus status; | |
| switch (header.type) { | |
| case Http2FrameType::DATA: | |
| status = StartDecodingDataPayload(&subset); | |
| break; | |
| case Http2FrameType::HEADERS: | |
| status = StartDecodingHeadersPayload(&subset); | |
| break; | |
| case Http2FrameType::PRIORITY: | |
| status = StartDecodingPriorityPayload(&subset); | |
| break; | |
| case Http2FrameType::RST_STREAM: | |
| status = StartDecodingRstStreamPayload(&subset); | |
| break; | |
| case Http2FrameType::SETTINGS: | |
| status = StartDecodingSettingsPayload(&subset); | |
| break; | |
| case Http2FrameType::PUSH_PROMISE: | |
| status = StartDecodingPushPromisePayload(&subset); | |
| break; | |
| case Http2FrameType::PING: | |
| status = StartDecodingPingPayload(&subset); | |
| break; | |
| case Http2FrameType::GOAWAY: | |
| status = StartDecodingGoAwayPayload(&subset); | |
| break; | |
| case Http2FrameType::WINDOW_UPDATE: | |
| status = StartDecodingWindowUpdatePayload(&subset); | |
| break; | |
| case Http2FrameType::CONTINUATION: | |
| status = StartDecodingContinuationPayload(&subset); | |
| break; | |
| case Http2FrameType::ALTSVC: | |
| status = StartDecodingAltSvcPayload(&subset); | |
| break; | |
| case Http2FrameType::PRIORITY_UPDATE: | |
| status = StartDecodingPriorityUpdatePayload(&subset); | |
| break; | |
| default: | |
| status = StartDecodingUnknownPayload(&subset); | |
| break; | |
| } | |
| if (status == DecodeStatus::kDecodeDone) { | |
| state_ = State::kStartDecodingHeader; | |
| return status; | |
| } else if (status == DecodeStatus::kDecodeInProgress) { | |
| state_ = State::kResumeDecodingPayload; | |
| return status; | |
| } else { | |
| state_ = State::kDiscardPayload; | |
| return status; | |
| } | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingPayload(DecodeBuffer* db) { | |
| // The decode buffer can extend across many frames. Make sure that the | |
| // buffer we pass to the start method that is specific to the frame type | |
| // does not exend beyond this frame. | |
| size_t remaining = frame_decoder_state_.remaining_total_payload(); | |
| QUICHE_DCHECK_LE(remaining, frame_header().payload_length); | |
| DecodeBufferSubset subset(db, remaining); | |
| DecodeStatus status; | |
| switch (frame_header().type) { | |
| case Http2FrameType::DATA: | |
| status = ResumeDecodingDataPayload(&subset); | |
| break; | |
| case Http2FrameType::HEADERS: | |
| status = ResumeDecodingHeadersPayload(&subset); | |
| break; | |
| case Http2FrameType::PRIORITY: | |
| status = ResumeDecodingPriorityPayload(&subset); | |
| break; | |
| case Http2FrameType::RST_STREAM: | |
| status = ResumeDecodingRstStreamPayload(&subset); | |
| break; | |
| case Http2FrameType::SETTINGS: | |
| status = ResumeDecodingSettingsPayload(&subset); | |
| break; | |
| case Http2FrameType::PUSH_PROMISE: | |
| status = ResumeDecodingPushPromisePayload(&subset); | |
| break; | |
| case Http2FrameType::PING: | |
| status = ResumeDecodingPingPayload(&subset); | |
| break; | |
| case Http2FrameType::GOAWAY: | |
| status = ResumeDecodingGoAwayPayload(&subset); | |
| break; | |
| case Http2FrameType::WINDOW_UPDATE: | |
| status = ResumeDecodingWindowUpdatePayload(&subset); | |
| break; | |
| case Http2FrameType::CONTINUATION: | |
| status = ResumeDecodingContinuationPayload(&subset); | |
| break; | |
| case Http2FrameType::ALTSVC: | |
| status = ResumeDecodingAltSvcPayload(&subset); | |
| break; | |
| case Http2FrameType::PRIORITY_UPDATE: | |
| status = ResumeDecodingPriorityUpdatePayload(&subset); | |
| break; | |
| default: | |
| status = ResumeDecodingUnknownPayload(&subset); | |
| break; | |
| } | |
| if (status == DecodeStatus::kDecodeDone) { | |
| state_ = State::kStartDecodingHeader; | |
| return status; | |
| } else if (status == DecodeStatus::kDecodeInProgress) { | |
| return status; | |
| } else { | |
| state_ = State::kDiscardPayload; | |
| return status; | |
| } | |
| } | |
| // Clear any of the flags in the frame header that aren't set in valid_flags. | |
| void Http2FrameDecoder::RetainFlags(uint8_t valid_flags) { | |
| frame_decoder_state_.RetainFlags(valid_flags); | |
| } | |
| // Clear all of the flags in the frame header; for use with frame types that | |
| // don't define any flags, such as WINDOW_UPDATE. | |
| void Http2FrameDecoder::ClearFlags() { frame_decoder_state_.ClearFlags(); } | |
| DecodeStatus Http2FrameDecoder::StartDecodingAltSvcPayload(DecodeBuffer* db) { | |
| ClearFlags(); | |
| return altsvc_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingAltSvcPayload(DecodeBuffer* db) { | |
| // The frame is not paddable. | |
| QUICHE_DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), | |
| frame_decoder_state_.remaining_payload()); | |
| return altsvc_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingContinuationPayload( | |
| DecodeBuffer* db) { | |
| RetainFlags(Http2FrameFlag::END_HEADERS); | |
| return continuation_payload_decoder_.StartDecodingPayload( | |
| &frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingContinuationPayload( | |
| DecodeBuffer* db) { | |
| // The frame is not paddable. | |
| QUICHE_DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), | |
| frame_decoder_state_.remaining_payload()); | |
| return continuation_payload_decoder_.ResumeDecodingPayload( | |
| &frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingDataPayload(DecodeBuffer* db) { | |
| RetainFlags(Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED); | |
| return data_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingDataPayload(DecodeBuffer* db) { | |
| return data_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingGoAwayPayload(DecodeBuffer* db) { | |
| ClearFlags(); | |
| return goaway_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingGoAwayPayload(DecodeBuffer* db) { | |
| // The frame is not paddable. | |
| QUICHE_DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), | |
| frame_decoder_state_.remaining_payload()); | |
| return goaway_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingHeadersPayload(DecodeBuffer* db) { | |
| RetainFlags(Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS | | |
| Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY); | |
| return headers_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingHeadersPayload(DecodeBuffer* db) { | |
| QUICHE_DCHECK_LE(frame_decoder_state_.remaining_payload_and_padding(), | |
| frame_header().payload_length); | |
| return headers_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingPingPayload(DecodeBuffer* db) { | |
| RetainFlags(Http2FrameFlag::ACK); | |
| return ping_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingPingPayload(DecodeBuffer* db) { | |
| // The frame is not paddable. | |
| QUICHE_DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), | |
| frame_decoder_state_.remaining_payload()); | |
| return ping_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingPriorityPayload(DecodeBuffer* db) { | |
| ClearFlags(); | |
| return priority_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingPriorityPayload( | |
| DecodeBuffer* db) { | |
| // The frame is not paddable. | |
| QUICHE_DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), | |
| frame_decoder_state_.remaining_payload()); | |
| return priority_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingPriorityUpdatePayload( | |
| DecodeBuffer* db) { | |
| ClearFlags(); | |
| return priority_payload_update_decoder_.StartDecodingPayload( | |
| &frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingPriorityUpdatePayload( | |
| DecodeBuffer* db) { | |
| // The frame is not paddable. | |
| QUICHE_DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), | |
| frame_decoder_state_.remaining_payload()); | |
| return priority_payload_update_decoder_.ResumeDecodingPayload( | |
| &frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingPushPromisePayload( | |
| DecodeBuffer* db) { | |
| RetainFlags(Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED); | |
| return push_promise_payload_decoder_.StartDecodingPayload( | |
| &frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingPushPromisePayload( | |
| DecodeBuffer* db) { | |
| QUICHE_DCHECK_LE(frame_decoder_state_.remaining_payload_and_padding(), | |
| frame_header().payload_length); | |
| return push_promise_payload_decoder_.ResumeDecodingPayload( | |
| &frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingRstStreamPayload( | |
| DecodeBuffer* db) { | |
| ClearFlags(); | |
| return rst_stream_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingRstStreamPayload( | |
| DecodeBuffer* db) { | |
| // The frame is not paddable. | |
| QUICHE_DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), | |
| frame_decoder_state_.remaining_payload()); | |
| return rst_stream_payload_decoder_.ResumeDecodingPayload( | |
| &frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingSettingsPayload(DecodeBuffer* db) { | |
| RetainFlags(Http2FrameFlag::ACK); | |
| return settings_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingSettingsPayload( | |
| DecodeBuffer* db) { | |
| // The frame is not paddable. | |
| QUICHE_DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), | |
| frame_decoder_state_.remaining_payload()); | |
| return settings_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingUnknownPayload(DecodeBuffer* db) { | |
| // We don't known what type of frame this is, so we don't know which flags | |
| // are valid, so we don't touch them. | |
| return unknown_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingUnknownPayload(DecodeBuffer* db) { | |
| // We don't known what type of frame this is, so we treat it as not paddable. | |
| QUICHE_DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), | |
| frame_decoder_state_.remaining_payload()); | |
| return unknown_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, | |
| db); | |
| } | |
| DecodeStatus Http2FrameDecoder::StartDecodingWindowUpdatePayload( | |
| DecodeBuffer* db) { | |
| ClearFlags(); | |
| return window_update_payload_decoder_.StartDecodingPayload( | |
| &frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::ResumeDecodingWindowUpdatePayload( | |
| DecodeBuffer* db) { | |
| // The frame is not paddable. | |
| QUICHE_DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), | |
| frame_decoder_state_.remaining_payload()); | |
| return window_update_payload_decoder_.ResumeDecodingPayload( | |
| &frame_decoder_state_, db); | |
| } | |
| DecodeStatus Http2FrameDecoder::DiscardPayload(DecodeBuffer* db) { | |
| QUICHE_DVLOG(2) << "remaining_payload=" | |
| << frame_decoder_state_.remaining_payload_ | |
| << "; remaining_padding=" | |
| << frame_decoder_state_.remaining_padding_; | |
| frame_decoder_state_.remaining_payload_ += | |
| frame_decoder_state_.remaining_padding_; | |
| frame_decoder_state_.remaining_padding_ = 0; | |
| const size_t avail = frame_decoder_state_.AvailablePayload(db); | |
| QUICHE_DVLOG(2) << "avail=" << avail; | |
| if (avail > 0) { | |
| frame_decoder_state_.ConsumePayload(avail); | |
| db->AdvanceCursor(avail); | |
| } | |
| if (frame_decoder_state_.remaining_payload_ == 0) { | |
| state_ = State::kStartDecodingHeader; | |
| return DecodeStatus::kDecodeDone; | |
| } | |
| return DecodeStatus::kDecodeInProgress; | |
| } | |
| } // namespace http2 |