| // Copyright 2018 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 "net/third_party/quiche/src/quic/core/qpack/qpack_instruction_decoder.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" |
| |
| namespace quic { |
| |
| namespace { |
| |
| // Maximum length of header name and header value. This limits the amount of |
| // memory the peer can make the decoder allocate when sending string literals. |
| const size_t kStringLiteralLengthLimit = 1024 * 1024; |
| |
| } // namespace |
| |
| QpackInstructionDecoder::QpackInstructionDecoder(const QpackLanguage* language, |
| Delegate* delegate) |
| : language_(language), |
| delegate_(delegate), |
| s_bit_(false), |
| varint_(0), |
| varint2_(0), |
| is_huffman_encoded_(false), |
| string_length_(0), |
| error_detected_(false), |
| state_(State::kStartInstruction) {} |
| |
| void QpackInstructionDecoder::Decode(QuicStringPiece data) { |
| DCHECK(!data.empty()); |
| DCHECK(!error_detected_); |
| |
| while (true) { |
| size_t bytes_consumed = 0; |
| |
| switch (state_) { |
| case State::kStartInstruction: |
| DoStartInstruction(data); |
| break; |
| case State::kStartField: |
| DoStartField(); |
| break; |
| case State::kReadBit: |
| DoReadBit(data); |
| break; |
| case State::kVarintStart: |
| bytes_consumed = DoVarintStart(data); |
| break; |
| case State::kVarintResume: |
| bytes_consumed = DoVarintResume(data); |
| break; |
| case State::kVarintDone: |
| DoVarintDone(); |
| break; |
| case State::kReadString: |
| bytes_consumed = DoReadString(data); |
| break; |
| case State::kReadStringDone: |
| DoReadStringDone(); |
| break; |
| } |
| |
| if (error_detected_) { |
| return; |
| } |
| |
| DCHECK_LE(bytes_consumed, data.size()); |
| |
| data = QuicStringPiece(data.data() + bytes_consumed, |
| data.size() - bytes_consumed); |
| |
| // Stop processing if no more data but next state would require it. |
| if (data.empty() && (state_ != State::kStartField) && |
| (state_ != State::kVarintDone) && (state_ != State::kReadStringDone)) { |
| return; |
| } |
| } |
| } |
| |
| bool QpackInstructionDecoder::AtInstructionBoundary() const { |
| return state_ == State::kStartInstruction; |
| } |
| |
| void QpackInstructionDecoder::DoStartInstruction(QuicStringPiece data) { |
| DCHECK(!data.empty()); |
| |
| instruction_ = LookupOpcode(data[0]); |
| field_ = instruction_->fields.begin(); |
| |
| state_ = State::kStartField; |
| } |
| |
| void QpackInstructionDecoder::DoStartField() { |
| if (field_ == instruction_->fields.end()) { |
| // Completed decoding this instruction. |
| |
| if (!delegate_->OnInstructionDecoded(instruction_)) { |
| error_detected_ = true; |
| return; |
| } |
| |
| state_ = State::kStartInstruction; |
| return; |
| } |
| |
| switch (field_->type) { |
| case QpackInstructionFieldType::kSbit: |
| case QpackInstructionFieldType::kName: |
| case QpackInstructionFieldType::kValue: |
| state_ = State::kReadBit; |
| return; |
| case QpackInstructionFieldType::kVarint: |
| case QpackInstructionFieldType::kVarint2: |
| state_ = State::kVarintStart; |
| return; |
| } |
| } |
| |
| void QpackInstructionDecoder::DoReadBit(QuicStringPiece data) { |
| DCHECK(!data.empty()); |
| |
| switch (field_->type) { |
| case QpackInstructionFieldType::kSbit: { |
| const uint8_t bitmask = field_->param; |
| s_bit_ = (data[0] & bitmask) == bitmask; |
| |
| ++field_; |
| state_ = State::kStartField; |
| |
| return; |
| } |
| case QpackInstructionFieldType::kName: |
| case QpackInstructionFieldType::kValue: { |
| const uint8_t prefix_length = field_->param; |
| DCHECK_GE(7, prefix_length); |
| const uint8_t bitmask = 1 << prefix_length; |
| is_huffman_encoded_ = (data[0] & bitmask) == bitmask; |
| |
| state_ = State::kVarintStart; |
| |
| return; |
| } |
| default: |
| DCHECK(false); |
| } |
| } |
| |
| size_t QpackInstructionDecoder::DoVarintStart(QuicStringPiece data) { |
| DCHECK(!data.empty()); |
| DCHECK(field_->type == QpackInstructionFieldType::kVarint || |
| field_->type == QpackInstructionFieldType::kVarint2 || |
| field_->type == QpackInstructionFieldType::kName || |
| field_->type == QpackInstructionFieldType::kValue); |
| |
| http2::DecodeBuffer buffer(data.data() + 1, data.size() - 1); |
| http2::DecodeStatus status = |
| varint_decoder_.Start(data[0], field_->param, &buffer); |
| |
| size_t bytes_consumed = 1 + buffer.Offset(); |
| switch (status) { |
| case http2::DecodeStatus::kDecodeDone: |
| state_ = State::kVarintDone; |
| return bytes_consumed; |
| case http2::DecodeStatus::kDecodeInProgress: |
| state_ = State::kVarintResume; |
| return bytes_consumed; |
| case http2::DecodeStatus::kDecodeError: |
| OnError("Encoded integer too large."); |
| return bytes_consumed; |
| default: |
| QUIC_BUG << "Unknown decode status " << status; |
| return bytes_consumed; |
| } |
| } |
| |
| size_t QpackInstructionDecoder::DoVarintResume(QuicStringPiece data) { |
| DCHECK(!data.empty()); |
| DCHECK(field_->type == QpackInstructionFieldType::kVarint || |
| field_->type == QpackInstructionFieldType::kVarint2 || |
| field_->type == QpackInstructionFieldType::kName || |
| field_->type == QpackInstructionFieldType::kValue); |
| |
| http2::DecodeBuffer buffer(data); |
| http2::DecodeStatus status = varint_decoder_.Resume(&buffer); |
| |
| size_t bytes_consumed = buffer.Offset(); |
| switch (status) { |
| case http2::DecodeStatus::kDecodeDone: |
| state_ = State::kVarintDone; |
| return bytes_consumed; |
| case http2::DecodeStatus::kDecodeInProgress: |
| DCHECK_EQ(bytes_consumed, data.size()); |
| DCHECK(buffer.Empty()); |
| return bytes_consumed; |
| case http2::DecodeStatus::kDecodeError: |
| OnError("Encoded integer too large."); |
| return bytes_consumed; |
| default: |
| QUIC_BUG << "Unknown decode status " << status; |
| return bytes_consumed; |
| } |
| } |
| |
| void QpackInstructionDecoder::DoVarintDone() { |
| DCHECK(field_->type == QpackInstructionFieldType::kVarint || |
| field_->type == QpackInstructionFieldType::kVarint2 || |
| field_->type == QpackInstructionFieldType::kName || |
| field_->type == QpackInstructionFieldType::kValue); |
| |
| if (field_->type == QpackInstructionFieldType::kVarint) { |
| varint_ = varint_decoder_.value(); |
| |
| ++field_; |
| state_ = State::kStartField; |
| return; |
| } |
| |
| if (field_->type == QpackInstructionFieldType::kVarint2) { |
| varint2_ = varint_decoder_.value(); |
| |
| ++field_; |
| state_ = State::kStartField; |
| return; |
| } |
| |
| string_length_ = varint_decoder_.value(); |
| if (string_length_ > kStringLiteralLengthLimit) { |
| OnError("String literal too long."); |
| return; |
| } |
| |
| std::string* const string = |
| (field_->type == QpackInstructionFieldType::kName) ? &name_ : &value_; |
| string->clear(); |
| |
| if (string_length_ == 0) { |
| ++field_; |
| state_ = State::kStartField; |
| return; |
| } |
| |
| string->reserve(string_length_); |
| |
| state_ = State::kReadString; |
| } |
| |
| size_t QpackInstructionDecoder::DoReadString(QuicStringPiece data) { |
| DCHECK(!data.empty()); |
| DCHECK(field_->type == QpackInstructionFieldType::kName || |
| field_->type == QpackInstructionFieldType::kValue); |
| |
| std::string* const string = |
| (field_->type == QpackInstructionFieldType::kName) ? &name_ : &value_; |
| DCHECK_LT(string->size(), string_length_); |
| |
| size_t bytes_consumed = |
| std::min(string_length_ - string->size(), data.size()); |
| string->append(data.data(), bytes_consumed); |
| |
| DCHECK_LE(string->size(), string_length_); |
| if (string->size() == string_length_) { |
| state_ = State::kReadStringDone; |
| } |
| return bytes_consumed; |
| } |
| |
| void QpackInstructionDecoder::DoReadStringDone() { |
| DCHECK(field_->type == QpackInstructionFieldType::kName || |
| field_->type == QpackInstructionFieldType::kValue); |
| |
| std::string* const string = |
| (field_->type == QpackInstructionFieldType::kName) ? &name_ : &value_; |
| DCHECK_EQ(string->size(), string_length_); |
| |
| if (is_huffman_encoded_) { |
| huffman_decoder_.Reset(); |
| // HpackHuffmanDecoder::Decode() cannot perform in-place decoding. |
| std::string decoded_value; |
| huffman_decoder_.Decode(*string, &decoded_value); |
| if (!huffman_decoder_.InputProperlyTerminated()) { |
| OnError("Error in Huffman-encoded string."); |
| return; |
| } |
| *string = std::move(decoded_value); |
| } |
| |
| ++field_; |
| state_ = State::kStartField; |
| } |
| |
| const QpackInstruction* QpackInstructionDecoder::LookupOpcode( |
| uint8_t byte) const { |
| for (const auto* instruction : *language_) { |
| if ((byte & instruction->opcode.mask) == instruction->opcode.value) { |
| return instruction; |
| } |
| } |
| // |language_| should be defined such that instruction opcodes cover every |
| // possible input. |
| DCHECK(false); |
| return nullptr; |
| } |
| |
| void QpackInstructionDecoder::OnError(QuicStringPiece error_message) { |
| DCHECK(!error_detected_); |
| |
| error_detected_ = true; |
| delegate_->OnError(error_message); |
| } |
| |
| } // namespace quic |