| // 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 "quiche/common/capsule.h" |
| |
| #include <type_traits> |
| |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "quiche/common/platform/api/quiche_bug_tracker.h" |
| #include "quiche/common/platform/api/quiche_logging.h" |
| #include "quiche/common/quiche_data_reader.h" |
| #include "quiche/common/quiche_data_writer.h" |
| #include "quiche/common/quiche_ip_address.h" |
| #include "quiche/web_transport/web_transport.h" |
| |
| namespace quiche { |
| |
| std::string CapsuleTypeToString(CapsuleType capsule_type) { |
| switch (capsule_type) { |
| case CapsuleType::DATAGRAM: |
| return "DATAGRAM"; |
| case CapsuleType::LEGACY_DATAGRAM: |
| return "LEGACY_DATAGRAM"; |
| case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT: |
| return "LEGACY_DATAGRAM_WITHOUT_CONTEXT"; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| return "CLOSE_WEBTRANSPORT_SESSION"; |
| case CapsuleType::ADDRESS_REQUEST: |
| return "ADDRESS_REQUEST"; |
| case CapsuleType::ADDRESS_ASSIGN: |
| return "ADDRESS_ASSIGN"; |
| case CapsuleType::ROUTE_ADVERTISEMENT: |
| return "ROUTE_ADVERTISEMENT"; |
| } |
| 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; |
| } |
| |
| 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 inline capsule structs must have these properties"); |
| datagram_capsule_ = DatagramCapsule(); |
| break; |
| case CapsuleType::LEGACY_DATAGRAM: |
| static_assert( |
| std::is_standard_layout<LegacyDatagramCapsule>::value && |
| std::is_trivially_destructible<LegacyDatagramCapsule>::value, |
| "All inline capsule structs must have these properties"); |
| legacy_datagram_capsule_ = LegacyDatagramCapsule(); |
| break; |
| case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT: |
| static_assert( |
| std::is_standard_layout<LegacyDatagramWithoutContextCapsule>::value && |
| std::is_trivially_destructible< |
| LegacyDatagramWithoutContextCapsule>::value, |
| "All inline capsule structs must have these properties"); |
| legacy_datagram_without_context_capsule_ = |
| LegacyDatagramWithoutContextCapsule(); |
| break; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| static_assert( |
| std::is_standard_layout<CloseWebTransportSessionCapsule>::value && |
| std::is_trivially_destructible< |
| CloseWebTransportSessionCapsule>::value, |
| "All inline capsule structs must have these properties"); |
| close_web_transport_session_capsule_ = CloseWebTransportSessionCapsule(); |
| break; |
| case CapsuleType::ADDRESS_REQUEST: |
| address_request_capsule_ = new AddressRequestCapsule(); |
| break; |
| case CapsuleType::ADDRESS_ASSIGN: |
| address_assign_capsule_ = new AddressAssignCapsule(); |
| break; |
| case CapsuleType::ROUTE_ADVERTISEMENT: |
| route_advertisement_capsule_ = new RouteAdvertisementCapsule(); |
| break; |
| default: |
| unknown_capsule_data_ = absl::string_view(); |
| break; |
| } |
| } |
| |
| void Capsule::Free() { |
| switch (capsule_type_) { |
| // Inlined capsule types. |
| case CapsuleType::DATAGRAM: |
| case CapsuleType::LEGACY_DATAGRAM: |
| case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT: |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| // Do nothing, these are guaranteed to be trivially destructible. |
| break; |
| // Out-of-line capsule types. |
| case CapsuleType::ADDRESS_REQUEST: |
| delete address_request_capsule_; |
| break; |
| case CapsuleType::ADDRESS_ASSIGN: |
| delete address_assign_capsule_; |
| break; |
| case CapsuleType::ROUTE_ADVERTISEMENT: |
| delete route_advertisement_capsule_; |
| break; |
| } |
| capsule_type_ = static_cast<CapsuleType>(0x17); // Reserved unknown value. |
| unknown_capsule_data_ = absl::string_view(); |
| } |
| Capsule::~Capsule() { Free(); } |
| |
| // static |
| Capsule Capsule::Datagram(absl::string_view http_datagram_payload) { |
| Capsule capsule(CapsuleType::DATAGRAM); |
| capsule.datagram_capsule().http_datagram_payload = http_datagram_payload; |
| return capsule; |
| } |
| |
| // static |
| Capsule Capsule::LegacyDatagram(absl::string_view http_datagram_payload) { |
| Capsule capsule(CapsuleType::LEGACY_DATAGRAM); |
| capsule.legacy_datagram_capsule().http_datagram_payload = |
| http_datagram_payload; |
| return capsule; |
| } |
| |
| // static |
| Capsule Capsule::LegacyDatagramWithoutContext( |
| absl::string_view http_datagram_payload) { |
| Capsule capsule(CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT); |
| capsule.legacy_datagram_without_context_capsule().http_datagram_payload = |
| http_datagram_payload; |
| return capsule; |
| } |
| |
| // static |
| Capsule Capsule::CloseWebTransportSession( |
| webtransport::SessionErrorCode 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::AddressRequest() { |
| return Capsule(CapsuleType::ADDRESS_REQUEST); |
| } |
| |
| // static |
| Capsule Capsule::AddressAssign() { |
| return Capsule(CapsuleType::ADDRESS_ASSIGN); |
| } |
| |
| // static |
| Capsule Capsule::RouteAdvertisement() { |
| return Capsule(CapsuleType::ROUTE_ADVERTISEMENT); |
| } |
| |
| // 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) { |
| Free(); |
| capsule_type_ = other.capsule_type_; |
| switch (capsule_type_) { |
| case CapsuleType::DATAGRAM: |
| datagram_capsule_ = other.datagram_capsule_; |
| break; |
| case CapsuleType::LEGACY_DATAGRAM: |
| legacy_datagram_capsule_ = other.legacy_datagram_capsule_; |
| break; |
| case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT: |
| legacy_datagram_without_context_capsule_ = |
| other.legacy_datagram_without_context_capsule_; |
| break; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| close_web_transport_session_capsule_ = |
| other.close_web_transport_session_capsule_; |
| break; |
| case CapsuleType::ADDRESS_ASSIGN: |
| address_assign_capsule_ = new AddressAssignCapsule(); |
| *address_assign_capsule_ = *other.address_assign_capsule_; |
| break; |
| case CapsuleType::ADDRESS_REQUEST: |
| address_request_capsule_ = new AddressRequestCapsule(); |
| *address_request_capsule_ = *other.address_request_capsule_; |
| break; |
| case CapsuleType::ROUTE_ADVERTISEMENT: |
| route_advertisement_capsule_ = new RouteAdvertisementCapsule(); |
| *route_advertisement_capsule_ = *other.route_advertisement_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_.http_datagram_payload == |
| other.datagram_capsule_.http_datagram_payload; |
| case CapsuleType::LEGACY_DATAGRAM: |
| return legacy_datagram_capsule_.http_datagram_payload == |
| other.legacy_datagram_capsule_.http_datagram_payload; |
| case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT: |
| return legacy_datagram_without_context_capsule_.http_datagram_payload == |
| other.legacy_datagram_without_context_capsule_ |
| .http_datagram_payload; |
| 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; |
| case CapsuleType::ADDRESS_REQUEST: |
| return address_request_capsule_->requested_addresses == |
| other.address_request_capsule_->requested_addresses; |
| case CapsuleType::ADDRESS_ASSIGN: |
| return address_assign_capsule_->assigned_addresses == |
| other.address_assign_capsule_->assigned_addresses; |
| case CapsuleType::ROUTE_ADVERTISEMENT: |
| return route_advertisement_capsule_->ip_address_ranges == |
| other.route_advertisement_capsule_->ip_address_ranges; |
| default: |
| return unknown_capsule_data_ == other.unknown_capsule_data_; |
| } |
| } |
| |
| std::string DatagramCapsule::ToString() const { |
| return absl::StrCat("DATAGRAM[", |
| absl::BytesToHexString(http_datagram_payload), "]"); |
| } |
| |
| std::string LegacyDatagramCapsule::ToString() const { |
| return absl::StrCat("LEGACY_DATAGRAM[", |
| absl::BytesToHexString(http_datagram_payload), "]"); |
| } |
| |
| std::string LegacyDatagramWithoutContextCapsule::ToString() const { |
| return absl::StrCat("LEGACY_DATAGRAM_WITHOUT_CONTEXT[", |
| absl::BytesToHexString(http_datagram_payload), "]"); |
| } |
| |
| std::string CloseWebTransportSessionCapsule::ToString() const { |
| return absl::StrCat("CLOSE_WEBTRANSPORT_SESSION(error_code=", error_code, |
| ",error_message=\"", error_message, "\")"); |
| } |
| |
| std::string AddressRequestCapsule::ToString() const { |
| std::string rv = "ADDRESS_REQUEST["; |
| for (auto requested_address : requested_addresses) { |
| absl::StrAppend(&rv, "(", requested_address.request_id, "-", |
| requested_address.ip_prefix.ToString(), ")"); |
| } |
| absl::StrAppend(&rv, "]"); |
| return rv; |
| } |
| |
| std::string AddressAssignCapsule::ToString() const { |
| std::string rv = "ADDRESS_ASSIGN["; |
| for (auto assigned_address : assigned_addresses) { |
| absl::StrAppend(&rv, "(", assigned_address.request_id, "-", |
| assigned_address.ip_prefix.ToString(), ")"); |
| } |
| absl::StrAppend(&rv, "]"); |
| return rv; |
| } |
| |
| std::string RouteAdvertisementCapsule::ToString() const { |
| std::string rv = "ROUTE_ADVERTISEMENT["; |
| for (auto ip_address_range : ip_address_ranges) { |
| absl::StrAppend(&rv, "(", ip_address_range.start_ip_address.ToString(), "-", |
| ip_address_range.end_ip_address.ToString(), "-", |
| static_cast<int>(ip_address_range.ip_protocol), ")"); |
| } |
| absl::StrAppend(&rv, "]"); |
| return rv; |
| } |
| |
| std::string Capsule::ToString() const { |
| switch (capsule_type_) { |
| case CapsuleType::DATAGRAM: |
| return datagram_capsule_.ToString(); |
| case CapsuleType::LEGACY_DATAGRAM: |
| return legacy_datagram_capsule_.ToString(); |
| case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT: |
| return legacy_datagram_without_context_capsule_.ToString(); |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| return close_web_transport_session_capsule_.ToString(); |
| case CapsuleType::ADDRESS_REQUEST: |
| return address_request_capsule_->ToString(); |
| case CapsuleType::ADDRESS_ASSIGN: |
| return address_assign_capsule_->ToString(); |
| case CapsuleType::ROUTE_ADVERTISEMENT: |
| return route_advertisement_capsule_->ToString(); |
| default: |
| return absl::StrCat(CapsuleTypeToString(capsule_type_), "[", |
| absl::BytesToHexString(unknown_capsule_data_), "]"); |
| } |
| } |
| |
| 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) { |
| size_t capsule_type_length = QuicheDataWriter::GetVarInt62Len( |
| static_cast<uint64_t>(capsule.capsule_type())); |
| size_t capsule_data_length; |
| switch (capsule.capsule_type()) { |
| case CapsuleType::DATAGRAM: |
| capsule_data_length = |
| capsule.datagram_capsule().http_datagram_payload.length(); |
| break; |
| case CapsuleType::LEGACY_DATAGRAM: |
| capsule_data_length = |
| capsule.legacy_datagram_capsule().http_datagram_payload.length(); |
| break; |
| case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT: |
| capsule_data_length = capsule.legacy_datagram_without_context_capsule() |
| .http_datagram_payload.length(); |
| break; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| capsule_data_length = |
| sizeof(webtransport::SessionErrorCode) + |
| capsule.close_web_transport_session_capsule().error_message.size(); |
| break; |
| case CapsuleType::ADDRESS_REQUEST: |
| capsule_data_length = 0; |
| for (auto requested_address : |
| capsule.address_request_capsule().requested_addresses) { |
| capsule_data_length += |
| QuicheDataWriter::GetVarInt62Len(requested_address.request_id) + 1 + |
| (requested_address.ip_prefix.address().IsIPv4() |
| ? QuicheIpAddress::kIPv4AddressSize |
| : QuicheIpAddress::kIPv6AddressSize) + |
| 1; |
| } |
| break; |
| case CapsuleType::ADDRESS_ASSIGN: |
| capsule_data_length = 0; |
| for (auto assigned_address : |
| capsule.address_assign_capsule().assigned_addresses) { |
| capsule_data_length += |
| QuicheDataWriter::GetVarInt62Len(assigned_address.request_id) + 1 + |
| (assigned_address.ip_prefix.address().IsIPv4() |
| ? QuicheIpAddress::kIPv4AddressSize |
| : QuicheIpAddress::kIPv6AddressSize) + |
| 1; |
| } |
| break; |
| case CapsuleType::ROUTE_ADVERTISEMENT: |
| capsule_data_length = 0; |
| for (auto ip_address_range : |
| capsule.route_advertisement_capsule().ip_address_ranges) { |
| capsule_data_length += 1 + |
| (ip_address_range.start_ip_address.IsIPv4() |
| ? QuicheIpAddress::kIPv4AddressSize |
| : QuicheIpAddress::kIPv6AddressSize) * |
| 2 + |
| 1; |
| } |
| break; |
| default: |
| capsule_data_length = capsule.unknown_capsule_data().length(); |
| break; |
| } |
| size_t capsule_length_length = |
| QuicheDataWriter::GetVarInt62Len(capsule_data_length); |
| size_t total_capsule_length = |
| capsule_type_length + capsule_length_length + capsule_data_length; |
| quiche::QuicheBuffer buffer(allocator, total_capsule_length); |
| QuicheDataWriter writer(buffer.size(), buffer.data()); |
| if (!writer.WriteVarInt62(static_cast<uint64_t>(capsule.capsule_type()))) { |
| QUICHE_BUG(capsule type write fail) << "Failed to write CAPSULE type"; |
| return {}; |
| } |
| if (!writer.WriteVarInt62(capsule_data_length)) { |
| QUICHE_BUG(capsule length write fail) << "Failed to write CAPSULE length"; |
| return {}; |
| } |
| switch (capsule.capsule_type()) { |
| case CapsuleType::DATAGRAM: |
| if (!writer.WriteStringPiece( |
| capsule.datagram_capsule().http_datagram_payload)) { |
| QUICHE_BUG(datagram capsule payload write fail) |
| << "Failed to write DATAGRAM CAPSULE payload"; |
| return {}; |
| } |
| break; |
| case CapsuleType::LEGACY_DATAGRAM: |
| if (!writer.WriteStringPiece( |
| capsule.legacy_datagram_capsule().http_datagram_payload)) { |
| QUICHE_BUG(datagram legacy capsule payload write fail) |
| << "Failed to write LEGACY_DATAGRAM CAPSULE payload"; |
| return {}; |
| } |
| break; |
| case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT: |
| if (!writer.WriteStringPiece( |
| capsule.legacy_datagram_without_context_capsule() |
| .http_datagram_payload)) { |
| QUICHE_BUG(datagram legacy without context capsule payload write fail) |
| << "Failed to write LEGACY_DATAGRAM_WITHOUT_CONTEXT CAPSULE " |
| "payload"; |
| return {}; |
| } |
| break; |
| case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: |
| if (!writer.WriteUInt32( |
| capsule.close_web_transport_session_capsule().error_code)) { |
| QUICHE_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)) { |
| QUICHE_BUG(close webtransport session capsule error message write fail) |
| << "Failed to write CLOSE_WEBTRANSPORT_SESSION error message"; |
| return {}; |
| } |
| break; |
| case CapsuleType::ADDRESS_REQUEST: |
| for (auto requested_address : |
| capsule.address_request_capsule().requested_addresses) { |
| if (!writer.WriteVarInt62(requested_address.request_id)) { |
| QUICHE_BUG(address request capsule id write fail) |
| << "Failed to write ADDRESS_REQUEST ID"; |
| return {}; |
| } |
| if (!writer.WriteUInt8( |
| requested_address.ip_prefix.address().IsIPv4() ? 4 : 6)) { |
| QUICHE_BUG(address request capsule family write fail) |
| << "Failed to write ADDRESS_REQUEST family"; |
| return {}; |
| } |
| if (!writer.WriteStringPiece( |
| requested_address.ip_prefix.address().ToPackedString())) { |
| QUICHE_BUG(address request capsule address write fail) |
| << "Failed to write ADDRESS_REQUEST address"; |
| return {}; |
| } |
| if (!writer.WriteUInt8(requested_address.ip_prefix.prefix_length())) { |
| QUICHE_BUG(address request capsule prefix length write fail) |
| << "Failed to write ADDRESS_REQUEST prefix length"; |
| return {}; |
| } |
| } |
| break; |
| case CapsuleType::ADDRESS_ASSIGN: |
| for (auto assigned_address : |
| capsule.address_assign_capsule().assigned_addresses) { |
| if (!writer.WriteVarInt62(assigned_address.request_id)) { |
| QUICHE_BUG(address request capsule id write fail) |
| << "Failed to write ADDRESS_ASSIGN ID"; |
| return {}; |
| } |
| if (!writer.WriteUInt8( |
| assigned_address.ip_prefix.address().IsIPv4() ? 4 : 6)) { |
| QUICHE_BUG(address request capsule family write fail) |
| << "Failed to write ADDRESS_ASSIGN family"; |
| return {}; |
| } |
| if (!writer.WriteStringPiece( |
| assigned_address.ip_prefix.address().ToPackedString())) { |
| QUICHE_BUG(address request capsule address write fail) |
| << "Failed to write ADDRESS_ASSIGN address"; |
| return {}; |
| } |
| if (!writer.WriteUInt8(assigned_address.ip_prefix.prefix_length())) { |
| QUICHE_BUG(address request capsule prefix length write fail) |
| << "Failed to write ADDRESS_ASSIGN prefix length"; |
| return {}; |
| } |
| } |
| break; |
| case CapsuleType::ROUTE_ADVERTISEMENT: |
| for (auto ip_address_range : |
| capsule.route_advertisement_capsule().ip_address_ranges) { |
| if (!writer.WriteUInt8( |
| ip_address_range.start_ip_address.IsIPv4() ? 4 : 6)) { |
| QUICHE_BUG(route advertisement capsule family write fail) |
| << "Failed to write ROUTE_ADVERTISEMENT family"; |
| return {}; |
| } |
| if (!writer.WriteStringPiece( |
| ip_address_range.start_ip_address.ToPackedString())) { |
| QUICHE_BUG(route advertisement capsule start address write fail) |
| << "Failed to write ROUTE_ADVERTISEMENT start address"; |
| return {}; |
| } |
| if (!writer.WriteStringPiece( |
| ip_address_range.end_ip_address.ToPackedString())) { |
| QUICHE_BUG(route advertisement capsule end address write fail) |
| << "Failed to write ROUTE_ADVERTISEMENT end address"; |
| return {}; |
| } |
| if (!writer.WriteUInt8(ip_address_range.ip_protocol)) { |
| QUICHE_BUG(route advertisement capsule IP protocol write fail) |
| << "Failed to write ROUTE_ADVERTISEMENT IP protocol"; |
| return {}; |
| } |
| } |
| break; |
| default: |
| if (!writer.WriteStringPiece(capsule.unknown_capsule_data())) { |
| QUICHE_BUG(capsule data write fail) << "Failed to write CAPSULE data"; |
| return {}; |
| } |
| break; |
| } |
| if (writer.remaining() != 0) { |
| QUICHE_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; |
| } |
| QuicheDataReader capsule_fragment_reader(buffered_data_); |
| uint64_t capsule_type64; |
| if (!capsule_fragment_reader.ReadVarInt62(&capsule_type64)) { |
| QUICHE_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)) { |
| QUICHE_DVLOG(2) |
| << "Partial read: not enough data to read capsule length or " |
| "full capsule data"; |
| return 0; |
| } |
| QuicheDataReader capsule_data_reader(capsule_data); |
| Capsule capsule(static_cast<CapsuleType>(capsule_type64)); |
| switch (capsule.capsule_type()) { |
| case CapsuleType::DATAGRAM: |
| capsule.datagram_capsule().http_datagram_payload = |
| capsule_data_reader.ReadRemainingPayload(); |
| break; |
| case CapsuleType::LEGACY_DATAGRAM: |
| capsule.legacy_datagram_capsule().http_datagram_payload = |
| capsule_data_reader.ReadRemainingPayload(); |
| break; |
| case CapsuleType::LEGACY_DATAGRAM_WITHOUT_CONTEXT: |
| capsule.legacy_datagram_without_context_capsule().http_datagram_payload = |
| 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; |
| case CapsuleType::ADDRESS_REQUEST: { |
| while (!capsule_data_reader.IsDoneReading()) { |
| PrefixWithId requested_address; |
| if (!capsule_data_reader.ReadVarInt62(&requested_address.request_id)) { |
| ReportParseFailure( |
| "Unable to parse capsule ADDRESS_REQUEST request ID"); |
| return 0; |
| } |
| uint8_t address_family; |
| if (!capsule_data_reader.ReadUInt8(&address_family)) { |
| ReportParseFailure("Unable to parse capsule ADDRESS_REQUEST family"); |
| return 0; |
| } |
| if (address_family != 4 && address_family != 6) { |
| ReportParseFailure("Bad ADDRESS_REQUEST family"); |
| return 0; |
| } |
| absl::string_view ip_address_bytes; |
| if (!capsule_data_reader.ReadStringPiece( |
| &ip_address_bytes, address_family == 4 |
| ? QuicheIpAddress::kIPv4AddressSize |
| : QuicheIpAddress::kIPv6AddressSize)) { |
| ReportParseFailure("Unable to read capsule ADDRESS_REQUEST address"); |
| return 0; |
| } |
| quiche::QuicheIpAddress ip_address; |
| if (!ip_address.FromPackedString(ip_address_bytes.data(), |
| ip_address_bytes.size())) { |
| ReportParseFailure("Unable to parse capsule ADDRESS_REQUEST address"); |
| return 0; |
| } |
| uint8_t ip_prefix_length; |
| if (!capsule_data_reader.ReadUInt8(&ip_prefix_length)) { |
| ReportParseFailure( |
| "Unable to parse capsule ADDRESS_REQUEST IP prefix length"); |
| return 0; |
| } |
| if (ip_prefix_length > |
| quiche::QuicheIpPrefix(ip_address).prefix_length()) { |
| ReportParseFailure("Invalid IP prefix length"); |
| return 0; |
| } |
| requested_address.ip_prefix = |
| quiche::QuicheIpPrefix(ip_address, ip_prefix_length); |
| capsule.address_request_capsule().requested_addresses.push_back( |
| requested_address); |
| } |
| } break; |
| case CapsuleType::ADDRESS_ASSIGN: { |
| while (!capsule_data_reader.IsDoneReading()) { |
| PrefixWithId assigned_address; |
| if (!capsule_data_reader.ReadVarInt62(&assigned_address.request_id)) { |
| ReportParseFailure( |
| "Unable to parse capsule ADDRESS_ASSIGN request ID"); |
| return 0; |
| } |
| uint8_t address_family; |
| if (!capsule_data_reader.ReadUInt8(&address_family)) { |
| ReportParseFailure("Unable to parse capsule ADDRESS_ASSIGN family"); |
| return 0; |
| } |
| if (address_family != 4 && address_family != 6) { |
| ReportParseFailure("Bad ADDRESS_ASSIGN family"); |
| return 0; |
| } |
| absl::string_view ip_address_bytes; |
| if (!capsule_data_reader.ReadStringPiece( |
| &ip_address_bytes, address_family == 4 |
| ? QuicheIpAddress::kIPv4AddressSize |
| : QuicheIpAddress::kIPv6AddressSize)) { |
| ReportParseFailure("Unable to read capsule ADDRESS_ASSIGN address"); |
| return 0; |
| } |
| quiche::QuicheIpAddress ip_address; |
| if (!ip_address.FromPackedString(ip_address_bytes.data(), |
| ip_address_bytes.size())) { |
| ReportParseFailure("Unable to parse capsule ADDRESS_ASSIGN address"); |
| return 0; |
| } |
| uint8_t ip_prefix_length; |
| if (!capsule_data_reader.ReadUInt8(&ip_prefix_length)) { |
| ReportParseFailure( |
| "Unable to parse capsule ADDRESS_ASSIGN IP prefix length"); |
| return 0; |
| } |
| if (ip_prefix_length > |
| quiche::QuicheIpPrefix(ip_address).prefix_length()) { |
| ReportParseFailure("Invalid IP prefix length"); |
| return 0; |
| } |
| assigned_address.ip_prefix = |
| quiche::QuicheIpPrefix(ip_address, ip_prefix_length); |
| capsule.address_assign_capsule().assigned_addresses.push_back( |
| assigned_address); |
| } |
| } break; |
| case CapsuleType::ROUTE_ADVERTISEMENT: { |
| while (!capsule_data_reader.IsDoneReading()) { |
| uint8_t address_family; |
| if (!capsule_data_reader.ReadUInt8(&address_family)) { |
| ReportParseFailure( |
| "Unable to parse capsule ROUTE_ADVERTISEMENT family"); |
| return 0; |
| } |
| if (address_family != 4 && address_family != 6) { |
| ReportParseFailure("Bad ROUTE_ADVERTISEMENT family"); |
| return 0; |
| } |
| IpAddressRange ip_address_range; |
| absl::string_view start_ip_address_bytes; |
| if (!capsule_data_reader.ReadStringPiece( |
| &start_ip_address_bytes, |
| address_family == 4 ? QuicheIpAddress::kIPv4AddressSize |
| : QuicheIpAddress::kIPv6AddressSize)) { |
| ReportParseFailure( |
| "Unable to read capsule ROUTE_ADVERTISEMENT start address"); |
| return 0; |
| } |
| if (!ip_address_range.start_ip_address.FromPackedString( |
| start_ip_address_bytes.data(), start_ip_address_bytes.size())) { |
| ReportParseFailure( |
| "Unable to parse capsule ROUTE_ADVERTISEMENT start address"); |
| return 0; |
| } |
| absl::string_view end_ip_address_bytes; |
| if (!capsule_data_reader.ReadStringPiece( |
| &end_ip_address_bytes, |
| address_family == 4 ? QuicheIpAddress::kIPv4AddressSize |
| : QuicheIpAddress::kIPv6AddressSize)) { |
| ReportParseFailure( |
| "Unable to read capsule ROUTE_ADVERTISEMENT end address"); |
| return 0; |
| } |
| if (!ip_address_range.end_ip_address.FromPackedString( |
| end_ip_address_bytes.data(), end_ip_address_bytes.size())) { |
| ReportParseFailure( |
| "Unable to parse capsule ROUTE_ADVERTISEMENT end address"); |
| return 0; |
| } |
| if (!capsule_data_reader.ReadUInt8(&ip_address_range.ip_protocol)) { |
| ReportParseFailure( |
| "Unable to parse capsule ROUTE_ADVERTISEMENT IP protocol"); |
| return 0; |
| } |
| capsule.route_advertisement_capsule().ip_address_ranges.push_back( |
| ip_address_range); |
| } |
| } 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_) { |
| QUICHE_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"); |
| } |
| } |
| |
| bool PrefixWithId::operator==(const PrefixWithId& other) const { |
| return request_id == other.request_id && ip_prefix == other.ip_prefix; |
| } |
| |
| bool IpAddressRange::operator==(const IpAddressRange& other) const { |
| return start_ip_address == other.start_ip_address && |
| end_ip_address == other.end_ip_address && |
| ip_protocol == other.ip_protocol; |
| } |
| |
| bool AddressAssignCapsule::operator==(const AddressAssignCapsule& other) const { |
| return assigned_addresses == other.assigned_addresses; |
| } |
| |
| bool AddressRequestCapsule::operator==( |
| const AddressRequestCapsule& other) const { |
| return requested_addresses == other.requested_addresses; |
| } |
| |
| bool RouteAdvertisementCapsule::operator==( |
| const RouteAdvertisementCapsule& other) const { |
| return ip_address_ranges == other.ip_address_ranges; |
| } |
| |
| } // namespace quiche |