blob: 459a4758e46b4570b25b5b99bf4cc4587c9ee094 [file] [log] [blame]
// 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/hpack/decoder/hpack_decoder_state.h"
#include <utility>
#include "quiche/http2/http2_constants.h"
#include "quiche/common/platform/api/quiche_logging.h"
namespace http2 {
namespace {
std::string ExtractString(HpackDecoderStringBuffer* string_buffer) {
if (string_buffer->IsBuffered()) {
return string_buffer->ReleaseString();
} else {
auto result = std::string(string_buffer->str());
string_buffer->Reset();
return result;
}
}
} // namespace
HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener)
: listener_(listener),
final_header_table_size_(Http2SettingsInfo::DefaultHeaderTableSize()),
lowest_header_table_size_(final_header_table_size_),
require_dynamic_table_size_update_(false),
allow_dynamic_table_size_update_(true),
saw_dynamic_table_size_update_(false),
error_(HpackDecodingError::kOk) {
QUICHE_CHECK(listener_);
}
HpackDecoderState::~HpackDecoderState() = default;
void HpackDecoderState::ApplyHeaderTableSizeSetting(
uint32_t header_table_size) {
QUICHE_DVLOG(2) << "HpackDecoderState::ApplyHeaderTableSizeSetting("
<< header_table_size << ")";
QUICHE_DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
if (header_table_size < lowest_header_table_size_) {
lowest_header_table_size_ = header_table_size;
}
final_header_table_size_ = header_table_size;
QUICHE_DVLOG(2) << "low water mark: " << lowest_header_table_size_;
QUICHE_DVLOG(2) << "final limit: " << final_header_table_size_;
}
// Called to notify this object that we're starting to decode an HPACK block
// (e.g. a HEADERS or PUSH_PROMISE frame's header has been decoded).
void HpackDecoderState::OnHeaderBlockStart() {
QUICHE_DVLOG(2) << "HpackDecoderState::OnHeaderBlockStart";
// This instance can't be reused after an error has been detected, as we must
// assume that the encoder and decoder compression states are no longer
// synchronized.
QUICHE_DCHECK(error_ == HpackDecodingError::kOk)
<< HpackDecodingErrorToString(error_);
QUICHE_DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
allow_dynamic_table_size_update_ = true;
saw_dynamic_table_size_update_ = false;
// If the peer has acknowledged a HEADER_TABLE_SIZE smaller than that which
// its HPACK encoder has been using, then the next HPACK block it sends MUST
// start with a Dynamic Table Size Update entry that is at least as low as
// lowest_header_table_size_. That may be followed by another as great as
// final_header_table_size_, if those are different.
require_dynamic_table_size_update_ =
(lowest_header_table_size_ <
decoder_tables_.current_header_table_size() ||
final_header_table_size_ < decoder_tables_.header_table_size_limit());
QUICHE_DVLOG(2) << "HpackDecoderState::OnHeaderListStart "
<< "require_dynamic_table_size_update_="
<< require_dynamic_table_size_update_;
listener_->OnHeaderListStart();
}
void HpackDecoderState::OnIndexedHeader(size_t index) {
QUICHE_DVLOG(2) << "HpackDecoderState::OnIndexedHeader: " << index;
if (error_ != HpackDecodingError::kOk) {
return;
}
if (require_dynamic_table_size_update_) {
ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate);
return;
}
allow_dynamic_table_size_update_ = false;
const HpackStringPair* entry = decoder_tables_.Lookup(index);
if (entry != nullptr) {
listener_->OnHeader(entry->name, entry->value);
} else {
ReportError(HpackDecodingError::kInvalidIndex);
}
}
void HpackDecoderState::OnNameIndexAndLiteralValue(
HpackEntryType entry_type, size_t name_index,
HpackDecoderStringBuffer* value_buffer) {
QUICHE_DVLOG(2) << "HpackDecoderState::OnNameIndexAndLiteralValue "
<< entry_type << ", " << name_index << ", "
<< value_buffer->str();
if (error_ != HpackDecodingError::kOk) {
return;
}
if (require_dynamic_table_size_update_) {
ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate);
return;
}
allow_dynamic_table_size_update_ = false;
const HpackStringPair* entry = decoder_tables_.Lookup(name_index);
if (entry != nullptr) {
std::string value(ExtractString(value_buffer));
listener_->OnHeader(entry->name, value);
if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
decoder_tables_.Insert(entry->name, std::move(value));
}
} else {
ReportError(HpackDecodingError::kInvalidNameIndex);
}
}
void HpackDecoderState::OnLiteralNameAndValue(
HpackEntryType entry_type, HpackDecoderStringBuffer* name_buffer,
HpackDecoderStringBuffer* value_buffer) {
QUICHE_DVLOG(2) << "HpackDecoderState::OnLiteralNameAndValue " << entry_type
<< ", " << name_buffer->str() << ", " << value_buffer->str();
if (error_ != HpackDecodingError::kOk) {
return;
}
if (require_dynamic_table_size_update_) {
ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate);
return;
}
allow_dynamic_table_size_update_ = false;
std::string name(ExtractString(name_buffer));
std::string value(ExtractString(value_buffer));
listener_->OnHeader(name, value);
if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
decoder_tables_.Insert(std::move(name), std::move(value));
}
}
void HpackDecoderState::OnDynamicTableSizeUpdate(size_t size_limit) {
QUICHE_DVLOG(2) << "HpackDecoderState::OnDynamicTableSizeUpdate "
<< size_limit << ", required="
<< (require_dynamic_table_size_update_ ? "true" : "false")
<< ", allowed="
<< (allow_dynamic_table_size_update_ ? "true" : "false");
if (error_ != HpackDecodingError::kOk) {
return;
}
QUICHE_DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
if (!allow_dynamic_table_size_update_) {
// At most two dynamic table size updates allowed at the start, and not
// after a header.
ReportError(HpackDecodingError::kDynamicTableSizeUpdateNotAllowed);
return;
}
if (require_dynamic_table_size_update_) {
// The new size must not be greater than the low water mark.
if (size_limit > lowest_header_table_size_) {
ReportError(HpackDecodingError::
kInitialDynamicTableSizeUpdateIsAboveLowWaterMark);
return;
}
require_dynamic_table_size_update_ = false;
} else if (size_limit > final_header_table_size_) {
// The new size must not be greater than the final max header table size
// that the peer acknowledged.
ReportError(
HpackDecodingError::kDynamicTableSizeUpdateIsAboveAcknowledgedSetting);
return;
}
decoder_tables_.DynamicTableSizeUpdate(size_limit);
if (saw_dynamic_table_size_update_) {
allow_dynamic_table_size_update_ = false;
} else {
saw_dynamic_table_size_update_ = true;
}
// We no longer need to keep an eye out for a lower header table size.
lowest_header_table_size_ = final_header_table_size_;
}
void HpackDecoderState::OnHpackDecodeError(HpackDecodingError error) {
QUICHE_DVLOG(2) << "HpackDecoderState::OnHpackDecodeError "
<< HpackDecodingErrorToString(error);
if (error_ == HpackDecodingError::kOk) {
ReportError(error);
}
}
void HpackDecoderState::OnHeaderBlockEnd() {
QUICHE_DVLOG(2) << "HpackDecoderState::OnHeaderBlockEnd";
if (error_ != HpackDecodingError::kOk) {
return;
}
if (require_dynamic_table_size_update_) {
// Apparently the HPACK block was empty, but we needed it to contain at
// least 1 dynamic table size update.
ReportError(HpackDecodingError::kMissingDynamicTableSizeUpdate);
} else {
listener_->OnHeaderListEnd();
}
}
void HpackDecoderState::ReportError(HpackDecodingError error) {
QUICHE_DVLOG(2) << "HpackDecoderState::ReportError is new="
<< (error_ == HpackDecodingError::kOk ? "true" : "false")
<< ", error: " << HpackDecodingErrorToString(error);
if (error_ == HpackDecodingError::kOk) {
listener_->OnHeaderErrorDetected(HpackDecodingErrorToString(error));
error_ = error;
}
}
} // namespace http2