Implement PRIORITY_UPDATE frame in HttpEncoder and HttpDecoder.
gfe-relnote: n/a, change to QUIC v99-only code. Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99.
PiperOrigin-RevId: 289120155
Change-Id: I85525c7fef4cccd7ecef9e1e24a704c656895f75
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
index e3ca73e..f8a73f4 100644
--- a/quic/core/http/http_decoder.cc
+++ b/quic/core/http/http_decoder.cc
@@ -166,6 +166,9 @@
break;
case static_cast<uint64_t>(HttpFrameType::DUPLICATE_PUSH):
break;
+ case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE):
+ continue_processing = visitor_->OnPriorityUpdateFrameStart(header_length);
+ break;
default:
continue_processing =
visitor_->OnUnknownFrameStart(current_frame_type_, header_length);
@@ -289,6 +292,12 @@
BufferFramePayload(reader);
break;
}
+ case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE): {
+ // TODO(bnc): Avoid buffering if the entire frame is present, and
+ // instead parse directly out of |reader|.
+ BufferFramePayload(reader);
+ break;
+ }
default: {
QuicByteCount bytes_to_read = std::min<QuicByteCount>(
remaining_frame_length_, reader->BytesRemaining());
@@ -406,6 +415,17 @@
continue_processing = visitor_->OnDuplicatePushFrame(frame);
break;
}
+ case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE): {
+ // 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 (!ParsePriorityUpdateFrame(&reader, &frame)) {
+ return false;
+ }
+ continue_processing = visitor_->OnPriorityUpdateFrame(frame);
+ break;
+ }
default: {
continue_processing = visitor_->OnUnknownFrameEnd();
break;
@@ -515,6 +535,41 @@
return true;
}
+bool HttpDecoder::ParsePriorityUpdateFrame(QuicDataReader* reader,
+ PriorityUpdateFrame* frame) {
+ uint8_t prioritized_element_type;
+ if (!reader->ReadUInt8(&prioritized_element_type)) {
+ // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
+ RaiseError(QUIC_INVALID_FRAME_DATA,
+ "Unable to read prioritized element type.");
+ return false;
+ }
+
+ if (prioritized_element_type != REQUEST_STREAM &&
+ prioritized_element_type != PUSH_STREAM) {
+ // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
+ RaiseError(QUIC_INVALID_FRAME_DATA, "Invalid prioritized element type.");
+ return false;
+ }
+
+ frame->prioritized_element_type =
+ static_cast<PrioritizedElementType>(prioritized_element_type);
+
+ if (!reader->ReadVarInt62(&frame->prioritized_element_id)) {
+ // TODO(b/124216424): Use HTTP_MALFORMED_FRAME.
+ RaiseError(QUIC_INVALID_FRAME_DATA,
+ "Unable to read prioritized element id.");
+ return false;
+ }
+
+ quiche::QuicheStringPiece 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):
@@ -528,6 +583,9 @@
return sizeof(PushId);
case static_cast<uint64_t>(HttpFrameType::DUPLICATE_PUSH):
return sizeof(PushId);
+ case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE):
+ // 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 a77ce6c..426a86e 100644
--- a/quic/core/http/http_decoder.h
+++ b/quic/core/http/http_decoder.h
@@ -92,6 +92,13 @@
// Called when a PUSH_PROMISE frame has been completely processed.
virtual bool OnPushPromiseFrameEnd() = 0;
+ // Called when a PRIORITY_UPDATE frame has been received.
+ // |header_length| contains PRIORITY_UPDATE frame length and payload length.
+ virtual bool OnPriorityUpdateFrameStart(QuicByteCount header_length) = 0;
+
+ // Called when a PRIORITY_UPDATE frame has been successfully parsed.
+ virtual bool OnPriorityUpdateFrame(const PriorityUpdateFrame& frame) = 0;
+
// Called when a frame of unknown type |frame_type| has been received.
// Frame type might be reserved, Visitor must make sure to ignore.
// |header_length| contains frame length and payload length.
@@ -177,6 +184,10 @@
// 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|.
+ bool ParsePriorityUpdateFrame(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 942edfd..3a67395 100644
--- a/quic/core/http/http_decoder_test.cc
+++ b/quic/core/http/http_decoder_test.cc
@@ -63,6 +63,9 @@
bool(quiche::QuicheStringPiece payload));
MOCK_METHOD0(OnPushPromiseFrameEnd, bool());
+ MOCK_METHOD1(OnPriorityUpdateFrameStart, bool(QuicByteCount header_length));
+ MOCK_METHOD1(OnPriorityUpdateFrame, bool(const PriorityUpdateFrame& frame));
+
MOCK_METHOD2(OnUnknownFrameStart, bool(uint64_t, QuicByteCount));
MOCK_METHOD1(OnUnknownFramePayload, bool(quiche::QuicheStringPiece));
MOCK_METHOD0(OnUnknownFrameEnd, bool());
@@ -88,6 +91,9 @@
.WillByDefault(Return(true));
ON_CALL(visitor_, OnPushPromiseFramePayload(_)).WillByDefault(Return(true));
ON_CALL(visitor_, OnPushPromiseFrameEnd()).WillByDefault(Return(true));
+ ON_CALL(visitor_, OnPriorityUpdateFrameStart(_))
+ .WillByDefault(Return(true));
+ ON_CALL(visitor_, OnPriorityUpdateFrame(_)).WillByDefault(Return(true));
ON_CALL(visitor_, OnUnknownFrameStart(_, _)).WillByDefault(Return(true));
ON_CALL(visitor_, OnUnknownFramePayload(_)).WillByDefault(Return(true));
ON_CALL(visitor_, OnUnknownFrameEnd()).WillByDefault(Return(true));
@@ -144,7 +150,7 @@
const QuicByteCount payload_lengths[] = {0, 14, 100};
const uint64_t frame_types[] = {
0x21, 0x40, 0x5f, 0x7e, 0x9d, // some reserved frame types
- 0x06, 0x0f, 0x14 // some unknown, not reserved frame types
+ 0x06, 0x6f, 0x14 // some unknown, not reserved frame types
};
for (auto payload_length : payload_lengths) {
@@ -949,6 +955,124 @@
EXPECT_EQ("", decoder_.error_detail());
}
+TEST_F(HttpDecoderTest, PriorityUpdateFrame) {
+ InSequence s;
+ std::string input1 = quiche::QuicheTextUtils::HexDecode(
+ "0f" // type (PRIORITY_UPDATE)
+ "02" // length
+ "00" // prioritized element type: REQUEST_STREAM
+ "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(2)).WillOnce(Return(false));
+ quiche::QuicheStringPiece remaining_input(input1);
+ QuicByteCount processed_bytes =
+ ProcessInputWithGarbageAppended(remaining_input);
+ EXPECT_EQ(2u, 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(2));
+ 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(2));
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrame(priority_update1));
+ ProcessInputCharByChar(input1);
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ std::string input2 = quiche::QuicheTextUtils::HexDecode(
+ "0f" // type (PRIORIRTY)
+ "05" // length
+ "80" // prioritized element type: PUSH_STREAM
+ "05" // prioritized element id
+ "666f6f"); // priority field value: "foo"
+
+ PriorityUpdateFrame priority_update2;
+ priority_update2.prioritized_element_type = PUSH_STREAM;
+ priority_update2.prioritized_element_id = 0x05;
+ priority_update2.priority_field_value = "foo";
+
+ // Visitor pauses processing.
+ EXPECT_CALL(visitor_, OnPriorityUpdateFrameStart(2)).WillOnce(Return(false));
+ remaining_input = input2;
+ processed_bytes = ProcessInputWithGarbageAppended(remaining_input);
+ EXPECT_EQ(2u, 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(2));
+ 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(2));
+ 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 = quiche::QuicheTextUtils::HexDecode(
+ "80" // prioritized element type: PUSH_STREAM
+ "4005"); // prioritized element id
+ std::string payload2 = quiche::QuicheTextUtils::HexDecode(
+ "42"); // invalid prioritized element type
+ struct {
+ const char* const payload;
+ size_t payload_length;
+ const char* const error_message;
+ } kTestData[] = {
+ {payload1.data(), 0, "Unable to read prioritized element type."},
+ {payload1.data(), 1, "Unable to read prioritized element id."},
+ {payload1.data(), 2, "Unable to read prioritized element id."},
+ {payload2.data(), 1, "Invalid prioritized element type."},
+ };
+
+ for (const auto& test_data : kTestData) {
+ std::string input;
+ input.push_back(15u); // type PRIORITY_UPDATE
+ input.push_back(test_data.payload_length);
+ size_t header_length = input.size();
+ input.append(test_data.payload, 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_INVALID_FRAME_DATA));
+ EXPECT_EQ(test_data.error_message, decoder.error_detail());
+ }
+}
+
} // namespace test
} // namespace quic
diff --git a/quic/core/http/http_encoder.cc b/quic/core/http/http_encoder.cc
index 2f35897..00bb980 100644
--- a/quic/core/http/http_encoder.cc
+++ b/quic/core/http/http_encoder.cc
@@ -213,4 +213,32 @@
return 0;
}
+// static
+QuicByteCount HttpEncoder::SerializePriorityUpdateFrame(
+ const PriorityUpdateFrame& priority_update,
+ std::unique_ptr<char[]>* output) {
+ QuicByteCount payload_length =
+ kPriorityFirstByteLength +
+ QuicDataWriter::GetVarInt62Len(priority_update.prioritized_element_id) +
+ priority_update.priority_field_value.size();
+ QuicByteCount total_length =
+ GetTotalLength(payload_length, HttpFrameType::PRIORITY_UPDATE);
+
+ output->reset(new char[total_length]);
+ QuicDataWriter writer(total_length, output->get());
+
+ if (WriteFrameHeader(payload_length, HttpFrameType::PRIORITY_UPDATE,
+ &writer) &&
+ writer.WriteUInt8(priority_update.prioritized_element_type) &&
+ 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;
+}
+
} // namespace quic
diff --git a/quic/core/http/http_encoder.h b/quic/core/http/http_encoder.h
index b856187..ce58408 100644
--- a/quic/core/http/http_encoder.h
+++ b/quic/core/http/http_encoder.h
@@ -65,6 +65,12 @@
static QuicByteCount SerializeDuplicatePushFrame(
const DuplicatePushFrame& duplicate_push,
std::unique_ptr<char[]>* output);
+
+ // Serializes a PRIORITY_UPDATE frame into a new buffer stored in |output|.
+ // Returns the length of the buffer on success, or 0 otherwise.
+ static QuicByteCount SerializePriorityUpdateFrame(
+ const PriorityUpdateFrame& priority_update,
+ std::unique_ptr<char[]>* output);
};
} // namespace quic
diff --git a/quic/core/http/http_encoder_test.cc b/quic/core/http/http_encoder_test.cc
index 5ad79d8..316d6be 100644
--- a/quic/core/http/http_encoder_test.cc
+++ b/quic/core/http/http_encoder_test.cc
@@ -149,5 +149,38 @@
"DUPLICATE_PUSH", buffer.get(), length, output, QUICHE_ARRAYSIZE(output));
}
+TEST(HttpEncoderTest, SerializePriorityUpdateFrame) {
+ PriorityUpdateFrame priority_update1;
+ priority_update1.prioritized_element_type = REQUEST_STREAM;
+ priority_update1.prioritized_element_id = 0x03;
+ char output1[] = {0x0f, // type (PRIORITY_UPDATE)
+ 0x02, // length
+ 0x00, // prioritized element type: REQUEST_STREAM
+ 0x03}; // prioritized element id
+
+ std::unique_ptr<char[]> buffer;
+ uint64_t length =
+ HttpEncoder::SerializePriorityUpdateFrame(priority_update1, &buffer);
+ EXPECT_EQ(QUICHE_ARRAYSIZE(output1), length);
+ quiche::test::CompareCharArraysWithHexError("PRIORITY_UPDATE", buffer.get(),
+ length, output1,
+ QUICHE_ARRAYSIZE(output1));
+
+ PriorityUpdateFrame priority_update2;
+ priority_update2.prioritized_element_type = PUSH_STREAM;
+ priority_update2.prioritized_element_id = 0x05;
+ priority_update2.priority_field_value = "foo";
+ char output2[] = {0x0f, // type (PRIORIRTY)
+ 0x05, // length
+ 0x80, // prioritized element type: PUSH_STREAM
+ 0x05, // prioritized element id
+ 'f', 'o', 'o'}; // priority field value
+ length = HttpEncoder::SerializePriorityUpdateFrame(priority_update2, &buffer);
+ EXPECT_EQ(QUICHE_ARRAYSIZE(output2), length);
+ quiche::test::CompareCharArraysWithHexError("PRIORITY_UPDATE", buffer.get(),
+ length, output2,
+ QUICHE_ARRAYSIZE(output2));
+}
+
} // namespace test
} // namespace quic
diff --git a/quic/core/http/http_frames.h b/quic/core/http/http_frames.h
index 6ad6da7..e1e184f 100644
--- a/quic/core/http/http_frames.h
+++ b/quic/core/http/http_frames.h
@@ -26,7 +26,8 @@
PUSH_PROMISE = 0x5,
GOAWAY = 0x7,
MAX_PUSH_ID = 0xD,
- DUPLICATE_PUSH = 0xE
+ DUPLICATE_PUSH = 0xE,
+ PRIORITY_UPDATE = 0XF,
};
// 7.2.1. DATA
@@ -142,6 +143,46 @@
}
};
+// https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html
+//
+// The PRIORITY_UPDATE (type=0x0f) frame specifies the sender-advised priority
+// of a stream
+
+// Length of a priority frame's first byte.
+const QuicByteCount kPriorityFirstByteLength = 1;
+
+enum PrioritizedElementType : uint8_t {
+ REQUEST_STREAM = 0x00,
+ PUSH_STREAM = 0x80,
+};
+
+struct QUIC_EXPORT_PRIVATE PriorityUpdateFrame {
+ PrioritizedElementType prioritized_element_type = REQUEST_STREAM;
+ uint64_t prioritized_element_id = 0;
+ std::string priority_field_value;
+
+ bool operator==(const PriorityUpdateFrame& rhs) const {
+ return std::tie(prioritized_element_type, prioritized_element_id,
+ priority_field_value) ==
+ std::tie(rhs.prioritized_element_type, rhs.prioritized_element_id,
+ rhs.priority_field_value);
+ }
+ std::string ToString() const {
+ return quiche::QuicheStrCat(
+ "Priority Frame : {prioritized_element_type: ",
+ static_cast<int>(prioritized_element_type),
+ ", prioritized_element_id: ", prioritized_element_id,
+ ", priority_field_value: ", priority_field_value, "}");
+ }
+
+ friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+ std::ostream& os,
+ const PriorityUpdateFrame& s) {
+ os << s.ToString();
+ return os;
+ }
+};
+
} // namespace quic
#endif // QUICHE_QUIC_CORE_HTTP_HTTP_FRAMES_H_
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index 2269faf..283b54e 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -122,6 +122,16 @@
return false;
}
+ bool OnPriorityUpdateFrameStart(QuicByteCount /*header_length*/) override {
+ // TODO(b/147306124): Implement.
+ return true;
+ }
+
+ bool OnPriorityUpdateFrame(const PriorityUpdateFrame& /*frame*/) override {
+ // TODO(b/147306124): Implement.
+ return true;
+ }
+
bool OnUnknownFrameStart(uint64_t /* frame_type */,
QuicByteCount /* header_length */) override {
// Ignore unknown frame types.
diff --git a/quic/core/http/quic_receive_control_stream.h b/quic/core/http/quic_receive_control_stream.h
index 58a0be7..52f9663 100644
--- a/quic/core/http/quic_receive_control_stream.h
+++ b/quic/core/http/quic_receive_control_stream.h
@@ -38,6 +38,8 @@
// Called from HttpDecoderVisitor.
bool OnSettingsFrameStart(QuicByteCount header_length);
bool OnSettingsFrame(const SettingsFrame& settings);
+ bool OnPriorityUpdateFrameStart(QuicByteCount header_length);
+ bool OnPriorityUpdateFrame(const PriorityUpdateFrame& priority);
// False until a SETTINGS frame is received.
bool settings_frame_received_;
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 4d36df0..819caef 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -147,6 +147,16 @@
return stream_->OnPushPromiseFrameEnd();
}
+ bool OnPriorityUpdateFrameStart(QuicByteCount /*header_length*/) override {
+ CloseConnectionOnWrongFrame("Priority update");
+ return false;
+ }
+
+ bool OnPriorityUpdateFrame(const PriorityUpdateFrame& /*frame*/) override {
+ CloseConnectionOnWrongFrame("Priority update");
+ return false;
+ }
+
bool OnUnknownFrameStart(uint64_t frame_type,
QuicByteCount header_length) override {
return stream_->OnUnknownFrameStart(frame_type, header_length);