blob: 8733d2dd393046c8f3a11af75d8e000d95cce1dd [file] [log] [blame]
// Copyright (c) 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/quic/core/qpack/qpack_decoder.h"
#include "base/logging.h"
#include "net/third_party/http2/decoder/decode_buffer.h"
#include "net/third_party/http2/decoder/decode_status.h"
#include "net/third_party/quic/core/qpack/qpack_constants.h"
namespace quic {
QpackDecoder::ProgressiveDecoder::ProgressiveDecoder(
QpackHeaderTable* header_table,
QpackDecoder::HeadersHandlerInterface* handler)
: header_table_(header_table),
handler_(handler),
state_(State::kStart),
decoding_(true),
error_detected_(false),
literal_name_(false),
literal_value_(false),
name_length_(0),
value_length_(0),
is_huffman_(false) {}
void QpackDecoder::ProgressiveDecoder::Decode(QuicStringPiece data) {
DCHECK(decoding_);
if (data.empty() || error_detected_) {
return;
}
while (true) {
size_t bytes_consumed = 0;
switch (state_) {
case State::kStart:
bytes_consumed = DoStart(data);
break;
case State::kVarintResume:
bytes_consumed = DoVarintResume(data);
break;
case State::kVarintDone:
DoVarintDone();
break;
case State::kReadName:
bytes_consumed = DoReadName(data);
break;
case State::kDecodeName:
DoDecodeName();
break;
case State::kValueLengthStart:
bytes_consumed = DoValueLengthStart(data);
break;
case State::kValueLengthResume:
bytes_consumed = DoValueLengthResume(data);
break;
case State::kValueLengthDone:
DoValueLengthDone();
break;
case State::kReadValue:
bytes_consumed = DoReadValue(data);
break;
case State::kDecodeValue:
DoDecodeValue();
break;
case State::kDone:
DoDone();
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::kVarintDone) &&
(state_ != State::kDecodeName) && (state_ != State::kValueLengthDone) &&
(state_ != State::kDecodeValue) && (state_ != State::kDone)) {
return;
}
}
}
void QpackDecoder::ProgressiveDecoder::EndHeaderBlock() {
DCHECK(decoding_);
decoding_ = false;
if (error_detected_) {
return;
}
if (state_ == State::kStart) {
handler_->OnDecodingCompleted();
} else {
OnError("Incomplete header block.");
}
}
size_t QpackDecoder::ProgressiveDecoder::DoStart(QuicStringPiece data) {
DCHECK(!data.empty());
size_t prefix_length = 0;
if ((data[0] & kIndexedHeaderFieldOpcodeMask) == kIndexedHeaderFieldOpcode) {
if ((data[0] & kIndexedHeaderFieldStaticBit) !=
kIndexedHeaderFieldStaticBit) {
// TODO(bnc): Implement.
OnError("Indexed Header Field with dynamic entry not implemented.");
return 0;
}
prefix_length = kIndexedHeaderFieldPrefixLength;
literal_name_ = false;
literal_value_ = false;
} else if ((data[0] & kIndexedHeaderFieldPostBaseOpcodeMask) ==
kIndexedHeaderFieldPostBaseOpcode) {
// TODO(bnc): Implement.
OnError("Indexed Header Field With Post-Base Index not implemented.");
return 0;
} else if ((data[0] & kLiteralHeaderFieldNameReferenceOpcodeMask) ==
kLiteralHeaderFieldNameReferenceOpcode) {
if ((data[0] & kLiteralHeaderFieldNameReferenceStaticBit) !=
kLiteralHeaderFieldNameReferenceStaticBit) {
// TODO(bnc): Implement.
OnError(
"Literal Header Field With Name Reference with dynamic entry not "
"implemented.");
return 0;
}
prefix_length = kLiteralHeaderFieldNameReferencePrefixLength;
literal_name_ = false;
literal_value_ = true;
} else if ((data[0] & kLiteralHeaderFieldPostBaseOpcodeMask) ==
kLiteralHeaderFieldPostBaseOpcode) {
// TODO(bnc): Implement.
OnError(
"Literal Header Field With Post-Base Name Reference not implemented.");
return 0;
} else {
DCHECK_EQ(kLiteralHeaderFieldOpcode,
data[0] & kLiteralHeaderFieldOpcodeMask);
is_huffman_ =
(data[0] & kLiteralNameHuffmanMask) == kLiteralNameHuffmanMask;
prefix_length = kLiteralHeaderFieldPrefixLength;
literal_name_ = true;
literal_value_ = true;
}
http2::DecodeBuffer buffer(data.data() + 1, data.size() - 1);
http2::DecodeStatus status =
varint_decoder_.Start(data[0], prefix_length, &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;
}
#if defined(STARBOARD)
NOTREACHED();
return 0;
#endif
}
size_t QpackDecoder::ProgressiveDecoder::DoVarintResume(QuicStringPiece data) {
DCHECK(!data.empty());
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;
}
#if defined(STARBOARD)
NOTREACHED();
return 0;
#endif
}
void QpackDecoder::ProgressiveDecoder::DoVarintDone() {
if (literal_name_) {
name_length_ = varint_decoder_.value();
name_.clear();
name_.reserve(name_length_);
// Do not handle empty names differently. (They are probably forbidden by
// higher layers, but it is not enforced in this class.) If there is no
// more data to read, then processing stalls, but the instruction is not
// complete without the value, so OnHeaderDecoded() could not be called yet
// anyway.
state_ = State::kReadName;
return;
}
auto entry = header_table_->LookupEntry(varint_decoder_.value());
if (!entry) {
OnError("Invalid static table index.");
return;
}
if (literal_value_) {
name_.assign(entry->name().data(), entry->name().size());
state_ = State::kValueLengthStart;
return;
}
// Call OnHeaderDecoded() here instead of changing to State::kDone
// to prevent copying two strings.
handler_->OnHeaderDecoded(entry->name(), entry->value());
state_ = State::kStart;
}
size_t QpackDecoder::ProgressiveDecoder::DoReadName(QuicStringPiece data) {
DCHECK(!data.empty());
// |name_length_| might be zero.
DCHECK_LE(name_.size(), name_length_);
size_t bytes_consumed = std::min(name_length_ - name_.size(), data.size());
name_.append(data.data(), bytes_consumed);
DCHECK_LE(name_.size(), name_length_);
if (name_.size() == name_length_) {
state_ = State::kDecodeName;
}
return bytes_consumed;
}
void QpackDecoder::ProgressiveDecoder::DoDecodeName() {
DCHECK_EQ(name_.size(), name_length_);
if (is_huffman_) {
huffman_decoder_.Reset();
// HpackHuffmanDecoder::Decode() cannot perform in-place decoding.
QuicString decoded_name;
huffman_decoder_.Decode(name_, &decoded_name);
if (!huffman_decoder_.InputProperlyTerminated()) {
OnError("Error in Huffman-encoded name.");
return;
}
name_ = decoded_name;
}
state_ = State::kValueLengthStart;
}
size_t QpackDecoder::ProgressiveDecoder::DoValueLengthStart(
QuicStringPiece data) {
DCHECK(!data.empty());
DCHECK(literal_value_);
is_huffman_ =
(data[0] & kLiteralValueHuffmanMask) == kLiteralValueHuffmanMask;
http2::DecodeBuffer buffer(data.data() + 1, data.size() - 1);
http2::DecodeStatus status =
varint_decoder_.Start(data[0], kLiteralValuePrefixLength, &buffer);
size_t bytes_consumed = 1 + buffer.Offset();
switch (status) {
case http2::DecodeStatus::kDecodeDone:
state_ = State::kValueLengthDone;
return bytes_consumed;
case http2::DecodeStatus::kDecodeInProgress:
state_ = State::kValueLengthResume;
return bytes_consumed;
case http2::DecodeStatus::kDecodeError:
OnError("ValueLen too large.");
return bytes_consumed;
}
#if defined(STARBOARD)
NOTREACHED();
return 0;
#endif
}
size_t QpackDecoder::ProgressiveDecoder::DoValueLengthResume(
QuicStringPiece data) {
DCHECK(!data.empty());
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::kValueLengthDone;
return bytes_consumed;
case http2::DecodeStatus::kDecodeInProgress:
DCHECK_EQ(bytes_consumed, data.size());
DCHECK(buffer.Empty());
return bytes_consumed;
case http2::DecodeStatus::kDecodeError:
OnError("ValueLen too large.");
return bytes_consumed;
}
#if defined(STARBOARD)
NOTREACHED();
return 0;
#endif
}
void QpackDecoder::ProgressiveDecoder::DoValueLengthDone() {
value_.clear();
value_length_ = varint_decoder_.value();
// If value is empty, skip DoReadValue() and DoDecodeValue() and jump directly
// to DoDone(). This is so that OnHeaderDecoded() is called even if there is
// no more data.
if (value_length_ == 0) {
state_ = State::kDone;
return;
}
value_.reserve(value_length_);
state_ = State::kReadValue;
}
size_t QpackDecoder::ProgressiveDecoder::DoReadValue(QuicStringPiece data) {
DCHECK(!data.empty());
DCHECK_LT(0u, value_length_);
DCHECK_LT(value_.size(), value_length_);
size_t bytes_consumed = std::min(value_length_ - value_.size(), data.size());
value_.append(data.data(), bytes_consumed);
DCHECK_LE(value_.size(), value_length_);
if (value_.size() == value_length_) {
state_ = State::kDecodeValue;
}
return bytes_consumed;
}
void QpackDecoder::ProgressiveDecoder::DoDecodeValue() {
DCHECK_EQ(value_.size(), value_length_);
if (is_huffman_) {
huffman_decoder_.Reset();
// HpackHuffmanDecoder::Decode() cannot perform in-place decoding.
QuicString decoded_value;
huffman_decoder_.Decode(value_, &decoded_value);
if (!huffman_decoder_.InputProperlyTerminated()) {
OnError("Error in Huffman-encoded value.");
return;
}
value_ = decoded_value;
}
state_ = State::kDone;
}
void QpackDecoder::ProgressiveDecoder::DoDone() {
handler_->OnHeaderDecoded(name_, value_);
state_ = State::kStart;
}
void QpackDecoder::ProgressiveDecoder::OnError(QuicStringPiece error_message) {
DCHECK(!error_detected_);
error_detected_ = true;
handler_->OnDecodingErrorDetected(error_message);
}
std::unique_ptr<QpackDecoder::ProgressiveDecoder>
QpackDecoder::DecodeHeaderBlock(
QpackDecoder::HeadersHandlerInterface* handler) {
return std::make_unique<ProgressiveDecoder>(&header_table_, handler);
}
} // namespace quic