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);