Commit 8fc656b6 authored by birenroy's avatar birenroy Committed by Commit bot

Implements incremental decode in HPACK.

With incremental decode, headers may contain incomplete opcode. The change is divided into two parts: the HpackInputStream change in hpack_input_stream.h/cc, and the HpackDecoder change in Hpack_decoder.h/cc.

After new data are added to the buffer (by calling
HandleControlFrameHeadersData()), a new HpackInputStream is created
based on the current data in the buffer, and as many data as possible
are parsed. When an opcode is parsed successfully, remember the current
position in the header (by calling MarkCurrentPosition()), and move on
to the next opcode. If an opcode is incomplete, we return from the current
parsing with false, and mark need_more_data_ to be true.
The data have been successfully parsed will be removed
from the buffer. Since we incrementally decode headers as data arrive,
we remove the decode code in HandleControlFrameHeadersComplete().

This CL lands server change 116551169 by yasong.

BUG=488484

Review URL: https://codereview.chromium.org/1914193002

Cr-Commit-Position: refs/heads/master@{#389857}
parent 179a6e73
...@@ -26,15 +26,16 @@ HpackDecoder::HpackDecoder() ...@@ -26,15 +26,16 @@ HpackDecoder::HpackDecoder()
handler_(nullptr), handler_(nullptr),
total_header_bytes_(0), total_header_bytes_(0),
regular_header_seen_(false), regular_header_seen_(false),
header_block_started_(false) {} header_block_started_(false),
total_parsed_bytes_(0) {}
HpackDecoder::~HpackDecoder() {} HpackDecoder::~HpackDecoder() {}
bool HpackDecoder::HandleControlFrameHeadersData(const char* headers_data, bool HpackDecoder::HandleControlFrameHeadersData(const char* headers_data,
size_t headers_data_length) { size_t headers_data_length) {
decoded_block_.clear();
if (!header_block_started_) { if (!header_block_started_) {
header_block_started_ = true; header_block_started_ = true;
decoded_block_.clear();
if (handler_ != nullptr) { if (handler_ != nullptr) {
handler_->OnHeaderBlockStart(); handler_->OnHeaderBlockStart();
} }
...@@ -45,26 +46,44 @@ bool HpackDecoder::HandleControlFrameHeadersData(const char* headers_data, ...@@ -45,26 +46,44 @@ bool HpackDecoder::HandleControlFrameHeadersData(const char* headers_data,
} }
headers_block_buffer_.insert(headers_block_buffer_.end(), headers_data, headers_block_buffer_.insert(headers_block_buffer_.end(), headers_data,
headers_data + headers_data_length); headers_data + headers_data_length);
return true;
}
bool HpackDecoder::HandleControlFrameHeadersComplete(size_t* compressed_len) { // Parse as many data in buffer as possible. And remove the parsed data
// from buffer.
HpackInputStream input_stream(max_string_literal_size_, HpackInputStream input_stream(max_string_literal_size_,
headers_block_buffer_); headers_block_buffer_);
regular_header_seen_ = false;
if (compressed_len) {
*compressed_len = headers_block_buffer_.size();
}
while (input_stream.HasMoreData()) { while (input_stream.HasMoreData()) {
if (!DecodeNextOpcode(&input_stream)) { if (!DecodeNextOpcodeWrapper(&input_stream)) {
headers_block_buffer_.clear(); if (input_stream.NeedMoreData()) {
break;
}
return false; 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) {
regular_header_seen_ = false;
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) {
return false;
}
if (handler_ != nullptr) { if (handler_ != nullptr) {
handler_->OnHeaderBlockEnd(total_header_bytes_); handler_->OnHeaderBlockEnd(total_header_bytes_);
} }
headers_block_buffer_.clear(); headers_block_buffer_.clear();
total_parsed_bytes_ = 0;
header_block_started_ = false; header_block_started_ = false;
handler_ = nullptr; handler_ = nullptr;
return true; return true;
...@@ -104,6 +123,15 @@ bool HpackDecoder::HandleHeaderRepresentation(StringPiece name, ...@@ -104,6 +123,15 @@ bool HpackDecoder::HandleHeaderRepresentation(StringPiece name,
return true; 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) { bool HpackDecoder::DecodeNextOpcode(HpackInputStream* input_stream) {
// Implements 7.1: Indexed Header Field Representation. // Implements 7.1: Indexed Header Field Representation.
if (input_stream->MatchPrefixAndConsume(kIndexedOpcode)) { if (input_stream->MatchPrefixAndConsume(kIndexedOpcode)) {
......
...@@ -112,9 +112,14 @@ class NET_EXPORT_PRIVATE HpackDecoder { ...@@ -112,9 +112,14 @@ class NET_EXPORT_PRIVATE HpackDecoder {
// Flag to keep track of having seen the header block start. // Flag to keep track of having seen the header block start.
bool header_block_started_; bool header_block_started_;
// Total bytes have been removed from headers_block_buffer_.
// Its value is updated during incremental decoding.
uint32_t total_parsed_bytes_;
// Handlers for decoding HPACK opcodes and header representations // Handlers for decoding HPACK opcodes and header representations
// (or parts thereof). These methods return true on success and // (or parts thereof). These methods return true on success and
// false on error. // false on error.
bool DecodeNextOpcodeWrapper(HpackInputStream* input_stream);
bool DecodeNextOpcode(HpackInputStream* input_stream); bool DecodeNextOpcode(HpackInputStream* input_stream);
bool DecodeNextHeaderTableSizeUpdate(HpackInputStream* input_stream); bool DecodeNextHeaderTableSizeUpdate(HpackInputStream* input_stream);
bool DecodeNextIndexedHeader(HpackInputStream* input_stream); bool DecodeNextIndexedHeader(HpackInputStream* input_stream);
......
...@@ -37,6 +37,13 @@ class HpackDecoderPeer { ...@@ -37,6 +37,13 @@ class HpackDecoderPeer {
const SpdyHeaderBlock& decoded_block() const { const SpdyHeaderBlock& decoded_block() const {
return decoder_->decoded_block_; return decoder_->decoded_block_;
} }
bool DecodeNextStringLiteral(HpackInputStream* in,
bool is_header_key,
StringPiece* str) {
return decoder_->DecodeNextStringLiteral(in, is_header_key, str);
}
const string& headers_block_buffer() const { const string& headers_block_buffer() const {
return decoder_->headers_block_buffer_; return decoder_->headers_block_buffer_;
} }
...@@ -60,16 +67,26 @@ class HpackDecoderTest : public ::testing::TestWithParam<bool> { ...@@ -60,16 +67,26 @@ class HpackDecoderTest : public ::testing::TestWithParam<bool> {
protected: protected:
HpackDecoderTest() : decoder_(), decoder_peer_(&decoder_) {} HpackDecoderTest() : decoder_(), decoder_peer_(&decoder_) {}
void SetUp() override { handler_exists_ = GetParam(); }
bool DecodeHeaderBlock(StringPiece str) { bool DecodeHeaderBlock(StringPiece str) {
if (GetParam()) { if (handler_exists_) {
decoder_.HandleControlFrameHeadersStart(&handler_); decoder_.HandleControlFrameHeadersStart(&handler_);
} }
return decoder_.HandleControlFrameHeadersData(str.data(), str.size()) && return decoder_.HandleControlFrameHeadersData(str.data(), str.size()) &&
decoder_.HandleControlFrameHeadersComplete(nullptr); decoder_.HandleControlFrameHeadersComplete(nullptr);
} }
bool HandleControlFrameHeadersData(StringPiece str) {
return decoder_.HandleControlFrameHeadersData(str.data(), str.size());
}
bool HandleControlFrameHeadersComplete(size_t* size) {
return decoder_.HandleControlFrameHeadersComplete(size);
}
const SpdyHeaderBlock& decoded_block() const { const SpdyHeaderBlock& decoded_block() const {
if (GetParam()) { if (handler_exists_) {
return handler_.decoded_block(); return handler_.decoded_block();
} else { } else {
return decoder_peer_.decoded_block(); return decoder_peer_.decoded_block();
...@@ -95,13 +112,14 @@ class HpackDecoderTest : public ::testing::TestWithParam<bool> { ...@@ -95,13 +112,14 @@ class HpackDecoderTest : public ::testing::TestWithParam<bool> {
HpackDecoder decoder_; HpackDecoder decoder_;
test::HpackDecoderPeer decoder_peer_; test::HpackDecoderPeer decoder_peer_;
TestHeadersHandler handler_; TestHeadersHandler handler_;
bool handler_exists_;
}; };
INSTANTIATE_TEST_CASE_P(WithAndWithoutHeadersHandler, INSTANTIATE_TEST_CASE_P(WithAndWithoutHeadersHandler,
HpackDecoderTest, HpackDecoderTest,
::testing::Bool()); ::testing::Bool());
TEST_P(HpackDecoderTest, HandleControlFrameHeadersData) { TEST_P(HpackDecoderTest, AddHeaderDataWithHandleControlFrameHeadersData) {
// Strings under threshold are concatenated in the buffer. // Strings under threshold are concatenated in the buffer.
EXPECT_TRUE(decoder_.HandleControlFrameHeadersData("small string one", 16)); EXPECT_TRUE(decoder_.HandleControlFrameHeadersData("small string one", 16));
EXPECT_TRUE(decoder_.HandleControlFrameHeadersData("small string two", 16)); EXPECT_TRUE(decoder_.HandleControlFrameHeadersData("small string two", 16));
...@@ -113,8 +131,29 @@ TEST_P(HpackDecoderTest, HandleControlFrameHeadersData) { ...@@ -113,8 +131,29 @@ TEST_P(HpackDecoderTest, HandleControlFrameHeadersData) {
"small string onesmall string two"); "small string onesmall string two");
} }
// Decode with incomplete data in buffer.
TEST_P(HpackDecoderTest, DecodeWithIncompleteData) {
// No need to wait for more data.
EXPECT_TRUE(HandleControlFrameHeadersData("\x82\x85\x82"));
EXPECT_EQ("", decoder_peer_.headers_block_buffer());
// Need to wait for more data.
EXPECT_TRUE(
HandleControlFrameHeadersData("\x40\x03goo"
"\x03gar\xbe\x40\x04spam"));
EXPECT_EQ("\x40\x04spam", decoder_peer_.headers_block_buffer());
// Add the needed data.
EXPECT_TRUE(HandleControlFrameHeadersData("\x04gggs"));
EXPECT_EQ("", decoder_peer_.headers_block_buffer());
size_t size = 0;
EXPECT_TRUE(HandleControlFrameHeadersComplete(&size));
EXPECT_EQ(24u, size);
}
TEST_P(HpackDecoderTest, HandleHeaderRepresentation) { TEST_P(HpackDecoderTest, HandleHeaderRepresentation) {
if (GetParam()) { if (handler_exists_) {
decoder_.HandleControlFrameHeadersStart(&handler_); decoder_.HandleControlFrameHeadersStart(&handler_);
} }
...@@ -165,6 +204,26 @@ TEST_P(HpackDecoderTest, DecodeNextNameLiteral) { ...@@ -165,6 +204,26 @@ TEST_P(HpackDecoderTest, DecodeNextNameLiteral) {
EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_EQ("name", string_piece); EXPECT_EQ("name", string_piece);
EXPECT_FALSE(input_stream.HasMoreData()); EXPECT_FALSE(input_stream.HasMoreData());
EXPECT_FALSE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(6u, input_stream.ParsedBytes());
}
// Decoding an encoded name with an incomplete string literal.
TEST_P(HpackDecoderTest, DecodeNextNameLiteralWithIncompleteHeader) {
HpackInputStream input_stream(kLiteralBound,
StringPiece("\x00\x04name\x00\x02g", 9));
StringPiece string_piece;
EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_FALSE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(6u, input_stream.ParsedBytes());
EXPECT_FALSE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_TRUE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(8u, input_stream.ParsedBytes());
} }
TEST_P(HpackDecoderTest, DecodeNextNameLiteralWithHuffmanEncoding) { TEST_P(HpackDecoderTest, DecodeNextNameLiteralWithHuffmanEncoding) {
...@@ -175,6 +234,30 @@ TEST_P(HpackDecoderTest, DecodeNextNameLiteralWithHuffmanEncoding) { ...@@ -175,6 +234,30 @@ TEST_P(HpackDecoderTest, DecodeNextNameLiteralWithHuffmanEncoding) {
EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_EQ("custom-key", string_piece); EXPECT_EQ("custom-key", string_piece);
EXPECT_FALSE(input_stream.HasMoreData()); EXPECT_FALSE(input_stream.HasMoreData());
EXPECT_FALSE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(input.size(), input_stream.ParsedBytes());
}
// Decode with incomplete huffman encoding.
TEST_P(HpackDecoderTest, DecodeNextNameLiteralWithIncompleteHuffmanEncoding) {
// CHECK(huffman_table_.Initialize(kHpackHuffmanCode,
// arraysize(kHpackHuffmanCode)));
// Put two copies of the same huffman encoding into input.
string input = a2b_hex("008825a849e95ba97d7f008825a849e95ba97d7f");
input.resize(input.size() - 1); // Remove the last byte.
HpackInputStream input_stream(kLiteralBound, input);
StringPiece string_piece;
EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_FALSE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(10u, input_stream.ParsedBytes());
EXPECT_FALSE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_TRUE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(12u, input_stream.ParsedBytes());
} }
// Decoding an encoded name with a valid index should work. // Decoding an encoded name with a valid index should work.
...@@ -185,6 +268,9 @@ TEST_P(HpackDecoderTest, DecodeNextNameIndexed) { ...@@ -185,6 +268,9 @@ TEST_P(HpackDecoderTest, DecodeNextNameIndexed) {
EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_EQ(":authority", string_piece); EXPECT_EQ(":authority", string_piece);
EXPECT_FALSE(input_stream.HasMoreData()); EXPECT_FALSE(input_stream.HasMoreData());
EXPECT_FALSE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(1u, input_stream.ParsedBytes());
} }
// Decoding an encoded name with an invalid index should fail. // Decoding an encoded name with an invalid index should fail.
...@@ -194,6 +280,9 @@ TEST_P(HpackDecoderTest, DecodeNextNameInvalidIndex) { ...@@ -194,6 +280,9 @@ TEST_P(HpackDecoderTest, DecodeNextNameInvalidIndex) {
StringPiece string_piece; StringPiece string_piece;
EXPECT_FALSE(decoder_peer_.DecodeNextName(&input_stream, &string_piece)); EXPECT_FALSE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
EXPECT_FALSE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(1u, input_stream.ParsedBytes());
} }
// Decoding indexed static table field should work. // Decoding indexed static table field should work.
...@@ -353,6 +442,30 @@ TEST_P(HpackDecoderTest, LiteralHeaderNeverIndexedInvalidNameIndex) { ...@@ -353,6 +442,30 @@ TEST_P(HpackDecoderTest, LiteralHeaderNeverIndexedInvalidNameIndex) {
EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x1f\x2f\x03ooo"))); EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x1f\x2f\x03ooo")));
} }
// Decode with incomplete string literal.
TEST_P(HpackDecoderTest, StringLiteralIncomplete) {
const char input[] = "\x0c/sample/path\x06:path2\x0e/sample/path/";
HpackInputStream input_stream(kLiteralBound, input);
StringPiece str;
EXPECT_TRUE(
decoder_peer_.DecodeNextStringLiteral(&input_stream, false, &str));
EXPECT_FALSE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(13u, input_stream.ParsedBytes());
EXPECT_TRUE(
decoder_peer_.DecodeNextStringLiteral(&input_stream, false, &str));
EXPECT_FALSE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(20u, input_stream.ParsedBytes());
EXPECT_FALSE(
decoder_peer_.DecodeNextStringLiteral(&input_stream, false, &str));
EXPECT_TRUE(input_stream.NeedMoreData());
input_stream.MarkCurrentPosition();
EXPECT_EQ(21u, input_stream.ParsedBytes());
}
// Round-tripping the header set from E.2.1 should work. // Round-tripping the header set from E.2.1 should work.
TEST_P(HpackDecoderTest, BasicE21) { TEST_P(HpackDecoderTest, BasicE21) {
HpackEncoder encoder(ObtainHpackHuffmanTable()); HpackEncoder encoder(ObtainHpackHuffmanTable());
......
...@@ -19,7 +19,10 @@ HpackInputStream::HpackInputStream(uint32_t max_string_literal_size, ...@@ -19,7 +19,10 @@ HpackInputStream::HpackInputStream(uint32_t max_string_literal_size,
StringPiece buffer) StringPiece buffer)
: max_string_literal_size_(max_string_literal_size), : max_string_literal_size_(max_string_literal_size),
buffer_(buffer), buffer_(buffer),
bit_offset_(0) {} bit_offset_(0),
parsed_bytes_(0),
parsed_bytes_current_(0),
need_more_data_(false) {}
HpackInputStream::~HpackInputStream() {} HpackInputStream::~HpackInputStream() {}
...@@ -28,6 +31,11 @@ bool HpackInputStream::HasMoreData() const { ...@@ -28,6 +31,11 @@ bool HpackInputStream::HasMoreData() const {
} }
bool HpackInputStream::MatchPrefixAndConsume(HpackPrefix prefix) { bool HpackInputStream::MatchPrefixAndConsume(HpackPrefix prefix) {
if (buffer_.empty()) {
need_more_data_ = true;
return false;
}
DCHECK_GT(prefix.bit_size, 0u); DCHECK_GT(prefix.bit_size, 0u);
DCHECK_LE(prefix.bit_size, 8u); DCHECK_LE(prefix.bit_size, 8u);
...@@ -46,7 +54,11 @@ bool HpackInputStream::MatchPrefixAndConsume(HpackPrefix prefix) { ...@@ -46,7 +54,11 @@ bool HpackInputStream::MatchPrefixAndConsume(HpackPrefix prefix) {
} }
bool HpackInputStream::PeekNextOctet(uint8_t* next_octet) { bool HpackInputStream::PeekNextOctet(uint8_t* next_octet) {
if ((bit_offset_ > 0) || buffer_.empty()) { if (buffer_.empty()) {
need_more_data_ = true;
return false;
}
if ((bit_offset_ > 0)) {
DVLOG(1) << "HpackInputStream::PeekNextOctet bit_offset_=" << bit_offset_; DVLOG(1) << "HpackInputStream::PeekNextOctet bit_offset_=" << bit_offset_;
return false; return false;
} }
...@@ -61,6 +73,7 @@ bool HpackInputStream::DecodeNextOctet(uint8_t* next_octet) { ...@@ -61,6 +73,7 @@ bool HpackInputStream::DecodeNextOctet(uint8_t* next_octet) {
} }
buffer_.remove_prefix(1); buffer_.remove_prefix(1);
parsed_bytes_current_ += 1;
return true; return true;
} }
...@@ -76,7 +89,9 @@ bool HpackInputStream::DecodeNextUint32(uint32_t* I) { ...@@ -76,7 +89,9 @@ bool HpackInputStream::DecodeNextUint32(uint32_t* I) {
uint8_t next_marker = (1 << N) - 1; uint8_t next_marker = (1 << N) - 1;
uint8_t next_octet = 0; uint8_t next_octet = 0;
if (!DecodeNextOctet(&next_octet)) { if (!DecodeNextOctet(&next_octet)) {
DVLOG(1) << "HpackInputStream::DecodeNextUint32 initial octet error"; if (!need_more_data_) {
DVLOG(1) << "HpackInputStream::DecodeNextUint32 initial octet error";
}
return false; return false;
} }
*I = next_octet & next_marker; *I = next_octet & next_marker;
...@@ -86,7 +101,9 @@ bool HpackInputStream::DecodeNextUint32(uint32_t* I) { ...@@ -86,7 +101,9 @@ bool HpackInputStream::DecodeNextUint32(uint32_t* I) {
while (has_more && (shift < 32)) { while (has_more && (shift < 32)) {
uint8_t next_octet = 0; uint8_t next_octet = 0;
if (!DecodeNextOctet(&next_octet)) { if (!DecodeNextOctet(&next_octet)) {
DVLOG(1) << "HpackInputStream::DecodeNextUint32 shift=" << shift; if (!need_more_data_) {
DVLOG(1) << "HpackInputStream::DecodeNextUint32 shift=" << shift;
}
return false; return false;
} }
has_more = (next_octet & 0x80) != 0; has_more = (next_octet & 0x80) != 0;
...@@ -115,23 +132,28 @@ bool HpackInputStream::DecodeNextIdentityString(StringPiece* str) { ...@@ -115,23 +132,28 @@ bool HpackInputStream::DecodeNextIdentityString(StringPiece* str) {
} }
if (size > buffer_.size()) { if (size > buffer_.size()) {
need_more_data_ = true;
return false; return false;
} }
*str = StringPiece(buffer_.data(), size); *str = StringPiece(buffer_.data(), size);
buffer_.remove_prefix(size); buffer_.remove_prefix(size);
parsed_bytes_current_ += size;
return true; return true;
} }
bool HpackInputStream::DecodeNextHuffmanString(string* str) { bool HpackInputStream::DecodeNextHuffmanString(string* str) {
uint32_t encoded_size = 0; uint32_t encoded_size = 0;
if (!DecodeNextUint32(&encoded_size)) { if (!DecodeNextUint32(&encoded_size)) {
DVLOG(1) << "HpackInputStream::DecodeNextHuffmanString " if (!need_more_data_) {
<< "unable to decode size"; DVLOG(1) << "HpackInputStream::DecodeNextHuffmanString "
<< "unable to decode size";
}
return false; return false;
} }
if (encoded_size > buffer_.size()) { if (encoded_size > buffer_.size()) {
need_more_data_ = true;
DVLOG(1) << "HpackInputStream::DecodeNextHuffmanString " << encoded_size DVLOG(1) << "HpackInputStream::DecodeNextHuffmanString " << encoded_size
<< " > " << buffer_.size(); << " > " << buffer_.size();
return false; return false;
...@@ -140,6 +162,7 @@ bool HpackInputStream::DecodeNextHuffmanString(string* str) { ...@@ -140,6 +162,7 @@ bool HpackInputStream::DecodeNextHuffmanString(string* str) {
HpackInputStream bounded_reader(max_string_literal_size_, HpackInputStream bounded_reader(max_string_literal_size_,
StringPiece(buffer_.data(), encoded_size)); StringPiece(buffer_.data(), encoded_size));
buffer_.remove_prefix(encoded_size); buffer_.remove_prefix(encoded_size);
parsed_bytes_current_ += encoded_size;
// DecodeString will not append more than |max_string_literal_size_| chars // DecodeString will not append more than |max_string_literal_size_| chars
// to |str|. // to |str|.
...@@ -215,6 +238,7 @@ void HpackInputStream::ConsumeBits(size_t bit_count) { ...@@ -215,6 +238,7 @@ void HpackInputStream::ConsumeBits(size_t bit_count) {
CHECK_GT(buffer_.size(), 0u); CHECK_GT(buffer_.size(), 0u);
} }
buffer_.remove_prefix(byte_count); buffer_.remove_prefix(byte_count);
parsed_bytes_current_ += byte_count;
} }
void HpackInputStream::ConsumeByteRemainder() { void HpackInputStream::ConsumeByteRemainder() {
...@@ -223,4 +247,16 @@ void HpackInputStream::ConsumeByteRemainder() { ...@@ -223,4 +247,16 @@ void HpackInputStream::ConsumeByteRemainder() {
} }
} }
uint32_t HpackInputStream::ParsedBytes() const {
return parsed_bytes_;
}
bool HpackInputStream::NeedMoreData() const {
return need_more_data_;
}
void HpackInputStream::MarkCurrentPosition() {
parsed_bytes_ = parsed_bytes_current_;
}
} // namespace net } // namespace net
...@@ -22,12 +22,18 @@ ...@@ -22,12 +22,18 @@
namespace net { namespace net {
namespace test {
class HpackInputStreamPeer;
} // namespace test
typedef std::pair<size_t, uint32_t> InitialPeekResult; typedef std::pair<size_t, uint32_t> InitialPeekResult;
// An HpackInputStream handles all the low-level details of decoding // An HpackInputStream handles all the low-level details of decoding
// header fields. // header fields.
class NET_EXPORT_PRIVATE HpackInputStream { class NET_EXPORT_PRIVATE HpackInputStream {
public: public:
friend class test::HpackInputStreamPeer;
// |max_string_literal_size| is the largest that any one string // |max_string_literal_size| is the largest that any one string
// literal (header name or header value) can be. // literal (header name or header value) can be.
HpackInputStream(uint32_t max_string_literal_size, base::StringPiece buffer); HpackInputStream(uint32_t max_string_literal_size, base::StringPiece buffer);
...@@ -72,14 +78,31 @@ class NET_EXPORT_PRIVATE HpackInputStream { ...@@ -72,14 +78,31 @@ class NET_EXPORT_PRIVATE HpackInputStream {
// remaining bits in the current byte. // remaining bits in the current byte.
void ConsumeByteRemainder(); void ConsumeByteRemainder();
// Accessors for testing. // Return the total bytes that have been parsed SUCCESSFULLY.
uint32_t ParsedBytes() const;
// When incrementally decode the header, need to remember the current
// position in the buffer after we successfully decode one opcode.
void MarkCurrentPosition();
void SetBitOffsetForTest(size_t bit_offset) { bit_offset_ = bit_offset; } // Returning true indicates this instance of HpackInputStream
// doesn't have enough data to parse the current opcode, and we
// are done with this instance. When more data arrive, a new
// HpackInputStream should be created to restart the parsing.
bool NeedMoreData() const;
private: private:
const uint32_t max_string_literal_size_; const uint32_t max_string_literal_size_;
base::StringPiece buffer_; base::StringPiece buffer_;
size_t bit_offset_; size_t bit_offset_;
// Total number of bytes parsed successfully. Only get updated when an
// opcode is parsed successfully.
uint32_t parsed_bytes_;
// Total number of bytes parsed currently. Get updated when an octet,
// a number or a string has been parsed successfully. Can point to the
// middle of an opcode.
uint32_t parsed_bytes_current_;
bool need_more_data_;
bool PeekNextOctet(uint8_t* next_octet); bool PeekNextOctet(uint8_t* next_octet);
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
namespace net { namespace net {
namespace { namespace test {
using base::StringPiece; using base::StringPiece;
using std::string; using std::string;
...@@ -35,15 +35,33 @@ const char kEncodedHuffmanFixture[] = ...@@ -35,15 +35,33 @@ const char kEncodedHuffmanFixture[] =
const char kDecodedHuffmanFixture[] = const char kDecodedHuffmanFixture[] =
"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"; "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1";
class HpackInputStreamPeer {
public:
explicit HpackInputStreamPeer(HpackInputStream* input_stream)
: input_stream_(input_stream) {}
void SetBitOffsetForTest(size_t bit_offset) {
input_stream_->bit_offset_ = bit_offset;
}
uint32_t ParsedBytesCurrent() { return input_stream_->parsed_bytes_current_; }
private:
HpackInputStream* input_stream_;
};
// Utility function to decode an assumed-valid uint32_t with an N-bit // Utility function to decode an assumed-valid uint32_t with an N-bit
// prefix. // prefix.
uint32_t DecodeValidUint32(uint8_t N, StringPiece str) { uint32_t DecodeValidUint32(uint8_t N, StringPiece str) {
EXPECT_GT(N, 0); EXPECT_GT(N, 0);
EXPECT_LE(N, 8); EXPECT_LE(N, 8);
HpackInputStream input_stream(kLiteralBound, str); HpackInputStream input_stream(kLiteralBound, str);
input_stream.SetBitOffsetForTest(8 - N); HpackInputStreamPeer input_stream_peer(&input_stream);
input_stream_peer.SetBitOffsetForTest(8 - N);
uint32_t I; uint32_t I;
EXPECT_TRUE(input_stream.DecodeNextUint32(&I)); EXPECT_TRUE(input_stream.DecodeNextUint32(&I));
EXPECT_EQ(str.size(), input_stream_peer.ParsedBytesCurrent());
EXPECT_FALSE(input_stream.NeedMoreData());
return I; return I;
} }
...@@ -53,7 +71,8 @@ void ExpectDecodeUint32Invalid(uint8_t N, StringPiece str) { ...@@ -53,7 +71,8 @@ void ExpectDecodeUint32Invalid(uint8_t N, StringPiece str) {
EXPECT_GT(N, 0); EXPECT_GT(N, 0);
EXPECT_LE(N, 8); EXPECT_LE(N, 8);
HpackInputStream input_stream(kLiteralBound, str); HpackInputStream input_stream(kLiteralBound, str);
input_stream.SetBitOffsetForTest(8 - N); HpackInputStreamPeer input_stream_peer(&input_stream);
input_stream_peer.SetBitOffsetForTest(8 - N);
uint32_t I; uint32_t I;
EXPECT_FALSE(input_stream.DecodeNextUint32(&I)); EXPECT_FALSE(input_stream.DecodeNextUint32(&I));
} }
...@@ -478,12 +497,15 @@ TEST(HpackInputStreamTest, SevenByteIntegersOneToSevenBitPrefixes) { ...@@ -478,12 +497,15 @@ TEST(HpackInputStreamTest, SevenByteIntegersOneToSevenBitPrefixes) {
// Decoding a valid encoded string literal should work. // Decoding a valid encoded string literal should work.
TEST(HpackInputStreamTest, DecodeNextIdentityString) { TEST(HpackInputStreamTest, DecodeNextIdentityString) {
HpackInputStream input_stream(kLiteralBound, "\x0estring literal"); HpackInputStream input_stream(kLiteralBound, "\x0estring literal");
HpackInputStreamPeer input_stream_peer(&input_stream);
EXPECT_TRUE(input_stream.HasMoreData()); EXPECT_TRUE(input_stream.HasMoreData());
StringPiece string_piece; StringPiece string_piece;
EXPECT_TRUE(input_stream.DecodeNextIdentityString(&string_piece)); EXPECT_TRUE(input_stream.DecodeNextIdentityString(&string_piece));
EXPECT_EQ("string literal", string_piece); EXPECT_EQ("string literal", string_piece);
EXPECT_FALSE(input_stream.HasMoreData()); EXPECT_FALSE(input_stream.HasMoreData());
EXPECT_EQ(string_piece.size() + 1, input_stream_peer.ParsedBytesCurrent());
EXPECT_FALSE(input_stream.NeedMoreData());
} }
// Decoding an encoded string literal with size larger than // Decoding an encoded string literal with size larger than
...@@ -494,6 +516,7 @@ TEST(HpackInputStreamTest, DecodeNextIdentityStringSizeLimit) { ...@@ -494,6 +516,7 @@ TEST(HpackInputStreamTest, DecodeNextIdentityStringSizeLimit) {
EXPECT_TRUE(input_stream.HasMoreData()); EXPECT_TRUE(input_stream.HasMoreData());
StringPiece string_piece; StringPiece string_piece;
EXPECT_FALSE(input_stream.DecodeNextIdentityString(&string_piece)); EXPECT_FALSE(input_stream.DecodeNextIdentityString(&string_piece));
EXPECT_FALSE(input_stream.NeedMoreData());
} }
// Decoding an encoded string literal with size larger than the // Decoding an encoded string literal with size larger than the
...@@ -505,16 +528,20 @@ TEST(HpackInputStreamTest, DecodeNextIdentityStringNotEnoughInput) { ...@@ -505,16 +528,20 @@ TEST(HpackInputStreamTest, DecodeNextIdentityStringNotEnoughInput) {
EXPECT_TRUE(input_stream.HasMoreData()); EXPECT_TRUE(input_stream.HasMoreData());
StringPiece string_piece; StringPiece string_piece;
EXPECT_FALSE(input_stream.DecodeNextIdentityString(&string_piece)); EXPECT_FALSE(input_stream.DecodeNextIdentityString(&string_piece));
EXPECT_TRUE(input_stream.NeedMoreData());
} }
TEST(HpackInputStreamTest, DecodeNextHuffmanString) { TEST(HpackInputStreamTest, DecodeNextHuffmanString) {
string output, input(a2b_hex(kEncodedHuffmanFixture)); string output, input(a2b_hex(kEncodedHuffmanFixture));
HpackInputStream input_stream(arraysize(kDecodedHuffmanFixture) - 1, input); HpackInputStream input_stream(arraysize(kDecodedHuffmanFixture) - 1, input);
HpackInputStreamPeer input_stream_peer(&input_stream);
EXPECT_TRUE(input_stream.HasMoreData()); EXPECT_TRUE(input_stream.HasMoreData());
EXPECT_TRUE(input_stream.DecodeNextHuffmanString(&output)); EXPECT_TRUE(input_stream.DecodeNextHuffmanString(&output));
EXPECT_EQ(kDecodedHuffmanFixture, output); EXPECT_EQ(kDecodedHuffmanFixture, output);
EXPECT_FALSE(input_stream.HasMoreData()); EXPECT_FALSE(input_stream.HasMoreData());
EXPECT_FALSE(input_stream.NeedMoreData());
EXPECT_EQ(46u, input_stream_peer.ParsedBytesCurrent());
} }
TEST(HpackInputStreamTest, DecodeNextHuffmanStringSizeLimit) { TEST(HpackInputStreamTest, DecodeNextHuffmanStringSizeLimit) {
...@@ -525,6 +552,7 @@ TEST(HpackInputStreamTest, DecodeNextHuffmanStringSizeLimit) { ...@@ -525,6 +552,7 @@ TEST(HpackInputStreamTest, DecodeNextHuffmanStringSizeLimit) {
// Decoded string overflows the max string literal. // Decoded string overflows the max string literal.
EXPECT_TRUE(input_stream.HasMoreData()); EXPECT_TRUE(input_stream.HasMoreData());
EXPECT_FALSE(input_stream.DecodeNextHuffmanString(&output)); EXPECT_FALSE(input_stream.DecodeNextHuffmanString(&output));
EXPECT_FALSE(input_stream.NeedMoreData());
} }
TEST(HpackInputStreamTest, DecodeNextHuffmanStringNotEnoughInput) { TEST(HpackInputStreamTest, DecodeNextHuffmanStringNotEnoughInput) {
...@@ -535,6 +563,7 @@ TEST(HpackInputStreamTest, DecodeNextHuffmanStringNotEnoughInput) { ...@@ -535,6 +563,7 @@ TEST(HpackInputStreamTest, DecodeNextHuffmanStringNotEnoughInput) {
// Not enough buffer for declared encoded length. // Not enough buffer for declared encoded length.
EXPECT_TRUE(input_stream.HasMoreData()); EXPECT_TRUE(input_stream.HasMoreData());
EXPECT_FALSE(input_stream.DecodeNextHuffmanString(&output)); EXPECT_FALSE(input_stream.DecodeNextHuffmanString(&output));
EXPECT_TRUE(input_stream.NeedMoreData());
} }
TEST(HpackInputStreamTest, PeekBitsAndConsume) { TEST(HpackInputStreamTest, PeekBitsAndConsume) {
...@@ -705,6 +734,75 @@ TEST(HpackInputStreamTest, ConsumeByteRemainder) { ...@@ -705,6 +734,75 @@ TEST(HpackInputStreamTest, ConsumeByteRemainder) {
EXPECT_FALSE(input_stream.HasMoreData()); EXPECT_FALSE(input_stream.HasMoreData());
} }
} // namespace TEST(HpackInputStreamTest, IncompleteHeaderMatchPrefixAndConsume) {
HpackInputStream input_stream(kLiteralBound, "");
HpackInputStreamPeer input_stream_peer(&input_stream);
EXPECT_FALSE(input_stream.MatchPrefixAndConsume(kIndexedOpcode));
EXPECT_EQ(0u, input_stream_peer.ParsedBytesCurrent());
EXPECT_TRUE(input_stream.NeedMoreData());
}
TEST(HpackInputStreamTest, IncompleteHeaderDecodeNextUint32) {
// First byte only
HpackInputStream input_stream1(kLiteralBound, "\xff");
HpackInputStreamPeer input_stream1_peer(&input_stream1);
EXPECT_TRUE(input_stream1.MatchPrefixAndConsume(kIndexedOpcode));
uint32_t result;
EXPECT_FALSE(input_stream1.DecodeNextUint32(&result));
EXPECT_TRUE(input_stream1.NeedMoreData());
EXPECT_EQ(1u, input_stream1_peer.ParsedBytesCurrent());
// No last byte
HpackInputStream input_stream2(kLiteralBound, "\xff\x80\x80\x80");
HpackInputStreamPeer input_stream2_peer(&input_stream2);
EXPECT_TRUE(input_stream2.MatchPrefixAndConsume(kIndexedOpcode));
EXPECT_FALSE(input_stream2.DecodeNextUint32(&result));
EXPECT_TRUE(input_stream2.NeedMoreData());
EXPECT_EQ(4u, input_stream2_peer.ParsedBytesCurrent());
// Error happens before finishing parsing.
HpackInputStream input_stream3(kLiteralBound, "\xff\xff\xff\xff\xff\xff\xff");
HpackInputStreamPeer input_stream3_peer(&input_stream3);
EXPECT_TRUE(input_stream3.MatchPrefixAndConsume(kIndexedOpcode));
EXPECT_FALSE(input_stream3.DecodeNextUint32(&result));
EXPECT_FALSE(input_stream3.NeedMoreData());
EXPECT_EQ(6u, input_stream3_peer.ParsedBytesCurrent());
}
TEST(HpackInputStreamTest, IncompleteHeaderDecodeNextIdentityString) {
HpackInputStream input_stream1(kLiteralBound, "\x0estring litera");
HpackInputStreamPeer input_stream1_peer(&input_stream1);
StringPiece string_piece;
EXPECT_FALSE(input_stream1.DecodeNextIdentityString(&string_piece));
// Only parsed first byte.
EXPECT_EQ(1u, input_stream1_peer.ParsedBytesCurrent());
EXPECT_TRUE(input_stream1.NeedMoreData());
HpackInputStream input_stream2(kLiteralBound, "\x0e");
HpackInputStreamPeer input_stream2_peer(&input_stream2);
EXPECT_FALSE(input_stream2.DecodeNextIdentityString(&string_piece));
// Only parsed first byte.
EXPECT_EQ(1u, input_stream2_peer.ParsedBytesCurrent());
EXPECT_TRUE(input_stream2.NeedMoreData());
}
TEST(HpackInputStreamTest, IncompleteHeaderDecodeNextHuffmanString) {
string output, input(a2b_hex(kEncodedHuffmanFixture));
input.resize(input.size() - 1); // Remove last byte.
HpackInputStream input_stream1(arraysize(kDecodedHuffmanFixture) - 1, input);
HpackInputStreamPeer input_stream1_peer(&input_stream1);
EXPECT_FALSE(input_stream1.DecodeNextHuffmanString(&output));
EXPECT_EQ(1u, input_stream1_peer.ParsedBytesCurrent());
EXPECT_TRUE(input_stream1.NeedMoreData());
input.erase(1, input.size()); // Remove all bytes except the first one.
HpackInputStream input_stream2(arraysize(kDecodedHuffmanFixture) - 1, input);
HpackInputStreamPeer input_stream2_peer(&input_stream2);
EXPECT_FALSE(input_stream2.DecodeNextHuffmanString(&output));
EXPECT_EQ(1u, input_stream2_peer.ParsedBytesCurrent());
EXPECT_TRUE(input_stream2.NeedMoreData());
}
} // namespace test
} // namespace net } // namespace net
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment