Implement new PRIORITY_UPDATE frame.
PRIORITY_UPDATE frame type and wire format has changed in
https://tools.ietf.org/html/draft-ietf-httpbis-priority-02. Implement new frame
for request streams, but not the one for push streams since that is currently
unused.
Protected by FLAGS_quic_reloadable_flag_quic_new_priority_update_frame.
PiperOrigin-RevId: 341724034
Change-Id: I42e63bbb84c62f54abc28989e9eaa24685ef1596
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
index ab21d34..1a160e9 100644
--- a/quic/core/http/http_decoder.cc
+++ b/quic/core/http/http_decoder.cc
@@ -14,6 +14,7 @@
#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
#include "net/third_party/quiche/src/quic/core/quic_types.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
namespace quic {
@@ -236,6 +237,14 @@
case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE):
continue_processing = visitor_->OnPriorityUpdateFrameStart(header_length);
break;
+ case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM):
+ if (GetQuicReloadableFlag(quic_new_priority_update_frame)) {
+ QUIC_CODE_COUNT_N(quic_new_priority_update_frame, 2, 2);
+ continue_processing =
+ visitor_->OnPriorityUpdateFrameStart(header_length);
+ break;
+ }
+ ABSL_FALLTHROUGH_INTENDED;
default:
continue_processing = visitor_->OnUnknownFrameStart(
current_frame_type_, header_length, current_frame_length_);
@@ -364,6 +373,15 @@
BufferFramePayload(reader);
break;
}
+ case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM): {
+ if (GetQuicReloadableFlag(quic_new_priority_update_frame)) {
+ // TODO(bnc): Avoid buffering if the entire frame is present, and
+ // instead parse directly out of |reader|.
+ BufferFramePayload(reader);
+ break;
+ }
+ ABSL_FALLTHROUGH_INTENDED;
+ }
default: {
QuicByteCount bytes_to_read = std::min<QuicByteCount>(
remaining_frame_length_, reader->BytesRemaining());
@@ -468,6 +486,20 @@
continue_processing = visitor_->OnPriorityUpdateFrame(frame);
break;
}
+ case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM): {
+ if (GetQuicReloadableFlag(quic_new_priority_update_frame)) {
+ // TODO(bnc): Avoid buffering if the entire frame is present, and
+ // instead parse directly out of |reader|.
+ PriorityUpdateFrame frame;
+ QuicDataReader reader(buffer_.data(), current_frame_length_);
+ if (!ParseNewPriorityUpdateFrame(&reader, &frame)) {
+ return false;
+ }
+ continue_processing = visitor_->OnPriorityUpdateFrame(frame);
+ break;
+ }
+ ABSL_FALLTHROUGH_INTENDED;
+ }
default: {
continue_processing = visitor_->OnUnknownFrameEnd();
break;
@@ -603,6 +635,22 @@
return true;
}
+bool HttpDecoder::ParseNewPriorityUpdateFrame(QuicDataReader* reader,
+ PriorityUpdateFrame* frame) {
+ frame->prioritized_element_type = REQUEST_STREAM;
+
+ if (!reader->ReadVarInt62(&frame->prioritized_element_id)) {
+ RaiseError(QUIC_HTTP_FRAME_ERROR, "Unable to read prioritized element id.");
+ return false;
+ }
+
+ absl::string_view priority_field_value = reader->ReadRemainingPayload();
+ frame->priority_field_value =
+ std::string(priority_field_value.data(), priority_field_value.size());
+
+ return true;
+}
+
QuicByteCount HttpDecoder::MaxFrameLength(uint64_t frame_type) {
switch (frame_type) {
case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH):
@@ -617,6 +665,9 @@
case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE):
// This limit is arbitrary.
return 1024 * 1024;
+ case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM):
+ // This limit is arbitrary.
+ return 1024 * 1024;
default:
// Other frames require no data buffering, so it's safe to have no limit.
return std::numeric_limits<QuicByteCount>::max();
diff --git a/quic/core/http/http_decoder.h b/quic/core/http/http_decoder.h
index 77937f5..479f444 100644
--- a/quic/core/http/http_decoder.h
+++ b/quic/core/http/http_decoder.h
@@ -197,10 +197,16 @@
// Parses the payload of a SETTINGS frame from |reader| into |frame|.
bool ParseSettingsFrame(QuicDataReader* reader, SettingsFrame* frame);
- // Parses the payload of a PRIORITY_UPDATE frame from |reader| into |frame|.
+ // Parses the payload of a PRIORITY_UPDATE frame (draft-01, type 0x0f)
+ // from |reader| into |frame|.
bool ParsePriorityUpdateFrame(QuicDataReader* reader,
PriorityUpdateFrame* frame);
+ // Parses the payload of a PRIORITY_UPDATE frame (draft-02, type 0xf0700)
+ // from |reader| into |frame|.
+ bool ParseNewPriorityUpdateFrame(QuicDataReader* reader,
+ PriorityUpdateFrame* frame);
+
// Returns the max frame size of a given |frame_type|.
QuicByteCount MaxFrameLength(uint64_t frame_type);
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc
index 7702b0d..d56626f 100644
--- a/quic/core/http/http_decoder_test.cc
+++ b/quic/core/http/http_decoder_test.cc
@@ -14,6 +14,7 @@
#include "net/third_party/quiche/src/quic/core/http/http_frames.h"
#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
#include "net/third_party/quiche/src/common/platform/api/quiche_str_cat.h"
@@ -1000,7 +1001,7 @@
EXPECT_EQ("", decoder_.error_detail());
std::string input2 = absl::HexStringToBytes(
- "0f" // type (PRIORIRTY)
+ "0f" // type (PRIORITY_UPDATE)
"05" // length
"80" // prioritized element type: PUSH_STREAM
"05" // prioritized element id
@@ -1040,6 +1041,90 @@
EXPECT_EQ("", decoder_.error_detail());
}
+TEST_F(HttpDecoderTest, NewPriorityUpdateFrame) {
+ if (!GetQuicReloadableFlag(quic_new_priority_update_frame)) {
+ return;
+ }
+
+ InSequence s;
+ std::string input1 = absl::HexStringToBytes(
+ "800f0700" // type (PRIORITY_UPDATE)
+ "01" // length
+ "03"); // prioritized element id
+
+ PriorityUpdateFrame priority_update1;
+ priority_update1.prioritized_element_type = REQUEST_STREAM;
+ priority_update1.prioritized_element_id = 0x03;
+
+ // Visitor pauses processing.
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(5)).WillOnce(Return(false));
+ absl::string_view remaining_input(input1);
+ QuicByteCount processed_bytes =
+ ProcessInputWithGarbageAppended(remaining_input);
+ EXPECT_EQ(5u, processed_bytes);
+ remaining_input = remaining_input.substr(processed_bytes);
+
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update1))
+ .WillOnce(Return(false));
+ processed_bytes = ProcessInputWithGarbageAppended(remaining_input);
+ EXPECT_EQ(remaining_input.size(), processed_bytes);
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(5));
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update1));
+ EXPECT_EQ(input1.size(), ProcessInput(input1));
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incrementally.
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(5));
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update1));
+ ProcessInputCharByChar(input1);
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ std::string input2 = absl::HexStringToBytes(
+ "800f0700" // type (PRIORITY_UPDATE)
+ "04" // length
+ "05" // prioritized element id
+ "666f6f"); // priority field value: "foo"
+
+ PriorityUpdateFrame priority_update2;
+ priority_update2.prioritized_element_type = REQUEST_STREAM;
+ priority_update2.prioritized_element_id = 0x05;
+ priority_update2.priority_field_value = "foo";
+
+ // Visitor pauses processing.
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(5)).WillOnce(Return(false));
+ remaining_input = input2;
+ processed_bytes = ProcessInputWithGarbageAppended(remaining_input);
+ EXPECT_EQ(5u, processed_bytes);
+ remaining_input = remaining_input.substr(processed_bytes);
+
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update2))
+ .WillOnce(Return(false));
+ processed_bytes = ProcessInputWithGarbageAppended(remaining_input);
+ EXPECT_EQ(remaining_input.size(), processed_bytes);
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(5));
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update2));
+ EXPECT_EQ(input2.size(), ProcessInput(input2));
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incrementally.
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(5));
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update2));
+ ProcessInputCharByChar(input2);
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
TEST_F(HttpDecoderTest, CorruptPriorityUpdateFrame) {
std::string payload1 = absl::HexStringToBytes(
"80" // prioritized element type: PUSH_STREAM
@@ -1076,6 +1161,40 @@
}
}
+TEST_F(HttpDecoderTest, CorruptNewPriorityUpdateFrame) {
+ if (!GetQuicReloadableFlag(quic_new_priority_update_frame)) {
+ return;
+ }
+
+ std::string payload =
+ absl::HexStringToBytes("4005"); // prioritized element id
+ struct {
+ size_t payload_length;
+ const char* const error_message;
+ } kTestData[] = {
+ {0, "Unable to read prioritized element id."},
+ {1, "Unable to read prioritized element id."},
+ };
+
+ for (const auto& test_data : kTestData) {
+ std::string input =
+ absl::HexStringToBytes("800f0700"); // type PRIORITY_UPDATE
+ input.push_back(test_data.payload_length);
+ size_t header_length = input.size();
+ input.append(payload.data(), test_data.payload_length);
+
+ HttpDecoder decoder(&visitor_);
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(header_length));
+ EXPECT_CALL(visitor_, OnError(&decoder));
+
+ QuicByteCount processed_bytes =
+ decoder.ProcessInput(input.data(), input.size());
+ EXPECT_EQ(input.size(), processed_bytes);
+ EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+ EXPECT_EQ(test_data.error_message, decoder.error_detail());
+ }
+}
+
TEST_F(HttpDecoderTest, DecodeSettings) {
std::string input = absl::HexStringToBytes(
"04" // type (SETTINGS)
diff --git a/quic/core/http/http_encoder.cc b/quic/core/http/http_encoder.cc
index 7707705..4f86d45 100644
--- a/quic/core/http/http_encoder.cc
+++ b/quic/core/http/http_encoder.cc
@@ -202,6 +202,37 @@
QuicByteCount HttpEncoder::SerializePriorityUpdateFrame(
const PriorityUpdateFrame& priority_update,
std::unique_ptr<char[]>* output) {
+ if (GetQuicReloadableFlag(quic_new_priority_update_frame)) {
+ QUIC_CODE_COUNT_N(quic_new_priority_update_frame, 1, 2);
+
+ if (priority_update.prioritized_element_type != REQUEST_STREAM) {
+ QUIC_BUG << "PRIORITY_UPDATE for push streams not implemented";
+ return 0;
+ }
+
+ QuicByteCount payload_length =
+ QuicDataWriter::GetVarInt62Len(priority_update.prioritized_element_id) +
+ priority_update.priority_field_value.size();
+ QuicByteCount total_length = GetTotalLength(
+ payload_length, HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM);
+
+ output->reset(new char[total_length]);
+ QuicDataWriter writer(total_length, output->get());
+
+ if (WriteFrameHeader(payload_length,
+ HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM,
+ &writer) &&
+ writer.WriteVarInt62(priority_update.prioritized_element_id) &&
+ writer.WriteBytes(priority_update.priority_field_value.data(),
+ priority_update.priority_field_value.size())) {
+ return total_length;
+ }
+
+ QUIC_DLOG(ERROR) << "Http encoder failed when attempting to serialize "
+ "PRIORITY_UPDATE frame.";
+ return 0;
+ }
+
QuicByteCount payload_length =
kPriorityFirstByteLength +
QuicDataWriter::GetVarInt62Len(priority_update.prioritized_element_id) +
diff --git a/quic/core/http/http_encoder_test.cc b/quic/core/http/http_encoder_test.cc
index 50afdd3..2466326 100644
--- a/quic/core/http/http_encoder_test.cc
+++ b/quic/core/http/http_encoder_test.cc
@@ -5,6 +5,7 @@
#include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
#include "absl/base/macros.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
#include "net/third_party/quiche/src/common/test_tools/quiche_test_utils.h"
@@ -133,6 +134,24 @@
}
TEST(HttpEncoderTest, SerializePriorityUpdateFrame) {
+ if (GetQuicReloadableFlag(quic_new_priority_update_frame)) {
+ PriorityUpdateFrame priority_update1;
+ priority_update1.prioritized_element_type = REQUEST_STREAM;
+ priority_update1.prioritized_element_id = 0x03;
+ char output1[] = {0x80, 0x0f, 0x07, 0x00, // type (PRIORITY_UPDATE)
+ 0x01, // length
+ 0x03}; // prioritized element id
+
+ std::unique_ptr<char[]> buffer;
+ uint64_t length =
+ HttpEncoder::SerializePriorityUpdateFrame(priority_update1, &buffer);
+ EXPECT_EQ(ABSL_ARRAYSIZE(output1), length);
+ quiche::test::CompareCharArraysWithHexError("PRIORITY_UPDATE", buffer.get(),
+ length, output1,
+ ABSL_ARRAYSIZE(output1));
+ return;
+ }
+
PriorityUpdateFrame priority_update1;
priority_update1.prioritized_element_type = REQUEST_STREAM;
priority_update1.prioritized_element_id = 0x03;
diff --git a/quic/core/http/http_frames.h b/quic/core/http/http_frames.h
index 00df2bc..ed7ad2c 100644
--- a/quic/core/http/http_frames.h
+++ b/quic/core/http/http_frames.h
@@ -18,7 +18,7 @@
namespace quic {
-enum class HttpFrameType : uint8_t {
+enum class HttpFrameType {
DATA = 0x0,
HEADERS = 0x1,
CANCEL_PUSH = 0X3,
@@ -26,7 +26,10 @@
PUSH_PROMISE = 0x5,
GOAWAY = 0x7,
MAX_PUSH_ID = 0xD,
+ // https://tools.ietf.org/html/draft-ietf-httpbis-priority-01
PRIORITY_UPDATE = 0XF,
+ // https://tools.ietf.org/html/draft-ietf-httpbis-priority-02
+ PRIORITY_UPDATE_REQUEST_STREAM = 0xF0700,
};
// 7.2.1. DATA
@@ -132,8 +135,11 @@
// https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html
//
-// The PRIORITY_UPDATE (type=0x0f) frame specifies the sender-advised priority
-// of a stream
+// The PRIORITY_UPDATE frame specifies the sender-advised priority of a stream.
+// https://tools.ietf.org/html/draft-ietf-httpbis-priority-01 uses frame type
+// 0x0f, both for request streams and push streams.
+// https://tools.ietf.org/html/draft-ietf-httpbis-priority-02 uses frame types
+// 0xf0700 for request streams and 0xf0701 for push streams (not implemented).
// Length of a priority frame's first byte.
const QuicByteCount kPriorityFirstByteLength = 1;
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 4ad3d64..5590db2 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -59,6 +59,7 @@
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_granular_qpack_error_codes, false)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_key_update_supported, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_let_connection_handle_pings, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_new_priority_update_frame, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_process_undecryptable_packets_after_async_decrypt_callback, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_record_received_min_ack_delay, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_reject_spdy_frames, true)