| // 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/str_cat.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/core/quic_types.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::LEGACY_DATAGRAM: |
| return "LEGACY_DATAGRAM"; |
| case CapsuleType::DATAGRAM_WITH_CONTEXT: |
| return "DATAGRAM_WITH_CONTEXT"; |
| case CapsuleType::DATAGRAM_WITHOUT_CONTEXT: |
| return "DATAGRAM_WITHOUT_CONTEXT"; |
| case CapsuleType::REGISTER_DATAGRAM_NO_CONTEXT: |
| return "REGISTER_DATAGRAM_NO_CONTEXT"; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| return "CLOSE_WEBTRANSPORT_SESSION"; |
| } |
| 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::CLOSE_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::LEGACY_DATAGRAM: |
| static_assert( |
| std::is_standard_layout<LegacyDatagramCapsule>::value && |
| std::is_trivially_destructible<LegacyDatagramCapsule>::value, |
| "All capsule structs must have these properties"); |
| legacy_datagram_capsule_ = LegacyDatagramCapsule(); |
| break; |
| case CapsuleType::DATAGRAM_WITH_CONTEXT: |
| static_assert( |
| std::is_standard_layout<DatagramWithContextCapsule>::value && |
| std::is_trivially_destructible<DatagramWithContextCapsule>::value, |
| "All capsule structs must have these properties"); |
| datagram_with_context_capsule_ = DatagramWithContextCapsule(); |
| break; |
| case CapsuleType::DATAGRAM_WITHOUT_CONTEXT: |
| static_assert( |
| std::is_standard_layout<DatagramWithoutContextCapsule>::value && |
| std::is_trivially_destructible< |
| DatagramWithoutContextCapsule>::value, |
| "All capsule structs must have these properties"); |
| datagram_without_context_capsule_ = DatagramWithoutContextCapsule(); |
| 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; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| static_assert( |
| std::is_standard_layout<CloseWebTransportSessionCapsule>::value && |
| std::is_trivially_destructible< |
| CloseWebTransportSessionCapsule>::value, |
| "All capsule structs must have these properties"); |
| close_web_transport_session_capsule_ = CloseWebTransportSessionCapsule(); |
| break; |
| default: |
| unknown_capsule_data_ = absl::string_view(); |
| break; |
| } |
| } |
| |
| // static |
| Capsule Capsule::LegacyDatagram( |
| absl::optional<QuicDatagramContextId> context_id, |
| absl::string_view http_datagram_payload) { |
| Capsule capsule(CapsuleType::LEGACY_DATAGRAM); |
| capsule.legacy_datagram_capsule().context_id = context_id; |
| capsule.legacy_datagram_capsule().http_datagram_payload = |
| http_datagram_payload; |
| return capsule; |
| } |
| |
| // static |
| Capsule Capsule::DatagramWithContext(QuicDatagramContextId context_id, |
| absl::string_view http_datagram_payload) { |
| Capsule capsule(CapsuleType::DATAGRAM_WITH_CONTEXT); |
| capsule.datagram_with_context_capsule().context_id = context_id; |
| capsule.datagram_with_context_capsule().http_datagram_payload = |
| http_datagram_payload; |
| return capsule; |
| } |
| |
| // static |
| Capsule Capsule::DatagramWithoutContext( |
| absl::string_view http_datagram_payload) { |
| Capsule capsule(CapsuleType::DATAGRAM_WITHOUT_CONTEXT); |
| capsule.datagram_without_context_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::CloseWebTransportSession(WebTransportSessionError error_code, |
| absl::string_view error_message) { |
| Capsule capsule(CapsuleType::CLOSE_WEBTRANSPORT_SESSION); |
| capsule.close_web_transport_session_capsule().error_code = error_code; |
| capsule.close_web_transport_session_capsule().error_message = error_message; |
| 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::LEGACY_DATAGRAM: |
| legacy_datagram_capsule_ = other.legacy_datagram_capsule_; |
| break; |
| case CapsuleType::DATAGRAM_WITH_CONTEXT: |
| datagram_with_context_capsule_ = other.datagram_with_context_capsule_; |
| break; |
| case CapsuleType::DATAGRAM_WITHOUT_CONTEXT: |
| datagram_without_context_capsule_ = |
| other.datagram_without_context_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; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| close_web_transport_session_capsule_ = |
| other.close_web_transport_session_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::LEGACY_DATAGRAM: |
| return legacy_datagram_capsule_.context_id == |
| other.legacy_datagram_capsule_.context_id && |
| legacy_datagram_capsule_.http_datagram_payload == |
| other.legacy_datagram_capsule_.http_datagram_payload; |
| case CapsuleType::DATAGRAM_WITH_CONTEXT: |
| return datagram_with_context_capsule_.context_id == |
| other.datagram_with_context_capsule_.context_id && |
| datagram_with_context_capsule_.http_datagram_payload == |
| other.datagram_with_context_capsule_.http_datagram_payload; |
| case CapsuleType::DATAGRAM_WITHOUT_CONTEXT: |
| return datagram_without_context_capsule_.http_datagram_payload == |
| other.datagram_without_context_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; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| return close_web_transport_session_capsule_.error_code == |
| other.close_web_transport_session_capsule_.error_code && |
| close_web_transport_session_capsule_.error_message == |
| other.close_web_transport_session_capsule_.error_message; |
| 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::LEGACY_DATAGRAM: |
| if (legacy_datagram_capsule_.context_id.has_value()) { |
| absl::StrAppend(&rv, "(", legacy_datagram_capsule_.context_id.value(), |
| ")"); |
| } |
| absl::StrAppend(&rv, "[", |
| absl::BytesToHexString( |
| legacy_datagram_capsule_.http_datagram_payload), |
| "]"); |
| break; |
| case CapsuleType::DATAGRAM_WITH_CONTEXT: |
| absl::StrAppend(&rv, "(", datagram_with_context_capsule_.context_id, ")[", |
| absl::BytesToHexString( |
| datagram_with_context_capsule_.http_datagram_payload), |
| "]"); |
| break; |
| case CapsuleType::DATAGRAM_WITHOUT_CONTEXT: |
| absl::StrAppend( |
| &rv, "[", |
| absl::BytesToHexString( |
| datagram_without_context_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; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| absl::StrAppend( |
| &rv, "(error_code=", close_web_transport_session_capsule_.error_code, |
| ",error_message=\"", |
| close_web_transport_session_capsule_.error_message, "\")"); |
| 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); |
| } |
| |
| quiche::QuicheBuffer SerializeCapsule( |
| const Capsule& capsule, quiche::QuicheBufferAllocator* allocator) { |
| QuicByteCount capsule_type_length = QuicDataWriter::GetVarInt62Len( |
| static_cast<uint64_t>(capsule.capsule_type())); |
| QuicByteCount capsule_data_length; |
| switch (capsule.capsule_type()) { |
| case CapsuleType::LEGACY_DATAGRAM: |
| capsule_data_length = |
| capsule.legacy_datagram_capsule().http_datagram_payload.length(); |
| if (capsule.legacy_datagram_capsule().context_id.has_value()) { |
| capsule_data_length += QuicDataWriter::GetVarInt62Len( |
| capsule.legacy_datagram_capsule().context_id.value()); |
| } |
| break; |
| case CapsuleType::DATAGRAM_WITH_CONTEXT: |
| capsule_data_length = |
| QuicDataWriter::GetVarInt62Len( |
| capsule.datagram_with_context_capsule().context_id) + |
| capsule.datagram_with_context_capsule() |
| .http_datagram_payload.length(); |
| break; |
| case CapsuleType::DATAGRAM_WITHOUT_CONTEXT: |
| capsule_data_length = capsule.datagram_without_context_capsule() |
| .http_datagram_payload.length(); |
| 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; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| capsule_data_length = |
| sizeof(WebTransportSessionError) + |
| capsule.close_web_transport_session_capsule().error_message.size(); |
| 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; |
| quiche::QuicheBuffer 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 {}; |
| } |
| if (!writer.WriteVarInt62(capsule_data_length)) { |
| QUIC_BUG(capsule length write fail) << "Failed to write CAPSULE length"; |
| return {}; |
| } |
| switch (capsule.capsule_type()) { |
| case CapsuleType::LEGACY_DATAGRAM: |
| if (capsule.legacy_datagram_capsule().context_id.has_value()) { |
| if (!writer.WriteVarInt62( |
| capsule.legacy_datagram_capsule().context_id.value())) { |
| QUIC_BUG(datagram capsule context ID write fail) |
| << "Failed to write LEGACY_DATAGRAM CAPSULE context ID"; |
| return {}; |
| } |
| } |
| if (!writer.WriteStringPiece( |
| capsule.legacy_datagram_capsule().http_datagram_payload)) { |
| QUIC_BUG(datagram capsule payload write fail) |
| << "Failed to write LEGACY_DATAGRAM CAPSULE payload"; |
| return {}; |
| } |
| break; |
| case CapsuleType::DATAGRAM_WITH_CONTEXT: |
| if (!writer.WriteVarInt62( |
| capsule.datagram_with_context_capsule().context_id)) { |
| QUIC_BUG(datagram capsule context ID write fail) |
| << "Failed to write DATAGRAM_WITH_CONTEXT CAPSULE context ID"; |
| return {}; |
| } |
| if (!writer.WriteStringPiece( |
| capsule.datagram_with_context_capsule().http_datagram_payload)) { |
| QUIC_BUG(datagram capsule payload write fail) |
| << "Failed to write DATAGRAM_WITH_CONTEXT CAPSULE payload"; |
| return {}; |
| } |
| break; |
| case CapsuleType::DATAGRAM_WITHOUT_CONTEXT: |
| if (!writer.WriteStringPiece(capsule.datagram_without_context_capsule() |
| .http_datagram_payload)) { |
| QUIC_BUG(datagram capsule payload write fail) |
| << "Failed to write DATAGRAM_WITHOUT_CONTEXT CAPSULE payload"; |
| return {}; |
| } |
| 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 {}; |
| } |
| 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 {}; |
| } |
| 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 {}; |
| } |
| 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 {}; |
| } |
| 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 {}; |
| } |
| 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 {}; |
| } |
| 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 {}; |
| } |
| 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 {}; |
| } |
| break; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| if (!writer.WriteUInt32( |
| capsule.close_web_transport_session_capsule().error_code)) { |
| QUIC_BUG(close webtransport session capsule error code write fail) |
| << "Failed to write CLOSE_WEBTRANSPORT_SESSION error code"; |
| return {}; |
| } |
| if (!writer.WriteStringPiece( |
| capsule.close_web_transport_session_capsule().error_message)) { |
| QUIC_BUG(close webtransport session capsule error message write fail) |
| << "Failed to write CLOSE_WEBTRANSPORT_SESSION error message"; |
| return {}; |
| } |
| break; |
| default: |
| if (!writer.WriteStringPiece(capsule.unknown_capsule_data())) { |
| QUIC_BUG(capsule data write fail) << "Failed to write CAPSULE data"; |
| return {}; |
| } |
| break; |
| } |
| if (writer.remaining() != 0) { |
| QUIC_BUG(capsule write length mismatch) |
| << "CAPSULE serialization wrote " << writer.length() << " instead of " |
| << writer.capacity(); |
| return {}; |
| } |
| return buffer; |
| } |
| |
| bool CapsuleParser::IngestCapsuleFragment(absl::string_view capsule_fragment) { |
| if (parsing_error_occurred_) { |
| return false; |
| } |
| absl::StrAppend(&buffered_data_, 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); |
| } |
| static constexpr size_t kMaxCapsuleBufferSize = 1024 * 1024; |
| if (buffered_data_.size() > kMaxCapsuleBufferSize) { |
| buffered_data_.clear(); |
| ReportParseFailure("Refusing to buffer too much capsule data"); |
| return false; |
| } |
| 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::LEGACY_DATAGRAM: |
| if (datagram_context_id_present_) { |
| uint64_t context_id; |
| if (!capsule_data_reader.ReadVarInt62(&context_id)) { |
| ReportParseFailure( |
| "Unable to parse capsule LEGACY_DATAGRAM context ID"); |
| return 0; |
| } |
| capsule.legacy_datagram_capsule().context_id = context_id; |
| } |
| capsule.legacy_datagram_capsule().http_datagram_payload = |
| capsule_data_reader.ReadRemainingPayload(); |
| break; |
| case CapsuleType::DATAGRAM_WITH_CONTEXT: |
| uint64_t context_id; |
| if (!capsule_data_reader.ReadVarInt62(&context_id)) { |
| ReportParseFailure( |
| "Unable to parse capsule DATAGRAM_WITH_CONTEXT context ID"); |
| return 0; |
| } |
| capsule.datagram_with_context_capsule().context_id = context_id; |
| capsule.datagram_with_context_capsule().http_datagram_payload = |
| capsule_data_reader.ReadRemainingPayload(); |
| break; |
| case CapsuleType::DATAGRAM_WITHOUT_CONTEXT: |
| capsule.datagram_without_context_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; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| if (!capsule_data_reader.ReadUInt32( |
| &capsule.close_web_transport_session_capsule().error_code)) { |
| ReportParseFailure( |
| "Unable to parse capsule CLOSE_WEBTRANSPORT_SESSION error code"); |
| return 0; |
| } |
| capsule.close_web_transport_session_capsule().error_message = |
| 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 |