Commit cd7dc430 authored by Dan Zhang's avatar Dan Zhang Committed by Commit Bot

Synchronise internal QPACK implementation. Add qpack code into BUILD file.

Remove unnecessary forward declare of RandomBase in random_util_helper.h

Merge quic part internal change: 210537018, 211551817

R=rch@chromium.org

Change-Id: Idd96a1b6308dca93d68682490323d3ab7dee3f4f
Reviewed-on: https://chromium-review.googlesource.com/1228838
Commit-Queue: Dan Zhang <danzh@chromium.org>
Reviewed-by: default avatarBence Béky <bnc@chromium.org>
Reviewed-by: default avatarRyan Hamilton <rch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#592437}
parent 48d0cee6
......@@ -1385,6 +1385,13 @@ component("net") {
"third_party/quic/core/http/spdy_utils.cc",
"third_party/quic/core/http/spdy_utils.h",
"third_party/quic/core/packet_number_indexed_queue.h",
"third_party/quic/core/qpack/qpack_constants.h",
"third_party/quic/core/qpack/qpack_decoder.cc",
"third_party/quic/core/qpack/qpack_decoder.h",
"third_party/quic/core/qpack/qpack_encoder.cc",
"third_party/quic/core/qpack/qpack_encoder.h",
"third_party/quic/core/qpack/qpack_header_table.cc",
"third_party/quic/core/qpack/qpack_header_table.h",
"third_party/quic/core/quic_ack_listener_interface.cc",
"third_party/quic/core/quic_ack_listener_interface.h",
"third_party/quic/core/quic_alarm.cc",
......@@ -5002,6 +5009,8 @@ test("net_unittests") {
"third_party/quic/core/http/quic_header_list_test.cc",
"third_party/quic/core/http/quic_headers_stream_test.cc",
"third_party/quic/core/packet_number_indexed_queue_test.cc",
"third_party/quic/core/qpack/qpack_decoder_test.cc",
"third_party/quic/core/qpack/qpack_encoder_test.cc",
"third_party/quic/core/quic_alarm_test.cc",
"third_party/quic/core/quic_arena_scoped_ptr_test.cc",
"third_party/quic/core/quic_bandwidth_test.cc",
......@@ -5091,6 +5100,10 @@ test("net_unittests") {
"third_party/quic/core/http/quic_spdy_session_test.cc",
"third_party/quic/core/http/quic_spdy_stream_test.cc",
"third_party/quic/core/http/spdy_utils_test.cc",
"third_party/quic/core/qpack/qpack_header_table_test.cc",
"third_party/quic/core/qpack/qpack_round_trip_test.cc",
"third_party/quic/core/qpack/qpack_test_utils.cc",
"third_party/quic/core/qpack/qpack_test_utils.h",
"third_party/quic/core/quic_dispatcher_test.cc",
"third_party/quic/core/quic_lru_cache_test.cc",
"third_party/quic/core/quic_one_block_arena_test.cc",
......
......@@ -7,8 +7,6 @@ namespace http2 {
namespace test {
class RandomBase;
inline Http2String RandomString(RandomBase* random,
int len,
Http2StringPiece alphabet) {
......
......@@ -15,6 +15,7 @@ namespace quic {
// 5.4.2.1. Indexed Header Field
const uint8_t kIndexedHeaderFieldOpcode = 0b10000000;
const uint8_t kIndexedHeaderFieldOpcodeMask = 0b10000000;
const uint8_t kIndexedHeaderFieldStaticBit = 0b01000000;
const uint8_t kIndexedHeaderFieldPrefixLength = 6;
// 5.4.2.2. Indexed Header Field With Post-Base Index
......@@ -25,6 +26,7 @@ const uint8_t kIndexedHeaderFieldPostBasePrefixLength = 4;
// 5.4.2.3. Literal Header Field With Name Reference
const uint8_t kLiteralHeaderFieldNameReferenceOpcode = 0b01000000;
const uint8_t kLiteralHeaderFieldNameReferenceOpcodeMask = 0b11000000;
const uint8_t kLiteralHeaderFieldNameReferenceStaticBit = 0b00010000;
const uint8_t kLiteralHeaderFieldNameReferencePrefixLength = 4;
// 5.4.2.4. Literal Header Field With Post-Base Name Reference
......
......@@ -12,11 +12,15 @@
namespace quic {
QpackDecoder::ProgressiveDecoder::ProgressiveDecoder(
QpackHeaderTable* header_table,
QpackDecoder::HeadersHandlerInterface* handler)
: handler_(handler),
state_(State::kParseOpcode),
: 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) {}
......@@ -28,21 +32,18 @@ void QpackDecoder::ProgressiveDecoder::Decode(QuicStringPiece data) {
return;
}
size_t bytes_consumed = 0;
while (true) {
size_t bytes_consumed = 0;
switch (state_) {
case State::kParseOpcode:
bytes_consumed = DoParseOpcode(data);
break;
case State::kNameLengthStart:
bytes_consumed = DoNameLengthStart(data);
case State::kStart:
bytes_consumed = DoStart(data);
break;
case State::kNameLengthResume:
bytes_consumed = DoNameLengthResume(data);
case State::kVarintResume:
bytes_consumed = DoVarintResume(data);
break;
case State::kNameLengthDone:
DoNameLengthDone();
bytes_consumed = 0;
case State::kVarintDone:
DoVarintDone();
break;
case State::kNameString:
bytes_consumed = DoNameString(data);
......@@ -55,14 +56,12 @@ void QpackDecoder::ProgressiveDecoder::Decode(QuicStringPiece data) {
break;
case State::kValueLengthDone:
DoValueLengthDone();
bytes_consumed = 0;
break;
case State::kValueString:
bytes_consumed = DoValueString(data);
break;
case State::kDone:
DoDone();
bytes_consumed = 0;
break;
}
......@@ -76,7 +75,7 @@ void QpackDecoder::ProgressiveDecoder::Decode(QuicStringPiece data) {
data.size() - bytes_consumed);
// Stop processing if no more data but next state would require it.
if (data.empty() && (state_ != State::kNameLengthDone) &&
if (data.empty() && (state_ != State::kVarintDone) &&
(state_ != State::kValueLengthDone) && (state_ != State::kDone)) {
return;
}
......@@ -91,77 +90,81 @@ void QpackDecoder::ProgressiveDecoder::EndHeaderBlock() {
return;
}
if (state_ == State::kParseOpcode) {
if (state_ == State::kStart) {
handler_->OnDecodingCompleted();
} else {
OnError("Incomplete header block.");
}
}
// This method always returns 0 since some bits of the byte containing the
// opcode must be parsed by other methods.
size_t QpackDecoder::ProgressiveDecoder::DoParseOpcode(QuicStringPiece data) {
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 not implemented.");
OnError("Indexed Header Field with dynamic entry not implemented.");
return 0;
}
if ((data[0] & kIndexedHeaderFieldPostBaseOpcodeMask) ==
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;
}
if ((data[0] & kLiteralHeaderFieldNameReferenceOpcodeMask) ==
} else if ((data[0] & kLiteralHeaderFieldNameReferenceOpcodeMask) ==
kLiteralHeaderFieldNameReferenceOpcode) {
if ((data[0] & kLiteralHeaderFieldNameReferenceStaticBit) !=
kLiteralHeaderFieldNameReferenceStaticBit) {
// TODO(bnc): Implement.
OnError("Literal Header Field With Name Reference not implemented.");
OnError(
"Literal Header Field With Name Reference with dynamic entry not "
"implemented.");
return 0;
}
if ((data[0] & kLiteralHeaderFieldPostBaseOpcodeMask) ==
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;
}
DCHECK_EQ(kLiteralHeaderFieldOpcode, data[0] & kLiteralHeaderFieldOpcodeMask);
state_ = State::kNameLengthStart;
return 0;
}
size_t QpackDecoder::ProgressiveDecoder::DoNameLengthStart(
QuicStringPiece data) {
DCHECK(!data.empty());
} else {
DCHECK_EQ(kLiteralHeaderFieldOpcode,
data[0] & kLiteralHeaderFieldOpcodeMask);
is_huffman_ = (data[0] & kLiteralNameHuffmanMask) == kLiteralNameHuffmanMask;
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], kLiteralHeaderFieldPrefixLength, &buffer);
varint_decoder_.Start(data[0], prefix_length, &buffer);
size_t bytes_consumed = 1 + buffer.Offset();
switch (status) {
case http2::DecodeStatus::kDecodeDone:
state_ = State::kNameLengthDone;
state_ = State::kVarintDone;
return bytes_consumed;
case http2::DecodeStatus::kDecodeInProgress:
state_ = State::kNameLengthResume;
state_ = State::kVarintResume;
return bytes_consumed;
case http2::DecodeStatus::kDecodeError:
OnError("NameLen too large in literal header field without reference.");
OnError("Encoded integer too large.");
return bytes_consumed;
}
}
size_t QpackDecoder::ProgressiveDecoder::DoNameLengthResume(
QuicStringPiece data) {
size_t QpackDecoder::ProgressiveDecoder::DoVarintResume(QuicStringPiece data) {
DCHECK(!data.empty());
http2::DecodeBuffer buffer(data);
......@@ -170,34 +173,48 @@ size_t QpackDecoder::ProgressiveDecoder::DoNameLengthResume(
size_t bytes_consumed = buffer.Offset();
switch (status) {
case http2::DecodeStatus::kDecodeDone:
state_ = State::kNameLengthDone;
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("NameLen too large in literal header field without reference.");
OnError("Encoded integer too large.");
return bytes_consumed;
}
}
void QpackDecoder::ProgressiveDecoder::DoNameLengthDone() {
name_.clear();
void QpackDecoder::ProgressiveDecoder::DoVarintDone() {
if (literal_name_) {
name_length_ = varint_decoder_.value();
name_.clear();
name_.reserve(name_length_);
state_ = State::kNameString;
return;
}
if (name_length_ == 0) {
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;
}
name_.reserve(name_length_);
state_ = State::kNameString;
// 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::DoNameString(QuicStringPiece data) {
DCHECK(!data.empty());
DCHECK_LT(name_.size(), name_length_);
DCHECK_LE(name_.size(), name_length_);
size_t bytes_consumed = std::min(name_length_ - name_.size(), data.size());
name_.append(data.data(), bytes_consumed);
......@@ -227,6 +244,7 @@ size_t QpackDecoder::ProgressiveDecoder::DoNameString(QuicStringPiece data) {
size_t QpackDecoder::ProgressiveDecoder::DoValueLengthStart(
QuicStringPiece data) {
DCHECK(!data.empty());
DCHECK(literal_value_);
is_huffman_ =
(data[0] & kLiteralValueHuffmanMask) == kLiteralValueHuffmanMask;
......@@ -244,7 +262,7 @@ size_t QpackDecoder::ProgressiveDecoder::DoValueLengthStart(
state_ = State::kValueLengthResume;
return bytes_consumed;
case http2::DecodeStatus::kDecodeError:
OnError("ValueLen too large in literal header field without reference.");
OnError("ValueLen too large.");
return bytes_consumed;
}
}
......@@ -266,7 +284,7 @@ size_t QpackDecoder::ProgressiveDecoder::DoValueLengthResume(
DCHECK(buffer.Empty());
return bytes_consumed;
case http2::DecodeStatus::kDecodeError:
OnError("ValueLen too large in literal header field without reference.");
OnError("ValueLen too large.");
return bytes_consumed;
}
}
......@@ -315,7 +333,7 @@ size_t QpackDecoder::ProgressiveDecoder::DoValueString(QuicStringPiece data) {
void QpackDecoder::ProgressiveDecoder::DoDone() {
handler_->OnHeaderDecoded(name_, value_);
state_ = State::kParseOpcode;
state_ = State::kStart;
}
void QpackDecoder::ProgressiveDecoder::OnError(QuicStringPiece error_message) {
......@@ -328,7 +346,7 @@ void QpackDecoder::ProgressiveDecoder::OnError(QuicStringPiece error_message) {
std::unique_ptr<QpackDecoder::ProgressiveDecoder>
QpackDecoder::DecodeHeaderBlock(
QpackDecoder::HeadersHandlerInterface* handler) {
return std::make_unique<ProgressiveDecoder>(handler);
return std::make_unique<ProgressiveDecoder>(&header_table_, handler);
}
} // namespace quic
......@@ -9,6 +9,7 @@
#include "net/third_party/http2/hpack/huffman/hpack_huffman_decoder.h"
#include "net/third_party/http2/hpack/varint/hpack_varint_decoder.h"
#include "net/third_party/quic/core/qpack/qpack_header_table.h"
#include "net/third_party/quic/platform/api/quic_export.h"
#include "net/third_party/quic/platform/api/quic_string.h"
#include "net/third_party/quic/platform/api/quic_string_piece.h"
......@@ -48,7 +49,11 @@ class QUIC_EXPORT_PRIVATE QpackDecoder {
// Class to decode a single header block.
class QUIC_EXPORT_PRIVATE ProgressiveDecoder {
public:
explicit ProgressiveDecoder(HeadersHandlerInterface* handler);
ProgressiveDecoder() = delete;
ProgressiveDecoder(QpackHeaderTable* header_table,
HeadersHandlerInterface* handler);
ProgressiveDecoder(const ProgressiveDecoder&) = delete;
ProgressiveDecoder& operator=(const ProgressiveDecoder&) = delete;
~ProgressiveDecoder() = default;
// Provide a data fragment to decode.
......@@ -60,24 +65,27 @@ class QUIC_EXPORT_PRIVATE QpackDecoder {
private:
enum class State {
kParseOpcode,
kNameLengthStart,
kNameLengthResume,
kNameLengthDone,
// Every instruction starts encoding an integer on the first octet:
// either an index or the length of the name string literal.
kStart,
kVarintResume,
kVarintDone,
// This might be followed by the name as a string literal.
kNameString,
// This might be followed by the length of the value.
kValueLengthStart,
kValueLengthResume,
kValueLengthDone,
// This might be followed by the value as a string literal.
kValueString,
kDone,
};
// One method for each state. Some take input data and return the number of
// octets processed. Some only change internal state.
size_t DoParseOpcode(QuicStringPiece data);
size_t DoNameLengthStart(QuicStringPiece data);
size_t DoNameLengthResume(QuicStringPiece data);
void DoNameLengthDone();
size_t DoStart(QuicStringPiece data);
size_t DoVarintResume(QuicStringPiece data);
void DoVarintDone();
size_t DoNameString(QuicStringPiece data);
size_t DoValueLengthStart(QuicStringPiece data);
size_t DoValueLengthResume(QuicStringPiece data);
......@@ -87,6 +95,7 @@ class QUIC_EXPORT_PRIVATE QpackDecoder {
void OnError(QuicStringPiece error_message);
const QpackHeaderTable* const header_table_;
HeadersHandlerInterface* handler_;
State state_;
http2::HpackVarintDecoder varint_decoder_;
......@@ -98,19 +107,28 @@ class QUIC_EXPORT_PRIVATE QpackDecoder {
// True if a decoding error has been detected.
bool error_detected_;
// Temporarily store decoded length for header name.
// Must be reset when header name is completely parsed.
// The following variables are used to carry information between states
// within a single header field. That is, a value assigned while decoding
// one header field shall never be used for decoding subsequent header
// fields.
// True if the header field name is encoded as a string literal.
bool literal_name_;
// True if the header field value is encoded as a string literal.
bool literal_value_;
// Decoded length for header name.
size_t name_length_;
// Temporarily store decoded length for header value.
// Must be reset when header value is completely parsed.
// Decoded length for header value.
size_t value_length_;
// Temporarily store whether the currently parsed string (name or value) is
// Whether the currently parsed string (name or value) is
// Huffman encoded.
bool is_huffman_;
// Temporarily store decoded header name and value.
// Decoded header name and value.
QuicString name_;
QuicString value_;
};
......@@ -120,6 +138,9 @@ class QUIC_EXPORT_PRIVATE QpackDecoder {
// is destroyed or the decoder calls |handler->OnHeaderBlockEnd()|.
std::unique_ptr<ProgressiveDecoder> DecodeHeaderBlock(
HeadersHandlerInterface* handler);
private:
QpackHeaderTable header_table_;
};
} // namespace quic
......
......@@ -9,8 +9,6 @@
#include "net/third_party/quic/platform/api/quic_test.h"
#include "net/third_party/quic/platform/api/quic_text_utils.h"
#include "net/third_party/spdy/core/spdy_header_block.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::StrictMock;
using ::testing::Values;
......@@ -70,36 +68,43 @@ TEST_P(QpackDecoderTest, Empty) {
}
TEST_P(QpackDecoderTest, EmptyName) {
EXPECT_CALL(handler_, OnHeaderDecoded("", "foo"));
EXPECT_CALL(handler_,
OnHeaderDecoded(QuicStringPiece(""), QuicStringPiece("foo")));
EXPECT_CALL(handler_, OnDecodingCompleted());
Decode(QuicTextUtils::HexDecode("2003666f6f"));
}
TEST_P(QpackDecoderTest, EmptyValue) {
EXPECT_CALL(handler_, OnHeaderDecoded("foo", ""));
EXPECT_CALL(handler_,
OnHeaderDecoded(QuicStringPiece("foo"), QuicStringPiece("")));
EXPECT_CALL(handler_, OnDecodingCompleted());
Decode(QuicTextUtils::HexDecode("23666f6f00"));
}
TEST_P(QpackDecoderTest, EmptyNameAndValue) {
EXPECT_CALL(handler_, OnHeaderDecoded("", ""));
EXPECT_CALL(handler_,
OnHeaderDecoded(QuicStringPiece(""), QuicStringPiece("")));
EXPECT_CALL(handler_, OnDecodingCompleted());
Decode(QuicTextUtils::HexDecode("2000"));
}
TEST_P(QpackDecoderTest, Simple) {
EXPECT_CALL(handler_, OnHeaderDecoded("foo", "bar"));
EXPECT_CALL(handler_,
OnHeaderDecoded(QuicStringPiece("foo"), QuicStringPiece("bar")));
EXPECT_CALL(handler_, OnDecodingCompleted());
Decode(QuicTextUtils::HexDecode("23666f6f03626172"));
}
TEST_P(QpackDecoderTest, Multiple) {
EXPECT_CALL(handler_, OnHeaderDecoded("foo", "bar"));
EXPECT_CALL(handler_, OnHeaderDecoded("foobaar", std::string(127, 'a')));
EXPECT_CALL(handler_,
OnHeaderDecoded(QuicStringPiece("foo"), QuicStringPiece("bar")));
QuicString str(127, 'a');
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece("foobaar"),
QuicStringPiece(str)));
EXPECT_CALL(handler_, OnDecodingCompleted());
Decode(QuicTextUtils::HexDecode(
......@@ -115,39 +120,56 @@ TEST_P(QpackDecoderTest, Multiple) {
}
TEST_P(QpackDecoderTest, NameLenTooLarge) {
EXPECT_CALL(
handler_,
OnDecodingErrorDetected(
"NameLen too large in literal header field without reference."));
EXPECT_CALL(handler_, OnDecodingErrorDetected(
QuicStringPiece("Encoded integer too large.")));
Decode(QuicTextUtils::HexDecode("27ffffffffff"));
}
TEST_P(QpackDecoderTest, ValueLenTooLarge) {
EXPECT_CALL(
handler_,
OnDecodingErrorDetected(
"ValueLen too large in literal header field without reference."));
EXPECT_CALL(handler_,
OnDecodingErrorDetected(QuicStringPiece("ValueLen too large.")));
Decode(QuicTextUtils::HexDecode("23666f6f7fffffffffff"));
}
TEST_P(QpackDecoderTest, IncompleteHeaderBlock) {
EXPECT_CALL(handler_, OnDecodingErrorDetected("Incomplete header block."));
EXPECT_CALL(handler_, OnDecodingErrorDetected(
QuicStringPiece("Incomplete header block.")));
Decode(QuicTextUtils::HexDecode("2366"));
}
TEST_P(QpackDecoderTest, HuffmanSimple) {
EXPECT_CALL(handler_, OnHeaderDecoded("custom-key", "custom-value"));
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece("custom-key"),
QuicStringPiece("custom-value")));
EXPECT_CALL(handler_, OnDecodingCompleted());
Decode(QuicTextUtils::HexDecode("2f0125a849e95ba97d7f8925a849e95bb8e8b4bf"));
Decode(QuicTextUtils::HexDecode(
QuicStringPiece("2f0125a849e95ba97d7f8925a849e95bb8e8b4bf")));
}
TEST_P(QpackDecoderTest, AlternatingHuffmanNonHuffman) {
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece("custom-key"),
QuicStringPiece("custom-value")))
.Times(4);
EXPECT_CALL(handler_, OnDecodingCompleted());
Decode(QuicTextUtils::HexDecode(
"2f0125a849e95ba97d7f" // Huffman-encoded name.
"8925a849e95bb8e8b4bf" // Huffman-encoded value.
"2703637573746f6d2d6b6579" // Non-Huffman encoded name.
"0c637573746f6d2d76616c7565" // Non-Huffman encoded value.
"2f0125a849e95ba97d7f" // Huffman-encoded name.
"0c637573746f6d2d76616c7565" // Non-Huffman encoded value.
"2703637573746f6d2d6b6579" // Non-Huffman encoded name.
"8925a849e95bb8e8b4bf" // Huffman-encoded value.
));
}
TEST_P(QpackDecoderTest, HuffmanNameDoesNotHaveEOSPrefix) {
EXPECT_CALL(handler_,
OnDecodingErrorDetected("Error in Huffman-encoded name."));
EXPECT_CALL(handler_, OnDecodingErrorDetected(
QuicStringPiece("Error in Huffman-encoded name.")));
// 'y' ends in 0b0 on the most significant bit of the last byte.
// The remaining 7 bits must be a prefix of EOS, which is all 1s.
......@@ -155,8 +177,8 @@ TEST_P(QpackDecoderTest, HuffmanNameDoesNotHaveEOSPrefix) {
}
TEST_P(QpackDecoderTest, HuffmanValueDoesNotHaveEOSPrefix) {
EXPECT_CALL(handler_,
OnDecodingErrorDetected("Error in Huffman-encoded value."));
EXPECT_CALL(handler_, OnDecodingErrorDetected(QuicStringPiece(
"Error in Huffman-encoded value.")));
// 'e' ends in 0b101, taking up the 3 most significant bits of the last byte.
// The remaining 5 bits must be a prefix of EOS, which is all 1s.
......@@ -164,8 +186,8 @@ TEST_P(QpackDecoderTest, HuffmanValueDoesNotHaveEOSPrefix) {
}
TEST_P(QpackDecoderTest, HuffmanNameEOSPrefixTooLong) {
EXPECT_CALL(handler_,
OnDecodingErrorDetected("Error in Huffman-encoded name."));
EXPECT_CALL(handler_, OnDecodingErrorDetected(
QuicStringPiece("Error in Huffman-encoded name.")));
// The trailing EOS prefix must be at most 7 bits long. Appending one octet
// with value 0xff is invalid, even though 0b111111111111111 (15 bits) is a
......@@ -175,8 +197,8 @@ TEST_P(QpackDecoderTest, HuffmanNameEOSPrefixTooLong) {
}
TEST_P(QpackDecoderTest, HuffmanValueEOSPrefixTooLong) {
EXPECT_CALL(handler_,
OnDecodingErrorDetected("Error in Huffman-encoded value."));
EXPECT_CALL(handler_, OnDecodingErrorDetected(QuicStringPiece(
"Error in Huffman-encoded value.")));
// The trailing EOS prefix must be at most 7 bits long. Appending one octet
// with value 0xff is invalid, even though 0b1111111111111 (13 bits) is a
......@@ -185,6 +207,58 @@ TEST_P(QpackDecoderTest, HuffmanValueEOSPrefixTooLong) {
QuicTextUtils::HexDecode("2f0125a849e95ba97d7f8a25a849e95bb8e8b4bfff"));
}
TEST_P(QpackDecoderTest, StaticTable) {
// A header name that has multiple entries with different values.
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece(":method"),
QuicStringPiece("GET")));
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece(":method"),
QuicStringPiece("POST")));
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece(":method"),
QuicStringPiece("CONNECT")));
// A header name that has a single entry with non-empty value.
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece("accept-encoding"),
QuicStringPiece("gzip, deflate")));
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece("accept-encoding"),
QuicStringPiece("brotli")));
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece("accept-encoding"),
QuicStringPiece("")));
// A header name that has a single entry with empty value.
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece("cache-control"),
QuicStringPiece("")));
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece("cache-control"),
QuicStringPiece("foo")));
EXPECT_CALL(handler_, OnDecodingCompleted());
Decode(QuicTextUtils::HexDecode(
"c2c35207434f4e4e454354d05f010662726f746c695f0100d85f0903666f6f"));
}
TEST_P(QpackDecoderTest, TooLowStaticTableIndex) {
// This is the first entry in the static table with index 1.
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece(":authority"),
QuicStringPiece("")));
// Addressing entry 0 should trigger an error.
EXPECT_CALL(handler_, OnDecodingErrorDetected(
QuicStringPiece("Invalid static table index.")));
Decode(QuicTextUtils::HexDecode("c1c0"));
}
TEST_P(QpackDecoderTest, TooHighStaticTableIndex) {
// This is the last entry in the static table with index 61.
EXPECT_CALL(handler_, OnHeaderDecoded(QuicStringPiece("www-authenticate"),
QuicStringPiece("")));
// Addressing entry 62 should trigger an error.
EXPECT_CALL(handler_, OnDecodingErrorDetected(
QuicStringPiece("Invalid static table index.")));
Decode(QuicTextUtils::HexDecode("fdfe"));
}
} // namespace
} // namespace test
} // namespace quic
......@@ -8,6 +8,7 @@
#include <cstdint>
#include <memory>
#include "net/third_party/quic/core/qpack/qpack_header_table.h"
#include "net/third_party/quic/platform/api/quic_export.h"
#include "net/third_party/spdy/core/hpack/hpack_encoder.h"
#include "net/third_party/spdy/core/spdy_header_block.h"
......@@ -26,6 +27,9 @@ class QUIC_EXPORT_PRIVATE QpackEncoder {
// during the lifetime of the returned ProgressiveEncoder instance.
std::unique_ptr<spdy::HpackEncoder::ProgressiveEncoder> EncodeHeaderSet(
const spdy::SpdyHeaderBlock* header_list);
private:
QpackHeaderTable header_table_;
};
} // namespace quic
......
......@@ -95,6 +95,36 @@ TEST_P(QpackEncoderTest, Multiple) {
output);
}
TEST_P(QpackEncoderTest, StaticTable) {
{
spdy::SpdyHeaderBlock header_list;
header_list[":method"] = "GET";
header_list["accept-encoding"] = "gzip, deflate";
header_list["cache-control"] = "";
QuicString output = Encode(&header_list);
EXPECT_EQ(QuicTextUtils::HexDecode("c2d0d8"), output);
}
{
spdy::SpdyHeaderBlock header_list;
header_list[":method"] = "POST";
header_list["accept-encoding"] = "brotli";
header_list["cache-control"] = "foo";
QuicString output = Encode(&header_list);
DLOG(INFO) << QuicTextUtils::HexEncode(output);
EXPECT_EQ(QuicTextUtils::HexDecode("c35f01858ec3a6837f5f098294e7"), output);
}
{
spdy::SpdyHeaderBlock header_list;
header_list[":method"] = "CONNECT";
header_list["accept-encoding"] = "";
QuicString output = Encode(&header_list);
EXPECT_EQ(QuicTextUtils::HexDecode("5207434f4e4e4543545f0100"), output);
}
}
} // namespace
} // namespace test
} // namespace quic
#include "net/third_party/quic/core/qpack/qpack_header_table.h"
#include "base/logging.h"
#include "net/third_party/spdy/core/hpack/hpack_constants.h"
#include "net/third_party/spdy/core/hpack/hpack_entry.h"
#include "net/third_party/spdy/core/hpack/hpack_static_table.h"
namespace quic {
// Currently using HPACK static tables.
// TODO(bnc): QPACK is likely to get its own static table. When this happens,
// fork HpackStaticTable code and modify static table.
QpackHeaderTable::QpackHeaderTable()
: static_entries_(spdy::ObtainHpackStaticTable().GetStaticEntries()),
static_index_(spdy::ObtainHpackStaticTable().GetStaticIndex()),
static_name_index_(spdy::ObtainHpackStaticTable().GetStaticNameIndex()) {}
QpackHeaderTable::~QpackHeaderTable() = default;
const spdy::HpackEntry* QpackHeaderTable::LookupEntry(size_t index) const {
// Static table indexing starts with 1.
if (index == 0 || index > static_entries_.size()) {
return nullptr;
}
return &static_entries_[index - 1];
}
QpackHeaderTable::MatchType QpackHeaderTable::FindHeaderField(
QuicStringPiece name,
QuicStringPiece value,
size_t* index) const {
spdy::HpackEntry query(name, value);
UnorderedEntrySet::const_iterator static_index_it =
static_index_.find(&query);
if (static_index_it != static_index_.end()) {
DCHECK((*static_index_it)->IsStatic());
// Static table indexing starts with 1.
*index = (*static_index_it)->InsertionIndex() + 1;
return MatchType::kNameAndValue;
}
NameToEntryMap::const_iterator static_name_index_it =
static_name_index_.find(name);
if (static_name_index_it != static_name_index_.end()) {
DCHECK(static_name_index_it->second->IsStatic());
// Static table indexing starts with 1.
*index = static_name_index_it->second->InsertionIndex() + 1;
return MatchType::kName;
}
return MatchType::kNoMatch;
}
} // namespace quic
#ifndef NET_THIRD_PARTY_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_
#define NET_THIRD_PARTY_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_
#include <cstddef>
#include "net/third_party/quic/platform/api/quic_export.h"
#include "net/third_party/quic/platform/api/quic_string_piece.h"
#include "net/third_party/spdy/core/hpack/hpack_header_table.h"
namespace quic {
// This class manages the QPACK static and dynamic tables.
// TODO(bnc): Implement dynamic table.
class QUIC_EXPORT_PRIVATE QpackHeaderTable {
public:
using EntryTable = spdy::HpackHeaderTable::EntryTable;
using EntryHasher = spdy::HpackHeaderTable::EntryHasher;
using EntriesEq = spdy::HpackHeaderTable::EntriesEq;
using UnorderedEntrySet = spdy::HpackHeaderTable::UnorderedEntrySet;
using NameToEntryMap = spdy::HpackHeaderTable::NameToEntryMap;
// Result of header table lookup.
enum class MatchType { kNameAndValue, kName, kNoMatch };
QpackHeaderTable();
QpackHeaderTable(const QpackHeaderTable&) = delete;
QpackHeaderTable& operator=(const QpackHeaderTable&) = delete;
~QpackHeaderTable();
// Returns the entry at given index, or nullptr on error.
const spdy::HpackEntry* LookupEntry(size_t index) const;
// Returns the index of an entry with matching name and value if such exists,
// otherwise one with matching name is such exists.
MatchType FindHeaderField(QuicStringPiece name,
QuicStringPiece value,
size_t* index) const;
private:
// |static_entries_|, |static_index_|, |static_name_index_| are owned by
// HpackStaticTable singleton.
// Tracks HpackEntries by index.
const EntryTable& static_entries_;
// Tracks the unique HpackEntry for a given header name and value.
const UnorderedEntrySet& static_index_;
// Tracks the first static entry for each name in the static table.
const NameToEntryMap& static_name_index_;
};
} // namespace quic
#endif // NET_THIRD_PARTY_QUIC_CORE_QPACK_QPACK_HEADER_TABLE_H_
#include "net/third_party/quic/core/qpack/qpack_header_table.h"
#include "net/third_party/quic/platform/api/quic_test.h"
#include "net/third_party/spdy/core/hpack/hpack_entry.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace quic {
namespace test {
namespace {
class QpackHeaderTableTest : public QuicTest {
protected:
QpackHeaderTable table_;
};
TEST_F(QpackHeaderTableTest, LookupEntry) {
const auto* entry = table_.LookupEntry(0);
EXPECT_FALSE(entry);
entry = table_.LookupEntry(1);
EXPECT_EQ(":authority", entry->name());
EXPECT_EQ("", entry->value());
entry = table_.LookupEntry(2);
EXPECT_EQ(":method", entry->name());
EXPECT_EQ("GET", entry->value());
entry = table_.LookupEntry(61);
EXPECT_EQ("www-authenticate", entry->name());
EXPECT_EQ("", entry->value());
entry = table_.LookupEntry(62);
EXPECT_FALSE(entry);
}
TEST_F(QpackHeaderTableTest, FindHeaderField) {
// A header name that has multiple entries with different values.
size_t index = 0;
QpackHeaderTable::MatchType matchtype =
table_.FindHeaderField(":method", "GET", &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kNameAndValue, matchtype);
EXPECT_EQ(2u, index);
matchtype = table_.FindHeaderField(":method", "POST", &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kNameAndValue, matchtype);
EXPECT_EQ(3u, index);
matchtype = table_.FindHeaderField(":method", "CONNECT", &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kName, matchtype);
EXPECT_EQ(2u, index);
// A header name that has a single entry with non-empty value.
matchtype =
table_.FindHeaderField("accept-encoding", "gzip, deflate", &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kNameAndValue, matchtype);
EXPECT_EQ(16u, index);
matchtype = table_.FindHeaderField("accept-encoding", "brotli", &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kName, matchtype);
EXPECT_EQ(16u, index);
matchtype = table_.FindHeaderField("accept-encoding", "", &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kName, matchtype);
EXPECT_EQ(16u, index);
// A header name that has a single entry with empty value.
matchtype = table_.FindHeaderField("cache-control", "", &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kNameAndValue, matchtype);
EXPECT_EQ(24u, index);
matchtype = table_.FindHeaderField("cache-control", "foo", &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kName, matchtype);
EXPECT_EQ(24u, index);
// No matching header name.
matchtype = table_.FindHeaderField("foo", "", &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kNoMatch, matchtype);
matchtype = table_.FindHeaderField("foo", "bar", &index);
EXPECT_EQ(QpackHeaderTable::MatchType::kNoMatch, matchtype);
}
} // namespace
} // namespace test
} // namespace quic
......@@ -90,6 +90,41 @@ TEST_P(QpackRoundTripTest, MultipleWithLongEntries) {
EXPECT_EQ(header_list, output);
}
TEST_P(QpackRoundTripTest, StaticTable) {
{
spdy::SpdyHeaderBlock header_list;
header_list[":method"] = "GET";
header_list["accept-encoding"] = "gzip, deflate";
header_list["cache-control"] = "";
header_list["foo"] = "bar";
header_list[":path"] = "/";
spdy::SpdyHeaderBlock output = EncodeThenDecode(header_list);
EXPECT_EQ(header_list, output);
}
{
spdy::SpdyHeaderBlock header_list;
header_list[":method"] = "POST";
header_list["accept-encoding"] = "brotli";
header_list["cache-control"] = "foo";
header_list["foo"] = "bar";
header_list[":path"] = "/";
spdy::SpdyHeaderBlock output = EncodeThenDecode(header_list);
EXPECT_EQ(header_list, output);
}
{
spdy::SpdyHeaderBlock header_list;
header_list[":method"] = "CONNECT";
header_list["accept-encoding"] = "";
header_list["foo"] = "bar";
header_list[":path"] = "/";
spdy::SpdyHeaderBlock output = EncodeThenDecode(header_list);
EXPECT_EQ(header_list, output);
}
}
} // namespace
} // namespace test
} // namespace quic
......@@ -8,6 +8,7 @@
#include <limits>
#include "net/third_party/quic/core/qpack/qpack_encoder.h"
#include "net/third_party/quic/platform/api/quic_test.h"
namespace quic {
namespace test {
......
......@@ -11,7 +11,6 @@
#include "net/third_party/quic/platform/api/quic_string.h"
#include "net/third_party/quic/platform/api/quic_string_piece.h"
#include "net/third_party/spdy/core/spdy_header_block.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace quic {
namespace test {
......
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