blob: c06e5e136dc5c072ecb55a10137886d961ce01c6 [file] [log] [blame]
// Copyright 2014 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/spdy/hpack/hpack_decoder.h"
#include <utility>
#include "base/logging.h"
#include "net/spdy/hpack/hpack_constants.h"
#include "net/spdy/hpack/hpack_entry.h"
#include "net/spdy/platform/api/spdy_estimate_memory_usage.h"
#include "net/spdy/spdy_flags.h"
namespace net {
using std::string;
HpackDecoder::HpackDecoder()
: handler_(nullptr),
total_header_bytes_(0),
total_parsed_bytes_(0),
header_block_started_(false),
size_updates_seen_(0),
size_updates_allowed_(true),
incremental_decode_(false) {}
HpackDecoder::~HpackDecoder() {}
void HpackDecoder::ApplyHeaderTableSizeSetting(size_t size_setting) {
header_table_.SetSettingsHeaderTableSize(size_setting);
}
void HpackDecoder::HandleControlFrameHeadersStart(
SpdyHeadersHandlerInterface* handler) {
handler_ = handler;
total_header_bytes_ = 0;
}
bool HpackDecoder::HandleControlFrameHeadersData(const char* headers_data,
size_t headers_data_length) {
if (!header_block_started_) {
decoded_block_.clear();
header_block_started_ = true;
size_updates_allowed_ = true;
size_updates_seen_ = 0;
if (handler_ != nullptr) {
handler_->OnHeaderBlockStart();
}
}
size_t new_size = headers_block_buffer_.size() + headers_data_length;
if (max_decode_buffer_size_bytes_ > 0 &&
new_size > max_decode_buffer_size_bytes_) {
DVLOG(1) << "max_decode_buffer_size_bytes_ < new_size: "
<< max_decode_buffer_size_bytes_ << " < " << new_size;
return false;
}
headers_block_buffer_.insert(headers_block_buffer_.end(), headers_data,
headers_data + headers_data_length);
// Parse as many whole HPACK entries in the buffer as possible,
// and then remove the parsed data from the buffer.
HpackInputStream input_stream(headers_block_buffer_);
while (input_stream.HasMoreData()) {
if (!DecodeNextOpcodeWrapper(&input_stream)) {
if (input_stream.NeedMoreData()) {
break;
}
DVLOG(1) << "!DecodeNextOpcodeWrapper";
return false;
}
}
uint32_t parsed_bytes = input_stream.ParsedBytes();
DCHECK_GE(headers_block_buffer_.size(), parsed_bytes);
headers_block_buffer_.erase(0, parsed_bytes);
total_parsed_bytes_ += parsed_bytes;
return true;
}
bool HpackDecoder::HandleControlFrameHeadersComplete(size_t* compressed_len) {
if (compressed_len != nullptr) {
*compressed_len = total_parsed_bytes_;
}
// Data in headers_block_buffer_ should have been parsed by
// HandleControlFrameHeadersData and removed.
if (headers_block_buffer_.size() > 0) {
DVLOG(1) << "headers_block_buffer_.size() should be zero, but is "
<< headers_block_buffer_.size();
return false;
}
if (handler_ != nullptr) {
if (FLAGS_chromium_http2_flag_log_compressed_size) {
handler_->OnHeaderBlockEnd(total_header_bytes_, total_parsed_bytes_);
} else {
handler_->OnHeaderBlockEnd(total_header_bytes_);
}
}
headers_block_buffer_.clear();
total_parsed_bytes_ = 0;
header_block_started_ = false;
handler_ = nullptr;
return true;
}
const SpdyHeaderBlock& HpackDecoder::decoded_block() const {
return decoded_block_;
}
void HpackDecoder::SetHeaderTableDebugVisitor(
std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
header_table_.set_debug_visitor(std::move(visitor));
}
void HpackDecoder::set_max_decode_buffer_size_bytes(
size_t max_decode_buffer_size_bytes) {
max_decode_buffer_size_bytes_ = max_decode_buffer_size_bytes;
}
size_t HpackDecoder::EstimateMemoryUsage() const {
return SpdyEstimateMemoryUsage(header_table_) +
SpdyEstimateMemoryUsage(headers_block_buffer_) +
SpdyEstimateMemoryUsage(decoded_block_) +
SpdyEstimateMemoryUsage(key_buffer_) +
SpdyEstimateMemoryUsage(value_buffer_);
}
bool HpackDecoder::HandleHeaderRepresentation(SpdyStringPiece name,
SpdyStringPiece value) {
size_updates_allowed_ = false;
total_header_bytes_ += name.size() + value.size();
if (handler_ == nullptr) {
decoded_block_.AppendValueOrAddHeader(name, value);
} else {
DCHECK(decoded_block_.empty());
handler_->OnHeader(name, value);
}
return true;
}
bool HpackDecoder::DecodeNextOpcodeWrapper(HpackInputStream* input_stream) {
if (DecodeNextOpcode(input_stream)) {
// Decoding next opcode succeeds. Mark total bytes parsed successfully.
input_stream->MarkCurrentPosition();
return true;
}
return false;
}
bool HpackDecoder::DecodeNextOpcode(HpackInputStream* input_stream) {
// Implements 7.1: Indexed Header Field Representation.
if (input_stream->MatchPrefixAndConsume(kIndexedOpcode)) {
return DecodeNextIndexedHeader(input_stream);
}
// Implements 7.2.1: Literal Header Field with Incremental Indexing.
if (input_stream->MatchPrefixAndConsume(kLiteralIncrementalIndexOpcode)) {
return DecodeNextLiteralHeader(input_stream, true);
}
// Implements 7.2.2: Literal Header Field without Indexing.
if (input_stream->MatchPrefixAndConsume(kLiteralNoIndexOpcode)) {
return DecodeNextLiteralHeader(input_stream, false);
}
// Implements 7.2.3: Literal Header Field never Indexed.
// TODO(jgraettinger): Preserve the never-indexed bit.
if (input_stream->MatchPrefixAndConsume(kLiteralNeverIndexOpcode)) {
return DecodeNextLiteralHeader(input_stream, false);
}
// Implements 7.3: Header Table Size Update.
if (input_stream->MatchPrefixAndConsume(kHeaderTableSizeUpdateOpcode)) {
// Header table size updates cannot appear mid-block.
return DecodeNextHeaderTableSizeUpdate(input_stream);
}
// Unrecognized opcode.
return false;
}
bool HpackDecoder::DecodeNextHeaderTableSizeUpdate(
HpackInputStream* input_stream) {
uint32_t size = 0;
if (!input_stream->DecodeNextUint32(&size)) {
return false;
}
if (!size_updates_allowed_) {
DVLOG(1) << "Size updates not allowed after header entries.";
return false;
}
++size_updates_seen_;
if (size_updates_seen_ > 2) {
DVLOG(1) << "Too many size updates at the start of the block.";
return false;
}
if (size > header_table_.settings_size_bound()) {
DVLOG(1) << "Size (" << size << ") exceeds SETTINGS limit ("
<< header_table_.settings_size_bound() << ")";
return false;
}
header_table_.SetMaxSize(size);
return true;
}
bool HpackDecoder::DecodeNextIndexedHeader(HpackInputStream* input_stream) {
uint32_t index = 0;
if (!input_stream->DecodeNextUint32(&index)) {
return false;
}
const HpackEntry* entry = header_table_.GetByIndex(index);
if (entry == NULL) {
DVLOG(1) << "Index " << index << " is not valid.";
return false;
}
return HandleHeaderRepresentation(entry->name(), entry->value());
}
bool HpackDecoder::DecodeNextLiteralHeader(HpackInputStream* input_stream,
bool should_index) {
SpdyStringPiece name;
if (!DecodeNextName(input_stream, &name)) {
return false;
}
SpdyStringPiece value;
if (!DecodeNextStringLiteral(input_stream, false, &value)) {
return false;
}
if (!HandleHeaderRepresentation(name, value)) {
return false;
}
if (!should_index) {
return true;
}
ignore_result(header_table_.TryAddEntry(name, value));
return true;
}
bool HpackDecoder::DecodeNextName(HpackInputStream* input_stream,
SpdyStringPiece* next_name) {
uint32_t index_or_zero = 0;
if (!input_stream->DecodeNextUint32(&index_or_zero)) {
DVLOG(1) << "Failed to decode the next uint.";
return false;
}
if (index_or_zero == 0) {
return DecodeNextStringLiteral(input_stream, true, next_name);
}
const HpackEntry* entry = header_table_.GetByIndex(index_or_zero);
if (entry == NULL) {
DVLOG(1) << "index " << index_or_zero << " is not valid.";
return false;
}
if (entry->IsStatic()) {
*next_name = entry->name();
} else {
// |entry| could be evicted as part of this insertion. Preemptively copy.
key_buffer_.assign(entry->name().data(), entry->name().size());
*next_name = key_buffer_;
}
return true;
}
bool HpackDecoder::DecodeNextStringLiteral(HpackInputStream* input_stream,
bool is_key,
SpdyStringPiece* output) {
if (input_stream->MatchPrefixAndConsume(kStringLiteralHuffmanEncoded)) {
string* buffer = is_key ? &key_buffer_ : &value_buffer_;
bool result = input_stream->DecodeNextHuffmanString(buffer);
*output = SpdyStringPiece(*buffer);
return result;
} else if (input_stream->MatchPrefixAndConsume(
kStringLiteralIdentityEncoded)) {
return input_stream->DecodeNextIdentityString(output);
} else {
DVLOG(1) << "String literal is neither Huffman nor identity encoded!";
return false;
}
}
} // namespace net