blob: 23ef25ab38dfe9422f199dcbad4189f182d27b38 [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_entry_decoder.h"
#include <stddef.h>
#include <cstdint>
#include "absl/base/macros.h"
#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_flag_utils.h"
#include "quiche/common/platform/api/quiche_logging.h"
namespace http2 {
namespace {
// Converts calls from HpackStringDecoder when decoding a header name into the
// appropriate HpackEntryDecoderListener::OnName* calls.
class NameDecoderListener {
public:
explicit NameDecoderListener(HpackEntryDecoderListener* listener)
: listener_(listener) {}
bool OnStringStart(bool huffman_encoded, size_t len) {
listener_->OnNameStart(huffman_encoded, len);
return true;
}
void OnStringData(const char* data, size_t len) {
listener_->OnNameData(data, len);
}
void OnStringEnd() { listener_->OnNameEnd(); }
private:
HpackEntryDecoderListener* listener_;
};
// Converts calls from HpackStringDecoder when decoding a header value into
// the appropriate HpackEntryDecoderListener::OnValue* calls.
class ValueDecoderListener {
public:
explicit ValueDecoderListener(HpackEntryDecoderListener* listener)
: listener_(listener) {}
bool OnStringStart(bool huffman_encoded, size_t len) {
listener_->OnValueStart(huffman_encoded, len);
return true;
}
void OnStringData(const char* data, size_t len) {
listener_->OnValueData(data, len);
}
void OnStringEnd() { listener_->OnValueEnd(); }
private:
HpackEntryDecoderListener* listener_;
};
} // namespace
DecodeStatus HpackEntryDecoder::Start(DecodeBuffer* db,
HpackEntryDecoderListener* listener) {
QUICHE_DCHECK(db != nullptr);
QUICHE_DCHECK(listener != nullptr);
QUICHE_DCHECK(db->HasData());
DecodeStatus status = entry_type_decoder_.Start(db);
switch (status) {
case DecodeStatus::kDecodeDone:
// The type of the entry and its varint fit into the current decode
// buffer.
if (entry_type_decoder_.entry_type() == HpackEntryType::kIndexedHeader) {
// The entry consists solely of the entry type and varint.
// This is by far the most common case in practice.
listener->OnIndexedHeader(entry_type_decoder_.varint());
return DecodeStatus::kDecodeDone;
}
state_ = EntryDecoderState::kDecodedType;
return Resume(db, listener);
case DecodeStatus::kDecodeInProgress:
// Hit the end of the decode buffer before fully decoding
// the entry type and varint.
QUICHE_DCHECK_EQ(0u, db->Remaining());
state_ = EntryDecoderState::kResumeDecodingType;
return status;
case DecodeStatus::kDecodeError:
QUICHE_CODE_COUNT_N(decompress_failure_3, 11, 23);
error_ = HpackDecodingError::kIndexVarintError;
// The varint must have been invalid (too long).
return status;
}
QUICHE_BUG(http2_bug_63_1) << "Unreachable";
return DecodeStatus::kDecodeError;
}
DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db,
HpackEntryDecoderListener* listener) {
QUICHE_DCHECK(db != nullptr);
QUICHE_DCHECK(listener != nullptr);
DecodeStatus status;
do {
switch (state_) {
case EntryDecoderState::kResumeDecodingType:
// entry_type_decoder_ returned kDecodeInProgress when last called.
QUICHE_DVLOG(1) << "kResumeDecodingType: db->Remaining="
<< db->Remaining();
status = entry_type_decoder_.Resume(db);
if (status == DecodeStatus::kDecodeError) {
QUICHE_CODE_COUNT_N(decompress_failure_3, 12, 23);
error_ = HpackDecodingError::kIndexVarintError;
}
if (status != DecodeStatus::kDecodeDone) {
return status;
}
state_ = EntryDecoderState::kDecodedType;
ABSL_FALLTHROUGH_INTENDED;
case EntryDecoderState::kDecodedType:
// entry_type_decoder_ returned kDecodeDone, now need to decide how
// to proceed.
QUICHE_DVLOG(1) << "kDecodedType: db->Remaining=" << db->Remaining();
if (DispatchOnType(listener)) {
// All done.
return DecodeStatus::kDecodeDone;
}
continue;
case EntryDecoderState::kStartDecodingName:
QUICHE_DVLOG(1) << "kStartDecodingName: db->Remaining="
<< db->Remaining();
{
NameDecoderListener ncb(listener);
status = string_decoder_.Start(db, &ncb);
}
if (status != DecodeStatus::kDecodeDone) {
// On the assumption that the status is kDecodeInProgress, set
// state_ accordingly; unnecessary if status is kDecodeError, but
// that will only happen if the varint encoding the name's length
// is too long.
state_ = EntryDecoderState::kResumeDecodingName;
if (status == DecodeStatus::kDecodeError) {
QUICHE_CODE_COUNT_N(decompress_failure_3, 13, 23);
error_ = HpackDecodingError::kNameLengthVarintError;
}
return status;
}
state_ = EntryDecoderState::kStartDecodingValue;
ABSL_FALLTHROUGH_INTENDED;
case EntryDecoderState::kStartDecodingValue:
QUICHE_DVLOG(1) << "kStartDecodingValue: db->Remaining="
<< db->Remaining();
{
ValueDecoderListener vcb(listener);
status = string_decoder_.Start(db, &vcb);
}
if (status == DecodeStatus::kDecodeError) {
QUICHE_CODE_COUNT_N(decompress_failure_3, 14, 23);
error_ = HpackDecodingError::kValueLengthVarintError;
}
if (status == DecodeStatus::kDecodeDone) {
// Done with decoding the literal value, so we've reached the
// end of the header entry.
return status;
}
// On the assumption that the status is kDecodeInProgress, set
// state_ accordingly; unnecessary if status is kDecodeError, but
// that will only happen if the varint encoding the value's length
// is too long.
state_ = EntryDecoderState::kResumeDecodingValue;
return status;
case EntryDecoderState::kResumeDecodingName:
// The literal name was split across decode buffers.
QUICHE_DVLOG(1) << "kResumeDecodingName: db->Remaining="
<< db->Remaining();
{
NameDecoderListener ncb(listener);
status = string_decoder_.Resume(db, &ncb);
}
if (status != DecodeStatus::kDecodeDone) {
// On the assumption that the status is kDecodeInProgress, set
// state_ accordingly; unnecessary if status is kDecodeError, but
// that will only happen if the varint encoding the name's length
// is too long.
state_ = EntryDecoderState::kResumeDecodingName;
if (status == DecodeStatus::kDecodeError) {
QUICHE_CODE_COUNT_N(decompress_failure_3, 15, 23);
error_ = HpackDecodingError::kNameLengthVarintError;
}
return status;
}
state_ = EntryDecoderState::kStartDecodingValue;
break;
case EntryDecoderState::kResumeDecodingValue:
// The literal value was split across decode buffers.
QUICHE_DVLOG(1) << "kResumeDecodingValue: db->Remaining="
<< db->Remaining();
{
ValueDecoderListener vcb(listener);
status = string_decoder_.Resume(db, &vcb);
}
if (status == DecodeStatus::kDecodeError) {
QUICHE_CODE_COUNT_N(decompress_failure_3, 16, 23);
error_ = HpackDecodingError::kValueLengthVarintError;
}
if (status == DecodeStatus::kDecodeDone) {
// Done with decoding the value, therefore the entry as a whole.
return status;
}
// On the assumption that the status is kDecodeInProgress, set
// state_ accordingly; unnecessary if status is kDecodeError, but
// that will only happen if the varint encoding the value's length
// is too long.
state_ = EntryDecoderState::kResumeDecodingValue;
return status;
}
} while (true);
}
bool HpackEntryDecoder::DispatchOnType(HpackEntryDecoderListener* listener) {
const HpackEntryType entry_type = entry_type_decoder_.entry_type();
const uint32_t varint = static_cast<uint32_t>(entry_type_decoder_.varint());
switch (entry_type) {
case HpackEntryType::kIndexedHeader:
// The entry consists solely of the entry type and varint. See:
// http://httpwg.org/specs/rfc7541.html#indexed.header.representation
listener->OnIndexedHeader(varint);
return true;
case HpackEntryType::kIndexedLiteralHeader:
case HpackEntryType::kUnindexedLiteralHeader:
case HpackEntryType::kNeverIndexedLiteralHeader:
// The entry has a literal value, and if the varint is zero also has a
// literal name preceding the value. See:
// http://httpwg.org/specs/rfc7541.html#literal.header.representation
listener->OnStartLiteralHeader(entry_type, varint);
if (varint == 0) {
state_ = EntryDecoderState::kStartDecodingName;
} else {
state_ = EntryDecoderState::kStartDecodingValue;
}
return false;
case HpackEntryType::kDynamicTableSizeUpdate:
// The entry consists solely of the entry type and varint. FWIW, I've
// never seen this type of entry in production (primarily browser
// traffic) so if you're designing an HPACK successor someday, consider
// dropping it or giving it a much longer prefix. See:
// http://httpwg.org/specs/rfc7541.html#encoding.context.update
listener->OnDynamicTableSizeUpdate(varint);
return true;
}
QUICHE_BUG(http2_bug_63_2) << "Unreachable, entry_type=" << entry_type;
return true;
}
void HpackEntryDecoder::OutputDebugString(std::ostream& out) const {
out << "HpackEntryDecoder(state=" << state_ << ", " << entry_type_decoder_
<< ", " << string_decoder_ << ")";
}
std::string HpackEntryDecoder::DebugString() const {
std::stringstream s;
s << *this;
return s.str();
}
std::ostream& operator<<(std::ostream& out, const HpackEntryDecoder& v) {
v.OutputDebugString(out);
return out;
}
std::ostream& operator<<(std::ostream& out,
HpackEntryDecoder::EntryDecoderState state) {
typedef HpackEntryDecoder::EntryDecoderState EntryDecoderState;
switch (state) {
case EntryDecoderState::kResumeDecodingType:
return out << "kResumeDecodingType";
case EntryDecoderState::kDecodedType:
return out << "kDecodedType";
case EntryDecoderState::kStartDecodingName:
return out << "kStartDecodingName";
case EntryDecoderState::kResumeDecodingName:
return out << "kResumeDecodingName";
case EntryDecoderState::kStartDecodingValue:
return out << "kStartDecodingValue";
case EntryDecoderState::kResumeDecodingValue:
return out << "kResumeDecodingValue";
}
return out << static_cast<int>(state);
}
} // namespace http2