First capsule interop attempt
diff --git a/quic/core/http/http_constants.cc b/quic/core/http/http_constants.cc
index f3ed523..2c0ec24 100644
--- a/quic/core/http/http_constants.cc
+++ b/quic/core/http/http_constants.cc
@@ -17,7 +17,8 @@
RETURN_STRING_LITERAL(SETTINGS_QPACK_MAX_TABLE_CAPACITY);
RETURN_STRING_LITERAL(SETTINGS_MAX_FIELD_SECTION_SIZE);
RETURN_STRING_LITERAL(SETTINGS_QPACK_BLOCKED_STREAMS);
- RETURN_STRING_LITERAL(SETTINGS_H3_DATAGRAM);
+ RETURN_STRING_LITERAL(SETTINGS_H3_DATAGRAM_DRAFT00);
+ RETURN_STRING_LITERAL(SETTINGS_H3_DATAGRAM_DRAFT03);
RETURN_STRING_LITERAL(SETTINGS_WEBTRANS_DRAFT00);
}
return absl::StrCat("UNSUPPORTED_SETTINGS_TYPE(", identifier, ")");
diff --git a/quic/core/http/http_constants.h b/quic/core/http/http_constants.h
index c13a1a8..94328ac 100644
--- a/quic/core/http/http_constants.h
+++ b/quic/core/http/http_constants.h
@@ -38,8 +38,10 @@
// Same value as spdy::SETTINGS_MAX_HEADER_LIST_SIZE.
SETTINGS_MAX_FIELD_SECTION_SIZE = 0x06,
SETTINGS_QPACK_BLOCKED_STREAMS = 0x07,
- // draft-ietf-masque-h3-datagram.
- SETTINGS_H3_DATAGRAM = 0x276,
+ // draft-ietf-masque-h3-datagram-00.
+ SETTINGS_H3_DATAGRAM_DRAFT00 = 0x276,
+ // draft-ietf-masque-h3-datagram-03.
+ SETTINGS_H3_DATAGRAM_DRAFT03 = 0xffd276,
// draft-ietf-webtrans-http3-00
SETTINGS_WEBTRANS_DRAFT00 = 0x2b603742,
};
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
index b20789b..c22fc32 100644
--- a/quic/core/http/http_decoder.cc
+++ b/quic/core/http/http_decoder.cc
@@ -266,6 +266,8 @@
case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH):
continue_processing = visitor_->OnAcceptChFrameStart(header_length);
break;
+ case static_cast<uint64_t>(HttpFrameType::CAPSULE):
+ break;
default:
continue_processing = visitor_->OnUnknownFrameStart(
current_frame_type_, header_length, current_frame_length_);
@@ -335,6 +337,10 @@
continue_processing = BufferOrParsePayload(reader);
break;
}
+ case static_cast<uint64_t>(HttpFrameType::CAPSULE): {
+ continue_processing = BufferOrParsePayload(reader);
+ break;
+ }
default: {
continue_processing = HandleUnknownFramePayload(reader);
break;
@@ -401,6 +407,12 @@
continue_processing = BufferOrParsePayload(reader);
break;
}
+ case static_cast<uint64_t>(HttpFrameType::CAPSULE): {
+ // If frame payload is not empty, FinishParsing() is skipped.
+ QUICHE_DCHECK_EQ(0u, current_frame_length_);
+ continue_processing = BufferOrParsePayload(reader);
+ break;
+ }
default:
continue_processing = visitor_->OnUnknownFrameEnd();
}
@@ -537,6 +549,13 @@
}
return visitor_->OnAcceptChFrame(frame);
}
+ case static_cast<uint64_t>(HttpFrameType::CAPSULE): {
+ CapsuleFrame frame;
+ if (!ParseCapsuleFrame(reader, &frame)) {
+ return false;
+ }
+ return visitor_->OnCapsuleFrame(frame);
+ }
default:
// Only above frame types are parsed by ParseEntirePayload().
QUICHE_NOTREACHED();
@@ -662,6 +681,59 @@
return true;
}
+bool HttpDecoder::ParseCapsuleFrame(QuicDataReader* reader,
+ CapsuleFrame* frame) {
+ uint64_t capsule_type64;
+ if (!reader->ReadVarInt62(&capsule_type64)) {
+ RaiseError(QUIC_HTTP_FRAME_ERROR, "Unable to parse capsule type");
+ return false;
+ }
+ *frame = CapsuleFrame(static_cast<CapsuleType>(capsule_type64));
+ switch (frame->capsule_type) {
+ case CapsuleType::REGISTER_DATAGRAM_CONTEXT:
+ if (!reader->ReadVarInt62(
+ &frame->register_datagram_context_capsule.context_id)) {
+ RaiseError(
+ QUIC_HTTP_FRAME_ERROR,
+ "Unable to parse capsule REGISTER_DATAGRAM_CONTEXT context ID");
+ return false;
+ }
+ frame->register_datagram_context_capsule.context_extensions =
+ reader->ReadRemainingPayload();
+ break;
+ case CapsuleType::CLOSE_DATAGRAM_CONTEXT:
+ if (!reader->ReadVarInt62(
+ &frame->close_datagram_context_capsule.context_id)) {
+ RaiseError(QUIC_HTTP_FRAME_ERROR,
+ "Unable to parse capsule CLOSE_DATAGRAM_CONTEXT context ID");
+ return false;
+ }
+ frame->close_datagram_context_capsule.context_extensions =
+ reader->ReadRemainingPayload();
+ break;
+ case CapsuleType::DATAGRAM:
+ if (datagram_context_id_present_) {
+ uint64_t context_id;
+ if (!reader->ReadVarInt62(&context_id)) {
+ RaiseError(QUIC_HTTP_FRAME_ERROR,
+ "Unable to parse capsule DATAGRAM context ID");
+ return false;
+ }
+ frame->datagram_capsule.context_id = context_id;
+ }
+ frame->datagram_capsule.http_datagram_payload =
+ reader->ReadRemainingPayload();
+ break;
+ case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT:
+ frame->register_datagram_no_context_capsule.context_extensions =
+ reader->ReadRemainingPayload();
+ break;
+ default:
+ frame->unknown_capsule_data = reader->ReadRemainingPayload();
+ }
+ return true;
+}
+
QuicByteCount HttpDecoder::MaxFrameLength(uint64_t frame_type) {
switch (frame_type) {
case static_cast<uint64_t>(HttpFrameType::SETTINGS):
@@ -682,6 +754,9 @@
case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH):
// This limit is arbitrary.
return 1024 * 1024;
+ case static_cast<uint64_t>(HttpFrameType::CAPSULE):
+ // 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 7cbf205..8d96c84 100644
--- a/quic/core/http/http_decoder.h
+++ b/quic/core/http/http_decoder.h
@@ -94,6 +94,9 @@
// Called when an ACCEPT_CH frame has been successfully parsed.
virtual bool OnAcceptChFrame(const AcceptChFrame& frame) = 0;
+ // Called when a CAPSULE frame has been successfully parsed.
+ virtual bool OnCapsuleFrame(const CapsuleFrame& frame) = 0;
+
// Called when a WEBTRANSPORT_STREAM frame type and the session ID varint
// immediately following it has been received. Any further parsing should
// be done by the stream itself, and not the parser. Note that this does not
@@ -148,6 +151,10 @@
// Returns true if input data processed so far ends on a frame boundary.
bool AtFrameBoundary() const { return state_ == STATE_READING_FRAME_TYPE; }
+ void set_datagram_context_id_present(bool datagram_context_id_present) {
+ datagram_context_id_present_ = datagram_context_id_present;
+ }
+
private:
friend test::HttpDecoderPeer;
@@ -230,6 +237,9 @@
// Parses the payload of an ACCEPT_CH frame from |reader| into |frame|.
bool ParseAcceptChFrame(QuicDataReader* reader, AcceptChFrame* frame);
+ // Parses the payload of an CAPSULE frame from |reader| into |frame|.
+ bool ParseCapsuleFrame(QuicDataReader* reader, CapsuleFrame* frame);
+
// Returns the max frame size of a given |frame_type|.
QuicByteCount MaxFrameLength(uint64_t frame_type);
@@ -237,6 +247,8 @@
Visitor* const visitor_; // Unowned.
// Whether WEBTRANSPORT_STREAM should be parsed.
bool allow_web_transport_stream_;
+ // Whether HTTP Datagram Context IDs are present.
+ bool datagram_context_id_present_ = false;
// Current state of the parsing.
HttpDecoderState state_;
// Type of the frame currently being parsed.
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc
index 7fa09ba..6a8491e 100644
--- a/quic/core/http/http_decoder_test.cc
+++ b/quic/core/http/http_decoder_test.cc
@@ -89,6 +89,7 @@
(QuicByteCount header_length),
(override));
MOCK_METHOD(bool, OnAcceptChFrame, (const AcceptChFrame& frame), (override));
+ MOCK_METHOD(bool, OnCapsuleFrame, (const CapsuleFrame& frame), (override));
MOCK_METHOD(void,
OnWebTransportStreamFrameType,
(QuicByteCount header_length, WebTransportSessionId session_id),
@@ -125,6 +126,7 @@
ON_CALL(visitor_, OnPriorityUpdateFrame(_)).WillByDefault(Return(true));
ON_CALL(visitor_, OnAcceptChFrameStart(_)).WillByDefault(Return(true));
ON_CALL(visitor_, OnAcceptChFrame(_)).WillByDefault(Return(true));
+ ON_CALL(visitor_, OnCapsuleFrame(_)).WillByDefault(Return(true));
ON_CALL(visitor_, OnUnknownFrameStart(_, _, _)).WillByDefault(Return(true));
ON_CALL(visitor_, OnUnknownFramePayload(_)).WillByDefault(Return(true));
ON_CALL(visitor_, OnUnknownFrameEnd()).WillByDefault(Return(true));
@@ -1043,6 +1045,88 @@
EXPECT_EQ("", decoder_.error_detail());
}
+TEST_F(HttpDecoderTest, RegisterDatagramContextCapsuleFrame) {
+ std::string input = absl::HexStringToBytes(
+ "80ffcab5" // type (CAPSULE)
+ "06" // length
+ "00" // capsule type (REGISTER_DATAGRAM_CONTEXT)
+ "08" // context ID
+ "01020304"); // context extensions
+
+ CapsuleFrame frame(CapsuleType::REGISTER_DATAGRAM_CONTEXT);
+ frame.register_datagram_context_capsule.context_id = 8;
+ std::array<char, 4> context_extensions = {1, 2, 3, 4};
+ frame.register_datagram_context_capsule.context_extensions =
+ absl::string_view(context_extensions.data(), context_extensions.size());
+ EXPECT_CALL(visitor_, OnCapsuleFrame(frame));
+ EXPECT_EQ(ProcessInput(input), input.size());
+}
+
+TEST_F(HttpDecoderTest, CloseDatagramContextCapsuleFrame) {
+ std::string input = absl::HexStringToBytes(
+ "80ffcab5" // type (CAPSULE)
+ "06" // length
+ "01" // capsule type (CLOSE_DATAGRAM_CONTEXT)
+ "08" // context ID
+ "01020304"); // context extensions
+
+ CapsuleFrame frame(CapsuleType::CLOSE_DATAGRAM_CONTEXT);
+ frame.close_datagram_context_capsule.context_id = 8;
+ std::array<char, 4> context_extensions = {1, 2, 3, 4};
+ frame.close_datagram_context_capsule.context_extensions =
+ absl::string_view(context_extensions.data(), context_extensions.size());
+ EXPECT_CALL(visitor_, OnCapsuleFrame(frame));
+ EXPECT_EQ(ProcessInput(input), input.size());
+}
+
+TEST_F(HttpDecoderTest, DatagramWithoutContextCapsuleFrame) {
+ std::string input = absl::HexStringToBytes(
+ "80ffcab5" // type (CAPSULE)
+ "05" // length
+ "02" // capsule type (DATAGRAM)
+ "01020304"); // context extensions
+
+ CapsuleFrame frame(CapsuleType::DATAGRAM);
+ std::array<char, 4> datagram_payload = {1, 2, 3, 4};
+ frame.datagram_capsule.http_datagram_payload =
+ absl::string_view(datagram_payload.data(), datagram_payload.size());
+ EXPECT_CALL(visitor_, OnCapsuleFrame(frame));
+ EXPECT_EQ(ProcessInput(input), input.size());
+}
+
+TEST_F(HttpDecoderTest, DatagramWithContextCapsuleFrame) {
+ decoder_.set_datagram_context_id_present(true);
+ std::string input = absl::HexStringToBytes(
+ "80ffcab5" // type (CAPSULE)
+ "06" // length
+ "02" // capsule type (DATAGRAM)
+ "08" // context ID
+ "01020304"); // context extensions
+
+ CapsuleFrame frame(CapsuleType::DATAGRAM);
+ frame.datagram_capsule.context_id = 8;
+ std::array<char, 4> datagram_payload = {1, 2, 3, 4};
+ frame.datagram_capsule.http_datagram_payload =
+ absl::string_view(datagram_payload.data(), datagram_payload.size());
+ EXPECT_CALL(visitor_, OnCapsuleFrame(frame));
+ EXPECT_EQ(ProcessInput(input), input.size());
+}
+
+TEST_F(HttpDecoderTest, RegisterDatagramNoContextCapsuleFrame) {
+ std::string input = absl::HexStringToBytes(
+ "80ffcab5" // type (CAPSULE)
+ "05" // length
+ "03" // capsule type (REGISTER_DATAGRAM_NO_CONTEXT)
+ "01020304"); // context extensions
+
+ CapsuleFrame frame(CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT);
+ std::array<char, 4> context_extensions = {1, 2, 3, 4};
+ frame.register_datagram_no_context_capsule.context_extensions =
+ absl::string_view(context_extensions.data(), context_extensions.size());
+ EXPECT_CALL(visitor_, OnCapsuleFrame(frame));
+ EXPECT_EQ(ProcessInput(input), input.size());
+}
+
TEST_F(HttpDecoderTest, WebTransportStreamDisabled) {
InSequence s;
diff --git a/quic/core/http/http_encoder.cc b/quic/core/http/http_encoder.cc
index 25f6200..d9d161b 100644
--- a/quic/core/http/http_encoder.cc
+++ b/quic/core/http/http_encoder.cc
@@ -276,4 +276,143 @@
return 0;
}
+// static
+QuicByteCount HttpEncoder::SerializeCapsuleFrame(
+ const CapsuleFrame& capsule_frame, std::unique_ptr<char[]>* output) {
+ QuicByteCount capsule_type_length = QuicDataWriter::GetVarInt62Len(
+ static_cast<uint64_t>(capsule_frame.capsule_type));
+ QuicByteCount capsule_data_length;
+ switch (capsule_frame.capsule_type) {
+ case CapsuleType::REGISTER_DATAGRAM_CONTEXT:
+ capsule_data_length =
+ QuicDataWriter::GetVarInt62Len(
+ capsule_frame.register_datagram_context_capsule.context_id) +
+ capsule_frame.register_datagram_context_capsule.context_extensions
+ .length();
+ break;
+ case CapsuleType::CLOSE_DATAGRAM_CONTEXT:
+ capsule_data_length =
+ QuicDataWriter::GetVarInt62Len(
+ capsule_frame.close_datagram_context_capsule.context_id) +
+ capsule_frame.close_datagram_context_capsule.context_extensions
+ .length();
+ break;
+ case CapsuleType::DATAGRAM:
+ capsule_data_length =
+ capsule_frame.datagram_capsule.http_datagram_payload.length();
+ if (capsule_frame.datagram_capsule.context_id.has_value()) {
+ capsule_data_length += QuicDataWriter::GetVarInt62Len(
+ capsule_frame.datagram_capsule.context_id.value());
+ }
+ break;
+ case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT:
+ capsule_data_length = capsule_frame.register_datagram_no_context_capsule
+ .context_extensions.length();
+ break;
+ default:
+ capsule_data_length = capsule_frame.unknown_capsule_data.length();
+ break;
+ }
+ QuicByteCount frame_length_field_value =
+ capsule_type_length + capsule_data_length;
+ QuicByteCount total_frame_length =
+ QuicDataWriter::GetVarInt62Len(
+ static_cast<uint64_t>(HttpFrameType::CAPSULE)) +
+ QuicDataWriter::GetVarInt62Len(frame_length_field_value) +
+ capsule_type_length + capsule_data_length;
+ *output = std::make_unique<char[]>(total_frame_length);
+ QuicDataWriter writer(total_frame_length, output->get());
+ if (!writer.WriteVarInt62(static_cast<uint64_t>(HttpFrameType::CAPSULE))) {
+ QUIC_BUG(capsule frame type write fail)
+ << "Failed to write CAPSULE frame type";
+ return 0;
+ }
+ if (!writer.WriteVarInt62(frame_length_field_value)) {
+ QUIC_BUG(capsule frame length write fail)
+ << "Failed to write CAPSULE frame length";
+ return 0;
+ }
+ if (!writer.WriteVarInt62(
+ static_cast<uint64_t>(capsule_frame.capsule_type))) {
+ QUIC_BUG(capsule type write fail) << "Failed to write CAPSULE type";
+ return 0;
+ }
+ switch (capsule_frame.capsule_type) {
+ case CapsuleType::REGISTER_DATAGRAM_CONTEXT:
+ if (!writer.WriteVarInt62(
+ capsule_frame.register_datagram_context_capsule.context_id)) {
+ QUIC_BUG(register context capsule context ID write fail)
+ << "Failed to write REGISTER_DATAGRAM_CONTEXT CAPSULE context ID";
+ return 0;
+ }
+ if (!writer.WriteBytes(capsule_frame.register_datagram_context_capsule
+ .context_extensions.data(),
+ capsule_frame.register_datagram_context_capsule
+ .context_extensions.length())) {
+ QUIC_BUG(register context capsule extensions write fail)
+ << "Failed to write REGISTER_DATAGRAM_CONTEXT CAPSULE extensions";
+ return 0;
+ }
+ break;
+ case CapsuleType::CLOSE_DATAGRAM_CONTEXT:
+ if (!writer.WriteVarInt62(
+ capsule_frame.close_datagram_context_capsule.context_id)) {
+ QUIC_BUG(close context capsule context ID write fail)
+ << "Failed to write CLOSE_DATAGRAM_CONTEXT CAPSULE context ID";
+ return 0;
+ }
+ if (!writer.WriteBytes(capsule_frame.close_datagram_context_capsule
+ .context_extensions.data(),
+ capsule_frame.close_datagram_context_capsule
+ .context_extensions.length())) {
+ QUIC_BUG(close context capsule extensions write fail)
+ << "Failed to write CLOSE_DATAGRAM_CONTEXT CAPSULE extensions";
+ return 0;
+ }
+ break;
+ case CapsuleType::DATAGRAM:
+ if (capsule_frame.datagram_capsule.context_id.has_value()) {
+ if (!writer.WriteVarInt62(
+ capsule_frame.datagram_capsule.context_id.value())) {
+ QUIC_BUG(datagram capsule context ID write fail)
+ << "Failed to write DATAGRAM CAPSULE context ID";
+ return 0;
+ }
+ }
+ if (!writer.WriteBytes(
+ capsule_frame.datagram_capsule.http_datagram_payload.data(),
+ capsule_frame.datagram_capsule.http_datagram_payload.length())) {
+ QUIC_BUG(datagram capsule payload write fail)
+ << "Failed to write DATAGRAM CAPSULE payload";
+ return 0;
+ }
+ break;
+ case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT:
+ if (!writer.WriteBytes(capsule_frame.register_datagram_no_context_capsule
+ .context_extensions.data(),
+ capsule_frame.register_datagram_no_context_capsule
+ .context_extensions.length())) {
+ QUIC_BUG(register no context capsule extensions write fail)
+ << "Failed to write REGISTER_DATAGRAM_NO_CONTEXT CAPSULE "
+ "extensions";
+ return 0;
+ }
+ break;
+ default:
+ if (!writer.WriteBytes(capsule_frame.unknown_capsule_data.data(),
+ capsule_frame.unknown_capsule_data.length())) {
+ QUIC_BUG(capsule data write fail) << "Failed to write CAPSULE data";
+ return 0;
+ }
+ break;
+ }
+ if (writer.remaining() != 0) {
+ QUIC_BUG(capsule write length mismatch)
+ << "CAPSULE serialization wrote " << writer.length() << " instead of "
+ << writer.capacity();
+ return 0;
+ }
+ return total_frame_length;
+}
+
} // namespace quic
diff --git a/quic/core/http/http_encoder.h b/quic/core/http/http_encoder.h
index 585e739..6c8ab49 100644
--- a/quic/core/http/http_encoder.h
+++ b/quic/core/http/http_encoder.h
@@ -66,6 +66,10 @@
static QuicByteCount SerializeWebTransportStreamFrameHeader(
WebTransportSessionId session_id,
std::unique_ptr<char[]>* output);
+
+ // Serializes a CAPSULE frame as specified in draft-ietf-masque-h3-datagram.
+ static QuicByteCount SerializeCapsuleFrame(const CapsuleFrame& capsule_frame,
+ 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 c2b0f36..144c414 100644
--- a/quic/core/http/http_encoder_test.cc
+++ b/quic/core/http/http_encoder_test.cc
@@ -5,6 +5,7 @@
#include "quic/core/http/http_encoder.h"
#include "absl/base/macros.h"
+#include "quic/core/http/http_frames.h"
#include "quic/core/quic_simple_buffer_allocator.h"
#include "quic/platform/api/quic_flags.h"
#include "quic/platform/api/quic_test.h"
@@ -138,5 +139,62 @@
"WEBTRANSPORT_STREAM", buffer.get(), length, output, sizeof(output));
}
+TEST(HttpEncoderTest, SerializeRegisterDatagramContextCapsule) {
+ CapsuleFrame capsule_frame(CapsuleType::REGISTER_DATAGRAM_CONTEXT);
+ capsule_frame.register_datagram_context_capsule.context_id = 4;
+ uint8_t output[] = {0x80, 0xff, 0xca, 0xb5, // type (CAPSULE)
+ 0x02, // frame length.
+ 0x00, // capsule type (REGISTER_DATAGRAM_CONTEXT).
+ 0x04}; // context ID.
+ std::unique_ptr<char[]> buffer;
+ uint64_t length = HttpEncoder::SerializeCapsuleFrame(capsule_frame, &buffer);
+ quiche::test::CompareCharArraysWithHexError(
+ "REGISTER_DATAGRAM_CONTEXT", buffer.get(), length,
+ reinterpret_cast<char*>(output), sizeof(output));
+}
+
+TEST(HttpEncoderTest, SerializeCloseDatagramContextCapsule) {
+ CapsuleFrame capsule_frame(CapsuleType::CLOSE_DATAGRAM_CONTEXT);
+ capsule_frame.close_datagram_context_capsule.context_id = 4;
+ uint8_t output[] = {0x80, 0xff, 0xca, 0xb5, // type (CAPSULE)
+ 0x02, // frame length.
+ 0x01, // capsule type (CLOSE_DATAGRAM_CONTEXT).
+ 0x04}; // context ID.
+ std::unique_ptr<char[]> buffer;
+ uint64_t length = HttpEncoder::SerializeCapsuleFrame(capsule_frame, &buffer);
+ quiche::test::CompareCharArraysWithHexError(
+ "CLOSE_DATAGRAM_CONTEXT", buffer.get(), length,
+ reinterpret_cast<char*>(output), sizeof(output));
+}
+
+TEST(HttpEncoderTest, SerializeDatagramCapsule) {
+ uint8_t http_datagram_payload[5] = {0x21, 0x22, 0x23, 0x24, 0x25};
+ CapsuleFrame capsule_frame(CapsuleType::DATAGRAM);
+ capsule_frame.datagram_capsule.http_datagram_payload =
+ absl::string_view(reinterpret_cast<char*>(http_datagram_payload),
+ sizeof(http_datagram_payload));
+ uint8_t output[] = {0x80, 0xff, 0xca, 0xb5, // type (CAPSULE)
+ 0x06, // frame length.
+ 0x02, // capsule type (DATAGRAM).
+ 0x21, 0x22, 0x23, 0x24, 0x25}; // payload.
+ std::unique_ptr<char[]> buffer;
+ uint64_t length = HttpEncoder::SerializeCapsuleFrame(capsule_frame, &buffer);
+ quiche::test::CompareCharArraysWithHexError("DATAGRAM", buffer.get(), length,
+ reinterpret_cast<char*>(output),
+ sizeof(output));
+}
+
+TEST(HttpEncoderTest, SerializeRegisterDatagramNoContextCapsule) {
+ CapsuleFrame capsule_frame(CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT);
+ uint8_t output[] = {0x80, 0xff, 0xca, 0xb5, // type (CAPSULE)
+ 0x01, // frame length.
+ 0x03}; // capsule type (REGISTER_DATAGRAM_NO_CONTEXT).
+ std::unique_ptr<char[]> buffer;
+ uint64_t length = HttpEncoder::SerializeCapsuleFrame(capsule_frame, &buffer);
+ quiche::test::CompareCharArraysWithHexError(
+ "REGISTER_DATAGRAM_NO_CONTEXT", buffer.get(), length,
+ reinterpret_cast<char*>(output), sizeof(output));
+}
+
} // namespace test
} // namespace quic
diff --git a/quic/core/http/http_frames.h b/quic/core/http/http_frames.h
index 6a00ad7..d678062 100644
--- a/quic/core/http/http_frames.h
+++ b/quic/core/http/http_frames.h
@@ -7,6 +7,7 @@
#include <algorithm>
#include <cstdint>
+#include <limits>
#include <map>
#include <ostream>
#include <sstream>
@@ -14,6 +15,7 @@
#include "absl/container/flat_hash_map.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
#include "quic/core/http/http_constants.h"
#include "quic/core/quic_types.h"
#include "spdy/core/spdy_protocol.h"
@@ -37,6 +39,8 @@
PRIORITY_UPDATE_REQUEST_STREAM = 0xF0700,
// https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-00.html
WEBTRANSPORT_STREAM = 0x41,
+ // https://datatracker.ietf.org/doc/html/draft-ietf-masque-h3-datagram-03
+ CAPSULE = 0xffcab5,
};
// 7.2.1. DATA
@@ -182,6 +186,179 @@
}
};
+enum class CapsuleType : uint64_t {
+ // Casing in this enum matches the IETF specification.
+ REGISTER_DATAGRAM_CONTEXT = 0x00,
+ CLOSE_DATAGRAM_CONTEXT = 0x01,
+ DATAGRAM = 0x02,
+ REGISTER_DATAGRAM_NO_CONTEXT = 0x03,
+};
+
+inline std::string CapsuleTypeToString(CapsuleType capsule_type) {
+ switch (capsule_type) {
+ case CapsuleType::REGISTER_DATAGRAM_CONTEXT:
+ return "REGISTER_DATAGRAM_CONTEXT";
+ case CapsuleType::CLOSE_DATAGRAM_CONTEXT:
+ return "CLOSE_DATAGRAM_CONTEXT";
+ case CapsuleType::DATAGRAM:
+ return "DATAGRAM";
+ case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT:
+ return "REGISTER_DATAGRAM_NO_CONTEXT";
+ }
+ return absl::StrCat("Unknown(", static_cast<uint64_t>(capsule_type), ")");
+}
+
+inline std::ostream& operator<<(std::ostream& os,
+ const CapsuleType& capsule_type) {
+ os << CapsuleTypeToString(capsule_type);
+ return os;
+}
+
+// CAPSULE HTTP frame from draft-ietf-masque-h3-datagram.
+struct QUIC_EXPORT_PRIVATE CapsuleFrame {
+ CapsuleType capsule_type;
+ union {
+ struct {
+ QuicDatagramContextId context_id;
+ absl::string_view context_extensions;
+ } register_datagram_context_capsule;
+ struct {
+ QuicDatagramContextId context_id;
+ absl::string_view context_extensions;
+ } close_datagram_context_capsule;
+ struct {
+ absl::optional<QuicDatagramContextId> context_id;
+ absl::string_view http_datagram_payload;
+ } datagram_capsule;
+ struct {
+ absl::string_view context_extensions;
+ } register_datagram_no_context_capsule;
+ absl::string_view unknown_capsule_data;
+ };
+
+ explicit CapsuleFrame(CapsuleType capsule_type) : capsule_type(capsule_type) {
+ switch (capsule_type) {
+ case CapsuleType::REGISTER_DATAGRAM_CONTEXT:
+ register_datagram_context_capsule.context_id = 0;
+ register_datagram_context_capsule.context_extensions =
+ absl::string_view();
+ break;
+ case CapsuleType::CLOSE_DATAGRAM_CONTEXT:
+ close_datagram_context_capsule.context_id = 0;
+ close_datagram_context_capsule.context_extensions = absl::string_view();
+ break;
+ case CapsuleType::DATAGRAM:
+ datagram_capsule.context_id = absl::nullopt;
+ datagram_capsule.http_datagram_payload = absl::string_view();
+ break;
+ case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT:
+ register_datagram_no_context_capsule.context_extensions =
+ absl::string_view();
+ break;
+ default:
+ unknown_capsule_data = absl::string_view();
+ break;
+ }
+ }
+
+ CapsuleFrame()
+ : CapsuleFrame(
+ static_cast<CapsuleType>(std::numeric_limits<uint64_t>::max())) {}
+
+ CapsuleFrame& operator=(const CapsuleFrame& other) {
+ capsule_type = other.capsule_type;
+ switch (capsule_type) {
+ case CapsuleType::REGISTER_DATAGRAM_CONTEXT:
+ register_datagram_context_capsule.context_id =
+ other.register_datagram_context_capsule.context_id;
+ register_datagram_context_capsule.context_extensions =
+ other.register_datagram_context_capsule.context_extensions;
+ break;
+ case CapsuleType::CLOSE_DATAGRAM_CONTEXT:
+ close_datagram_context_capsule.context_id =
+ other.close_datagram_context_capsule.context_id;
+ close_datagram_context_capsule.context_extensions =
+ other.close_datagram_context_capsule.context_extensions;
+ break;
+ case CapsuleType::DATAGRAM:
+ datagram_capsule.context_id = other.datagram_capsule.context_id;
+ datagram_capsule.http_datagram_payload =
+ other.datagram_capsule.http_datagram_payload;
+ break;
+ case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT:
+ register_datagram_no_context_capsule.context_extensions =
+ other.register_datagram_no_context_capsule.context_extensions;
+ break;
+ default:
+ unknown_capsule_data = other.unknown_capsule_data;
+ break;
+ }
+ return *this;
+ }
+
+ CapsuleFrame(const CapsuleFrame& other) : CapsuleFrame(other.capsule_type) {
+ *this = other;
+ }
+
+ bool operator==(const CapsuleFrame& other) const {
+ if (capsule_type != other.capsule_type) {
+ return false;
+ }
+ switch (capsule_type) {
+ case CapsuleType::REGISTER_DATAGRAM_CONTEXT:
+ return register_datagram_context_capsule.context_id ==
+ other.register_datagram_context_capsule.context_id &&
+ register_datagram_context_capsule.context_extensions ==
+ other.register_datagram_context_capsule.context_extensions;
+ case CapsuleType::CLOSE_DATAGRAM_CONTEXT:
+ return close_datagram_context_capsule.context_id ==
+ other.close_datagram_context_capsule.context_id &&
+ close_datagram_context_capsule.context_extensions ==
+ other.close_datagram_context_capsule.context_extensions;
+ case CapsuleType::DATAGRAM:
+ return datagram_capsule.context_id ==
+ other.datagram_capsule.context_id &&
+ datagram_capsule.http_datagram_payload ==
+ other.datagram_capsule.http_datagram_payload;
+ case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT:
+ return register_datagram_no_context_capsule.context_extensions ==
+ other.register_datagram_no_context_capsule.context_extensions;
+ default:
+ return unknown_capsule_data == other.unknown_capsule_data;
+ }
+ }
+
+ std::string ToString() const {
+ std::string rv = CapsuleTypeToString(capsule_type);
+ switch (capsule_type) {
+ case CapsuleType::REGISTER_DATAGRAM_CONTEXT:
+ absl::StrAppend(&rv, "(", register_datagram_context_capsule.context_id,
+ ")");
+ break;
+ case CapsuleType::CLOSE_DATAGRAM_CONTEXT:
+ absl::StrAppend(&rv, "(", close_datagram_context_capsule.context_id,
+ ")");
+ break;
+ case CapsuleType::DATAGRAM:
+ if (datagram_capsule.context_id.has_value()) {
+ absl::StrAppend(&rv, "(", datagram_capsule.context_id.value(), ")");
+ }
+ break;
+ case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT:
+ break;
+ default:
+ break;
+ }
+ return rv;
+ }
+
+ friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+ std::ostream& os, const CapsuleFrame& frame) {
+ os << frame.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 9c164d0..b36bc7f 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -200,6 +200,10 @@
return true;
}
+bool QuicReceiveControlStream::OnCapsuleFrame(const CapsuleFrame& /*frame*/) {
+ return ValidateFrameType(HttpFrameType::CAPSULE);
+}
+
void QuicReceiveControlStream::OnWebTransportStreamFrameType(
QuicByteCount /*header_length*/,
WebTransportSessionId /*session_id*/) {
@@ -237,7 +241,8 @@
(spdy_session()->perspective() == Perspective::IS_CLIENT &&
frame_type == HttpFrameType::MAX_PUSH_ID) ||
(spdy_session()->perspective() == Perspective::IS_SERVER &&
- frame_type == HttpFrameType::ACCEPT_CH)) {
+ frame_type == HttpFrameType::ACCEPT_CH) ||
+ frame_type == HttpFrameType::CAPSULE) {
stream_delegate()->OnStreamError(
QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM,
absl::StrCat("Invalid frame type ", static_cast<int>(frame_type),
diff --git a/quic/core/http/quic_receive_control_stream.h b/quic/core/http/quic_receive_control_stream.h
index 3c61d3c..3390df0 100644
--- a/quic/core/http/quic_receive_control_stream.h
+++ b/quic/core/http/quic_receive_control_stream.h
@@ -51,6 +51,7 @@
bool OnPriorityUpdateFrame(const PriorityUpdateFrame& frame) override;
bool OnAcceptChFrameStart(QuicByteCount header_length) override;
bool OnAcceptChFrame(const AcceptChFrame& frame) override;
+ bool OnCapsuleFrame(const CapsuleFrame& frame) override;
void OnWebTransportStreamFrameType(QuicByteCount header_length,
WebTransportSessionId session_id) override;
bool OnUnknownFrameStart(uint64_t frame_type,
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc
index 3066cb3..7823b0f 100644
--- a/quic/core/http/quic_send_control_stream_test.cc
+++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -135,22 +135,24 @@
"61"); // payload "a"
if (QuicSpdySessionPeer::ShouldNegotiateHttp3Datagram(&session_)) {
expected_write_data = absl::HexStringToBytes(
- "00" // stream type: control stream
- "04" // frame type: SETTINGS frame
- "0e" // frame length
- "01" // SETTINGS_QPACK_MAX_TABLE_CAPACITY
- "40ff" // 255
- "06" // SETTINGS_MAX_HEADER_LIST_SIZE
- "4400" // 1024
- "07" // SETTINGS_QPACK_BLOCKED_STREAMS
- "10" // 16
- "4040" // 0x40 as the reserved settings id
- "14" // 20
- "4276" // SETTINGS_H3_DATAGRAM
- "01" // 1
- "4040" // 0x40 as the reserved frame type
- "01" // 1 byte frame length
- "61"); // payload "a"
+ "00" // stream type: control stream
+ "04" // frame type: SETTINGS frame
+ "0e" // frame length
+ "01" // SETTINGS_QPACK_MAX_TABLE_CAPACITY
+ "40ff" // 255
+ "06" // SETTINGS_MAX_HEADER_LIST_SIZE
+ "4400" // 1024
+ "07" // SETTINGS_QPACK_BLOCKED_STREAMS
+ "10" // 16
+ "4040" // 0x40 as the reserved settings id
+ "14" // 20
+ "4276" // SETTINGS_H3_DATAGRAM_DRAFT00
+ "01" // 1
+ "800ffd276" // SETTINGS_H3_DATAGRAM_DRAFT03
+ "01" // 1
+ "4040" // 0x40 as the reserved frame type
+ "01" // 1 byte frame length
+ "61"); // payload "a"
}
auto buffer = std::make_unique<char[]>(expected_write_data.size());
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index dc773ae..f01da54 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -135,6 +135,10 @@
session_->OnAcceptChFrameReceivedViaAlps(frame);
return true;
}
+ bool OnCapsuleFrame(const CapsuleFrame& /*frame*/) override {
+ error_detail_ = "CAPSULE frame forbidden";
+ return false;
+ }
void OnWebTransportStreamFrameType(
QuicByteCount /*header_length*/,
WebTransportSessionId /*session_id*/) override {
@@ -521,7 +525,8 @@
settings_.values[SETTINGS_MAX_FIELD_SECTION_SIZE] =
max_inbound_header_list_size_;
if (ShouldNegotiateHttp3Datagram() && version().UsesHttp3()) {
- settings_.values[SETTINGS_H3_DATAGRAM] = 1;
+ settings_.values[SETTINGS_H3_DATAGRAM_DRAFT00] = 1;
+ settings_.values[SETTINGS_H3_DATAGRAM_DRAFT03] = 1;
}
if (WillNegotiateWebTransport()) {
settings_.values[SETTINGS_WEBTRANS_DRAFT00] = 1;
@@ -1126,24 +1131,59 @@
absl::StrCat("received HTTP/2 specific setting in HTTP/3 session: ",
id));
return false;
- case SETTINGS_H3_DATAGRAM: {
+ case SETTINGS_H3_DATAGRAM_DRAFT00: {
if (!ShouldNegotiateHttp3Datagram()) {
break;
}
- QUIC_DVLOG(1) << ENDPOINT << "SETTINGS_H3_DATAGRAM received with value "
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_H3_DATAGRAM_DRAFT00 received with value "
<< value;
if (!version().UsesHttp3()) {
break;
}
if (value != 0 && value != 1) {
std::string error_details = absl::StrCat(
- "received SETTINGS_H3_DATAGRAM with invalid value ", value);
- QUIC_PEER_BUG(quic_peer_bug_10360_7) << ENDPOINT << error_details;
+ "received SETTINGS_H3_DATAGRAM_DRAFT00 with invalid value ",
+ value);
+ QUIC_PEER_BUG(bad SETTINGS_H3_DATAGRAM_DRAFT00)
+ << ENDPOINT << error_details;
CloseConnectionWithDetails(QUIC_HTTP_RECEIVE_SPDY_SETTING,
error_details);
return false;
}
- h3_datagram_supported_ = !!value;
+ if (value && http_datagram_support_ != HttpDatagramSupport::kDraft03) {
+ // If both draft-00 and draft-03 are supported, use draft-03.
+ http_datagram_support_ = HttpDatagramSupport::kDraft00;
+#if 0
+ // DO_NOT_SUBMIT hack around Ericsson bug (they're sending 00 instead of 03):
+ http_datagram_support_ = HttpDatagramSupport::kDraft03;
+#endif // 0
+ }
+ break;
+ }
+ case SETTINGS_H3_DATAGRAM_DRAFT03: {
+ if (!ShouldNegotiateHttp3Datagram()) {
+ break;
+ }
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_H3_DATAGRAM_DRAFT03 received with value "
+ << value;
+ if (!version().UsesHttp3()) {
+ break;
+ }
+ if (value != 0 && value != 1) {
+ std::string error_details = absl::StrCat(
+ "received SETTINGS_H3_DATAGRAM_DRAFT03 with invalid value ",
+ value);
+ QUIC_PEER_BUG(bad SETTINGS_H3_DATAGRAM_DRAFT03)
+ << ENDPOINT << error_details;
+ CloseConnectionWithDetails(QUIC_HTTP_RECEIVE_SPDY_SETTING,
+ error_details);
+ return false;
+ }
+ if (value) {
+ http_datagram_support_ = HttpDatagramSupport::kDraft03;
+ }
break;
}
case SETTINGS_WEBTRANS_DRAFT00:
@@ -1616,15 +1656,24 @@
QuicDatagramStreamId stream_id,
absl::optional<QuicDatagramContextId> context_id,
absl::string_view payload) {
+ if (!SupportsH3Datagram()) {
+ QUIC_BUG(send http datagram too early)
+ << "Refusing to send HTTP Datagram before SETTINGS received";
+ return MESSAGE_STATUS_INTERNAL_ERROR;
+ }
+ uint64_t stream_id_to_write = stream_id;
+ if (http_datagram_support_ != HttpDatagramSupport::kDraft00) {
+ stream_id_to_write /= kHttpDatagramStreamIdDivisor;
+ }
size_t slice_length =
- QuicDataWriter::GetVarInt62Len(stream_id) + payload.length();
+ QuicDataWriter::GetVarInt62Len(stream_id_to_write) + payload.length();
if (context_id.has_value()) {
slice_length += QuicDataWriter::GetVarInt62Len(context_id.value());
}
QuicBuffer buffer(connection()->helper()->GetStreamSendBufferAllocator(),
slice_length);
QuicDataWriter writer(slice_length, buffer.data());
- if (!writer.WriteVarInt62(stream_id)) {
+ if (!writer.WriteVarInt62(stream_id_to_write)) {
QUIC_BUG(h3 datagram stream ID write fail)
<< "Failed to write HTTP/3 datagram stream ID";
return MESSAGE_STATUS_INTERNAL_ERROR;
@@ -1665,8 +1714,8 @@
void QuicSpdySession::OnMessageReceived(absl::string_view message) {
QuicSession::OnMessageReceived(message);
- if (!h3_datagram_supported_) {
- QUIC_DLOG(ERROR) << "Ignoring unexpected received HTTP/3 datagram";
+ if (!SupportsH3Datagram()) {
+ QUIC_DLOG(INFO) << "Ignoring unexpected received HTTP/3 datagram";
return;
}
QuicDataReader reader(message);
@@ -1675,7 +1724,11 @@
QUIC_DLOG(ERROR) << "Failed to parse stream ID in received HTTP/3 datagram";
return;
}
- if (perspective() == Perspective::IS_SERVER) {
+ if (http_datagram_support_ != HttpDatagramSupport::kDraft00) {
+ stream_id64 *= kHttpDatagramStreamIdDivisor;
+ }
+ if (perspective() == Perspective::IS_SERVER &&
+ http_datagram_support_ == HttpDatagramSupport::kDraft00) {
auto it = h3_datagram_flow_id_to_stream_id_map_.find(stream_id64);
if (it == h3_datagram_flow_id_to_stream_id_map_.end()) {
QUIC_DLOG(INFO) << "Received unknown HTTP/3 datagram flow ID "
@@ -1705,10 +1758,14 @@
}
bool QuicSpdySession::SupportsWebTransport() {
- return WillNegotiateWebTransport() && h3_datagram_supported_ &&
+ return WillNegotiateWebTransport() && SupportsH3Datagram() &&
peer_supports_webtransport_;
}
+bool QuicSpdySession::SupportsH3Datagram() const {
+ return http_datagram_support_ != HttpDatagramSupport::kNone;
+}
+
WebTransportHttp3* QuicSpdySession::GetWebTransportSession(
WebTransportSessionId id) {
if (!SupportsWebTransport()) {
@@ -1840,6 +1897,25 @@
return false;
}
+std::string HttpDatagramSupportToString(
+ HttpDatagramSupport http_datagram_support) {
+ switch (http_datagram_support) {
+ case HttpDatagramSupport::kNone:
+ return "None";
+ case HttpDatagramSupport::kDraft00:
+ return "Draft00";
+ case HttpDatagramSupport::kDraft03:
+ return "Draft03";
+ }
+ return absl::StrCat("Unknown(", static_cast<int>(http_datagram_support), ")");
+}
+
+std::ostream& operator<<(std::ostream& os,
+ const HttpDatagramSupport& http_datagram_support) {
+ os << HttpDatagramSupportToString(http_datagram_support);
+ return os;
+}
+
#undef ENDPOINT // undef for jumbo builds
} // namespace quic
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 8085dd8..c471903 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -6,6 +6,7 @@
#define QUICHE_QUIC_CORE_HTTP_QUIC_SPDY_SESSION_H_
#include <cstddef>
+#include <cstdint>
#include <list>
#include <memory>
#include <string>
@@ -119,6 +120,19 @@
virtual void OnSettingsFrameResumed(const SettingsFrame& /*frame*/) {}
};
+// Whether draft-ietf-masque-h3-datagram is supported on this session and if so
+// which draft is currently in use.
+enum class HttpDatagramSupport : uint8_t {
+ kNone = 0, // HTTP Datagrams are not supported for this session.
+ kDraft00 = 1,
+ kDraft03 = 2,
+};
+
+QUIC_EXPORT_PRIVATE std::string HttpDatagramSupportToString(
+ HttpDatagramSupport http_datagram_support);
+QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+ std::ostream& os, const HttpDatagramSupport& http_datagram_support);
+
// A QUIC session for HTTP.
class QUIC_EXPORT_PRIVATE QuicSpdySession
: public QuicSession,
@@ -374,9 +388,11 @@
// extension.
virtual void OnAcceptChFrameReceivedViaAlps(const AcceptChFrame& /*frame*/);
- // Whether HTTP/3 datagrams are supported on this session, based on received
- // SETTINGS.
- bool h3_datagram_supported() const { return h3_datagram_supported_; }
+ // Whether HTTP datagrams are supported on this session and which draft is in
+ // use, based on received SETTINGS.
+ HttpDatagramSupport http_datagram_support() const {
+ return http_datagram_support_;
+ }
// This must not be used except by QuicSpdyStream::SendHttp3Datagram.
MessageStatus SendHttp3Datagram(
@@ -400,7 +416,7 @@
bool SupportsWebTransport();
// Indicates whether both the peer and us support HTTP/3 Datagrams.
- bool SupportsH3Datagram() { return h3_datagram_supported_; }
+ bool SupportsH3Datagram() const;
// Indicates whether the HTTP/3 session will indicate WebTransport support to
// the peer.
@@ -654,8 +670,9 @@
// frame has been sent yet.
absl::optional<uint64_t> last_sent_http3_goaway_id_;
- // Whether both this endpoint and our peer support HTTP/3 datagrams.
- bool h3_datagram_supported_ = false;
+ // Whether both this endpoint and our peer support HTTP datagrams and which
+ // draft is in use for this session.
+ HttpDatagramSupport http_datagram_support_ = HttpDatagramSupport::kNone;
// Whether the peer has indicated WebTransport support.
bool peer_supports_webtransport_ = false;
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index c42b8ff..371a8c3 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -549,7 +549,7 @@
void ReceiveWebTransportSettings() {
SettingsFrame settings;
- settings.values[SETTINGS_H3_DATAGRAM] = 1;
+ settings.values[SETTINGS_H3_DATAGRAM_DRAFT03] = 1;
settings.values[SETTINGS_WEBTRANS_DRAFT00] = 1;
std::string data =
std::string(1, kControlStream) + EncodeSettings(settings);
@@ -573,8 +573,14 @@
headers.OnHeaderBlockStart();
headers.OnHeader(":method", "CONNECT");
headers.OnHeader(":protocol", "webtransport");
- headers.OnHeader("datagram-flow-id", absl::StrCat(session_id));
+ if (session_.http_datagram_support() == HttpDatagramSupport::kDraft00) {
+ headers.OnHeader("datagram-flow-id", absl::StrCat(session_id));
+ }
stream->OnStreamHeaderList(/*fin=*/true, 0, headers);
+ if (session_.http_datagram_support() == HttpDatagramSupport::kDraft03) {
+ stream->OnCapsuleFrame(
+ CapsuleFrame(CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT));
+ }
WebTransportHttp3* web_transport =
session_.GetWebTransportSession(session_id);
ASSERT_TRUE(web_transport != nullptr);
@@ -3404,16 +3410,17 @@
EXPECT_EQ("multiple SETTINGS frames", error.value());
}
-TEST_P(QuicSpdySessionTestClient, H3DatagramSetting) {
+TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingDraft00) {
if (!version().UsesHttp3()) {
return;
}
session_.set_should_negotiate_h3_datagram(true);
// HTTP/3 datagrams aren't supported before SETTINGS are received.
- EXPECT_FALSE(session_.h3_datagram_supported());
+ EXPECT_FALSE(session_.SupportsH3Datagram());
+ EXPECT_EQ(session_.http_datagram_support(), HttpDatagramSupport::kNone);
// Receive SETTINGS.
SettingsFrame settings;
- settings.values[SETTINGS_H3_DATAGRAM] = 1;
+ settings.values[SETTINGS_H3_DATAGRAM_DRAFT00] = 1;
std::string data = std::string(1, kControlStream) + EncodeSettings(settings);
QuicStreamId stream_id =
GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 3);
@@ -3424,7 +3431,59 @@
EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(settings));
session_.OnStreamFrame(frame);
// HTTP/3 datagrams are now supported.
- EXPECT_TRUE(session_.h3_datagram_supported());
+ EXPECT_TRUE(session_.SupportsH3Datagram());
+ EXPECT_EQ(session_.http_datagram_support(), HttpDatagramSupport::kDraft00);
+}
+
+TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingDraft03) {
+ if (!version().UsesHttp3()) {
+ return;
+ }
+ session_.set_should_negotiate_h3_datagram(true);
+ // HTTP/3 datagrams aren't supported before SETTINGS are received.
+ EXPECT_FALSE(session_.SupportsH3Datagram());
+ EXPECT_EQ(session_.http_datagram_support(), HttpDatagramSupport::kNone);
+ // Receive SETTINGS.
+ SettingsFrame settings;
+ settings.values[SETTINGS_H3_DATAGRAM_DRAFT03] = 1;
+ std::string data = std::string(1, kControlStream) + EncodeSettings(settings);
+ QuicStreamId stream_id =
+ GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 3);
+ QuicStreamFrame frame(stream_id, /*fin=*/false, /*offset=*/0, data);
+ StrictMock<MockHttp3DebugVisitor> debug_visitor;
+ session_.set_debug_visitor(&debug_visitor);
+ EXPECT_CALL(debug_visitor, OnPeerControlStreamCreated(stream_id));
+ EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(settings));
+ session_.OnStreamFrame(frame);
+ // HTTP/3 datagrams are now supported.
+ EXPECT_TRUE(session_.SupportsH3Datagram());
+ EXPECT_EQ(session_.http_datagram_support(), HttpDatagramSupport::kDraft03);
+}
+
+TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingDraft00And03) {
+ if (!version().UsesHttp3()) {
+ return;
+ }
+ session_.set_should_negotiate_h3_datagram(true);
+ // HTTP/3 datagrams aren't supported before SETTINGS are received.
+ EXPECT_FALSE(session_.SupportsH3Datagram());
+ EXPECT_EQ(session_.http_datagram_support(), HttpDatagramSupport::kNone);
+ // Receive SETTINGS.
+ SettingsFrame settings;
+ settings.values[SETTINGS_H3_DATAGRAM_DRAFT00] = 1;
+ settings.values[SETTINGS_H3_DATAGRAM_DRAFT03] = 1;
+ std::string data = std::string(1, kControlStream) + EncodeSettings(settings);
+ QuicStreamId stream_id =
+ GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 3);
+ QuicStreamFrame frame(stream_id, /*fin=*/false, /*offset=*/0, data);
+ StrictMock<MockHttp3DebugVisitor> debug_visitor;
+ session_.set_debug_visitor(&debug_visitor);
+ EXPECT_CALL(debug_visitor, OnPeerControlStreamCreated(stream_id));
+ EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(settings));
+ session_.OnStreamFrame(frame);
+ // HTTP/3 datagrams are now supported.
+ EXPECT_TRUE(session_.SupportsH3Datagram());
+ EXPECT_EQ(session_.http_datagram_support(), HttpDatagramSupport::kDraft03);
}
TEST_P(QuicSpdySessionTestClient, WebTransportSetting) {
@@ -3444,7 +3503,7 @@
CompleteHandshake();
SettingsFrame server_settings;
- server_settings.values[SETTINGS_H3_DATAGRAM] = 1;
+ server_settings.values[SETTINGS_H3_DATAGRAM_DRAFT03] = 1;
server_settings.values[SETTINGS_WEBTRANS_DRAFT00] = 1;
std::string data =
std::string(1, kControlStream) + EncodeSettings(server_settings);
@@ -3474,7 +3533,7 @@
CompleteHandshake();
SettingsFrame server_settings;
- server_settings.values[SETTINGS_H3_DATAGRAM] = 1;
+ server_settings.values[SETTINGS_H3_DATAGRAM_DRAFT03] = 1;
server_settings.values[SETTINGS_WEBTRANS_DRAFT00] = 0;
std::string data =
std::string(1, kControlStream) + EncodeSettings(server_settings);
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index bc23474..810994e 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -15,6 +15,7 @@
#include "absl/strings/string_view.h"
#include "quic/core/http/http_constants.h"
#include "quic/core/http/http_decoder.h"
+#include "quic/core/http/http_frames.h"
#include "quic/core/http/quic_spdy_session.h"
#include "quic/core/http/spdy_utils.h"
#include "quic/core/http/web_transport_http3.h"
@@ -128,6 +129,10 @@
return false;
}
+ bool OnCapsuleFrame(const CapsuleFrame& frame) override {
+ return stream_->OnCapsuleFrame(frame);
+ }
+
void OnWebTransportStreamFrameType(
QuicByteCount header_length,
WebTransportSessionId session_id) override {
@@ -287,6 +292,13 @@
SetFinSent();
CloseWriteSide();
}
+
+ if (session()->perspective() == Perspective::IS_CLIENT && web_transport_) {
+ RegisterHttp3DatagramContextId(web_transport_->context_id(),
+ Http3DatagramContextExtensions(),
+ web_transport_.get());
+ }
+
return bytes_written;
}
@@ -1206,6 +1218,13 @@
protocol = header_value;
}
if (header_name == "datagram-flow-id") {
+ if (spdy_session_->http_datagram_support() !=
+ HttpDatagramSupport::kDraft00) {
+ QUIC_DLOG(ERROR) << ENDPOINT
+ << "Rejecting WebTransport due to unexpected "
+ "Datagram-Flow-Id header";
+ return;
+ }
if (flow_id.has_value() || header_value.empty()) {
return;
}
@@ -1217,16 +1236,26 @@
}
}
- if (method != "CONNECT" || protocol != "webtransport" ||
- !flow_id.has_value()) {
+ if (method != "CONNECT" || protocol != "webtransport") {
return;
}
- RegisterHttp3DatagramFlowId(*flow_id);
+ if (spdy_session_->http_datagram_support() == HttpDatagramSupport::kDraft00) {
+ if (!flow_id.has_value()) {
+ QUIC_DLOG(ERROR)
+ << ENDPOINT
+ << "Rejecting WebTransport due to missing Datagram-Flow-Id header";
+ return;
+ }
+ RegisterHttp3DatagramFlowId(*flow_id);
+ }
web_transport_ =
std::make_unique<WebTransportHttp3>(spdy_session_, this, id());
+ if (spdy_session_->http_datagram_support() != HttpDatagramSupport::kDraft00) {
+ return;
+ }
// If we're in draft-ietf-masque-h3-datagram-00 mode, pretend we also received
// a REGISTER_DATAGRAM_NO_CONTEXT capsule with no extensions.
// TODO(b/181256914) remove this when we remove support for
@@ -1255,14 +1284,12 @@
return;
}
- QuicDatagramStreamId stream_id = id();
- headers["datagram-flow-id"] = absl::StrCat(stream_id);
+ if (spdy_session_->http_datagram_support() == HttpDatagramSupport::kDraft00) {
+ headers["datagram-flow-id"] = absl::StrCat(id());
+ }
web_transport_ =
std::make_unique<WebTransportHttp3>(spdy_session_, this, id());
- RegisterHttp3DatagramContextId(web_transport_->context_id(),
- Http3DatagramContextExtensions(),
- web_transport_.get());
}
void QuicSpdyStream::OnCanWriteNewData() {
@@ -1323,6 +1350,79 @@
: session_id(session_id),
adapter(stream->spdy_session_, stream, stream->sequencer()) {}
+bool QuicSpdyStream::OnCapsuleFrame(const CapsuleFrame& frame) {
+ QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id() << " received capsule "
+ << frame;
+ if (!headers_decompressed_) {
+ QUIC_PEER_BUG(capsule before headers)
+ << ENDPOINT << "Stream " << id() << " received capsule " << frame
+ << " before headers";
+ return false;
+ }
+ switch (frame.capsule_type) {
+ case CapsuleType::REGISTER_DATAGRAM_CONTEXT:
+ if (datagram_registration_visitor_ == nullptr) {
+ QUIC_DLOG(ERROR) << ENDPOINT << "Received capsule " << frame
+ << " without any registration visitor";
+ return false;
+ }
+ datagram_registration_visitor_->OnContextReceived(
+ id(), frame.register_datagram_context_capsule.context_id,
+ Http3DatagramContextExtensions());
+ break;
+ case CapsuleType::CLOSE_DATAGRAM_CONTEXT:
+ if (datagram_registration_visitor_ == nullptr) {
+ QUIC_DLOG(ERROR) << ENDPOINT << "Received capsule " << frame
+ << " without any registration visitor";
+ return false;
+ }
+ datagram_registration_visitor_->OnContextClosed(
+ id(), frame.close_datagram_context_capsule.context_id,
+ Http3DatagramContextExtensions());
+ break;
+ case CapsuleType::DATAGRAM: {
+ Http3DatagramVisitor* visitor = nullptr;
+ if (frame.datagram_capsule.context_id.has_value()) {
+ auto it = datagram_context_visitors_.find(
+ frame.datagram_capsule.context_id.value());
+ if (it != datagram_context_visitors_.end()) {
+ visitor = it->second;
+ }
+ } else {
+ visitor = datagram_no_context_visitor_;
+ }
+ if (visitor == nullptr) {
+ QUIC_DLOG(ERROR) << ENDPOINT << "Received capsule " << frame
+ << " without any registration visitor";
+ return true;
+ }
+ visitor->OnHttp3Datagram(id(), frame.datagram_capsule.context_id,
+ frame.datagram_capsule.http_datagram_payload);
+ } break;
+ case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT:
+ if (datagram_registration_visitor_ == nullptr) {
+ QUIC_DLOG(ERROR) << ENDPOINT << "Received capsule " << frame
+ << " without any registration visitor";
+ return false;
+ }
+ datagram_registration_visitor_->OnContextReceived(
+ id(), /*context_id=*/absl::nullopt, Http3DatagramContextExtensions());
+ break;
+ }
+ return true;
+}
+
+void QuicSpdyStream::WriteCapsuleFrame(const CapsuleFrame& frame) {
+ QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id() << " sending capsule "
+ << frame;
+ std::unique_ptr<char[]> buffer;
+ QuicByteCount frame_length =
+ HttpEncoder::SerializeCapsuleFrame(frame, &buffer);
+ QUICHE_DCHECK_GT(frame_length, 0u);
+ WriteOrBufferData(absl::string_view(buffer.get(), frame_length),
+ /*fin=*/false, /*ack_listener=*/nullptr);
+}
+
MessageStatus QuicSpdyStream::SendHttp3Datagram(
absl::optional<QuicDatagramContextId> context_id,
absl::string_view payload) {
@@ -1368,19 +1468,22 @@
if (visitor == nullptr) {
QUIC_BUG(null datagram visitor)
<< ENDPOINT << "Null datagram visitor for stream ID " << id()
- << " context ID " << (context_id.has_value() ? context_id.value() : 0);
+ << " context ID "
+ << (context_id.has_value() ? absl::StrCat(context_id.value()) : "none");
return;
}
if (datagram_registration_visitor_ == nullptr) {
QUIC_BUG(context registration without registration visitor)
<< ENDPOINT << "Cannot register context ID "
- << (context_id.has_value() ? context_id.value() : 0)
+ << (context_id.has_value() ? absl::StrCat(context_id.value()) : "none")
<< " without registration visitor for stream ID " << id();
return;
}
QUIC_DLOG(INFO) << ENDPOINT << "Registering datagram context ID "
- << (context_id.has_value() ? context_id.value() : 0)
+ << (context_id.has_value() ? absl::StrCat(context_id.value())
+ : "none")
<< " with stream ID " << id();
+
if (context_id.has_value()) {
if (datagram_no_context_visitor_ != nullptr) {
QUIC_BUG(h3 datagram context ID mix1)
@@ -1392,28 +1495,45 @@
}
auto insertion_result =
datagram_context_visitors_.insert({context_id.value(), visitor});
- QUIC_BUG_IF(h3 datagram double context registration,
- !insertion_result.second)
- << ENDPOINT << "Attempted to doubly register HTTP/3 stream ID " << id()
- << " context ID " << context_id.value();
- return;
+ if (!insertion_result.second) {
+ QUIC_BUG(h3 datagram double context registration)
+ << ENDPOINT << "Attempted to doubly register HTTP/3 stream ID "
+ << id() << " context ID " << context_id.value();
+ return;
+ }
+ } else {
+ // Registration without a context ID.
+ if (!datagram_context_visitors_.empty()) {
+ QUIC_BUG(h3 datagram context ID mix2)
+ << ENDPOINT
+ << "Attempted to mix registrations with and without context IDs "
+ "for stream ID "
+ << id();
+ return;
+ }
+ if (datagram_no_context_visitor_ != nullptr) {
+ QUIC_BUG(h3 datagram double no context registration)
+ << ENDPOINT << "Attempted to doubly register HTTP/3 stream ID "
+ << id() << " with no context ID";
+ return;
+ }
+ datagram_no_context_visitor_ = visitor;
}
- // Registration without a context ID.
- if (!datagram_context_visitors_.empty()) {
- QUIC_BUG(h3 datagram context ID mix2)
- << ENDPOINT
- << "Attempted to mix registrations with and without context IDs "
- "for stream ID "
- << id();
- return;
+ if (spdy_session_->http_datagram_support() == HttpDatagramSupport::kDraft03) {
+ const bool is_client = session()->perspective() == Perspective::IS_CLIENT;
+ if (context_id.has_value()) {
+ const bool is_client_context = context_id.value() % 2 == 0;
+ if (is_client == is_client_context) {
+ CapsuleFrame capsule_frame(CapsuleType::REGISTER_DATAGRAM_CONTEXT);
+ capsule_frame.register_datagram_context_capsule.context_id =
+ context_id.value();
+ WriteCapsuleFrame(capsule_frame);
+ }
+ } else if (is_client) {
+ WriteCapsuleFrame(
+ CapsuleFrame(CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT));
+ }
}
- if (datagram_no_context_visitor_ != nullptr) {
- QUIC_BUG(h3 datagram double no context registration)
- << ENDPOINT << "Attempted to doubly register HTTP/3 stream ID " << id()
- << " with no context ID";
- return;
- }
- datagram_no_context_visitor_ = visitor;
}
void QuicSpdyStream::UnregisterHttp3DatagramContextId(
@@ -1421,26 +1541,34 @@
if (datagram_registration_visitor_ == nullptr) {
QUIC_BUG(context unregistration without registration visitor)
<< ENDPOINT << "Cannot unregister context ID "
- << (context_id.has_value() ? context_id.value() : 0)
+ << (context_id.has_value() ? absl::StrCat(context_id.value()) : "none")
<< " without registration visitor for stream ID " << id();
return;
}
QUIC_DLOG(INFO) << ENDPOINT << "Unregistering datagram context ID "
- << (context_id.has_value() ? context_id.value() : 0)
+ << (context_id.has_value() ? absl::StrCat(context_id.value())
+ : "none")
<< " with stream ID " << id();
if (context_id.has_value()) {
size_t num_erased = datagram_context_visitors_.erase(context_id.value());
QUIC_BUG_IF(h3 datagram unregister unknown context, num_erased != 1)
<< "Attempted to unregister unknown HTTP/3 context ID "
<< context_id.value() << " on stream ID " << id();
- return;
+ } else {
+ // Unregistration without a context ID.
+ QUIC_BUG_IF(h3 datagram unknown context unregistration,
+ datagram_no_context_visitor_ == nullptr)
+ << "Attempted to unregister unknown no context on HTTP/3 stream ID "
+ << id();
+ datagram_no_context_visitor_ = nullptr;
}
- // Unregistration without a context ID.
- QUIC_BUG_IF(h3 datagram unknown context unregistration,
- datagram_no_context_visitor_ == nullptr)
- << "Attempted to unregister unknown no context on HTTP/3 stream ID "
- << id();
- datagram_no_context_visitor_ = nullptr;
+ if (spdy_session_->http_datagram_support() == HttpDatagramSupport::kDraft03 &&
+ context_id.has_value()) {
+ CapsuleFrame capsule_frame(CapsuleType::CLOSE_DATAGRAM_CONTEXT);
+ capsule_frame.close_datagram_context_capsule.context_id =
+ context_id.value();
+ WriteCapsuleFrame(capsule_frame);
+ }
}
void QuicSpdyStream::MoveHttp3DatagramContextIdRegistration(
@@ -1449,12 +1577,13 @@
if (datagram_registration_visitor_ == nullptr) {
QUIC_BUG(context move without registration visitor)
<< ENDPOINT << "Cannot move context ID "
- << (context_id.has_value() ? context_id.value() : 0)
+ << (context_id.has_value() ? absl::StrCat(context_id.value()) : "none")
<< " without registration visitor for stream ID " << id();
return;
}
QUIC_DLOG(INFO) << ENDPOINT << "Moving datagram context ID "
- << (context_id.has_value() ? context_id.value() : 0)
+ << (context_id.has_value() ? absl::StrCat(context_id.value())
+ : "none")
<< " with stream ID " << id();
if (context_id.has_value()) {
QUIC_BUG_IF(h3 datagram move unknown context,
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h
index 4df1196..d9a784d 100644
--- a/quic/core/http/quic_spdy_stream.h
+++ b/quic/core/http/quic_spdy_stream.h
@@ -21,6 +21,7 @@
#include "absl/types/span.h"
#include "quic/core/http/http_decoder.h"
#include "quic/core/http/http_encoder.h"
+#include "quic/core/http/http_frames.h"
#include "quic/core/http/quic_header_list.h"
#include "quic/core/http/quic_spdy_stream_body_manager.h"
#include "quic/core/qpack/qpack_decoded_headers_accumulator.h"
@@ -253,6 +254,8 @@
// rejected due to buffer being full. |write_size| must be non-zero.
bool CanWriteNewBodyData(QuicByteCount write_size) const;
+ bool OnCapsuleFrame(const CapsuleFrame& frame);
+
// Sends an HTTP/3 datagram. The stream and context IDs are not part of
// |payload|.
MessageStatus SendHttp3Datagram(
@@ -410,6 +413,8 @@
ABSL_MUST_USE_RESULT bool WriteDataFrameHeader(QuicByteCount data_length,
bool force_write);
+ void WriteCapsuleFrame(const CapsuleFrame& frame);
+
QuicSpdySession* spdy_session_;
bool on_body_available_called_because_sequencer_is_closed_;
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index c19dc3c..50e4905 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -16,6 +16,7 @@
#include "absl/strings/string_view.h"
#include "quic/core/crypto/null_encrypter.h"
#include "quic/core/http/http_encoder.h"
+#include "quic/core/http/quic_spdy_session.h"
#include "quic/core/http/spdy_utils.h"
#include "quic/core/http/web_transport_http3.h"
#include "quic/core/quic_connection.h"
@@ -3017,7 +3018,7 @@
}
}
-TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeaders) {
+TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeadersDatagramDraft00) {
if (!UsesHttp3()) {
return;
}
@@ -3025,7 +3026,9 @@
InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
session_->set_should_negotiate_h3_datagram(true);
session_->EnableWebTransport();
- QuicSpdySessionPeer::EnableWebTransport(*session_);
+ QuicSpdySessionPeer::EnableWebTransport(session_.get());
+ QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
+ HttpDatagramSupport::kDraft00);
EXPECT_CALL(*stream_, WriteHeadersMock(false));
EXPECT_CALL(*session_, WritevData(stream_->id(), _, _, _, _, _))
@@ -3040,7 +3043,31 @@
EXPECT_EQ(stream_->id(), stream_->web_transport()->id());
}
-TEST_P(QuicSpdyStreamTest, ProcessIncomingWebTransportHeaders) {
+TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeadersDatagramDraft03) {
+ if (!UsesHttp3()) {
+ return;
+ }
+
+ InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
+ session_->set_should_negotiate_h3_datagram(true);
+ session_->EnableWebTransport();
+ QuicSpdySessionPeer::EnableWebTransport(session_.get());
+ QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
+ HttpDatagramSupport::kDraft03);
+
+ EXPECT_CALL(*stream_, WriteHeadersMock(false));
+ EXPECT_CALL(*session_, WritevData(stream_->id(), _, _, _, _, _))
+ .Times(AnyNumber());
+
+ spdy::SpdyHeaderBlock headers;
+ headers[":method"] = "CONNECT";
+ headers[":protocol"] = "webtransport";
+ stream_->WriteHeaders(std::move(headers), /*fin=*/false, nullptr);
+ ASSERT_TRUE(stream_->web_transport() != nullptr);
+ EXPECT_EQ(stream_->id(), stream_->web_transport()->id());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessIncomingWebTransportHeadersDraft03) {
if (!UsesHttp3()) {
return;
}
@@ -3048,7 +3075,36 @@
Initialize(kShouldProcessData);
session_->set_should_negotiate_h3_datagram(true);
session_->EnableWebTransport();
- QuicSpdySessionPeer::EnableWebTransport(*session_);
+ QuicSpdySessionPeer::EnableWebTransport(session_.get());
+ QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
+ HttpDatagramSupport::kDraft03);
+
+ headers_[":method"] = "CONNECT";
+ headers_[":protocol"] = "webtransport";
+
+ stream_->OnStreamHeadersPriority(
+ spdy::SpdyStreamPrecedence(kV3HighestPriority));
+ ProcessHeaders(false, headers_);
+ CapsuleFrame capsule_frame(CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT);
+ stream_->OnCapsuleFrame(capsule_frame);
+ EXPECT_EQ("", stream_->data());
+ EXPECT_FALSE(stream_->header_list().empty());
+ EXPECT_FALSE(stream_->IsDoneReading());
+ ASSERT_TRUE(stream_->web_transport() != nullptr);
+ EXPECT_EQ(stream_->id(), stream_->web_transport()->id());
+}
+
+TEST_P(QuicSpdyStreamTest, ProcessIncomingWebTransportHeadersDraft00) {
+ if (!UsesHttp3()) {
+ return;
+ }
+
+ Initialize(kShouldProcessData);
+ session_->set_should_negotiate_h3_datagram(true);
+ session_->EnableWebTransport();
+ QuicSpdySessionPeer::EnableWebTransport(session_.get());
+ QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
+ HttpDatagramSupport::kDraft00);
headers_[":method"] = "CONNECT";
headers_[":protocol"] = "webtransport";
@@ -3075,7 +3131,9 @@
Initialize(kShouldProcessData);
session_->set_should_negotiate_h3_datagram(true);
session_->EnableWebTransport();
- QuicSpdySessionPeer::EnableWebTransport(*session_);
+ QuicSpdySessionPeer::EnableWebTransport(session_.get());
+ QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
+ HttpDatagramSupport::kDraft00);
headers_[":method"] = "CONNECT";
headers_[":protocol"] = "webtransport";
@@ -3119,13 +3177,14 @@
stream_->UnregisterHttp3DatagramRegistrationVisitor();
}
-TEST_P(QuicSpdyStreamTest, H3DatagramRegistrationWithoutContext) {
+TEST_P(QuicSpdyStreamTest, HttpDatagramRegistrationWithoutContextDraft00) {
if (!UsesHttp3()) {
return;
}
- Initialize(kShouldProcessData);
+ InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
session_->set_should_negotiate_h3_datagram(true);
- QuicSpdySessionPeer::SetH3DatagramSupported(session_.get(), true);
+ QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
+ HttpDatagramSupport::kDraft00);
session_->RegisterHttp3DatagramFlowId(stream_->id(), stream_->id());
::testing::NiceMock<MockHttp3DatagramRegistrationVisitor>
h3_datagram_registration_visitor;
@@ -3168,23 +3227,39 @@
session_->UnregisterHttp3DatagramFlowId(stream_->id());
}
-TEST_P(QuicSpdyStreamTest, H3DatagramRegistrationWithContext) {
+TEST_P(QuicSpdyStreamTest, H3DatagramRegistrationWithoutContextDraft03) {
if (!UsesHttp3()) {
return;
}
- Initialize(kShouldProcessData);
+ InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
session_->set_should_negotiate_h3_datagram(true);
- QuicSpdySessionPeer::SetH3DatagramSupported(session_.get(), true);
- session_->RegisterHttp3DatagramFlowId(stream_->id(), stream_->id());
+ QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
+ HttpDatagramSupport::kDraft03);
::testing::NiceMock<MockHttp3DatagramRegistrationVisitor>
h3_datagram_registration_visitor;
SavingHttp3DatagramVisitor h3_datagram_visitor;
- absl::optional<QuicDatagramContextId> context_id = 42;
+ absl::optional<QuicDatagramContextId> context_id;
Http3DatagramContextExtensions extensions;
+ ASSERT_EQ(QuicDataWriter::GetVarInt62Len(stream_->id()), 1);
+ std::array<char, 256> datagram;
+ datagram[0] = stream_->id();
+ for (size_t i = 1; i < datagram.size(); i++) {
+ datagram[i] = i;
+ }
stream_->RegisterHttp3DatagramRegistrationVisitor(
&h3_datagram_registration_visitor);
+
+ // Expect us to send a REGISTER_DATAGRAM_NO_CONTEXT capsule.
+ EXPECT_CALL(*session_, WritevData(stream_->id(), _, _, _, _, _));
+
stream_->RegisterHttp3DatagramContextId(context_id, extensions,
&h3_datagram_visitor);
+ session_->OnMessageReceived(
+ absl::string_view(datagram.data(), datagram.size()));
+ EXPECT_THAT(h3_datagram_visitor.received_h3_datagrams(),
+ ElementsAre(SavingHttp3DatagramVisitor::SavedHttp3Datagram{
+ stream_->id(), context_id,
+ std::string(&datagram[1], datagram.size() - 1)}));
// Test move.
::testing::NiceMock<MockHttp3DatagramRegistrationVisitor>
h3_datagram_registration_visitor2;
@@ -3192,9 +3267,72 @@
SavingHttp3DatagramVisitor h3_datagram_visitor2;
stream_->MoveHttp3DatagramContextIdRegistration(context_id,
&h3_datagram_visitor2);
+ EXPECT_TRUE(h3_datagram_visitor2.received_h3_datagrams().empty());
+ session_->OnMessageReceived(
+ absl::string_view(datagram.data(), datagram.size()));
+ EXPECT_THAT(h3_datagram_visitor2.received_h3_datagrams(),
+ ElementsAre(SavingHttp3DatagramVisitor::SavedHttp3Datagram{
+ stream_->id(), context_id,
+ std::string(&datagram[1], datagram.size() - 1)}));
// Cleanup.
stream_->UnregisterHttp3DatagramContextId(context_id);
stream_->UnregisterHttp3DatagramRegistrationVisitor();
+}
+
+TEST_P(QuicSpdyStreamTest, HttpDatagramRegistrationWithContext) {
+ if (!UsesHttp3()) {
+ return;
+ }
+ InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT);
+ session_->set_should_negotiate_h3_datagram(true);
+ QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
+ HttpDatagramSupport::kDraft03);
+ ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor>
+ h3_datagram_registration_visitor;
+ SavingHttp3DatagramVisitor h3_datagram_visitor;
+ absl::optional<QuicDatagramContextId> context_id = 42;
+ Http3DatagramContextExtensions extensions;
+ ASSERT_EQ(QuicDataWriter::GetVarInt62Len(stream_->id()), 1);
+ std::array<char, 256> datagram;
+ datagram[0] = stream_->id();
+ datagram[1] = context_id.value();
+ for (size_t i = 2; i < datagram.size(); i++) {
+ datagram[i] = i;
+ }
+ stream_->RegisterHttp3DatagramRegistrationVisitor(
+ &h3_datagram_registration_visitor);
+
+ // Expect us to send a REGISTER_DATAGRAM_CONTEXT capsule.
+ EXPECT_CALL(*session_, WritevData(stream_->id(), _, _, _, _, _));
+
+ stream_->RegisterHttp3DatagramContextId(context_id, extensions,
+ &h3_datagram_visitor);
+ session_->OnMessageReceived(
+ absl::string_view(datagram.data(), datagram.size()));
+ EXPECT_THAT(h3_datagram_visitor.received_h3_datagrams(),
+ ElementsAre(SavingHttp3DatagramVisitor::SavedHttp3Datagram{
+ stream_->id(), context_id,
+ std::string(&datagram[2], datagram.size() - 2)}));
+ // Test move.
+ ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor>
+ h3_datagram_registration_visitor2;
+ stream_->MoveHttp3DatagramRegistration(&h3_datagram_registration_visitor2);
+ SavingHttp3DatagramVisitor h3_datagram_visitor2;
+ stream_->MoveHttp3DatagramContextIdRegistration(context_id,
+ &h3_datagram_visitor2);
+ EXPECT_TRUE(h3_datagram_visitor2.received_h3_datagrams().empty());
+ session_->OnMessageReceived(
+ absl::string_view(datagram.data(), datagram.size()));
+ EXPECT_THAT(h3_datagram_visitor2.received_h3_datagrams(),
+ ElementsAre(SavingHttp3DatagramVisitor::SavedHttp3Datagram{
+ stream_->id(), context_id,
+ std::string(&datagram[2], datagram.size() - 2)}));
+ // Cleanup.
+
+ // Expect us to send a CLOSE_DATAGRAM_CONTEXT capsule.
+ EXPECT_CALL(*session_, WritevData(stream_->id(), _, _, _, _, _));
+ stream_->UnregisterHttp3DatagramContextId(context_id);
+ stream_->UnregisterHttp3DatagramRegistrationVisitor();
session_->UnregisterHttp3DatagramFlowId(stream_->id());
}
@@ -3204,7 +3342,8 @@
}
Initialize(kShouldProcessData);
session_->set_should_negotiate_h3_datagram(true);
- QuicSpdySessionPeer::SetH3DatagramSupported(session_.get(), true);
+ QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
+ HttpDatagramSupport::kDraft03);
absl::optional<QuicDatagramContextId> context_id;
std::string h3_datagram_payload = {1, 2, 3, 4, 5, 6};
EXPECT_CALL(*connection_, SendMessage(1, _, false))
diff --git a/quic/core/quic_constants.h b/quic/core/quic_constants.h
index 162de89..97f7567 100644
--- a/quic/core/quic_constants.h
+++ b/quic/core/quic_constants.h
@@ -301,6 +301,10 @@
kDatagramContextIdIncrement = 2,
};
+enum : uint64_t {
+ kHttpDatagramStreamIdDivisor = 4,
+};
+
} // namespace quic
#endif // QUICHE_QUIC_CORE_QUIC_CONSTANTS_H_
diff --git a/quic/masque/masque_client_session.cc b/quic/masque/masque_client_session.cc
index 0d86569..f3c70e2 100644
--- a/quic/masque/masque_client_session.cc
+++ b/quic/masque/masque_client_session.cc
@@ -3,7 +3,9 @@
// found in the LICENSE file.
#include "quic/masque/masque_client_session.h"
+
#include "absl/algorithm/container.h"
+#include "absl/strings/str_cat.h"
#include "quic/core/http/spdy_utils.h"
#include "quic/core/quic_data_reader.h"
#include "quic/core/quic_utils.h"
@@ -101,7 +103,9 @@
headers[":scheme"] = "masque";
headers[":path"] = "/";
headers[":authority"] = target_server_address.ToString();
- SpdyUtils::AddDatagramFlowIdHeader(&headers, stream->id());
+ if (http_datagram_support() == HttpDatagramSupport::kDraft00) {
+ SpdyUtils::AddDatagramFlowIdHeader(&headers, stream->id());
+ }
size_t bytes_sent =
stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/false);
if (bytes_sent == 0) {
@@ -142,8 +146,8 @@
<< " compressed with stream ID " << connect_udp->stream()->id()
<< " context ID "
<< (connect_udp->context_id().has_value()
- ? connect_udp->context_id().value()
- : 0)
+ ? absl::StrCat(connect_udp->context_id().value())
+ : "none")
<< " and got message status "
<< MessageStatusToString(message_status);
}
@@ -182,8 +186,8 @@
QUIC_DLOG(INFO) << "Removing state for stream ID " << it->stream()->id()
<< " context ID "
<< (it->context_id().has_value()
- ? it->context_id().value()
- : 0);
+ ? absl::StrCat(it->context_id().value())
+ : "none");
auto* stream = it->stream();
it = connect_udp_client_states_.erase(it);
if (!stream->write_side_closed()) {
@@ -224,8 +228,8 @@
QUIC_DLOG(INFO) << "Stream " << stream_id
<< " was closed, removing state for context ID "
<< (it->context_id().has_value()
- ? it->context_id().value()
- : 0);
+ ? absl::StrCat(it->context_id().value())
+ : "none");
auto* encapsulated_client_session = it->encapsulated_client_session();
it = connect_udp_client_states_.erase(it);
encapsulated_client_session->CloseConnection(
@@ -241,6 +245,7 @@
}
bool MasqueClientSession::OnSettingsFrame(const SettingsFrame& frame) {
+ QUIC_DLOG(INFO) << "Received SETTINGS: " << frame;
if (!QuicSpdyClientSession::OnSettingsFrame(frame)) {
QUIC_DLOG(ERROR) << "Failed to parse received settings";
return false;
@@ -249,6 +254,7 @@
QUIC_DLOG(ERROR) << "Refusing to use MASQUE without HTTP/3 Datagrams";
return false;
}
+ QUIC_DLOG(INFO) << "Using HTTP Datagram: " << http_datagram_support();
owner_->OnSettingsReceived();
return true;
}
@@ -308,7 +314,8 @@
QUIC_DVLOG(1) << "Sent " << payload.size()
<< " bytes to connection for stream ID " << stream_id
<< " context ID "
- << (context_id.has_value() ? context_id.value() : 0);
+ << (context_id.has_value() ? absl::StrCat(context_id.value())
+ : "none");
}
void MasqueClientSession::ConnectUdpClientState::OnContextReceived(
@@ -321,11 +328,13 @@
return;
}
if (context_id != context_id_) {
- QUIC_DLOG(INFO) << "Ignoring unexpected context ID "
- << (context_id.has_value() ? context_id.value() : 0)
- << " instead of "
- << (context_id_.has_value() ? context_id_.value() : 0)
- << " on stream ID " << stream_->id();
+ QUIC_DLOG(INFO)
+ << "Ignoring unexpected context ID "
+ << (context_id.has_value() ? absl::StrCat(context_id.value()) : "none")
+ << " instead of "
+ << (context_id_.has_value() ? absl::StrCat(context_id_.value())
+ : "none")
+ << " on stream ID " << stream_->id();
return;
}
// Do nothing since the client registers first and we currently ignore
@@ -342,11 +351,13 @@
return;
}
if (context_id != context_id_) {
- QUIC_DLOG(INFO) << "Ignoring unexpected close of context ID "
- << (context_id.has_value() ? context_id.value() : 0)
- << " instead of "
- << (context_id_.has_value() ? context_id_.value() : 0)
- << " on stream ID " << stream_->id();
+ QUIC_DLOG(INFO)
+ << "Ignoring unexpected close of context ID "
+ << (context_id.has_value() ? absl::StrCat(context_id.value()) : "none")
+ << " instead of "
+ << (context_id_.has_value() ? absl::StrCat(context_id_.value())
+ : "none")
+ << " on stream ID " << stream_->id();
return;
}
QUIC_DLOG(INFO) << "Received datagram context close on stream ID "
diff --git a/quic/masque/masque_server_session.cc b/quic/masque/masque_server_session.cc
index effc17f..a38ae9d 100644
--- a/quic/masque/masque_server_session.cc
+++ b/quic/masque/masque_server_session.cc
@@ -202,13 +202,15 @@
QUIC_DLOG(ERROR) << "MASQUE request with bad method \"" << method << "\"";
return CreateBackendErrorResponse("400", "Bad method");
}
- absl::optional<QuicDatagramStreamId> flow_id =
- SpdyUtils::ParseDatagramFlowIdHeader(request_headers);
- if (!flow_id.has_value()) {
- QUIC_DLOG(ERROR)
- << "MASQUE request with bad or missing DatagramFlowId header";
- return CreateBackendErrorResponse("400",
- "Bad or missing DatagramFlowId header");
+ absl::optional<QuicDatagramStreamId> flow_id;
+ if (http_datagram_support() == HttpDatagramSupport::kDraft00) {
+ flow_id = SpdyUtils::ParseDatagramFlowIdHeader(request_headers);
+ if (!flow_id.has_value()) {
+ QUIC_DLOG(ERROR)
+ << "MASQUE request with bad or missing DatagramFlowId header";
+ return CreateBackendErrorResponse(
+ "400", "Bad or missing DatagramFlowId header");
+ }
}
QuicUrl url(absl::StrCat("https://", authority));
if (!url.IsValid() || url.PathParamsQuery() != "/") {
@@ -235,7 +237,9 @@
info_list, freeaddrinfo);
QuicSocketAddress target_server_address(info_list->ai_addr,
info_list->ai_addrlen);
- QUIC_DLOG(INFO) << "Got CONNECT_UDP request flow_id=" << *flow_id
+ QUIC_DLOG(INFO) << "Got CONNECT_UDP request on stream ID "
+ << request_handler->stream_id() << " flow_id="
+ << (flow_id.has_value() ? absl::StrCat(*flow_id) : "none")
<< " target_server_address=\"" << target_server_address
<< "\"";
@@ -264,20 +268,26 @@
<< request_handler->stream_id();
return CreateBackendErrorResponse("500", "Bad stream type");
}
- stream->RegisterHttp3DatagramFlowId(*flow_id);
+ if (flow_id.has_value()) {
+ stream->RegisterHttp3DatagramFlowId(*flow_id);
+ }
connect_udp_server_states_.push_back(
ConnectUdpServerState(stream, context_id, target_server_address,
fd_wrapper.extract_fd(), this));
- // TODO(b/181256914) remove this when we drop support for
- // draft-ietf-masque-h3-datagram-00 in favor of later drafts.
- Http3DatagramContextExtensions extensions;
- stream->RegisterHttp3DatagramContextId(context_id, extensions,
- &connect_udp_server_states_.back());
+ if (http_datagram_support() == HttpDatagramSupport::kDraft00) {
+ // TODO(b/181256914) remove this when we drop support for
+ // draft-ietf-masque-h3-datagram-00 in favor of later drafts.
+ Http3DatagramContextExtensions extensions;
+ stream->RegisterHttp3DatagramContextId(
+ context_id, extensions, &connect_udp_server_states_.back());
+ }
spdy::Http2HeaderBlock response_headers;
response_headers[":status"] = "200";
- SpdyUtils::AddDatagramFlowIdHeader(&response_headers, *flow_id);
+ if (flow_id.has_value()) {
+ SpdyUtils::AddDatagramFlowIdHeader(&response_headers, *flow_id);
+ }
auto response = std::make_unique<QuicBackendResponse>();
response->set_response_type(QuicBackendResponse::INCOMPLETE_RESPONSE);
response->set_headers(std::move(response_headers));
@@ -424,6 +434,19 @@
return std::string("MasqueServerSession-") + connection_id().ToString();
}
+bool MasqueServerSession::OnSettingsFrame(const SettingsFrame& frame) {
+ QUIC_DLOG(INFO) << "Received SETTINGS: " << frame;
+ if (!QuicSimpleServerSession::OnSettingsFrame(frame)) {
+ return false;
+ }
+ if (!SupportsH3Datagram()) {
+ QUIC_DLOG(ERROR) << "Refusing to use MASQUE without HTTP Datagrams";
+ return false;
+ }
+ QUIC_DLOG(INFO) << "Using HTTP Datagram: " << http_datagram_support();
+ return true;
+}
+
MasqueServerSession::ConnectUdpServerState::ConnectUdpServerState(
QuicSpdyStream* stream, absl::optional<QuicDatagramContextId> context_id,
const QuicSocketAddress& target_server_address, QuicUdpSocketFd fd,
@@ -440,10 +463,10 @@
MasqueServerSession::ConnectUdpServerState::~ConnectUdpServerState() {
if (stream() != nullptr) {
- stream()->UnregisterHttp3DatagramRegistrationVisitor();
if (context_registered_) {
stream()->UnregisterHttp3DatagramContextId(context_id());
}
+ stream()->UnregisterHttp3DatagramRegistrationVisitor();
}
if (fd_ == kQuicInvalidSocketFd) {
return;
@@ -515,17 +538,20 @@
context_id_ = context_id;
}
if (context_id != context_id_) {
- QUIC_DLOG(INFO) << "Ignoring unexpected context ID "
- << (context_id.has_value() ? context_id.value() : 0)
- << " instead of "
- << (context_id_.has_value() ? context_id_.value() : 0)
- << " on stream ID " << stream()->id();
+ QUIC_DLOG(INFO)
+ << "Ignoring unexpected context ID "
+ << (context_id.has_value() ? absl::StrCat(context_id.value()) : "none")
+ << " instead of "
+ << (context_id_.has_value() ? absl::StrCat(context_id_.value())
+ : "none")
+ << " on stream ID " << stream()->id();
return;
}
if (context_registered_) {
QUIC_BUG(MASQUE server double datagram context registration)
<< "Try to re-register stream ID " << stream_id << " context ID "
- << (context_id_.has_value() ? context_id_.value() : 0);
+ << (context_id_.has_value() ? absl::StrCat(context_id_.value())
+ : "none");
return;
}
context_registered_ = true;
@@ -543,11 +569,13 @@
return;
}
if (context_id != context_id_) {
- QUIC_DLOG(INFO) << "Ignoring unexpected close of context ID "
- << (context_id.has_value() ? context_id.value() : 0)
- << " instead of "
- << (context_id_.has_value() ? context_id_.value() : 0)
- << " on stream ID " << stream()->id();
+ QUIC_DLOG(INFO)
+ << "Ignoring unexpected close of context ID "
+ << (context_id.has_value() ? absl::StrCat(context_id.value()) : "none")
+ << " instead of "
+ << (context_id_.has_value() ? absl::StrCat(context_id_.value())
+ : "none")
+ << " on stream ID " << stream()->id();
return;
}
QUIC_DLOG(INFO) << "Received datagram context close on stream ID "
diff --git a/quic/masque/masque_server_session.h b/quic/masque/masque_server_session.h
index 1bec14c..5729655 100644
--- a/quic/masque/masque_server_session.h
+++ b/quic/masque/masque_server_session.h
@@ -136,6 +136,8 @@
bool context_registered_ = false;
};
+ // From QuicSpdySession.
+ bool OnSettingsFrame(const SettingsFrame& frame) override;
bool ShouldNegotiateHttp3Datagram() override { return true; }
MasqueServerBackend* masque_server_backend_; // Unowned.
diff --git a/quic/test_tools/quic_spdy_session_peer.cc b/quic/test_tools/quic_spdy_session_peer.cc
index a27f073..d0fd669 100644
--- a/quic/test_tools/quic_spdy_session_peer.cc
+++ b/quic/test_tools/quic_spdy_session_peer.cc
@@ -100,9 +100,9 @@
}
// static
-void QuicSpdySessionPeer::SetH3DatagramSupported(QuicSpdySession* session,
- bool h3_datagram_supported) {
- session->h3_datagram_supported_ = h3_datagram_supported;
+void QuicSpdySessionPeer::SetHttpDatagramSupport(
+ QuicSpdySession* session, HttpDatagramSupport http_datagram_support) {
+ session->http_datagram_support_ = http_datagram_support;
}
// static
@@ -112,10 +112,10 @@
}
// static
-void QuicSpdySessionPeer::EnableWebTransport(QuicSpdySession& session) {
- QUICHE_DCHECK(session.WillNegotiateWebTransport());
- session.h3_datagram_supported_ = true;
- session.peer_supports_webtransport_ = true;
+void QuicSpdySessionPeer::EnableWebTransport(QuicSpdySession* session) {
+ QUICHE_DCHECK(session->WillNegotiateWebTransport());
+ SetHttpDatagramSupport(session, HttpDatagramSupport::kDraft03);
+ session->peer_supports_webtransport_ = true;
}
} // namespace test
diff --git a/quic/test_tools/quic_spdy_session_peer.h b/quic/test_tools/quic_spdy_session_peer.h
index 0d06c4d..2aa204b 100644
--- a/quic/test_tools/quic_spdy_session_peer.h
+++ b/quic/test_tools/quic_spdy_session_peer.h
@@ -7,6 +7,7 @@
#include "quic/core/http/quic_receive_control_stream.h"
#include "quic/core/http/quic_send_control_stream.h"
+#include "quic/core/http/quic_spdy_session.h"
#include "quic/core/qpack/qpack_receive_stream.h"
#include "quic/core/qpack/qpack_send_stream.h"
#include "quic/core/quic_packets.h"
@@ -16,7 +17,6 @@
namespace quic {
class QuicHeadersStream;
-class QuicSpdySession;
namespace test {
@@ -50,10 +50,10 @@
QuicSpdySession* session);
static QpackReceiveStream* GetQpackEncoderReceiveStream(
QuicSpdySession* session);
- static void SetH3DatagramSupported(QuicSpdySession* session,
- bool h3_datagram_supported);
+ static void SetHttpDatagramSupport(QuicSpdySession* session,
+ HttpDatagramSupport http_datagram_support);
static bool ShouldNegotiateHttp3Datagram(QuicSpdySession* session);
- static void EnableWebTransport(QuicSpdySession& session);
+ static void EnableWebTransport(QuicSpdySession* session);
};
} // namespace test