Support latest HTTP Datagrams draft This CL updates our code to support draft-ietf-masque-h3-datagram-04, and the corresponding changes in draft-ietf-masque-connect-udp and draft-ietf-webtrans-http3. These features are disabled on our servers, so they do not require flag protection. PiperOrigin-RevId: 399795495
diff --git a/quic/core/http/capsule.cc b/quic/core/http/capsule.cc new file mode 100644 index 0000000..0f9eefc --- /dev/null +++ b/quic/core/http/capsule.cc
@@ -0,0 +1,558 @@ +// Copyright (c) 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quic/core/http/capsule.h" + +#include <type_traits> + +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quic/core/http/http_frames.h" +#include "quic/core/quic_data_reader.h" +#include "quic/core/quic_data_writer.h" +#include "quic/platform/api/quic_bug_tracker.h" +#include "common/platform/api/quiche_logging.h" + +namespace quic { + +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), ")"); +} + +std::ostream& operator<<(std::ostream& os, const CapsuleType& capsule_type) { + os << CapsuleTypeToString(capsule_type); + return os; +} + +std::string DatagramFormatTypeToString( + DatagramFormatType datagram_format_type) { + switch (datagram_format_type) { + case DatagramFormatType::UDP_PAYLOAD: + return "UDP_PAYLOAD"; + case DatagramFormatType::WEBTRANSPORT: + return "WEBTRANSPORT"; + } + return absl::StrCat("Unknown(", static_cast<uint64_t>(datagram_format_type), + ")"); +} + +std::ostream& operator<<(std::ostream& os, + const DatagramFormatType& datagram_format_type) { + os << DatagramFormatTypeToString(datagram_format_type); + return os; +} + +std::string ContextCloseCodeToString(ContextCloseCode context_close_code) { + switch (context_close_code) { + case ContextCloseCode::NO_ERROR: + return "NO_ERROR"; + case ContextCloseCode::UNKNOWN_FORMAT: + return "UNKNOWN_FORMAT"; + case ContextCloseCode::DENIED: + return "DENIED"; + case ContextCloseCode::RESOURCE_LIMIT: + return "RESOURCE_LIMIT"; + } + return absl::StrCat("Unknown(", static_cast<uint64_t>(context_close_code), + ")"); +} + +std::ostream& operator<<(std::ostream& os, + const ContextCloseCode& context_close_code) { + os << ContextCloseCodeToString(context_close_code); + return os; +} + +Capsule::Capsule(CapsuleType capsule_type) : capsule_type_(capsule_type) { + switch (capsule_type) { + case CapsuleType::DATAGRAM: + static_assert(std::is_standard_layout<DatagramCapsule>::value && + std::is_trivially_destructible<DatagramCapsule>::value, + "All capsule structs must have these properties"); + datagram_capsule_ = DatagramCapsule(); + break; + case CapsuleType::REGISTER_DATAGRAM_CONTEXT: + static_assert( + std::is_standard_layout<RegisterDatagramContextCapsule>::value && + std::is_trivially_destructible< + RegisterDatagramContextCapsule>::value, + "All capsule structs must have these properties"); + register_datagram_context_capsule_ = RegisterDatagramContextCapsule(); + break; + case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT: + static_assert( + std::is_standard_layout<RegisterDatagramNoContextCapsule>::value && + std::is_trivially_destructible< + RegisterDatagramNoContextCapsule>::value, + "All capsule structs must have these properties"); + register_datagram_no_context_capsule_ = + RegisterDatagramNoContextCapsule(); + break; + case CapsuleType::CLOSE_DATAGRAM_CONTEXT: + static_assert( + std::is_standard_layout<CloseDatagramContextCapsule>::value && + std::is_trivially_destructible< + CloseDatagramContextCapsule>::value, + "All capsule structs must have these properties"); + close_datagram_context_capsule_ = CloseDatagramContextCapsule(); + break; + default: + unknown_capsule_data_ = absl::string_view(); + break; + } +} + +// static +Capsule Capsule::Datagram(absl::optional<QuicDatagramContextId> context_id, + absl::string_view http_datagram_payload) { + Capsule capsule(CapsuleType::DATAGRAM); + capsule.datagram_capsule().context_id = context_id; + capsule.datagram_capsule().http_datagram_payload = http_datagram_payload; + return capsule; +} + +// static +Capsule Capsule::RegisterDatagramContext( + QuicDatagramContextId context_id, DatagramFormatType format_type, + absl::string_view format_additional_data) { + Capsule capsule(CapsuleType::REGISTER_DATAGRAM_CONTEXT); + capsule.register_datagram_context_capsule().context_id = context_id; + capsule.register_datagram_context_capsule().format_type = format_type; + capsule.register_datagram_context_capsule().format_additional_data = + format_additional_data; + return capsule; +} + +// static +Capsule Capsule::RegisterDatagramNoContext( + DatagramFormatType format_type, absl::string_view format_additional_data) { + Capsule capsule(CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT); + capsule.register_datagram_no_context_capsule().format_type = format_type; + capsule.register_datagram_no_context_capsule().format_additional_data = + format_additional_data; + return capsule; +} + +// static +Capsule Capsule::CloseDatagramContext(QuicDatagramContextId context_id, + ContextCloseCode close_code, + absl::string_view close_details) { + Capsule capsule(CapsuleType::CLOSE_DATAGRAM_CONTEXT); + capsule.close_datagram_context_capsule().context_id = context_id; + capsule.close_datagram_context_capsule().close_code = close_code; + capsule.close_datagram_context_capsule().close_details = close_details; + return capsule; +} + +// static +Capsule Capsule::Unknown(uint64_t capsule_type, + absl::string_view unknown_capsule_data) { + Capsule capsule(static_cast<CapsuleType>(capsule_type)); + capsule.unknown_capsule_data() = unknown_capsule_data; + return capsule; +} + +Capsule& Capsule::operator=(const Capsule& other) { + capsule_type_ = other.capsule_type_; + switch (capsule_type_) { + case CapsuleType::DATAGRAM: + datagram_capsule_ = other.datagram_capsule_; + break; + case CapsuleType::REGISTER_DATAGRAM_CONTEXT: + register_datagram_context_capsule_ = + other.register_datagram_context_capsule_; + break; + case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT: + register_datagram_no_context_capsule_ = + other.register_datagram_no_context_capsule_; + break; + case CapsuleType::CLOSE_DATAGRAM_CONTEXT: + close_datagram_context_capsule_ = other.close_datagram_context_capsule_; + break; + default: + unknown_capsule_data_ = other.unknown_capsule_data_; + break; + } + return *this; +} + +Capsule::Capsule(const Capsule& other) : Capsule(other.capsule_type_) { + *this = other; +} + +bool Capsule::operator==(const Capsule& other) const { + if (capsule_type_ != other.capsule_type_) { + return false; + } + switch (capsule_type_) { + 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_CONTEXT: + return register_datagram_context_capsule_.context_id == + other.register_datagram_context_capsule_.context_id && + register_datagram_context_capsule_.format_type == + other.register_datagram_context_capsule_.format_type && + register_datagram_context_capsule_.format_additional_data == + other.register_datagram_context_capsule_ + .format_additional_data; + case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT: + return register_datagram_no_context_capsule_.format_type == + other.register_datagram_no_context_capsule_.format_type && + register_datagram_no_context_capsule_.format_additional_data == + other.register_datagram_no_context_capsule_ + .format_additional_data; + case CapsuleType::CLOSE_DATAGRAM_CONTEXT: + return close_datagram_context_capsule_.context_id == + other.close_datagram_context_capsule_.context_id && + close_datagram_context_capsule_.close_code == + other.close_datagram_context_capsule_.close_code && + close_datagram_context_capsule_.close_details == + other.close_datagram_context_capsule_.close_details; + default: + return unknown_capsule_data_ == other.unknown_capsule_data_; + } +} + +std::string Capsule::ToString() const { + std::string rv = CapsuleTypeToString(capsule_type_); + switch (capsule_type_) { + case CapsuleType::DATAGRAM: + if (datagram_capsule_.context_id.has_value()) { + absl::StrAppend(&rv, "(", datagram_capsule_.context_id.value(), ")"); + } + absl::StrAppend( + &rv, "[", + absl::BytesToHexString(datagram_capsule_.http_datagram_payload), "]"); + break; + case CapsuleType::REGISTER_DATAGRAM_CONTEXT: + absl::StrAppend( + &rv, "(context_id=", register_datagram_context_capsule_.context_id, + ",format_type=", + DatagramFormatTypeToString( + register_datagram_context_capsule_.format_type), + "){", + absl::BytesToHexString( + register_datagram_context_capsule_.format_additional_data), + "}"); + break; + case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT: + absl::StrAppend( + &rv, "(format_type=", + DatagramFormatTypeToString( + register_datagram_no_context_capsule_.format_type), + "){", + absl::BytesToHexString( + register_datagram_no_context_capsule_.format_additional_data), + "}"); + break; + case CapsuleType::CLOSE_DATAGRAM_CONTEXT: + absl::StrAppend( + &rv, "(context_id=", close_datagram_context_capsule_.context_id, + ",close_code=", + ContextCloseCodeToString(close_datagram_context_capsule_.close_code), + ",close_details=\"", + absl::BytesToHexString(close_datagram_context_capsule_.close_details), + "\")"); + break; + default: + absl::StrAppend(&rv, "[", absl::BytesToHexString(unknown_capsule_data_), + "]"); + break; + } + return rv; +} + +std::ostream& operator<<(std::ostream& os, const Capsule& capsule) { + os << capsule.ToString(); + return os; +} + +CapsuleParser::CapsuleParser(Visitor* visitor) : visitor_(visitor) { + QUICHE_DCHECK_NE(visitor_, nullptr); +} + +QuicBuffer SerializeCapsule(const Capsule& capsule, + QuicBufferAllocator* allocator) { + QuicByteCount capsule_type_length = QuicDataWriter::GetVarInt62Len( + static_cast<uint64_t>(capsule.capsule_type())); + QuicByteCount capsule_data_length; + switch (capsule.capsule_type()) { + case CapsuleType::DATAGRAM: + capsule_data_length = + capsule.datagram_capsule().http_datagram_payload.length(); + if (capsule.datagram_capsule().context_id.has_value()) { + capsule_data_length += QuicDataWriter::GetVarInt62Len( + capsule.datagram_capsule().context_id.value()); + } + break; + case CapsuleType::REGISTER_DATAGRAM_CONTEXT: + capsule_data_length = + QuicDataWriter::GetVarInt62Len( + capsule.register_datagram_context_capsule().context_id) + + QuicDataWriter::GetVarInt62Len(static_cast<uint64_t>( + capsule.register_datagram_context_capsule().format_type)) + + capsule.register_datagram_context_capsule() + .format_additional_data.length(); + break; + case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT: + capsule_data_length = + QuicDataWriter::GetVarInt62Len(static_cast<uint64_t>( + capsule.register_datagram_no_context_capsule().format_type)) + + capsule.register_datagram_no_context_capsule() + .format_additional_data.length(); + break; + case CapsuleType::CLOSE_DATAGRAM_CONTEXT: + capsule_data_length = + QuicDataWriter::GetVarInt62Len( + capsule.close_datagram_context_capsule().context_id) + + QuicDataWriter::GetVarInt62Len(static_cast<uint64_t>( + capsule.close_datagram_context_capsule().close_code)) + + capsule.close_datagram_context_capsule().close_details.length(); + break; + default: + capsule_data_length = capsule.unknown_capsule_data().length(); + break; + } + QuicByteCount capsule_length_length = + QuicDataWriter::GetVarInt62Len(capsule_data_length); + QuicByteCount total_capsule_length = + capsule_type_length + capsule_length_length + capsule_data_length; + QuicBuffer buffer(allocator, total_capsule_length); + QuicDataWriter writer(buffer.size(), buffer.data()); + if (!writer.WriteVarInt62(static_cast<uint64_t>(capsule.capsule_type()))) { + QUIC_BUG(capsule type write fail) << "Failed to write CAPSULE type"; + return QuicBuffer(); + } + if (!writer.WriteVarInt62(capsule_data_length)) { + QUIC_BUG(capsule length write fail) << "Failed to write CAPSULE length"; + return QuicBuffer(); + } + switch (capsule.capsule_type()) { + case CapsuleType::DATAGRAM: + if (capsule.datagram_capsule().context_id.has_value()) { + if (!writer.WriteVarInt62( + capsule.datagram_capsule().context_id.value())) { + QUIC_BUG(datagram capsule context ID write fail) + << "Failed to write DATAGRAM CAPSULE context ID"; + return QuicBuffer(); + } + } + if (!writer.WriteStringPiece( + capsule.datagram_capsule().http_datagram_payload)) { + QUIC_BUG(datagram capsule payload write fail) + << "Failed to write DATAGRAM CAPSULE payload"; + return QuicBuffer(); + } + break; + case CapsuleType::REGISTER_DATAGRAM_CONTEXT: + if (!writer.WriteVarInt62( + capsule.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 QuicBuffer(); + } + if (!writer.WriteVarInt62(static_cast<uint64_t>( + capsule.register_datagram_context_capsule().format_type))) { + QUIC_BUG(register context capsule format type write fail) + << "Failed to write REGISTER_DATAGRAM_CONTEXT CAPSULE format type"; + return QuicBuffer(); + } + if (!writer.WriteStringPiece(capsule.register_datagram_context_capsule() + .format_additional_data)) { + QUIC_BUG(register context capsule additional data write fail) + << "Failed to write REGISTER_DATAGRAM_CONTEXT CAPSULE additional " + "data"; + return QuicBuffer(); + } + break; + case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT: + if (!writer.WriteVarInt62(static_cast<uint64_t>( + capsule.register_datagram_no_context_capsule().format_type))) { + QUIC_BUG(register no context capsule format type write fail) + << "Failed to write REGISTER_DATAGRAM_NO_CONTEXT CAPSULE format " + "type"; + return QuicBuffer(); + } + if (!writer.WriteStringPiece( + capsule.register_datagram_no_context_capsule() + .format_additional_data)) { + QUIC_BUG(register no context capsule additional data write fail) + << "Failed to write REGISTER_DATAGRAM_NO_CONTEXT CAPSULE " + "additional data"; + return QuicBuffer(); + } + break; + case CapsuleType::CLOSE_DATAGRAM_CONTEXT: + if (!writer.WriteVarInt62( + capsule.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 QuicBuffer(); + } + if (!writer.WriteVarInt62(static_cast<uint64_t>( + capsule.close_datagram_context_capsule().close_code))) { + QUIC_BUG(close context capsule close code write fail) + << "Failed to write CLOSE_DATAGRAM_CONTEXT CAPSULE close code"; + return QuicBuffer(); + } + if (!writer.WriteStringPiece( + capsule.close_datagram_context_capsule().close_details)) { + QUIC_BUG(close context capsule close details write fail) + << "Failed to write CLOSE_DATAGRAM_CONTEXT CAPSULE close details"; + return QuicBuffer(); + } + break; + default: + if (!writer.WriteStringPiece(capsule.unknown_capsule_data())) { + QUIC_BUG(capsule data write fail) << "Failed to write CAPSULE data"; + return QuicBuffer(); + } + break; + } + if (writer.remaining() != 0) { + QUIC_BUG(capsule write length mismatch) + << "CAPSULE serialization wrote " << writer.length() << " instead of " + << writer.capacity(); + return QuicBuffer(); + } + return buffer; +} + +bool CapsuleParser::IngestCapsuleFragment(absl::string_view capsule_fragment) { + if (parsing_error_occurred_) { + return false; + } + buffered_data_.append(capsule_fragment); + while (true) { + const size_t buffered_data_read = AttemptParseCapsule(); + if (parsing_error_occurred_) { + QUICHE_DCHECK_EQ(buffered_data_read, 0u); + buffered_data_.clear(); + return false; + } + if (buffered_data_read == 0) { + break; + } + buffered_data_.erase(0, buffered_data_read); + } + return true; +} + +size_t CapsuleParser::AttemptParseCapsule() { + QUICHE_DCHECK(!parsing_error_occurred_); + if (buffered_data_.empty()) { + return 0; + } + QuicDataReader capsule_fragment_reader(buffered_data_); + uint64_t capsule_type64; + if (!capsule_fragment_reader.ReadVarInt62(&capsule_type64)) { + QUIC_DVLOG(2) << "Partial read: not enough data to read capsule type"; + return 0; + } + absl::string_view capsule_data; + if (!capsule_fragment_reader.ReadStringPieceVarInt62(&capsule_data)) { + QUIC_DVLOG(2) << "Partial read: not enough data to read capsule length or " + "full capsule data"; + return 0; + } + QuicDataReader capsule_data_reader(capsule_data); + Capsule capsule(static_cast<CapsuleType>(capsule_type64)); + switch (capsule.capsule_type()) { + case CapsuleType::DATAGRAM: + if (datagram_context_id_present_) { + uint64_t context_id; + if (!capsule_data_reader.ReadVarInt62(&context_id)) { + ReportParseFailure("Unable to parse capsule DATAGRAM context ID"); + return 0; + } + capsule.datagram_capsule().context_id = context_id; + } + capsule.datagram_capsule().http_datagram_payload = + capsule_data_reader.ReadRemainingPayload(); + break; + case CapsuleType::REGISTER_DATAGRAM_CONTEXT: + if (!capsule_data_reader.ReadVarInt62( + &capsule.register_datagram_context_capsule().context_id)) { + ReportParseFailure( + "Unable to parse capsule REGISTER_DATAGRAM_CONTEXT context ID"); + return 0; + } + if (!capsule_data_reader.ReadVarInt62(reinterpret_cast<uint64_t*>( + &capsule.register_datagram_context_capsule().format_type))) { + ReportParseFailure( + "Unable to parse capsule REGISTER_DATAGRAM_CONTEXT format type"); + return 0; + } + capsule.register_datagram_context_capsule().format_additional_data = + capsule_data_reader.ReadRemainingPayload(); + break; + case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT: + if (!capsule_data_reader.ReadVarInt62(reinterpret_cast<uint64_t*>( + &capsule.register_datagram_no_context_capsule().format_type))) { + ReportParseFailure( + "Unable to parse capsule REGISTER_DATAGRAM_NO_CONTEXT format type"); + return 0; + } + capsule.register_datagram_no_context_capsule().format_additional_data = + capsule_data_reader.ReadRemainingPayload(); + break; + case CapsuleType::CLOSE_DATAGRAM_CONTEXT: + if (!capsule_data_reader.ReadVarInt62( + &capsule.close_datagram_context_capsule().context_id)) { + ReportParseFailure( + "Unable to parse capsule CLOSE_DATAGRAM_CONTEXT context ID"); + return 0; + } + if (!capsule_data_reader.ReadVarInt62(reinterpret_cast<uint64_t*>( + &capsule.close_datagram_context_capsule().close_code))) { + ReportParseFailure( + "Unable to parse capsule CLOSE_DATAGRAM_CONTEXT close code"); + return 0; + } + capsule.close_datagram_context_capsule().close_details = + capsule_data_reader.ReadRemainingPayload(); + break; + default: + capsule.unknown_capsule_data() = + capsule_data_reader.ReadRemainingPayload(); + } + if (!visitor_->OnCapsule(capsule)) { + ReportParseFailure("Visitor failed to process capsule"); + return 0; + } + return capsule_fragment_reader.PreviouslyReadPayload().length(); +} + +void CapsuleParser::ReportParseFailure(const std::string& error_message) { + if (parsing_error_occurred_) { + QUIC_BUG(multiple parse errors) << "Experienced multiple parse failures"; + return; + } + parsing_error_occurred_ = true; + visitor_->OnCapsuleParseFailure(error_message); +} + +void CapsuleParser::ErrorIfThereIsRemainingBufferedData() { + if (parsing_error_occurred_) { + return; + } + if (!buffered_data_.empty()) { + ReportParseFailure("Incomplete capsule left at the end of the stream"); + } +} + +} // namespace quic
diff --git a/quic/core/http/capsule.h b/quic/core/http/capsule.h new file mode 100644 index 0000000..664633a --- /dev/null +++ b/quic/core/http/capsule.h
@@ -0,0 +1,234 @@ +// Copyright (c) 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_HTTP_CAPSULE_H_ +#define QUICHE_QUIC_CORE_HTTP_CAPSULE_H_ + +#include <cstdint> +#include <string> + +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "quic/core/quic_buffer_allocator.h" +#include "quic/core/quic_data_reader.h" +#include "quic/core/quic_types.h" +#include "common/platform/api/quiche_logging.h" + +namespace quic { + +enum class CapsuleType : uint64_t { + // Casing in this enum matches the IETF specification. + DATAGRAM = 0xff37a0, + REGISTER_DATAGRAM_CONTEXT = 0xff37a1, + REGISTER_DATAGRAM_NO_CONTEXT = 0xff37a2, + CLOSE_DATAGRAM_CONTEXT = 0xff37a3, +}; + +QUIC_EXPORT_PRIVATE std::string CapsuleTypeToString(CapsuleType capsule_type); +QUIC_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os, + const CapsuleType& capsule_type); + +enum class DatagramFormatType : uint64_t { + // Casing in this enum matches the IETF specification. + UDP_PAYLOAD = 0xff6f00, + WEBTRANSPORT = 0xff7c00, +}; + +QUIC_EXPORT_PRIVATE std::string DatagramFormatTypeToString( + DatagramFormatType datagram_format_type); +QUIC_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& os, const DatagramFormatType& datagram_format_type); + +enum class ContextCloseCode : uint64_t { + // Casing in this enum matches the IETF specification. + NO_ERROR = 0xff78a0, + UNKNOWN_FORMAT = 0xff78a1, + DENIED = 0xff78a2, + RESOURCE_LIMIT = 0xff78a3, +}; + +QUIC_EXPORT_PRIVATE std::string ContextCloseCodeToString( + ContextCloseCode context_close_code); +QUIC_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& os, const ContextCloseCode& context_close_code); + +struct QUIC_EXPORT_PRIVATE DatagramCapsule { + absl::optional<QuicDatagramContextId> context_id; + absl::string_view http_datagram_payload; +}; +struct QUIC_EXPORT_PRIVATE RegisterDatagramContextCapsule { + QuicDatagramContextId context_id; + DatagramFormatType format_type; + absl::string_view format_additional_data; +}; +struct QUIC_EXPORT_PRIVATE RegisterDatagramNoContextCapsule { + DatagramFormatType format_type; + absl::string_view format_additional_data; +}; +struct QUIC_EXPORT_PRIVATE CloseDatagramContextCapsule { + QuicDatagramContextId context_id; + ContextCloseCode close_code; + absl::string_view close_details; +}; + +// Capsule from draft-ietf-masque-h3-datagram. +// IMPORTANT NOTE: Capsule does not own any of the absl::string_view memory it +// points to. Strings saved into a capsule must outlive the capsule object. Any +// code that sees a capsule in a callback needs to either process it immediately +// or perform its own deep copy. +class QUIC_EXPORT_PRIVATE Capsule { + public: + static Capsule Datagram( + absl::optional<QuicDatagramContextId> context_id = absl::nullopt, + absl::string_view http_datagram_payload = absl::string_view()); + static Capsule RegisterDatagramContext( + QuicDatagramContextId context_id, DatagramFormatType format_type, + absl::string_view format_additional_data = absl::string_view()); + static Capsule RegisterDatagramNoContext( + DatagramFormatType format_type, + absl::string_view format_additional_data = absl::string_view()); + static Capsule CloseDatagramContext( + QuicDatagramContextId context_id, + ContextCloseCode close_code = ContextCloseCode::NO_ERROR, + absl::string_view close_details = absl::string_view()); + static Capsule Unknown( + uint64_t capsule_type, + absl::string_view unknown_capsule_data = absl::string_view()); + + explicit Capsule(CapsuleType capsule_type); + Capsule(const Capsule& other); + Capsule& operator=(const Capsule& other); + bool operator==(const Capsule& other) const; + + // Human-readable information string for debugging purposes. + std::string ToString() const; + friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os, + const Capsule& capsule); + + CapsuleType capsule_type() const { return capsule_type_; } + DatagramCapsule& datagram_capsule() { + QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::DATAGRAM); + return datagram_capsule_; + } + const DatagramCapsule& datagram_capsule() const { + QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::DATAGRAM); + return datagram_capsule_; + } + RegisterDatagramContextCapsule& register_datagram_context_capsule() { + QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::REGISTER_DATAGRAM_CONTEXT); + return register_datagram_context_capsule_; + } + const RegisterDatagramContextCapsule& register_datagram_context_capsule() + const { + QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::REGISTER_DATAGRAM_CONTEXT); + return register_datagram_context_capsule_; + } + RegisterDatagramNoContextCapsule& register_datagram_no_context_capsule() { + QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT); + return register_datagram_no_context_capsule_; + } + const RegisterDatagramNoContextCapsule& register_datagram_no_context_capsule() + const { + QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT); + return register_datagram_no_context_capsule_; + } + CloseDatagramContextCapsule& close_datagram_context_capsule() { + QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::CLOSE_DATAGRAM_CONTEXT); + return close_datagram_context_capsule_; + } + const CloseDatagramContextCapsule& close_datagram_context_capsule() const { + QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::CLOSE_DATAGRAM_CONTEXT); + return close_datagram_context_capsule_; + } + absl::string_view& unknown_capsule_data() { + QUICHE_DCHECK(capsule_type_ != CapsuleType::DATAGRAM && + capsule_type_ != CapsuleType::REGISTER_DATAGRAM_CONTEXT && + capsule_type_ != CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT && + capsule_type_ != CapsuleType::CLOSE_DATAGRAM_CONTEXT) + << capsule_type_; + return unknown_capsule_data_; + } + const absl::string_view& unknown_capsule_data() const { + QUICHE_DCHECK(capsule_type_ != CapsuleType::DATAGRAM && + capsule_type_ != CapsuleType::REGISTER_DATAGRAM_CONTEXT && + capsule_type_ != CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT && + capsule_type_ != CapsuleType::CLOSE_DATAGRAM_CONTEXT) + << capsule_type_; + return unknown_capsule_data_; + } + + private: + CapsuleType capsule_type_; + union { + DatagramCapsule datagram_capsule_; + RegisterDatagramContextCapsule register_datagram_context_capsule_; + RegisterDatagramNoContextCapsule register_datagram_no_context_capsule_; + CloseDatagramContextCapsule close_datagram_context_capsule_; + absl::string_view unknown_capsule_data_; + }; +}; + +namespace test { +class CapsuleParserPeer; +} // namespace test + +class QUIC_EXPORT_PRIVATE CapsuleParser { + public: + class QUIC_EXPORT_PRIVATE Visitor { + public: + virtual ~Visitor() {} + + // Called when a capsule has been successfully parsed. The return value + // indicates whether the contents of the capsule are valid: if false is + // returned, the parse operation will be considered failed and + // OnCapsuleParseFailure will be called. Note that since Capsule does not + // own the memory backing its string_views, that memory is only valid until + // this callback returns. Visitors that wish to access the capsule later + // MUST make a deep copy before this returns. + virtual bool OnCapsule(const Capsule& capsule) = 0; + + virtual void OnCapsuleParseFailure(const std::string& error_message) = 0; + }; + + // |visitor| must be non-null, and must outlive CapsuleParser. + explicit CapsuleParser(Visitor* visitor); + + void set_datagram_context_id_present(bool datagram_context_id_present) { + datagram_context_id_present_ = datagram_context_id_present; + } + + // Ingests a capsule fragment (any fragment of bytes from the capsule data + // stream) and parses and complete capsules it encounters. Returns false if a + // parsing error occurred. + bool IngestCapsuleFragment(absl::string_view capsule_fragment); + + void ErrorIfThereIsRemainingBufferedData(); + + friend class test::CapsuleParserPeer; + + private: + // Attempts to parse a single capsule from |buffered_data_|. If a full capsule + // is not available, returns 0. If a parsing error occurs, returns 0. + // Otherwise, returns the number of bytes in the parsed capsule. + size_t AttemptParseCapsule(); + void ReportParseFailure(const std::string& error_message); + + // Whether HTTP Datagram Context IDs are present. + bool datagram_context_id_present_ = false; + // Whether a parsing error has occurred. + bool parsing_error_occurred_ = false; + // Visitor which will receive callbacks, unowned. + Visitor* visitor_; + + std::string buffered_data_; +}; + +// Serializes |capsule| into a newly allocated buffer. +QUIC_EXPORT_PRIVATE QuicBuffer SerializeCapsule(const Capsule& capsule, + QuicBufferAllocator* allocator); + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_HTTP_CAPSULE_H_
diff --git a/quic/core/http/capsule_test.cc b/quic/core/http/capsule_test.cc new file mode 100644 index 0000000..634cdae --- /dev/null +++ b/quic/core/http/capsule_test.cc
@@ -0,0 +1,299 @@ +// Copyright (c) 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quic/core/http/capsule.h" + +#include <cstddef> +#include <deque> +#include <string> +#include <vector> + +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "quic/platform/api/quic_test.h" +#include "quic/test_tools/quic_test_utils.h" +#include "common/test_tools/quiche_test_utils.h" + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; + +namespace quic { +namespace test { + +namespace { +constexpr DatagramFormatType kFakeFormatType = + static_cast<DatagramFormatType>(0x123456); +constexpr ContextCloseCode kFakeCloseCode = + static_cast<ContextCloseCode>(0x654321); +} // namespace + +class CapsuleParserPeer { + public: + static std::string* buffered_data(CapsuleParser* capsule_parser) { + return &capsule_parser->buffered_data_; + } +}; + +class MockVisitor : public CapsuleParser::Visitor { + public: + MockVisitor() { ON_CALL(*this, OnCapsule(_)).WillByDefault(Return(true)); } + ~MockVisitor() override = default; + MOCK_METHOD(bool, OnCapsule, (const Capsule& capsule), (override)); + MOCK_METHOD(void, OnCapsuleParseFailure, (const std::string& error_message), + (override)); +}; + +class CapsuleTest : public QuicTest { + public: + CapsuleTest() : capsule_parser_(&visitor_) {} + + void ValidateParserIsEmpty() { + EXPECT_CALL(visitor_, OnCapsule(_)).Times(0); + EXPECT_CALL(visitor_, OnCapsuleParseFailure(_)).Times(0); + capsule_parser_.ErrorIfThereIsRemainingBufferedData(); + EXPECT_TRUE(CapsuleParserPeer::buffered_data(&capsule_parser_)->empty()); + } + + void TestSerialization(const Capsule& capsule, + const std::string& expected_bytes) { + QuicBuffer serialized_capsule = + SerializeCapsule(capsule, SimpleBufferAllocator::Get()); + quiche::test::CompareCharArraysWithHexError( + "Serialized capsule", serialized_capsule.data(), + serialized_capsule.size(), expected_bytes.data(), + expected_bytes.size()); + } + + ::testing::StrictMock<MockVisitor> visitor_; + CapsuleParser capsule_parser_; +}; + +TEST_F(CapsuleTest, DatagramCapsule) { + std::string capsule_fragment = absl::HexStringToBytes( + "80ff37a0" // DATAGRAM capsule type + "08" // capsule length + "a1a2a3a4a5a6a7a8" // HTTP Datagram payload + ); + std::string datagram_payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); + Capsule expected_capsule = + Capsule::Datagram(/*context_id=*/absl::nullopt, datagram_payload); + { + EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); + } + ValidateParserIsEmpty(); + TestSerialization(expected_capsule, capsule_fragment); +} + +TEST_F(CapsuleTest, DatagramCapsuleWithContext) { + std::string capsule_fragment = absl::HexStringToBytes( + "80ff37a0" // DATAGRAM capsule type + "09" // capsule length + "04" // context ID + "a1a2a3a4a5a6a7a8" // HTTP Datagram payload + ); + capsule_parser_.set_datagram_context_id_present(true); + std::string datagram_payload = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); + Capsule expected_capsule = + Capsule::Datagram(/*context_id=*/4, datagram_payload); + { + EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); + } + ValidateParserIsEmpty(); + TestSerialization(expected_capsule, capsule_fragment); +} + +TEST_F(CapsuleTest, RegisterContextCapsule) { + std::string capsule_fragment = absl::HexStringToBytes( + "80ff37a1" // REGISTER_DATAGRAM_CONTEXT capsule type + "0d" // capsule length + "04" // context ID + "80123456" // 0x123456 datagram format type + "f1f2f3f4f5f6f7f8" // format additional data + ); + std::string format_additional_data = + absl::HexStringToBytes("f1f2f3f4f5f6f7f8"); + Capsule expected_capsule = Capsule::RegisterDatagramContext( + /*context_id=*/4, kFakeFormatType, format_additional_data); + { + EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); + } + ValidateParserIsEmpty(); + TestSerialization(expected_capsule, capsule_fragment); +} + +TEST_F(CapsuleTest, RegisterNoContextCapsule) { + std::string capsule_fragment = absl::HexStringToBytes( + "80ff37a2" // REGISTER_DATAGRAM_NO_CONTEXT capsule type + "0c" // capsule length + "80123456" // 0x123456 datagram format type + "f1f2f3f4f5f6f7f8" // format additional data + ); + std::string format_additional_data = + absl::HexStringToBytes("f1f2f3f4f5f6f7f8"); + Capsule expected_capsule = Capsule::RegisterDatagramNoContext( + kFakeFormatType, format_additional_data); + { + EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); + } + ValidateParserIsEmpty(); + TestSerialization(expected_capsule, capsule_fragment); +} + +TEST_F(CapsuleTest, CloseContextCapsule) { + std::string capsule_fragment = absl::HexStringToBytes( + "80ff37a3" // CLOSE_DATAGRAM_CONTEXT capsule type + "27" // capsule length + "04" // context ID + "80654321" // 0x654321 close code + ); + std::string close_details = "All your contexts are belong to us"; + capsule_fragment += close_details; + Capsule expected_capsule = Capsule::CloseDatagramContext( + /*context_id=*/4, kFakeCloseCode, close_details); + { + EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); + } + ValidateParserIsEmpty(); + TestSerialization(expected_capsule, capsule_fragment); +} + +TEST_F(CapsuleTest, UnknownCapsule) { + std::string capsule_fragment = absl::HexStringToBytes( + "33" // unknown capsule type of 0x33 + "08" // capsule length + "a1a2a3a4a5a6a7a8" // unknown capsule data + ); + std::string unknown_capsule_data = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); + Capsule expected_capsule = Capsule::Unknown(0x33, unknown_capsule_data); + { + EXPECT_CALL(visitor_, OnCapsule(expected_capsule)); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); + } + ValidateParserIsEmpty(); + TestSerialization(expected_capsule, capsule_fragment); +} + +TEST_F(CapsuleTest, TwoDatagramCapsules) { + std::string capsule_fragment = absl::HexStringToBytes( + "80ff37a0" // DATAGRAM capsule type + "08" // capsule length + "a1a2a3a4a5a6a7a8" // HTTP Datagram payload + "80ff37a0" // DATAGRAM capsule type + "08" // capsule length + "b1b2b3b4b5b6b7b8" // HTTP Datagram payload + ); + std::string datagram_payload1 = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); + std::string datagram_payload2 = absl::HexStringToBytes("b1b2b3b4b5b6b7b8"); + Capsule expected_capsule1 = + Capsule::Datagram(/*context_id=*/absl::nullopt, datagram_payload1); + Capsule expected_capsule2 = + Capsule::Datagram(/*context_id=*/absl::nullopt, datagram_payload2); + { + InSequence s; + EXPECT_CALL(visitor_, OnCapsule(expected_capsule1)); + EXPECT_CALL(visitor_, OnCapsule(expected_capsule2)); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); + } + ValidateParserIsEmpty(); +} + +TEST_F(CapsuleTest, TwoDatagramCapsulesPartialReads) { + std::string capsule_fragment1 = absl::HexStringToBytes( + "80ff37a0" // first capsule DATAGRAM capsule type + "08" // frist capsule length + "a1a2a3a4" // first half of HTTP Datagram payload of first capsule + ); + std::string capsule_fragment2 = absl::HexStringToBytes( + "a5a6a7a8" // second half of HTTP Datagram payload 1 + "80ff37a0" // second capsule DATAGRAM capsule type + ); + std::string capsule_fragment3 = absl::HexStringToBytes( + "08" // second capsule length + "b1b2b3b4b5b6b7b8" // HTTP Datagram payload of second capsule + ); + capsule_parser_.ErrorIfThereIsRemainingBufferedData(); + std::string datagram_payload1 = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); + std::string datagram_payload2 = absl::HexStringToBytes("b1b2b3b4b5b6b7b8"); + Capsule expected_capsule1 = + Capsule::Datagram(/*context_id=*/absl::nullopt, datagram_payload1); + Capsule expected_capsule2 = + Capsule::Datagram(/*context_id=*/absl::nullopt, datagram_payload2); + { + InSequence s; + EXPECT_CALL(visitor_, OnCapsule(expected_capsule1)); + EXPECT_CALL(visitor_, OnCapsule(expected_capsule2)); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment1)); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment2)); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment3)); + } + ValidateParserIsEmpty(); +} + +TEST_F(CapsuleTest, TwoDatagramCapsulesOneByteAtATime) { + std::string capsule_fragment = absl::HexStringToBytes( + "80ff37a0" // DATAGRAM capsule type + "08" // capsule length + "a1a2a3a4a5a6a7a8" // HTTP Datagram payload + "80ff37a0" // DATAGRAM capsule type + "08" // capsule length + "b1b2b3b4b5b6b7b8" // HTTP Datagram payload + ); + std::string datagram_payload1 = absl::HexStringToBytes("a1a2a3a4a5a6a7a8"); + std::string datagram_payload2 = absl::HexStringToBytes("b1b2b3b4b5b6b7b8"); + Capsule expected_capsule1 = + Capsule::Datagram(/*context_id=*/absl::nullopt, datagram_payload1); + Capsule expected_capsule2 = + Capsule::Datagram(/*context_id=*/absl::nullopt, datagram_payload2); + for (size_t i = 0; i < capsule_fragment.size(); i++) { + if (i < capsule_fragment.size() / 2 - 1) { + EXPECT_CALL(visitor_, OnCapsule(_)).Times(0); + ASSERT_TRUE( + capsule_parser_.IngestCapsuleFragment(capsule_fragment.substr(i, 1))); + } else if (i == capsule_fragment.size() / 2 - 1) { + EXPECT_CALL(visitor_, OnCapsule(expected_capsule1)); + ASSERT_TRUE( + capsule_parser_.IngestCapsuleFragment(capsule_fragment.substr(i, 1))); + EXPECT_TRUE(CapsuleParserPeer::buffered_data(&capsule_parser_)->empty()); + } else if (i < capsule_fragment.size() - 1) { + EXPECT_CALL(visitor_, OnCapsule(_)).Times(0); + ASSERT_TRUE( + capsule_parser_.IngestCapsuleFragment(capsule_fragment.substr(i, 1))); + } else { + EXPECT_CALL(visitor_, OnCapsule(expected_capsule2)); + ASSERT_TRUE( + capsule_parser_.IngestCapsuleFragment(capsule_fragment.substr(i, 1))); + EXPECT_TRUE(CapsuleParserPeer::buffered_data(&capsule_parser_)->empty()); + } + } + capsule_parser_.ErrorIfThereIsRemainingBufferedData(); + EXPECT_TRUE(CapsuleParserPeer::buffered_data(&capsule_parser_)->empty()); +} + +TEST_F(CapsuleTest, PartialCapsuleThenError) { + std::string capsule_fragment = absl::HexStringToBytes( + "80ff37a0" // DATAGRAM capsule type + "08" // capsule length + "a1a2a3a4" // first half of HTTP Datagram payload + ); + EXPECT_CALL(visitor_, OnCapsule(_)).Times(0); + { + EXPECT_CALL(visitor_, OnCapsuleParseFailure(_)).Times(0); + ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment)); + } + { + EXPECT_CALL(visitor_, + OnCapsuleParseFailure( + "Incomplete capsule left at the end of the stream")); + capsule_parser_.ErrorIfThereIsRemainingBufferedData(); + } +} + +} // namespace test +} // namespace quic
diff --git a/quic/core/http/http_constants.cc b/quic/core/http/http_constants.cc index f3ed523..851fd91 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_DRAFT04); 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..ce03a1e 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-04. + SETTINGS_H3_DATAGRAM_DRAFT04 = 0xffd277, // draft-ietf-webtrans-http3-00 SETTINGS_WEBTRANS_DRAFT00 = 0x2b603742, };
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc index 45de224..5943585 100644 --- a/quic/core/http/quic_send_control_stream_test.cc +++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -71,9 +71,7 @@ public: QuicSendControlStreamTest() : connection_(new StrictMock<MockQuicConnection>( - &helper_, - &alarm_factory_, - perspective(), + &helper_, &alarm_factory_, perspective(), SupportedVersions(GetParam().version))), session_(connection_) { ON_CALL(session_, WritevData(_, _, _, _, _, _)) @@ -104,8 +102,7 @@ QuicSendControlStream* send_control_stream_; }; -INSTANTIATE_TEST_SUITE_P(Tests, - QuicSendControlStreamTest, +INSTANTIATE_TEST_SUITE_P(Tests, QuicSendControlStreamTest, ::testing::ValuesIn(GetTestParams()), ::testing::PrintToStringParamName()); @@ -133,24 +130,27 @@ "4040" // 0x40 as the reserved frame type "01" // 1 byte frame length "61"); // payload "a" - if (QuicSpdySessionPeer::ShouldNegotiateHttp3Datagram(&session_)) { + if (QuicSpdySessionPeer::LocalHttpDatagramSupport(&session_) == + HttpDatagramSupport::kDraft00And04) { 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 + "800ffd277" // SETTINGS_H3_DATAGRAM_DRAFT04 + "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 06531a5..0cd2644 100644 --- a/quic/core/http/quic_spdy_session.cc +++ b/quic/core/http/quic_spdy_session.cc
@@ -193,8 +193,7 @@ header_list_.Clear(); } - void OnStreamFrameData(SpdyStreamId /*stream_id*/, - const char* /*data*/, + void OnStreamFrameData(SpdyStreamId /*stream_id*/, const char* /*data*/, size_t /*len*/) override { QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version())); CloseConnection("SPDY DATA frame received.", @@ -282,8 +281,7 @@ code); } - void OnDataFrameHeader(SpdyStreamId /*stream_id*/, - size_t /*length*/, + void OnDataFrameHeader(SpdyStreamId /*stream_id*/, size_t /*length*/, bool /*fin*/) override { QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version())); CloseConnection("SPDY DATA frame received.", @@ -316,13 +314,9 @@ QUIC_INVALID_HEADERS_STREAM_DATA); } - void OnHeaders(SpdyStreamId stream_id, - bool has_priority, - int weight, - SpdyStreamId /* parent_stream_id */, - bool /* exclusive */, - bool fin, - bool /*end*/) override { + void OnHeaders(SpdyStreamId stream_id, bool has_priority, int weight, + SpdyStreamId /* parent_stream_id */, bool /* exclusive */, + bool fin, bool /*end*/) override { if (!session_->IsConnected()) { return; } @@ -350,8 +344,7 @@ QUIC_INVALID_HEADERS_STREAM_DATA); } - void OnPushPromise(SpdyStreamId stream_id, - SpdyStreamId promised_stream_id, + void OnPushPromise(SpdyStreamId stream_id, SpdyStreamId promised_stream_id, bool /*end*/) override { QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version())); if (session_->perspective() != Perspective::IS_CLIENT) { @@ -367,10 +360,8 @@ void OnContinuation(SpdyStreamId /*stream_id*/, bool /*end*/) override {} - void OnPriority(SpdyStreamId stream_id, - SpdyStreamId /* parent_id */, - int weight, - bool /* exclusive */) override { + void OnPriority(SpdyStreamId stream_id, SpdyStreamId /* parent_id */, + int weight, bool /* exclusive */) override { QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version())); if (!session_->IsConnected()) { return; @@ -393,10 +384,8 @@ } // SpdyFramerDebugVisitorInterface implementation - void OnSendCompressedFrame(SpdyStreamId /*stream_id*/, - SpdyFrameType /*type*/, - size_t payload_len, - size_t frame_len) override { + void OnSendCompressedFrame(SpdyStreamId /*stream_id*/, SpdyFrameType /*type*/, + size_t payload_len, size_t frame_len) override { if (payload_len == 0) { QUIC_BUG(quic_bug_10360_1) << "Zero payload length."; return; @@ -518,8 +507,17 @@ qpack_maximum_blocked_streams_; settings_.values[SETTINGS_MAX_FIELD_SECTION_SIZE] = max_inbound_header_list_size_; - if (ShouldNegotiateHttp3Datagram() && version().UsesHttp3()) { - settings_.values[SETTINGS_H3_DATAGRAM] = 1; + if (version().UsesHttp3()) { + HttpDatagramSupport local_http_datagram_support = + LocalHttpDatagramSupport(); + if (local_http_datagram_support == HttpDatagramSupport::kDraft00 || + local_http_datagram_support == HttpDatagramSupport::kDraft00And04) { + settings_.values[SETTINGS_H3_DATAGRAM_DRAFT00] = 1; + } + if (local_http_datagram_support == HttpDatagramSupport::kDraft04 || + local_http_datagram_support == HttpDatagramSupport::kDraft00And04) { + settings_.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1; + } } if (WillNegotiateWebTransport()) { settings_.values[SETTINGS_WEBTRANS_DRAFT00] = 1; @@ -543,8 +541,7 @@ } void QuicSpdySession::OnStreamHeadersPriority( - QuicStreamId stream_id, - const spdy::SpdyStreamPrecedence& precedence) { + QuicStreamId stream_id, const spdy::SpdyStreamPrecedence& precedence) { QuicSpdyStream* stream = GetOrCreateSpdyDataStream(stream_id); if (!stream) { // It's quite possible to receive headers after a stream has been reset. @@ -553,8 +550,7 @@ stream->OnStreamHeadersPriority(precedence); } -void QuicSpdySession::OnStreamHeaderList(QuicStreamId stream_id, - bool fin, +void QuicSpdySession::OnStreamHeaderList(QuicStreamId stream_id, bool fin, size_t frame_len, const QuicHeaderList& header_list) { if (IsStaticStream(stream_id)) { @@ -593,8 +589,7 @@ } void QuicSpdySession::OnPriorityFrame( - QuicStreamId stream_id, - const spdy::SpdyStreamPrecedence& precedence) { + QuicStreamId stream_id, const spdy::SpdyStreamPrecedence& precedence) { QuicSpdyStream* stream = GetOrCreateSpdyDataStream(stream_id); if (!stream) { // It's quite possible to receive a PRIORITY frame after a stream has been @@ -671,9 +666,7 @@ } size_t QuicSpdySession::WriteHeadersOnHeadersStream( - QuicStreamId id, - SpdyHeaderBlock headers, - bool fin, + QuicStreamId id, SpdyHeaderBlock headers, bool fin, const spdy::SpdyStreamPrecedence& precedence, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) { QUICHE_DCHECK(!VersionUsesHttp3(transport_version())); @@ -686,8 +679,7 @@ } size_t QuicSpdySession::WritePriority(QuicStreamId id, - QuicStreamId parent_stream_id, - int weight, + QuicStreamId parent_stream_id, int weight, bool exclusive) { QUICHE_DCHECK(!VersionUsesHttp3(transport_version())); SpdyPriorityIR priority_frame(id, parent_stream_id, weight, exclusive); @@ -865,8 +857,7 @@ } void QuicSpdySession::OnNewEncryptionKeyAvailable( - EncryptionLevel level, - std::unique_ptr<QuicEncrypter> encrypter) { + EncryptionLevel level, std::unique_ptr<QuicEncrypter> encrypter) { QuicSession::OnNewEncryptionKeyAvailable(level, std::move(encrypter)); if (IsEncryptionEstablished()) { // Send H3 SETTINGs once encryption is established. @@ -874,13 +865,11 @@ } } -bool QuicSpdySession::ShouldNegotiateWebTransport() { - return false; -} +bool QuicSpdySession::ShouldNegotiateWebTransport() { return false; } bool QuicSpdySession::WillNegotiateWebTransport() { - return ShouldNegotiateHttp3Datagram() && version().UsesHttp3() && - ShouldNegotiateWebTransport(); + return LocalHttpDatagramSupport() != HttpDatagramSupport::kNone && + version().UsesHttp3() && ShouldNegotiateWebTransport(); } // True if there are open HTTP requests. @@ -902,12 +891,8 @@ } size_t QuicSpdySession::WriteHeadersOnHeadersStreamImpl( - QuicStreamId id, - spdy::SpdyHeaderBlock headers, - bool fin, - QuicStreamId parent_stream_id, - int weight, - bool exclusive, + QuicStreamId id, spdy::SpdyHeaderBlock headers, bool fin, + QuicStreamId parent_stream_id, int weight, bool exclusive, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) { QUICHE_DCHECK(!VersionUsesHttp3(transport_version())); @@ -941,10 +926,8 @@ } void QuicSpdySession::OnPromiseHeaderList( - QuicStreamId /*stream_id*/, - QuicStreamId /*promised_stream_id*/, - size_t /*frame_len*/, - const QuicHeaderList& /*header_list*/) { + QuicStreamId /*stream_id*/, QuicStreamId /*promised_stream_id*/, + size_t /*frame_len*/, const QuicHeaderList& /*header_list*/) { std::string error = "OnPromiseHeaderList should be overridden in client code."; QUIC_BUG(quic_bug_10360_6) << error; @@ -974,8 +957,7 @@ } absl::optional<std::string> QuicSpdySession::OnAlpsData( - const uint8_t* alps_data, - size_t alps_length) { + const uint8_t* alps_data, size_t alps_length) { AlpsFrameDecoder alps_frame_decoder(this); HttpDecoder decoder(&alps_frame_decoder); decoder.ProcessInput(reinterpret_cast<const char*>(alps_data), alps_length); @@ -1043,6 +1025,21 @@ return absl::nullopt; } +bool QuicSpdySession::VerifySettingIsZeroOrOne(uint64_t id, uint64_t value) { + if (value == 0 || value == 1) { + return true; + } + std::string error_details = absl::StrCat( + "Received ", + H3SettingsToString(static_cast<Http3AndQpackSettingsIdentifiers>(id)), + " with invalid value ", value); + QUIC_PEER_BUG(bad received setting) << ENDPOINT << error_details; + // TODO(dschinazi) use QUIC_HTTP_INVALID_SETTING_VALUE instead of + // QUIC_HTTP_RECEIVE_SPDY_SETTING once cl/396439351 lands. + CloseConnectionWithDetails(QUIC_HTTP_RECEIVE_SPDY_SETTING, error_details); + return false; +} + bool QuicSpdySession::OnSetting(uint64_t id, uint64_t value) { any_settings_received_ = true; @@ -1127,24 +1124,47 @@ absl::StrCat("received HTTP/2 specific setting in HTTP/3 session: ", id)); return false; - case SETTINGS_H3_DATAGRAM: { - if (!ShouldNegotiateHttp3Datagram()) { + case SETTINGS_H3_DATAGRAM_DRAFT00: { + HttpDatagramSupport local_http_datagram_support = + LocalHttpDatagramSupport(); + if (local_http_datagram_support != HttpDatagramSupport::kDraft00 && + local_http_datagram_support != HttpDatagramSupport::kDraft00And04) { 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; - CloseConnectionWithDetails(QUIC_HTTP_RECEIVE_SPDY_SETTING, - error_details); + if (!VerifySettingIsZeroOrOne(id, value)) { return false; } - h3_datagram_supported_ = !!value; + if (value && http_datagram_support_ != HttpDatagramSupport::kDraft04) { + // If both draft-00 and draft-04 are supported, use draft-04. + http_datagram_support_ = HttpDatagramSupport::kDraft00; + } + break; + } + case SETTINGS_H3_DATAGRAM_DRAFT04: { + HttpDatagramSupport local_http_datagram_support = + LocalHttpDatagramSupport(); + if (local_http_datagram_support != HttpDatagramSupport::kDraft04 && + local_http_datagram_support != HttpDatagramSupport::kDraft00And04) { + break; + } + QUIC_DVLOG(1) << ENDPOINT + << "SETTINGS_H3_DATAGRAM_DRAFT04 received with value " + << value; + if (!version().UsesHttp3()) { + break; + } + if (!VerifySettingIsZeroOrOne(id, value)) { + return false; + } + if (value) { + http_datagram_support_ = HttpDatagramSupport::kDraft04; + } break; } case SETTINGS_WEBTRANS_DRAFT00: @@ -1154,14 +1174,7 @@ QUIC_DVLOG(1) << ENDPOINT << "SETTINGS_ENABLE_WEBTRANSPORT received with value " << value; - if (value != 0 && value != 1) { - std::string error_details = absl::StrCat( - "received SETTINGS_ENABLE_WEBTRANSPORT with invalid value ", - value); - QUIC_PEER_BUG(invalid SETTINGS_ENABLE_WEBTRANSPORT value) - << ENDPOINT << error_details; - CloseConnectionWithDetails(QUIC_HTTP_RECEIVE_SPDY_SETTING, - error_details); + if (!VerifySettingIsZeroOrOne(id, value)) { return false; } peer_supports_webtransport_ = (value == 1); @@ -1234,8 +1247,7 @@ return false; } -void QuicSpdySession::OnHeaders(SpdyStreamId stream_id, - bool has_priority, +void QuicSpdySession::OnHeaders(SpdyStreamId stream_id, bool has_priority, const spdy::SpdyStreamPrecedence& precedence, bool fin) { if (has_priority) { @@ -1572,9 +1584,7 @@ // static void QuicSpdySession::LogHeaderCompressionRatioHistogram( - bool using_qpack, - bool is_sent, - QuicByteCount compressed, + bool using_qpack, bool is_sent, QuicByteCount compressed, QuicByteCount uncompressed) { if (compressed <= 0 || uncompressed <= 0) { return; @@ -1620,15 +1630,25 @@ 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 is sent divided by four as per the specification. + 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; @@ -1669,8 +1689,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); @@ -1679,7 +1699,12 @@ 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 ID is sent divided by four as per the specification. + 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 " @@ -1709,10 +1734,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()) { @@ -1743,8 +1772,7 @@ } void QuicSpdySession::AssociateIncomingWebTransportStreamWithSession( - WebTransportSessionId session_id, - QuicStreamId stream_id) { + WebTransportSessionId session_id, QuicStreamId stream_id) { if (QuicUtils::IsOutgoingStreamId(version(), stream_id, perspective())) { QUIC_BUG(AssociateIncomingWebTransportStreamWithSession got outgoing stream) << ENDPOINT @@ -1840,8 +1868,29 @@ session_->OnDatagramProcessed(status); } -bool QuicSpdySession::ShouldNegotiateHttp3Datagram() { - return false; +HttpDatagramSupport QuicSpdySession::LocalHttpDatagramSupport() { + return HttpDatagramSupport::kNone; +} + +std::string HttpDatagramSupportToString( + HttpDatagramSupport http_datagram_support) { + switch (http_datagram_support) { + case HttpDatagramSupport::kNone: + return "None"; + case HttpDatagramSupport::kDraft00: + return "Draft00"; + case HttpDatagramSupport::kDraft04: + return "Draft04"; + case HttpDatagramSupport::kDraft00And04: + return "Draft00And04"; + } + 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
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h index bb22f17..d8503cc 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> @@ -90,8 +91,8 @@ virtual void OnDataFrameReceived(QuicStreamId /*stream_id*/, QuicByteCount /*payload_length*/) {} virtual void OnHeadersFrameReceived( - QuicStreamId /*stream_id*/, - QuicByteCount /*compressed_headers_length*/) {} + QuicStreamId /*stream_id*/, QuicByteCount /*compressed_headers_length*/) { + } virtual void OnHeadersDecoded(QuicStreamId /*stream_id*/, QuicHeaderList /*headers*/) {} @@ -119,6 +120,20 @@ 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, + kDraft04 = 2, + kDraft00And04 = 3, // only used locally, we only negotiate one draft. +}; + +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, @@ -126,8 +141,7 @@ public QpackDecoder::EncoderStreamErrorDelegate { public: // Does not take ownership of |connection| or |visitor|. - QuicSpdySession(QuicConnection* connection, - QuicSession::Visitor* visitor, + QuicSpdySession(QuicConnection* connection, QuicSession::Visitor* visitor, const QuicConfig& config, const ParsedQuicVersionVector& supported_versions); QuicSpdySession(const QuicSpdySession&) = delete; @@ -148,14 +162,12 @@ // Called by |headers_stream_| when headers with a priority have been // received for a stream. This method will only be called for server streams. virtual void OnStreamHeadersPriority( - QuicStreamId stream_id, - const spdy::SpdyStreamPrecedence& precedence); + QuicStreamId stream_id, const spdy::SpdyStreamPrecedence& precedence); // Called by |headers_stream_| when headers have been completely received // for a stream. |fin| will be true if the fin flag was set in the headers // frame. - virtual void OnStreamHeaderList(QuicStreamId stream_id, - bool fin, + virtual void OnStreamHeaderList(QuicStreamId stream_id, bool fin, size_t frame_len, const QuicHeaderList& header_list); @@ -192,18 +204,14 @@ // If provided, |ack_notifier_delegate| will be registered to be notified when // we have seen ACKs for all packets resulting from this call. virtual size_t WriteHeadersOnHeadersStream( - QuicStreamId id, - spdy::SpdyHeaderBlock headers, - bool fin, + QuicStreamId id, spdy::SpdyHeaderBlock headers, bool fin, const spdy::SpdyStreamPrecedence& precedence, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener); // Writes an HTTP/2 PRIORITY frame the to peer. Returns the size in bytes of // the resulting PRIORITY frame. - size_t WritePriority(QuicStreamId id, - QuicStreamId parent_stream_id, - int weight, - bool exclusive); + size_t WritePriority(QuicStreamId id, QuicStreamId parent_stream_id, + int weight, bool exclusive); // Writes an HTTP/3 PRIORITY_UPDATE frame to the peer. void WriteHttp3PriorityUpdate(const PriorityUpdateFrame& priority_update); @@ -349,8 +357,7 @@ // In order for measurements for different protocol to be comparable, the // caller must ensure that uncompressed size is the total length of header // names and values without any overhead. - static void LogHeaderCompressionRatioHistogram(bool using_qpack, - bool is_sent, + static void LogHeaderCompressionRatioHistogram(bool using_qpack, bool is_sent, QuicByteCount compressed, QuicByteCount uncompressed); @@ -374,9 +381,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 +409,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. @@ -414,7 +423,7 @@ // until the SETTINGS are received. Only works for HTTP/3. bool ShouldBufferRequestsUntilSettings() { return version().UsesHttp3() && perspective() == Perspective::IS_SERVER && - ShouldNegotiateHttp3Datagram(); + LocalHttpDatagramSupport() != HttpDatagramSupport::kNone; } // Returns if the incoming bidirectional streams should process data. This is @@ -427,8 +436,7 @@ // Links the specified stream with a WebTransport session. If the session is // not present, it is buffered until a corresponding stream is found. void AssociateIncomingWebTransportStreamWithSession( - WebTransportSessionId session_id, - QuicStreamId stream_id); + WebTransportSessionId session_id, QuicStreamId stream_id); void ProcessBufferedWebTransportStreamsForSession(WebTransportHttp3* session); @@ -487,17 +495,12 @@ QuicStream* ProcessPendingStream(PendingStream* pending) override; size_t WriteHeadersOnHeadersStreamImpl( - QuicStreamId id, - spdy::SpdyHeaderBlock headers, - bool fin, - QuicStreamId parent_stream_id, - int weight, - bool exclusive, + QuicStreamId id, spdy::SpdyHeaderBlock headers, bool fin, + QuicStreamId parent_stream_id, int weight, bool exclusive, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener); void OnNewEncryptionKeyAvailable( - EncryptionLevel level, - std::unique_ptr<QuicEncrypter> encrypter) override; + EncryptionLevel level, std::unique_ptr<QuicEncrypter> encrypter) override; // Sets the maximum size of the header compression table spdy_framer_ is // willing to use to encode header blocks. @@ -520,8 +523,9 @@ // Called whenever a datagram is dequeued or dropped from datagram_queue(). virtual void OnDatagramProcessed(absl::optional<MessageStatus> status); - // Returns true if HTTP/3 datagram extension should be supported. - virtual bool ShouldNegotiateHttp3Datagram(); + // Returns which version of the HTTP/3 datagram extension we should advertise + // in settings and accept remote settings for. + virtual HttpDatagramSupport LocalHttpDatagramSupport(); private: friend class test::QuicSpdySessionPeer; @@ -547,10 +551,8 @@ // The following methods are called by the SimpleVisitor. // Called when a HEADERS frame has been received. - void OnHeaders(spdy::SpdyStreamId stream_id, - bool has_priority, - const spdy::SpdyStreamPrecedence& precedence, - bool fin); + void OnHeaders(spdy::SpdyStreamId stream_id, bool has_priority, + const spdy::SpdyStreamPrecedence& precedence, bool fin); // Called when a PRIORITY frame has been received. void OnPriority(spdy::SpdyStreamId stream_id, @@ -568,6 +570,8 @@ void FillSettingsFrame(); + bool VerifySettingIsZeroOrOne(uint64_t id, uint64_t value); + std::unique_ptr<QpackEncoder> qpack_encoder_; std::unique_ptr<QpackDecoder> qpack_decoder_; @@ -655,8 +659,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 80296d6..89beb70 100644 --- a/quic/core/http/quic_spdy_session_test.cc +++ b/quic/core/http/quic_spdy_session_test.cc
@@ -216,8 +216,7 @@ void OnBodyAvailable() override {} MOCK_METHOD(void, OnCanWrite, (), (override)); - MOCK_METHOD(bool, - RetransmitStreamData, + MOCK_METHOD(bool, RetransmitStreamData, (QuicStreamOffset, QuicByteCount, bool, TransmissionType), (override)); @@ -227,9 +226,7 @@ class TestSession : public QuicSpdySession { public: explicit TestSession(QuicConnection* connection) - : QuicSpdySession(connection, - nullptr, - DefaultQuicConfig(), + : QuicSpdySession(connection, nullptr, DefaultQuicConfig(), CurrentSupportedVersions()), crypto_stream_(this), writev_consumes_all_data_(false) { @@ -349,11 +346,11 @@ bool ShouldNegotiateWebTransport() override { return supports_webtransport_; } void set_supports_webtransport(bool value) { supports_webtransport_ = value; } - bool ShouldNegotiateHttp3Datagram() override { - return should_negotiate_h3_datagram_; + HttpDatagramSupport LocalHttpDatagramSupport() override { + return local_http_datagram_support_; } - void set_should_negotiate_h3_datagram(bool value) { - should_negotiate_h3_datagram_ = value; + void set_local_http_datagram_support(HttpDatagramSupport value) { + local_http_datagram_support_ = value; } MOCK_METHOD(void, OnAcceptChFrame, (const AcceptChFrame&), (override)); @@ -368,7 +365,7 @@ bool writev_consumes_all_data_; bool supports_webtransport_ = false; - bool should_negotiate_h3_datagram_ = false; + HttpDatagramSupport local_http_datagram_support_ = HttpDatagramSupport::kNone; }; class QuicSpdySessionTestBase : public QuicTestWithParam<ParsedQuicVersion> { @@ -383,11 +380,9 @@ protected: explicit QuicSpdySessionTestBase(Perspective perspective) - : connection_( - new StrictMock<MockQuicConnection>(&helper_, - &alarm_factory_, - perspective, - SupportedVersions(GetParam()))), + : connection_(new StrictMock<MockQuicConnection>( + &helper_, &alarm_factory_, perspective, + SupportedVersions(GetParam()))), session_(connection_) { session_.config()->SetInitialStreamFlowControlWindowToSend( kInitialStreamFlowControlWindowForTest); @@ -506,8 +501,7 @@ } QuicStreamId StreamCountToId(QuicStreamCount stream_count, - Perspective perspective, - bool bidirectional) { + Perspective perspective, bool bidirectional) { // Calculate and build up stream ID rather than use // GetFirst... because the test that relies on this method // needs to do the stream count where #1 is 0/1/2/3, and not @@ -543,7 +537,7 @@ void ReceiveWebTransportSettings() { SettingsFrame settings; - settings.values[SETTINGS_H3_DATAGRAM] = 1; + settings.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1; settings.values[SETTINGS_WEBTRANS_DRAFT00] = 1; std::string data = std::string(1, kControlStream) + EncodeSettings(settings); @@ -567,8 +561,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::kDraft00) { + stream->OnCapsule( + Capsule::RegisterDatagramNoContext(DatagramFormatType::WEBTRANSPORT)); + } WebTransportHttp3* web_transport = session_.GetWebTransportSession(session_id); ASSERT_TRUE(web_transport != nullptr); @@ -588,6 +588,11 @@ session_.OnStreamFrame(frame); } + void TestHttpDatagramSetting(HttpDatagramSupport local_support, + HttpDatagramSupport remote_support, + HttpDatagramSupport expected_support, + bool expected_datagram_supported); + MockQuicConnectionHelper helper_; MockAlarmFactory alarm_factory_; StrictMock<MockQuicConnection>* connection_; @@ -602,8 +607,7 @@ : QuicSpdySessionTestBase(Perspective::IS_SERVER) {} }; -INSTANTIATE_TEST_SUITE_P(Tests, - QuicSpdySessionTestServer, +INSTANTIATE_TEST_SUITE_P(Tests, QuicSpdySessionTestServer, ::testing::ValuesIn(AllSupportedVersions()), ::testing::PrintToStringParamName()); @@ -1849,8 +1853,7 @@ : QuicSpdySessionTestBase(Perspective::IS_CLIENT) {} }; -INSTANTIATE_TEST_SUITE_P(Tests, - QuicSpdySessionTestClient, +INSTANTIATE_TEST_SUITE_P(Tests, QuicSpdySessionTestClient, ::testing::ValuesIn(AllSupportedVersions()), ::testing::PrintToStringParamName()); @@ -3411,16 +3414,32 @@ EXPECT_EQ("multiple SETTINGS frames", error.value()); } -TEST_P(QuicSpdySessionTestClient, H3DatagramSetting) { +void QuicSpdySessionTestBase::TestHttpDatagramSetting( + HttpDatagramSupport local_support, HttpDatagramSupport remote_support, + HttpDatagramSupport expected_support, bool expected_datagram_supported) { if (!version().UsesHttp3()) { return; } - session_.set_should_negotiate_h3_datagram(true); + session_.set_local_http_datagram_support(local_support); // 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; + switch (remote_support) { + case HttpDatagramSupport::kNone: + break; + case HttpDatagramSupport::kDraft00: + settings.values[SETTINGS_H3_DATAGRAM_DRAFT00] = 1; + break; + case HttpDatagramSupport::kDraft04: + settings.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1; + break; + case HttpDatagramSupport::kDraft00And04: + settings.values[SETTINGS_H3_DATAGRAM_DRAFT00] = 1; + settings.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1; + break; + } std::string data = std::string(1, kControlStream) + EncodeSettings(settings); QuicStreamId stream_id = GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 3); @@ -3430,15 +3449,88 @@ 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_.h3_datagram_supported()); + EXPECT_EQ(session_.http_datagram_support(), expected_support); + EXPECT_EQ(session_.SupportsH3Datagram(), expected_datagram_supported); +} + +TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal00Remote00) { + TestHttpDatagramSetting( + /*local_support=*/HttpDatagramSupport::kDraft00, + /*remote_support=*/HttpDatagramSupport::kDraft00, + /*expected_support=*/HttpDatagramSupport::kDraft00, + /*expected_datagram_supported=*/true); +} + +TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal00Remote04) { + TestHttpDatagramSetting( + /*local_support=*/HttpDatagramSupport::kDraft00, + /*remote_support=*/HttpDatagramSupport::kDraft04, + /*expected_support=*/HttpDatagramSupport::kNone, + /*expected_datagram_supported=*/false); +} + +TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal00Remote00And04) { + TestHttpDatagramSetting( + /*local_support=*/HttpDatagramSupport::kDraft00, + /*remote_support=*/HttpDatagramSupport::kDraft00And04, + /*expected_support=*/HttpDatagramSupport::kDraft00, + /*expected_datagram_supported=*/true); +} + +TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal04Remote00) { + TestHttpDatagramSetting( + /*local_support=*/HttpDatagramSupport::kDraft04, + /*remote_support=*/HttpDatagramSupport::kDraft00, + /*expected_support=*/HttpDatagramSupport::kNone, + /*expected_datagram_supported=*/false); +} + +TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal04Remote04) { + TestHttpDatagramSetting( + /*local_support=*/HttpDatagramSupport::kDraft04, + /*remote_support=*/HttpDatagramSupport::kDraft04, + /*expected_support=*/HttpDatagramSupport::kDraft04, + /*expected_datagram_supported=*/true); +} + +TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal04Remote00And04) { + TestHttpDatagramSetting( + /*local_support=*/HttpDatagramSupport::kDraft04, + /*remote_support=*/HttpDatagramSupport::kDraft00And04, + /*expected_support=*/HttpDatagramSupport::kDraft04, + /*expected_datagram_supported=*/true); +} + +TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal00And04Remote00) { + TestHttpDatagramSetting( + /*local_support=*/HttpDatagramSupport::kDraft00And04, + /*remote_support=*/HttpDatagramSupport::kDraft00, + /*expected_support=*/HttpDatagramSupport::kDraft00, + /*expected_datagram_supported=*/true); +} + +TEST_P(QuicSpdySessionTestClient, HttpDatagramSettingLocal00And04Remote04) { + TestHttpDatagramSetting( + /*local_support=*/HttpDatagramSupport::kDraft00And04, + /*remote_support=*/HttpDatagramSupport::kDraft04, + /*expected_support=*/HttpDatagramSupport::kDraft04, + /*expected_datagram_supported=*/true); +} + +TEST_P(QuicSpdySessionTestClient, + HttpDatagramSettingLocal00And04Remote00And04) { + TestHttpDatagramSetting( + /*local_support=*/HttpDatagramSupport::kDraft00And04, + /*remote_support=*/HttpDatagramSupport::kDraft00And04, + /*expected_support=*/HttpDatagramSupport::kDraft04, + /*expected_datagram_supported=*/true); } TEST_P(QuicSpdySessionTestClient, WebTransportSetting) { if (!version().UsesHttp3()) { return; } - session_.set_should_negotiate_h3_datagram(true); + session_.set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); session_.set_supports_webtransport(true); EXPECT_FALSE(session_.SupportsWebTransport()); @@ -3451,7 +3543,7 @@ CompleteHandshake(); SettingsFrame server_settings; - server_settings.values[SETTINGS_H3_DATAGRAM] = 1; + server_settings.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1; server_settings.values[SETTINGS_WEBTRANS_DRAFT00] = 1; std::string data = std::string(1, kControlStream) + EncodeSettings(server_settings); @@ -3468,7 +3560,7 @@ if (!version().UsesHttp3()) { return; } - session_.set_should_negotiate_h3_datagram(true); + session_.set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); session_.set_supports_webtransport(true); EXPECT_FALSE(session_.SupportsWebTransport()); @@ -3481,7 +3573,7 @@ CompleteHandshake(); SettingsFrame server_settings; - server_settings.values[SETTINGS_H3_DATAGRAM] = 1; + server_settings.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1; server_settings.values[SETTINGS_WEBTRANS_DRAFT00] = 0; std::string data = std::string(1, kControlStream) + EncodeSettings(server_settings); @@ -3498,7 +3590,7 @@ if (!version().UsesHttp3()) { return; } - session_.set_should_negotiate_h3_datagram(true); + session_.set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); session_.set_supports_webtransport(true); EXPECT_FALSE(session_.SupportsWebTransport()); @@ -3515,7 +3607,7 @@ if (!version().UsesHttp3()) { return; } - session_.set_should_negotiate_h3_datagram(true); + session_.set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); session_.set_supports_webtransport(true); CompleteHandshake(); @@ -3548,7 +3640,7 @@ if (!version().UsesHttp3()) { return; } - session_.set_should_negotiate_h3_datagram(true); + session_.set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); session_.set_supports_webtransport(true); CompleteHandshake(); @@ -3589,7 +3681,7 @@ if (!version().UsesHttp3()) { return; } - session_.set_should_negotiate_h3_datagram(true); + session_.set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); session_.set_supports_webtransport(true); CompleteHandshake();
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc index c33e763..e9df950 100644 --- a/quic/core/http/quic_spdy_stream.cc +++ b/quic/core/http/quic_spdy_stream.cc
@@ -13,8 +13,10 @@ #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "quic/core/http/capsule.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" @@ -129,13 +131,11 @@ } void OnWebTransportStreamFrameType( - QuicByteCount header_length, - WebTransportSessionId session_id) override { + QuicByteCount header_length, WebTransportSessionId session_id) override { stream_->OnWebTransportStreamFrameType(header_length, session_id); } - bool OnUnknownFrameStart(uint64_t frame_type, - QuicByteCount header_length, + bool OnUnknownFrameStart(uint64_t frame_type, QuicByteCount header_length, QuicByteCount payload_length) override { return stream_->OnUnknownFrameStart(frame_type, header_length, payload_length); @@ -249,8 +249,7 @@ QuicSpdyStream::~QuicSpdyStream() {} size_t QuicSpdyStream::WriteHeaders( - SpdyHeaderBlock header_block, - bool fin, + SpdyHeaderBlock header_block, bool fin, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) { if (!AssertNotWebTransportDataStream("writing headers")) { return 0; @@ -286,6 +285,16 @@ SetFinSent(); CloseWriteSide(); } + + if (web_transport_ != nullptr && + session()->perspective() == Perspective::IS_CLIENT) { + // This will send a capsule and therefore needs to happen after headers have + // been sent. + RegisterHttp3DatagramContextId( + web_transport_->context_id(), DatagramFormatType::WEBTRANSPORT, + /*format_additional_data=*/absl::string_view(), web_transport_.get()); + } + return bytes_written; } @@ -355,8 +364,7 @@ return bytes_written; } -QuicConsumedData QuicSpdyStream::WritevBody(const struct iovec* iov, - int count, +QuicConsumedData QuicSpdyStream::WritevBody(const struct iovec* iov, int count, bool fin) { QuicMemSliceStorage storage( iov, count, @@ -399,8 +407,7 @@ } QuicConsumedData QuicSpdyStream::WriteBodySlices( - absl::Span<QuicMemSlice> slices, - bool fin) { + absl::Span<QuicMemSlice> slices, bool fin) { if (!VersionUsesHttp3(transport_version()) || slices.empty()) { return WriteMemSlices(slices, fin); } @@ -459,9 +466,7 @@ return body_manager_.HasBytesToRead(); } -void QuicSpdyStream::MarkTrailersConsumed() { - trailers_consumed_ = true; -} +void QuicSpdyStream::MarkTrailersConsumed() { trailers_consumed_ = true; } uint64_t QuicSpdyStream::total_body_bytes_read() const { if (VersionUsesHttp3(transport_version())) { @@ -483,14 +488,14 @@ } if (body_manager_.HasBytesToRead()) { - OnBodyAvailable(); + HandleBodyAvailable(); return; } if (sequencer()->IsClosed() && !on_body_available_called_because_sequencer_is_closed_) { on_body_available_called_because_sequencer_is_closed_ = true; - OnBodyAvailable(); + HandleBodyAvailable(); } } @@ -501,8 +506,7 @@ SetPriority(precedence); } -void QuicSpdyStream::OnStreamHeaderList(bool fin, - size_t frame_len, +void QuicSpdyStream::OnStreamHeaderList(bool fin, size_t frame_len, const QuicHeaderList& header_list) { if (!spdy_session()->user_agent_id().has_value()) { std::string uaid; @@ -595,14 +599,10 @@ spdy_session_->WriteHttp3PriorityUpdate(priority_update); } -void QuicSpdyStream::OnHeadersTooLarge() { - Reset(QUIC_HEADERS_TOO_LARGE); -} +void QuicSpdyStream::OnHeadersTooLarge() { Reset(QUIC_HEADERS_TOO_LARGE); } void QuicSpdyStream::OnInitialHeadersComplete( - bool fin, - size_t /*frame_len*/, - const QuicHeaderList& header_list) { + bool fin, size_t /*frame_len*/, const QuicHeaderList& header_list) { // TODO(b/134706391): remove |fin| argument. headers_decompressed_ = true; header_list_ = header_list; @@ -628,8 +628,7 @@ } void QuicSpdyStream::OnPromiseHeaderList( - QuicStreamId /* promised_id */, - size_t /* frame_len */, + QuicStreamId /* promised_id */, size_t /* frame_len */, const QuicHeaderList& /*header_list */) { // To be overridden in QuicSpdyClientStream. Not supported on // server side. @@ -638,9 +637,7 @@ } void QuicSpdyStream::OnTrailingHeadersComplete( - bool fin, - size_t /*frame_len*/, - const QuicHeaderList& header_list) { + bool fin, size_t /*frame_len*/, const QuicHeaderList& header_list) { // TODO(b/134706391): remove |fin| argument. QUICHE_DCHECK(!trailers_decompressed_); if (!VersionUsesHttp3(transport_version()) && fin_received()) { @@ -780,7 +777,7 @@ } if (!VersionUsesHttp3(transport_version())) { - OnBodyAvailable(); + HandleBodyAvailable(); return; } @@ -825,20 +822,20 @@ } } - // Do not call OnBodyAvailable() until headers are consumed. + // Do not call HandleBodyAvailable() until headers are consumed. if (!FinishedReadingHeaders()) { return; } if (body_manager_.HasBytesToRead()) { - OnBodyAvailable(); + HandleBodyAvailable(); return; } if (sequencer()->IsClosed() && !on_body_available_called_because_sequencer_is_closed_) { on_body_available_called_because_sequencer_is_closed_ = true; - OnBodyAvailable(); + HandleBodyAvailable(); } } @@ -1000,8 +997,7 @@ } QuicByteCount QuicSpdyStream::GetNumFrameHeadersInInterval( - QuicStreamOffset offset, - QuicByteCount data_length) const { + QuicStreamOffset offset, QuicByteCount data_length) const { QuicByteCount header_acked_length = 0; QuicIntervalSet<QuicStreamOffset> newly_acked(offset, offset + data_length); newly_acked.Intersection(unacked_frame_headers_offsets_); @@ -1072,8 +1068,7 @@ } void QuicSpdyStream::OnWebTransportStreamFrameType( - QuicByteCount header_length, - WebTransportSessionId session_id) { + QuicByteCount header_length, WebTransportSessionId session_id) { QUIC_DVLOG(1) << ENDPOINT << " Received WEBTRANSPORT_STREAM on stream " << id() << " for session " << session_id; sequencer()->MarkConsumed(header_length); @@ -1125,13 +1120,10 @@ return true; } -bool QuicSpdyStream::OnUnknownFrameEnd() { - return true; -} +bool QuicSpdyStream::OnUnknownFrameEnd() { return true; } size_t QuicSpdyStream::WriteHeadersImpl( - spdy::SpdyHeaderBlock header_block, - bool fin, + spdy::SpdyHeaderBlock header_block, bool fin, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) { if (!VersionUsesHttp3(transport_version())) { return spdy_session_->WriteHeadersOnHeadersStream( @@ -1217,6 +1209,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; } @@ -1228,23 +1227,33 @@ } } - 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. + // a REGISTER_DATAGRAM_NO_CONTEXT capsule. // TODO(b/181256914) remove this when we remove support for // draft-ietf-masque-h3-datagram-00 in favor of later drafts. - RegisterHttp3DatagramContextId(/*context_id=*/absl::nullopt, - Http3DatagramContextExtensions(), - web_transport_.get()); + RegisterHttp3DatagramContextId( + /*context_id=*/absl::nullopt, DatagramFormatType::WEBTRANSPORT, + /*format_additional_data=*/absl::string_view(), web_transport_.get()); } void QuicSpdyStream::MaybeProcessSentWebTransportHeaders( @@ -1266,14 +1275,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() { @@ -1329,11 +1336,101 @@ } QuicSpdyStream::WebTransportDataStream::WebTransportDataStream( - QuicSpdyStream* stream, - WebTransportSessionId session_id) + QuicSpdyStream* stream, WebTransportSessionId session_id) : session_id(session_id), adapter(stream->spdy_session_, stream, stream->sequencer()) {} +void QuicSpdyStream::HandleReceivedDatagram( + absl::optional<QuicDatagramContextId> context_id, + absl::string_view payload) { + Http3DatagramVisitor* visitor; + if (context_id.has_value()) { + auto it = datagram_context_visitors_.find(context_id.value()); + if (it == datagram_context_visitors_.end()) { + QUIC_DLOG(ERROR) << ENDPOINT + << "Received datagram without any visitor for context " + << context_id.value(); + return; + } + visitor = it->second; + } else { + if (datagram_no_context_visitor_ == nullptr) { + QUIC_DLOG(ERROR) + << ENDPOINT << "Received datagram without any visitor for no context"; + return; + } + visitor = datagram_no_context_visitor_; + } + visitor->OnHttp3Datagram(id(), context_id, payload); +} + +bool QuicSpdyStream::OnCapsule(const Capsule& capsule) { + QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id() << " received capsule " + << capsule; + if (!headers_decompressed_) { + QUIC_PEER_BUG(capsule before headers) + << ENDPOINT << "Stream " << id() << " received capsule " << capsule + << " before headers"; + return false; + } + switch (capsule.capsule_type()) { + case CapsuleType::DATAGRAM: { + HandleReceivedDatagram(capsule.datagram_capsule().context_id, + capsule.datagram_capsule().http_datagram_payload); + } break; + case CapsuleType::REGISTER_DATAGRAM_CONTEXT: + if (datagram_registration_visitor_ == nullptr) { + QUIC_DLOG(ERROR) << ENDPOINT << "Received capsule " << capsule + << " without any registration visitor"; + return false; + } + datagram_registration_visitor_->OnContextReceived( + id(), capsule.register_datagram_context_capsule().context_id, + capsule.register_datagram_context_capsule().format_type, + capsule.register_datagram_context_capsule().format_additional_data); + break; + case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT: + if (datagram_registration_visitor_ == nullptr) { + QUIC_DLOG(ERROR) << ENDPOINT << "Received capsule " << capsule + << " without any registration visitor"; + return false; + } + datagram_registration_visitor_->OnContextReceived( + id(), /*context_id=*/absl::nullopt, + capsule.register_datagram_no_context_capsule().format_type, + capsule.register_datagram_no_context_capsule() + .format_additional_data); + break; + case CapsuleType::CLOSE_DATAGRAM_CONTEXT: + if (datagram_registration_visitor_ == nullptr) { + QUIC_DLOG(ERROR) << ENDPOINT << "Received capsule " << capsule + << " without any registration visitor"; + return false; + } + datagram_registration_visitor_->OnContextClosed( + id(), capsule.close_datagram_context_capsule().context_id, + capsule.close_datagram_context_capsule().close_code, + capsule.close_datagram_context_capsule().close_details); + break; + } + return true; +} + +void QuicSpdyStream::OnCapsuleParseFailure(const std::string& error_message) { + QUIC_DLOG(ERROR) << ENDPOINT << "Capsule parse failure: " << error_message; + Reset(QUIC_BAD_APPLICATION_PAYLOAD); +} + +void QuicSpdyStream::WriteCapsule(const Capsule& capsule) { + QUIC_DLOG(INFO) << ENDPOINT << "Stream " << id() << " sending capsule " + << capsule; + QuicBuffer serialized_capsule = SerializeCapsule( + capsule, + spdy_session_->connection()->helper()->GetStreamSendBufferAllocator()); + QUICHE_DCHECK_GT(serialized_capsule.size(), 0u); + WriteOrBufferBody(serialized_capsule.AsStringView(), /*fin=*/false); +} + MessageStatus QuicSpdyStream::SendHttp3Datagram( absl::optional<QuicDatagramContextId> context_id, absl::string_view payload) { @@ -1349,8 +1446,15 @@ << ENDPOINT << "Null datagram registration visitor for" << id(); return; } + if (datagram_registration_visitor_ != nullptr) { + QUIC_BUG(double datagram registration visitor) + << ENDPOINT << "Double datagram registration visitor for" << id(); + return; + } QUIC_DLOG(INFO) << ENDPOINT << "Registering datagram stream ID " << id(); datagram_registration_visitor_ = visitor; + QUICHE_DCHECK(!capsule_parser_); + capsule_parser_.reset(new CapsuleParser(this)); } void QuicSpdyStream::UnregisterHttp3DatagramRegistrationVisitor() { @@ -1374,24 +1478,27 @@ void QuicSpdyStream::RegisterHttp3DatagramContextId( absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& /*extensions*/, + DatagramFormatType format_type, absl::string_view format_additional_data, Http3DatagramVisitor* visitor) { 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) @@ -1403,28 +1510,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; + } + capsule_parser_->set_datagram_context_id_present(true); + } 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; + capsule_parser_->set_datagram_context_id_present(false); } - // 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::kDraft04) { + 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) { + WriteCapsule(Capsule::RegisterDatagramContext( + context_id.value(), format_type, format_additional_data)); + } + } else if (is_client) { + WriteCapsule(Capsule::RegisterDatagramNoContext(format_type, + format_additional_data)); + } } - 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( @@ -1432,26 +1556,31 @@ 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::kDraft04 && + context_id.has_value()) { + WriteCapsule(Capsule::CloseDatagramContext(context_id.value())); + } } void QuicSpdyStream::MoveHttp3DatagramContextIdRegistration( @@ -1460,12 +1589,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, @@ -1496,7 +1626,6 @@ void QuicSpdyStream::OnDatagramReceived(QuicDataReader* reader) { absl::optional<QuicDatagramContextId> context_id; const bool context_id_present = !datagram_context_visitors_.empty(); - Http3DatagramVisitor* visitor; if (context_id_present) { QuicDatagramContextId parsed_context_id; if (!reader->ReadVarInt62(&parsed_context_id)) { @@ -1506,28 +1635,9 @@ return; } context_id = parsed_context_id; - auto it = datagram_context_visitors_.find(parsed_context_id); - if (it == datagram_context_visitors_.end()) { - // TODO(b/181256914) buffer unknown HTTP/3 datagrams for a short - // period of time in case they were reordered. - QUIC_DLOG(ERROR) << "Received unknown HTTP/3 datagram context ID " - << parsed_context_id << " on stream ID " << id(); - return; - } - visitor = it->second; - } else { - if (datagram_no_context_visitor_ == nullptr) { - // TODO(b/181256914) buffer unknown HTTP/3 datagrams for a short - // period of time in case they were reordered. - QUIC_DLOG(ERROR) - << "Received HTTP/3 datagram without any registrations on stream ID " - << id(); - return; - } - visitor = datagram_no_context_visitor_; } absl::string_view payload = reader->ReadRemainingPayload(); - visitor->OnHttp3Datagram(id(), context_id, payload); + HandleReceivedDatagram(context_id, payload); } void QuicSpdyStream::RegisterHttp3DatagramFlowId(QuicDatagramStreamId flow_id) { @@ -1535,5 +1645,29 @@ spdy_session_->RegisterHttp3DatagramFlowId(datagram_flow_id_.value(), id()); } +void QuicSpdyStream::HandleBodyAvailable() { + if (!capsule_parser_) { + OnBodyAvailable(); + return; + } + while (body_manager_.HasBytesToRead()) { + iovec iov; + int num_iov = GetReadableRegions(&iov, /*iov_len=*/1); + if (num_iov == 0) { + break; + } + if (!capsule_parser_->IngestCapsuleFragment(absl::string_view( + reinterpret_cast<const char*>(iov.iov_base), iov.iov_len))) { + break; + } + MarkConsumed(iov.iov_len); + } + // If we received a FIN, make sure that there isn't a partial capsule buffered + // in the capsule parser. + if (sequencer()->IsClosed()) { + capsule_parser_->ErrorIfThereIsRemainingBufferedData(); + } +} + #undef ENDPOINT // undef for jumbo builds } // namespace quic
diff --git a/quic/core/http/quic_spdy_stream.h b/quic/core/http/quic_spdy_stream.h index 65b3e5e..21dab65 100644 --- a/quic/core/http/quic_spdy_stream.h +++ b/quic/core/http/quic_spdy_stream.h
@@ -19,6 +19,7 @@ #include "absl/base/attributes.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" +#include "quic/core/http/capsule.h" #include "quic/core/http/http_decoder.h" #include "quic/core/http/http_encoder.h" #include "quic/core/http/quic_header_list.h" @@ -47,11 +48,10 @@ class QuicSpdySession; class WebTransportHttp3; -class QUIC_EXPORT_PRIVATE Http3DatagramContextExtensions {}; - // A QUIC stream that can send and receive HTTP2 (SPDY) headers. class QUIC_EXPORT_PRIVATE QuicSpdyStream : public QuicStream, + public CapsuleParser::Visitor, public QpackDecodedHeadersAccumulator::Visitor { public: // Visitor receives callbacks from the stream. @@ -72,8 +72,7 @@ virtual ~Visitor() {} }; - QuicSpdyStream(QuicStreamId id, - QuicSpdySession* spdy_session, + QuicSpdyStream(QuicStreamId id, QuicSpdySession* spdy_session, StreamType type); QuicSpdyStream(PendingStream* pending, QuicSpdySession* spdy_session); QuicSpdyStream(const QuicSpdyStream&) = delete; @@ -94,14 +93,12 @@ // Called by the session when decompressed headers have been completely // delivered to this stream. If |fin| is true, then this stream // should be closed; no more data will be sent by the peer. - virtual void OnStreamHeaderList(bool fin, - size_t frame_len, + virtual void OnStreamHeaderList(bool fin, size_t frame_len, const QuicHeaderList& header_list); // Called by the session when decompressed push promise headers have // been completely delivered to this stream. - virtual void OnPromiseHeaderList(QuicStreamId promised_id, - size_t frame_len, + virtual void OnPromiseHeaderList(QuicStreamId promised_id, size_t frame_len, const QuicHeaderList& header_list); // Called by the session when a PRIORITY frame has been been received for this @@ -126,8 +123,7 @@ // number of bytes sent, including data sent on the encoder stream when using // QPACK. virtual size_t WriteHeaders( - spdy::SpdyHeaderBlock header_block, - bool fin, + spdy::SpdyHeaderBlock header_block, bool fin, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener); // Sends |data| to the peer, or buffers if it can't be sent immediately. @@ -142,10 +138,8 @@ QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener); // Override to report newly acked bytes via ack_listener_. - bool OnStreamFrameAcked(QuicStreamOffset offset, - QuicByteCount data_length, - bool fin_acked, - QuicTime::Delta ack_delay_time, + bool OnStreamFrameAcked(QuicStreamOffset offset, QuicByteCount data_length, + bool fin_acked, QuicTime::Delta ack_delay_time, QuicTime receive_timestamp, QuicByteCount* newly_acked_length) override; @@ -252,6 +246,10 @@ // rejected due to buffer being full. |write_size| must be non-zero. bool CanWriteNewBodyData(QuicByteCount write_size) const; + // From CapsuleParser::Visitor. + bool OnCapsule(const Capsule& capsule) override; + void OnCapsuleParseFailure(const std::string& error_message) override; + // Sends an HTTP/3 datagram. The stream and context IDs are not part of // |payload|. MessageStatus SendHttp3Datagram( @@ -281,7 +279,8 @@ virtual void OnContextReceived( QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions) = 0; + DatagramFormatType format_type, + absl::string_view format_additional_data) = 0; // Called when a CLOSE_DATAGRAM_CONTEXT capsule is received. Note that this // contains the stream ID even if flow IDs from @@ -289,7 +288,7 @@ virtual void OnContextClosed( QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions) = 0; + ContextCloseCode close_code, absl::string_view close_details) = 0; }; // Registers |visitor| to receive HTTP/3 datagram context registrations. This @@ -315,7 +314,7 @@ // present, or always absent. void RegisterHttp3DatagramContextId( absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions, + DatagramFormatType format_type, absl::string_view format_additional_data, Http3DatagramVisitor* visitor); // Unregisters an HTTP/3 datagram context ID. Must be called on a previously @@ -345,15 +344,12 @@ // reset the stream. virtual void OnHeadersTooLarge(); - virtual void OnInitialHeadersComplete(bool fin, - size_t frame_len, + virtual void OnInitialHeadersComplete(bool fin, size_t frame_len, const QuicHeaderList& header_list); - virtual void OnTrailingHeadersComplete(bool fin, - size_t frame_len, + virtual void OnTrailingHeadersComplete(bool fin, size_t frame_len, const QuicHeaderList& header_list); virtual size_t WriteHeadersImpl( - spdy::SpdyHeaderBlock header_block, - bool fin, + spdy::SpdyHeaderBlock header_block, bool fin, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener); Visitor* visitor() { return visitor_; } @@ -392,8 +388,7 @@ bool OnHeadersFrameEnd(); void OnWebTransportStreamFrameType(QuicByteCount header_length, WebTransportSessionId session_id); - bool OnUnknownFrameStart(uint64_t frame_type, - QuicByteCount header_length, + bool OnUnknownFrameStart(uint64_t frame_type, QuicByteCount header_length, QuicByteCount payload_length); bool OnUnknownFramePayload(absl::string_view payload); bool OnUnknownFrameEnd(); @@ -411,6 +406,17 @@ ABSL_MUST_USE_RESULT bool WriteDataFrameHeader(QuicByteCount data_length, bool force_write); + // Simply calls OnBodyAvailable() unless capsules are in use, in which case + // pass the capsule fragments to the capsule manager. + void HandleBodyAvailable(); + + // Called when a datagram frame or capsule is received. + void HandleReceivedDatagram(absl::optional<QuicDatagramContextId> context_id, + absl::string_view payload); + + // Writes |capsule| onto the DATA stream. + void WriteCapsule(const Capsule& capsule); + QuicSpdySession* spdy_session_; bool on_body_available_called_because_sequencer_is_closed_; @@ -451,6 +457,8 @@ // the sequencer each time new stream data is processed. QuicSpdyStreamBodyManager body_manager_; + std::unique_ptr<CapsuleParser> capsule_parser_; + // Sequencer offset keeping track of how much data HttpDecoder has processed. // Initial value is zero for fresh streams, or sequencer()->NumBytesConsumed() // at time of construction if a PendingStream is converted to account for the
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc index 63a77ce..594b92c 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" @@ -256,16 +257,16 @@ bool ShouldNegotiateWebTransport() override { return enable_webtransport_; } void EnableWebTransport() { enable_webtransport_ = true; } - bool ShouldNegotiateHttp3Datagram() override { - return should_negotiate_h3_datagram_; + HttpDatagramSupport LocalHttpDatagramSupport() override { + return local_http_datagram_support_; } - void set_should_negotiate_h3_datagram(bool value) { - should_negotiate_h3_datagram_ = value; + void set_local_http_datagram_support(HttpDatagramSupport value) { + local_http_datagram_support_ = value; } private: bool enable_webtransport_ = false; - bool should_negotiate_h3_datagram_ = false; + HttpDatagramSupport local_http_datagram_support_ = HttpDatagramSupport::kNone; StrictMock<TestCryptoStream> crypto_stream_; }; @@ -3029,15 +3030,17 @@ } } -TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeaders) { +TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeadersDatagramDraft00) { if (!UsesHttp3()) { return; } InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT); - session_->set_should_negotiate_h3_datagram(true); + session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); 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(), _, _, _, _, _)) @@ -3052,15 +3055,68 @@ EXPECT_EQ(stream_->id(), stream_->web_transport()->id()); } -TEST_P(QuicSpdyStreamTest, ProcessIncomingWebTransportHeaders) { +TEST_P(QuicSpdyStreamTest, ProcessOutgoingWebTransportHeadersDatagramDraft04) { + if (!UsesHttp3()) { + return; + } + + InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT); + session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); + session_->EnableWebTransport(); + QuicSpdySessionPeer::EnableWebTransport(session_.get()); + QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(), + HttpDatagramSupport::kDraft04); + + 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, ProcessIncomingWebTransportHeadersDatagramDraft04) { if (!UsesHttp3()) { return; } Initialize(kShouldProcessData); - session_->set_should_negotiate_h3_datagram(true); + session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); session_->EnableWebTransport(); - QuicSpdySessionPeer::EnableWebTransport(*session_); + QuicSpdySessionPeer::EnableWebTransport(session_.get()); + QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(), + HttpDatagramSupport::kDraft04); + + headers_[":method"] = "CONNECT"; + headers_[":protocol"] = "webtransport"; + + stream_->OnStreamHeadersPriority( + spdy::SpdyStreamPrecedence(kV3HighestPriority)); + ProcessHeaders(false, headers_); + stream_->OnCapsule( + Capsule::RegisterDatagramNoContext(DatagramFormatType::WEBTRANSPORT)); + 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, ProcessIncomingWebTransportHeadersDatagramDraft00) { + if (!UsesHttp3()) { + return; + } + + Initialize(kShouldProcessData); + session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); + session_->EnableWebTransport(); + QuicSpdySessionPeer::EnableWebTransport(session_.get()); + QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(), + HttpDatagramSupport::kDraft00); headers_[":method"] = "CONNECT"; headers_[":protocol"] = "webtransport"; @@ -3085,9 +3141,11 @@ // draft-ietf-masque-h3-datagram-00 in favor of later drafts. Initialize(kShouldProcessData); - session_->set_should_negotiate_h3_datagram(true); + session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); session_->EnableWebTransport(); - QuicSpdySessionPeer::EnableWebTransport(*session_); + QuicSpdySessionPeer::EnableWebTransport(session_.get()); + QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(), + HttpDatagramSupport::kDraft00); headers_[":method"] = "CONNECT"; headers_[":protocol"] = "webtransport"; @@ -3131,19 +3189,20 @@ stream_->UnregisterHttp3DatagramRegistrationVisitor(); } -TEST_P(QuicSpdyStreamTest, H3DatagramRegistrationWithoutContext) { +TEST_P(QuicSpdyStreamTest, HttpDatagramRegistrationWithoutContextDraft00) { if (!UsesHttp3()) { return; } - Initialize(kShouldProcessData); - session_->set_should_negotiate_h3_datagram(true); - QuicSpdySessionPeer::SetH3DatagramSupported(session_.get(), true); + InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT); + session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); + QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(), + HttpDatagramSupport::kDraft00); session_->RegisterHttp3DatagramFlowId(stream_->id(), stream_->id()); ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor> h3_datagram_registration_visitor; SavingHttp3DatagramVisitor h3_datagram_visitor; absl::optional<QuicDatagramContextId> context_id; - Http3DatagramContextExtensions extensions; + absl::string_view format_additional_data; ASSERT_EQ(QuicDataWriter::GetVarInt62Len(stream_->id()), 1); std::array<char, 256> datagram; datagram[0] = stream_->id(); @@ -3152,8 +3211,9 @@ } stream_->RegisterHttp3DatagramRegistrationVisitor( &h3_datagram_registration_visitor); - stream_->RegisterHttp3DatagramContextId(context_id, extensions, - &h3_datagram_visitor); + stream_->RegisterHttp3DatagramContextId( + context_id, DatagramFormatType::UDP_PAYLOAD, format_additional_data, + &h3_datagram_visitor); session_->OnMessageReceived( absl::string_view(datagram.data(), datagram.size())); EXPECT_THAT(h3_datagram_visitor.received_h3_datagrams(), @@ -3180,23 +3240,41 @@ session_->UnregisterHttp3DatagramFlowId(stream_->id()); } -TEST_P(QuicSpdyStreamTest, H3DatagramRegistrationWithContext) { +TEST_P(QuicSpdyStreamTest, H3DatagramRegistrationWithoutContextDraft04) { if (!UsesHttp3()) { return; } - Initialize(kShouldProcessData); - session_->set_should_negotiate_h3_datagram(true); - QuicSpdySessionPeer::SetH3DatagramSupported(session_.get(), true); - session_->RegisterHttp3DatagramFlowId(stream_->id(), stream_->id()); + InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT); + session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft04); + QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(), + HttpDatagramSupport::kDraft04); ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor> h3_datagram_registration_visitor; SavingHttp3DatagramVisitor h3_datagram_visitor; - absl::optional<QuicDatagramContextId> context_id = 42; - Http3DatagramContextExtensions extensions; + absl::optional<QuicDatagramContextId> context_id; + absl::string_view format_additional_data; + 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); - stream_->RegisterHttp3DatagramContextId(context_id, extensions, - &h3_datagram_visitor); + + // Expect us to send a REGISTER_DATAGRAM_NO_CONTEXT capsule. + EXPECT_CALL(*session_, WritevData(stream_->id(), _, _, _, _, _)) + .Times(AtLeast(1)); + + stream_->RegisterHttp3DatagramContextId( + context_id, DatagramFormatType::UDP_PAYLOAD, format_additional_data, + &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; @@ -3204,9 +3282,75 @@ 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_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); + QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(), + HttpDatagramSupport::kDraft04); + ::testing::NiceMock<MockHttp3DatagramRegistrationVisitor> + h3_datagram_registration_visitor; + SavingHttp3DatagramVisitor h3_datagram_visitor; + absl::optional<QuicDatagramContextId> context_id = 42; + absl::string_view format_additional_data; + 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(), _, _, _, _, _)) + .Times(AtLeast(1)); + + stream_->RegisterHttp3DatagramContextId( + context_id, DatagramFormatType::UDP_PAYLOAD, format_additional_data, + &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(), _, _, _, _, _)) + .Times(AtLeast(1)); + stream_->UnregisterHttp3DatagramContextId(context_id); + stream_->UnregisterHttp3DatagramRegistrationVisitor(); session_->UnregisterHttp3DatagramFlowId(stream_->id()); } @@ -3215,8 +3359,9 @@ return; } Initialize(kShouldProcessData); - session_->set_should_negotiate_h3_datagram(true); - QuicSpdySessionPeer::SetH3DatagramSupported(session_.get(), true); + session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft00And04); + QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(), + HttpDatagramSupport::kDraft04); 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/http/web_transport_http3.cc b/quic/core/http/web_transport_http3.cc index b9b511c..9f5dd32 100644 --- a/quic/core/http/web_transport_http3.cc +++ b/quic/core/http/web_transport_http3.cc
@@ -9,6 +9,7 @@ #include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "quic/core/http/capsule.h" #include "quic/core/http/quic_spdy_session.h" #include "quic/core/http/quic_spdy_stream.h" #include "quic/core/quic_data_reader.h" @@ -201,13 +202,27 @@ void WebTransportHttp3::OnContextReceived( QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& /*extensions*/) { + DatagramFormatType format_type, absl::string_view format_additional_data) { if (stream_id != connect_stream_->id()) { QUIC_BUG(WT3 bad datagram context registration) << ENDPOINT << "Registered stream ID " << stream_id << ", expected " << connect_stream_->id(); return; } + if (format_type != DatagramFormatType::WEBTRANSPORT) { + QUIC_DLOG(INFO) << ENDPOINT << "Ignoring unexpected datagram format type " + << DatagramFormatTypeToString(format_type); + return; + } + if (!format_additional_data.empty()) { + QUIC_DLOG(ERROR) + << ENDPOINT + << "Received non-empty format additional data for context ID " + << (context_id_.has_value() ? context_id_.value() : 0) + << " on stream ID " << connect_stream_->id(); + session_->ResetStream(connect_stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD); + return; + } if (!context_is_known_) { context_is_known_ = true; context_id_ = context_id; @@ -229,15 +244,14 @@ return; } context_currently_registered_ = true; - Http3DatagramContextExtensions reply_extensions; - connect_stream_->RegisterHttp3DatagramContextId(context_id_, - reply_extensions, this); + connect_stream_->RegisterHttp3DatagramContextId( + context_id_, format_type, format_additional_data, this); } } void WebTransportHttp3::OnContextClosed( QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& /*extensions*/) { + ContextCloseCode close_code, absl::string_view close_details) { if (stream_id != connect_stream_->id()) { QUIC_BUG(WT3 bad datagram context registration) << ENDPOINT << "Closed context on stream ID " << stream_id @@ -252,9 +266,12 @@ << " on stream ID " << connect_stream_->id(); return; } - QUIC_DLOG(INFO) << ENDPOINT << "Received datagram context close on stream ID " - << connect_stream_->id() << ", resetting stream"; - session_->ResetStream(connect_stream_->id(), QUIC_STREAM_CANCELLED); + QUIC_DLOG(INFO) << ENDPOINT + << "Received datagram context close with close code " + << close_code << " close details \"" << close_details + << "\" on stream ID " << connect_stream_->id() + << ", resetting stream"; + session_->ResetStream(connect_stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD); } WebTransportHttp3UnidirectionalStream::WebTransportHttp3UnidirectionalStream(
diff --git a/quic/core/http/web_transport_http3.h b/quic/core/http/web_transport_http3.h index ea1897c..f2fdb5a 100644 --- a/quic/core/http/web_transport_http3.h +++ b/quic/core/http/web_transport_http3.h
@@ -76,12 +76,14 @@ absl::string_view payload) override; // From QuicSpdyStream::Http3DatagramRegistrationVisitor. - void OnContextReceived( - QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions) override; - void OnContextClosed( - QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions) override; + void OnContextReceived(QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + DatagramFormatType format_type, + absl::string_view format_additional_data) override; + void OnContextClosed(QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + ContextCloseCode close_code, + absl::string_view close_details) override; private: QuicSpdySession* const session_; // Unowned.
diff --git a/quic/core/quic_constants.h b/quic/core/quic_constants.h index 81c60e1..c53459c 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..1926968 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" @@ -11,20 +13,13 @@ namespace quic { MasqueClientSession::MasqueClientSession( - MasqueMode masque_mode, - const QuicConfig& config, + MasqueMode masque_mode, const QuicConfig& config, const ParsedQuicVersionVector& supported_versions, - QuicConnection* connection, - const QuicServerId& server_id, + QuicConnection* connection, const QuicServerId& server_id, QuicCryptoClientConfig* crypto_config, - QuicClientPushPromiseIndex* push_promise_index, - Owner* owner) - : QuicSpdyClientSession(config, - supported_versions, - connection, - server_id, - crypto_config, - push_promise_index), + QuicClientPushPromiseIndex* push_promise_index, Owner* owner) + : QuicSpdyClientSession(config, supported_versions, connection, server_id, + crypto_config, push_promise_index), masque_mode_(masque_mode), owner_(owner), compression_engine_(this) {} @@ -101,7 +96,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) { @@ -118,8 +115,7 @@ void MasqueClientSession::SendPacket( QuicConnectionId client_connection_id, - QuicConnectionId server_connection_id, - absl::string_view packet, + QuicConnectionId server_connection_id, absl::string_view packet, const QuicSocketAddress& target_server_address, EncapsulatedClientSession* encapsulated_client_session) { if (masque_mode_ == MasqueMode::kLegacy) { @@ -142,8 +138,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 +178,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()) { @@ -196,8 +192,7 @@ } void MasqueClientSession::OnConnectionClosed( - const QuicConnectionCloseFrame& frame, - ConnectionCloseSource source) { + const QuicConnectionCloseFrame& frame, ConnectionCloseSource source) { QuicSpdyClientSession::OnConnectionClosed(frame, source); // Close all encapsulated sessions. for (const auto& client_state : connect_udp_client_states_) { @@ -224,8 +219,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 +236,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 +245,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; } @@ -266,9 +263,9 @@ target_server_address_(target_server_address) { QUICHE_DCHECK_NE(masque_session_, nullptr); this->stream()->RegisterHttp3DatagramRegistrationVisitor(this); - Http3DatagramContextExtensions extensions; - this->stream()->RegisterHttp3DatagramContextId(this->context_id(), extensions, - this); + this->stream()->RegisterHttp3DatagramContextId( + this->context_id(), DatagramFormatType::UDP_PAYLOAD, + /*format_additional_data=*/absl::string_view(), this); } MasqueClientSession::ConnectUdpClientState::~ConnectUdpClientState() { @@ -308,24 +305,40 @@ 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( QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& /*extensions*/) { + DatagramFormatType format_type, absl::string_view format_additional_data) { if (stream_id != stream_->id()) { QUIC_BUG(MASQUE client bad datagram context registration) << "Registered stream ID " << stream_id << ", expected " << stream_->id(); return; } + if (format_type != DatagramFormatType::UDP_PAYLOAD) { + QUIC_DLOG(INFO) << "Ignoring unexpected datagram format type " + << DatagramFormatTypeToString(format_type); + return; + } + if (!format_additional_data.empty()) { + QUIC_DLOG(ERROR) + << "Received non-empty format additional data for context ID " + << (context_id_.has_value() ? context_id_.value() : 0) + << " on stream ID " << stream()->id(); + masque_session_->ResetStream(stream()->id(), QUIC_STREAM_CANCELLED); + 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 @@ -334,7 +347,7 @@ void MasqueClientSession::ConnectUdpClientState::OnContextClosed( QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& /*extensions*/) { + ContextCloseCode close_code, absl::string_view close_details) { if (stream_id != stream_->id()) { QUIC_BUG(MASQUE client bad datagram context registration) << "Closed context on stream ID " << stream_id << ", expected " @@ -342,15 +355,18 @@ 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 " - << stream_->id() << ", closing stream"; + QUIC_DLOG(INFO) << "Received datagram context close with close code " + << close_code << " close details \"" << close_details + << "\" on stream ID " << stream_->id() << ", closing stream"; masque_session_->ResetStream(stream_->id(), QUIC_STREAM_CANCELLED); }
diff --git a/quic/masque/masque_client_session.h b/quic/masque/masque_client_session.h index 1c13087..662783a 100644 --- a/quic/masque/masque_client_session.h +++ b/quic/masque/masque_client_session.h
@@ -144,14 +144,14 @@ absl::string_view payload) override; // From QuicSpdyStream::Http3DatagramRegistrationVisitor. - void OnContextReceived( - QuicStreamId stream_id, - absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions) override; - void OnContextClosed( - QuicStreamId stream_id, - absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions) override; + void OnContextReceived(QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + DatagramFormatType format_type, + absl::string_view format_additional_data) override; + void OnContextClosed(QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + ContextCloseCode close_code, + absl::string_view close_details) override; private: QuicSpdyClientStream* stream_; // Unowned. @@ -161,7 +161,9 @@ QuicSocketAddress target_server_address_; }; - bool ShouldNegotiateHttp3Datagram() override { return true; } + HttpDatagramSupport LocalHttpDatagramSupport() override { + return HttpDatagramSupport::kDraft00And04; + } const ConnectUdpClientState* GetOrCreateConnectUdpClientState( const QuicSocketAddress& target_server_address,
diff --git a/quic/masque/masque_epoll_client.cc b/quic/masque/masque_epoll_client.cc index ec47520..2b835cf 100644 --- a/quic/masque/masque_epoll_client.cc +++ b/quic/masque/masque_epoll_client.cc
@@ -68,7 +68,7 @@ return nullptr; } - masque_client->set_initial_max_packet_length(kDefaultMaxPacketSize); + masque_client->set_initial_max_packet_length(kMasqueMaxOuterPacketSize); masque_client->set_drop_response_body(false); if (!masque_client->Initialize()) { QUIC_LOG(ERROR) << "Failed to initialize masque_client";
diff --git a/quic/masque/masque_server_session.cc b/quic/masque/masque_server_session.cc index 575ac9f..693f009 100644 --- a/quic/masque/masque_server_session.cc +++ b/quic/masque/masque_server_session.cc
@@ -201,13 +201,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() != "/") { @@ -234,7 +236,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 << "\""; @@ -263,20 +267,27 @@ << 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. + stream->RegisterHttp3DatagramContextId( + context_id, DatagramFormatType::UDP_PAYLOAD, + /*format_additional_data=*/absl::string_view(), + &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)); @@ -423,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, @@ -439,10 +463,10 @@ MasqueServerSession::ConnectUdpServerState::~ConnectUdpServerState() { if (stream() != nullptr) { - stream()->UnregisterHttp3DatagramRegistrationVisitor(); if (context_registered_) { stream()->UnregisterHttp3DatagramContextId(context_id()); } + stream()->UnregisterHttp3DatagramRegistrationVisitor(); } if (fd_ == kQuicInvalidSocketFd) { return; @@ -502,39 +526,55 @@ void MasqueServerSession::ConnectUdpServerState::OnContextReceived( QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& /*extensions*/) { + DatagramFormatType format_type, absl::string_view format_additional_data) { if (stream_id != stream()->id()) { QUIC_BUG(MASQUE server bad datagram context registration) << "Registered stream ID " << stream_id << ", expected " << stream()->id(); return; } + if (format_type != DatagramFormatType::UDP_PAYLOAD) { + QUIC_DLOG(INFO) << "Ignoring unexpected datagram format type " + << DatagramFormatTypeToString(format_type); + return; + } + if (!format_additional_data.empty()) { + QUIC_DLOG(ERROR) + << "Received non-empty format additional data for context ID " + << (context_id_.has_value() ? context_id_.value() : 0) + << " on stream ID " << stream()->id(); + masque_session_->ResetStream(stream()->id(), QUIC_STREAM_CANCELLED); + return; + } if (!context_received_) { context_received_ = true; 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; - Http3DatagramContextExtensions reply_extensions; - stream()->RegisterHttp3DatagramContextId(context_id_, reply_extensions, this); + stream()->RegisterHttp3DatagramContextId(context_id_, format_type, + format_additional_data, this); } void MasqueServerSession::ConnectUdpServerState::OnContextClosed( QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& /*extensions*/) { + ContextCloseCode close_code, absl::string_view close_details) { if (stream_id != stream()->id()) { QUIC_BUG(MASQUE server bad datagram context registration) << "Closed context on stream ID " << stream_id << ", expected " @@ -542,15 +582,18 @@ 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 " - << stream()->id() << ", closing stream"; + QUIC_DLOG(INFO) << "Received datagram context close with close code " + << close_code << " close details \"" << close_details + << "\" on stream ID " << stream()->id() << ", closing stream"; masque_session_->ResetStream(stream()->id(), QUIC_STREAM_CANCELLED); }
diff --git a/quic/masque/masque_server_session.h b/quic/masque/masque_server_session.h index 1bec14c..b3c327f 100644 --- a/quic/masque/masque_server_session.h +++ b/quic/masque/masque_server_session.h
@@ -117,14 +117,14 @@ absl::string_view payload) override; // From QuicSpdyStream::Http3DatagramRegistrationVisitor. - void OnContextReceived( - QuicStreamId stream_id, - absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions) override; - void OnContextClosed( - QuicStreamId stream_id, - absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions) override; + void OnContextReceived(QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + DatagramFormatType format_type, + absl::string_view format_additional_data) override; + void OnContextClosed(QuicStreamId stream_id, + absl::optional<QuicDatagramContextId> context_id, + ContextCloseCode close_code, + absl::string_view close_details) override; private: QuicSpdyStream* stream_; @@ -136,7 +136,11 @@ bool context_registered_ = false; }; - bool ShouldNegotiateHttp3Datagram() override { return true; } + // From QuicSpdySession. + bool OnSettingsFrame(const SettingsFrame& frame) override; + HttpDatagramSupport LocalHttpDatagramSupport() override { + return HttpDatagramSupport::kDraft00And04; + } MasqueServerBackend* masque_server_backend_; // Unowned. Visitor* owner_; // Unowned.
diff --git a/quic/masque/masque_utils.h b/quic/masque/masque_utils.h index 8113047..abaeda7 100644 --- a/quic/masque/masque_utils.h +++ b/quic/masque/masque_utils.h
@@ -18,7 +18,10 @@ QUIC_NO_EXPORT QuicConfig MasqueEncapsulatedConfig(); // Maximum packet size for encapsulated connections. -enum : QuicByteCount { kMasqueMaxEncapsulatedPacketSize = 1300 }; +enum : QuicByteCount { + kMasqueMaxEncapsulatedPacketSize = 1300, + kMasqueMaxOuterPacketSize = 1350, +}; // Mode that MASQUE is operating in. enum class MasqueMode : uint8_t {
diff --git a/quic/test_tools/quic_spdy_session_peer.cc b/quic/test_tools/quic_spdy_session_peer.cc index a27f073..f0f99ee 100644 --- a/quic/test_tools/quic_spdy_session_peer.cc +++ b/quic/test_tools/quic_spdy_session_peer.cc
@@ -40,18 +40,14 @@ } void QuicSpdySessionPeer::SetMaxInboundHeaderListSize( - QuicSpdySession* session, - size_t max_inbound_header_size) { + QuicSpdySession* session, size_t max_inbound_header_size) { session->set_max_inbound_header_list_size(max_inbound_header_size); } // static size_t QuicSpdySessionPeer::WriteHeadersOnHeadersStream( - QuicSpdySession* session, - QuicStreamId id, - spdy::SpdyHeaderBlock headers, - bool fin, - const spdy::SpdyStreamPrecedence& precedence, + QuicSpdySession* session, QuicStreamId id, spdy::SpdyHeaderBlock headers, + bool fin, const spdy::SpdyStreamPrecedence& precedence, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener) { return session->WriteHeadersOnHeadersStream( id, std::move(headers), fin, precedence, std::move(ack_listener)); @@ -100,22 +96,22 @@ } // 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 -bool QuicSpdySessionPeer::ShouldNegotiateHttp3Datagram( +HttpDatagramSupport QuicSpdySessionPeer::LocalHttpDatagramSupport( QuicSpdySession* session) { - return session->ShouldNegotiateHttp3Datagram(); + return session->LocalHttpDatagramSupport(); } // 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::kDraft04); + 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..ba28e67 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 { @@ -32,11 +32,8 @@ static void SetMaxInboundHeaderListSize(QuicSpdySession* session, size_t max_inbound_header_size); static size_t WriteHeadersOnHeadersStream( - QuicSpdySession* session, - QuicStreamId id, - spdy::SpdyHeaderBlock headers, - bool fin, - const spdy::SpdyStreamPrecedence& precedence, + QuicSpdySession* session, QuicStreamId id, spdy::SpdyHeaderBlock headers, + bool fin, const spdy::SpdyStreamPrecedence& precedence, QuicReferenceCountedPointer<QuicAckListenerInterface> ack_listener); // |session| can't be nullptr. static QuicStreamId GetNextOutgoingUnidirectionalStreamId( @@ -50,10 +47,10 @@ QuicSpdySession* session); static QpackReceiveStream* GetQpackEncoderReceiveStream( QuicSpdySession* session); - static void SetH3DatagramSupported(QuicSpdySession* session, - bool h3_datagram_supported); - static bool ShouldNegotiateHttp3Datagram(QuicSpdySession* session); - static void EnableWebTransport(QuicSpdySession& session); + static void SetHttpDatagramSupport(QuicSpdySession* session, + HttpDatagramSupport http_datagram_support); + static HttpDatagramSupport LocalHttpDatagramSupport(QuicSpdySession* session); + static void EnableWebTransport(QuicSpdySession* session); }; } // namespace test
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h index 0831b81..7053829 100644 --- a/quic/test_tools/quic_test_utils.h +++ b/quic/test_tools/quic_test_utils.h
@@ -1995,13 +1995,14 @@ MOCK_METHOD(void, OnContextReceived, (QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions), + DatagramFormatType format_type, + absl::string_view format_additional_data), (override)); MOCK_METHOD(void, OnContextClosed, (QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, - const Http3DatagramContextExtensions& extensions), + ContextCloseCode close_code, absl::string_view close_details), (override)); };
diff --git a/quic/tools/quic_simple_client_session.cc b/quic/tools/quic_simple_client_session.cc index 07c4628..47c521b 100644 --- a/quic/tools/quic_simple_client_session.cc +++ b/quic/tools/quic_simple_client_session.cc
@@ -54,8 +54,9 @@ return enable_web_transport_; } -bool QuicSimpleClientSession::ShouldNegotiateHttp3Datagram() { - return enable_web_transport_; +HttpDatagramSupport QuicSimpleClientSession::LocalHttpDatagramSupport() { + return enable_web_transport_ ? HttpDatagramSupport::kDraft04 + : HttpDatagramSupport::kNone; } } // namespace quic
diff --git a/quic/tools/quic_simple_client_session.h b/quic/tools/quic_simple_client_session.h index 124e56f..1a6e694 100644 --- a/quic/tools/quic_simple_client_session.h +++ b/quic/tools/quic_simple_client_session.h
@@ -30,7 +30,7 @@ std::unique_ptr<QuicSpdyClientStream> CreateClientStream() override; bool ShouldNegotiateWebTransport() override; - bool ShouldNegotiateHttp3Datagram() override; + HttpDatagramSupport LocalHttpDatagramSupport() override; private: const bool drop_response_body_;
diff --git a/quic/tools/quic_simple_server_session.h b/quic/tools/quic_simple_server_session.h index 2cc3dcf..9746f26 100644 --- a/quic/tools/quic_simple_server_session.h +++ b/quic/tools/quic_simple_server_session.h
@@ -96,9 +96,11 @@ bool ShouldNegotiateWebTransport() override { return quic_simple_server_backend_->SupportsWebTransport(); } - bool ShouldNegotiateHttp3Datagram() override { - return QuicServerSessionBase::ShouldNegotiateHttp3Datagram() || - ShouldNegotiateWebTransport(); + HttpDatagramSupport LocalHttpDatagramSupport() override { + if (ShouldNegotiateWebTransport()) { + return HttpDatagramSupport::kDraft00And04; + } + return QuicServerSessionBase::LocalHttpDatagramSupport(); } private: