Add CONNECT-IP capsules
This CL introduces the capsules from draft-ietf-masque-connect-ip-03, along with tests. None of the new code is used yet.
PiperOrigin-RevId: 478570743
diff --git a/quiche/common/quiche_ip_address.cc b/quiche/common/quiche_ip_address.cc
index fd90deb..342597b 100644
--- a/quiche/common/quiche_ip_address.cc
+++ b/quiche/common/quiche_ip_address.cc
@@ -9,6 +9,7 @@
#include <cstring>
#include <string>
+#include "absl/strings/str_cat.h"
#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/quiche_ip_address_family.h"
@@ -223,4 +224,35 @@
return address_.v6;
}
+QuicheIpPrefix::QuicheIpPrefix() : prefix_length_(0) {}
+QuicheIpPrefix::QuicheIpPrefix(const QuicheIpAddress& address)
+ : address_(address) {
+ if (address_.IsIPv6()) {
+ prefix_length_ = QuicheIpAddress::kIPv6AddressSize * 8;
+ } else if (address_.IsIPv4()) {
+ prefix_length_ = QuicheIpAddress::kIPv4AddressSize * 8;
+ } else {
+ prefix_length_ = 0;
+ }
+}
+QuicheIpPrefix::QuicheIpPrefix(const QuicheIpAddress& address,
+ uint8_t prefix_length)
+ : address_(address), prefix_length_(prefix_length) {
+ QUICHE_DCHECK(prefix_length <= QuicheIpPrefix(address).prefix_length())
+ << "prefix_length cannot be longer than the size of the IP address";
+}
+
+std::string QuicheIpPrefix::ToString() const {
+ return absl::StrCat(address_.ToString(), "/", prefix_length_);
+}
+
+bool operator==(const QuicheIpPrefix& lhs, const QuicheIpPrefix& rhs) {
+ return lhs.address_ == rhs.address_ &&
+ lhs.prefix_length_ == rhs.prefix_length_;
+}
+
+bool operator!=(const QuicheIpPrefix& lhs, const QuicheIpPrefix& rhs) {
+ return !(lhs == rhs);
+}
+
} // namespace quiche
diff --git a/quiche/common/quiche_ip_address.h b/quiche/common/quiche_ip_address.h
index ef7714c..9267922 100644
--- a/quiche/common/quiche_ip_address.h
+++ b/quiche/common/quiche_ip_address.h
@@ -5,6 +5,7 @@
#ifndef QUICHE_COMMON_QUICHE_IP_ADDRESS_H_
#define QUICHE_COMMON_QUICHE_IP_ADDRESS_H_
+#include <cstdint>
#if defined(_WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
@@ -93,6 +94,37 @@
return os;
}
+// Represents an IP prefix, which is an IP address and a prefix length in bits.
+class QUICHE_EXPORT_PRIVATE QuicheIpPrefix {
+ public:
+ QuicheIpPrefix();
+ explicit QuicheIpPrefix(const QuicheIpAddress& address);
+ explicit QuicheIpPrefix(const QuicheIpAddress& address,
+ uint8_t prefix_length);
+
+ QuicheIpAddress address() const { return address_; }
+ uint8_t prefix_length() const { return prefix_length_; }
+ // Human-readable string representation of the prefix suitable for logging.
+ std::string ToString() const;
+
+ QuicheIpPrefix(const QuicheIpPrefix& other) = default;
+ QuicheIpPrefix& operator=(const QuicheIpPrefix& other) = default;
+ QuicheIpPrefix& operator=(QuicheIpPrefix&& other) = default;
+ QUICHE_EXPORT_PRIVATE friend bool operator==(const QuicheIpPrefix& lhs,
+ const QuicheIpPrefix& rhs);
+ QUICHE_EXPORT_PRIVATE friend bool operator!=(const QuicheIpPrefix& lhs,
+ const QuicheIpPrefix& rhs);
+
+ private:
+ QuicheIpAddress address_;
+ uint8_t prefix_length_;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const QuicheIpPrefix prefix) {
+ os << prefix.ToString();
+ return os;
+}
+
} // namespace quiche
#endif // QUICHE_COMMON_QUICHE_IP_ADDRESS_H_
diff --git a/quiche/quic/core/http/capsule.cc b/quiche/quic/core/http/capsule.cc
index 3fba85e..8f653f1 100644
--- a/quiche/quic/core/http/capsule.cc
+++ b/quiche/quic/core/http/capsule.cc
@@ -14,7 +14,9 @@
#include "quiche/quic/core/quic_data_writer.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_ip_address.h"
#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/quiche_ip_address.h"
namespace quic {
@@ -26,6 +28,12 @@
return "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), ")");
}
@@ -41,7 +49,7 @@
static_assert(
std::is_standard_layout<LegacyDatagramCapsule>::value &&
std::is_trivially_destructible<LegacyDatagramCapsule>::value,
- "All capsule structs must have these properties");
+ "All inline capsule structs must have these properties");
legacy_datagram_capsule_ = LegacyDatagramCapsule();
break;
case CapsuleType::DATAGRAM_WITHOUT_CONTEXT:
@@ -49,7 +57,7 @@
std::is_standard_layout<DatagramWithoutContextCapsule>::value &&
std::is_trivially_destructible<
DatagramWithoutContextCapsule>::value,
- "All capsule structs must have these properties");
+ "All inline capsule structs must have these properties");
datagram_without_context_capsule_ = DatagramWithoutContextCapsule();
break;
case CapsuleType::CLOSE_WEBTRANSPORT_SESSION:
@@ -57,18 +65,50 @@
std::is_standard_layout<CloseWebTransportSessionCapsule>::value &&
std::is_trivially_destructible<
CloseWebTransportSessionCapsule>::value,
- "All capsule structs must have these properties");
+ "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::LEGACY_DATAGRAM:
+ case CapsuleType::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::LegacyDatagram(
- absl::string_view http_datagram_payload) {
+Capsule Capsule::LegacyDatagram(absl::string_view http_datagram_payload) {
Capsule capsule(CapsuleType::LEGACY_DATAGRAM);
capsule.legacy_datagram_capsule().http_datagram_payload =
http_datagram_payload;
@@ -94,6 +134,21 @@
}
// 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));
@@ -102,6 +157,7 @@
}
Capsule& Capsule::operator=(const Capsule& other) {
+ Free();
capsule_type_ = other.capsule_type_;
switch (capsule_type_) {
case CapsuleType::LEGACY_DATAGRAM:
@@ -115,6 +171,18 @@
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;
@@ -142,6 +210,15 @@
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_;
}
@@ -169,6 +246,34 @@
",error_message=\"",
close_web_transport_session_capsule_.error_message, "\")");
break;
+ case CapsuleType::ADDRESS_REQUEST: {
+ absl::StrAppend(&rv, "[");
+ for (auto requested_address :
+ address_request_capsule_->requested_addresses) {
+ absl::StrAppend(&rv, "(", requested_address.request_id, "-",
+ requested_address.ip_prefix.ToString(), ")");
+ }
+ absl::StrAppend(&rv, "]");
+ } break;
+ case CapsuleType::ADDRESS_ASSIGN: {
+ absl::StrAppend(&rv, "[");
+ for (auto assigned_address :
+ address_assign_capsule_->assigned_addresses) {
+ absl::StrAppend(&rv, "(", assigned_address.request_id, "-",
+ assigned_address.ip_prefix.ToString(), ")");
+ }
+ absl::StrAppend(&rv, "]");
+ } break;
+ case CapsuleType::ROUTE_ADVERTISEMENT: {
+ absl::StrAppend(&rv, "[");
+ for (auto ip_address_range :
+ route_advertisement_capsule_->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, "]");
+ } break;
default:
absl::StrAppend(&rv, "[", absl::BytesToHexString(unknown_capsule_data_),
"]");
@@ -205,6 +310,42 @@
sizeof(WebTransportSessionError) +
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 +=
+ QuicDataWriter::GetVarInt62Len(requested_address.request_id) + 1 +
+ (requested_address.ip_prefix.address().IsIPv4()
+ ? QuicIpAddress::kIPv4AddressSize
+ : QuicIpAddress::kIPv6AddressSize) +
+ 1;
+ }
+ break;
+ case CapsuleType::ADDRESS_ASSIGN:
+ capsule_data_length = 0;
+ for (auto assigned_address :
+ capsule.address_assign_capsule().assigned_addresses) {
+ capsule_data_length +=
+ QuicDataWriter::GetVarInt62Len(assigned_address.request_id) + 1 +
+ (assigned_address.ip_prefix.address().IsIPv4()
+ ? QuicIpAddress::kIPv4AddressSize
+ : QuicIpAddress::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()
+ ? QuicIpAddress::kIPv4AddressSize
+ : QuicIpAddress::kIPv6AddressSize) *
+ 2 +
+ 1;
+ }
+ break;
default:
capsule_data_length = capsule.unknown_capsule_data().length();
break;
@@ -254,6 +395,88 @@
return {};
}
break;
+ case CapsuleType::ADDRESS_REQUEST:
+ for (auto requested_address :
+ capsule.address_request_capsule().requested_addresses) {
+ if (!writer.WriteVarInt62(requested_address.request_id)) {
+ QUIC_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)) {
+ QUIC_BUG(address request capsule family write fail)
+ << "Failed to write ADDRESS_REQUEST family";
+ return {};
+ }
+ if (!writer.WriteStringPiece(
+ requested_address.ip_prefix.address().ToPackedString())) {
+ QUIC_BUG(address request capsule address write fail)
+ << "Failed to write ADDRESS_REQUEST address";
+ return {};
+ }
+ if (!writer.WriteUInt8(requested_address.ip_prefix.prefix_length())) {
+ QUIC_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)) {
+ QUIC_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)) {
+ QUIC_BUG(address request capsule family write fail)
+ << "Failed to write ADDRESS_ASSIGN family";
+ return {};
+ }
+ if (!writer.WriteStringPiece(
+ assigned_address.ip_prefix.address().ToPackedString())) {
+ QUIC_BUG(address request capsule address write fail)
+ << "Failed to write ADDRESS_ASSIGN address";
+ return {};
+ }
+ if (!writer.WriteUInt8(assigned_address.ip_prefix.prefix_length())) {
+ QUIC_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)) {
+ QUIC_BUG(route advertisement capsule family write fail)
+ << "Failed to write ROUTE_ADVERTISEMENT family";
+ return {};
+ }
+ if (!writer.WriteStringPiece(
+ ip_address_range.start_ip_address.ToPackedString())) {
+ QUIC_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())) {
+ QUIC_BUG(route advertisement capsule end address write fail)
+ << "Failed to write ROUTE_ADVERTISEMENT end address";
+ return {};
+ }
+ if (!writer.WriteUInt8(ip_address_range.ip_protocol)) {
+ QUIC_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())) {
QUIC_BUG(capsule data write fail) << "Failed to write CAPSULE data";
@@ -334,6 +557,154 @@
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
+ ? QuicIpAddress::kIPv4AddressSize
+ : QuicIpAddress::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
+ ? QuicIpAddress::kIPv4AddressSize
+ : QuicIpAddress::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 ? QuicIpAddress::kIPv4AddressSize
+ : QuicIpAddress::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
+ ? QuicIpAddress::kIPv4AddressSize
+ : QuicIpAddress::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();
@@ -363,4 +734,28 @@
}
}
+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 quic
diff --git a/quiche/quic/core/http/capsule.h b/quiche/quic/core/http/capsule.h
index 7b4cc46..3d83a55 100644
--- a/quiche/quic/core/http/capsule.h
+++ b/quiche/quic/core/http/capsule.h
@@ -7,6 +7,7 @@
#include <cstdint>
#include <string>
+#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
@@ -15,15 +16,20 @@
#include "quiche/quic/core/quic_types.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/quiche_buffer_allocator.h"
+#include "quiche/common/quiche_ip_address.h"
namespace quic {
enum class CapsuleType : uint64_t {
- // Casing in this enum matches the IETF specification.
+ // Casing in this enum matches the IETF specifications.
LEGACY_DATAGRAM = 0xff37a0, // draft-ietf-masque-h3-datagram-04.
DATAGRAM_WITHOUT_CONTEXT =
0xff37a5, // draft-ietf-masque-h3-datagram-05 to -08.
CLOSE_WEBTRANSPORT_SESSION = 0x2843,
+ // draft-ietf-masque-connect-ip-03.
+ ADDRESS_ASSIGN = 0x1ECA6A00,
+ ADDRESS_REQUEST = 0x1ECA6A01,
+ ROUTE_ADVERTISEMENT = 0x1ECA6A02,
};
QUIC_EXPORT_PRIVATE std::string CapsuleTypeToString(CapsuleType capsule_type);
@@ -40,6 +46,29 @@
WebTransportSessionError error_code;
absl::string_view error_message;
};
+struct QUIC_EXPORT_PRIVATE PrefixWithId {
+ uint64_t request_id;
+ quiche::QuicheIpPrefix ip_prefix;
+ bool operator==(const PrefixWithId& other) const;
+};
+struct QUIC_EXPORT_PRIVATE IpAddressRange {
+ quiche::QuicheIpAddress start_ip_address;
+ quiche::QuicheIpAddress end_ip_address;
+ uint8_t ip_protocol;
+ bool operator==(const IpAddressRange& other) const;
+};
+struct QUIC_EXPORT_PRIVATE AddressAssignCapsule {
+ std::vector<PrefixWithId> assigned_addresses;
+ bool operator==(const AddressAssignCapsule& other) const;
+};
+struct QUIC_EXPORT_PRIVATE AddressRequestCapsule {
+ std::vector<PrefixWithId> requested_addresses;
+ bool operator==(const AddressRequestCapsule& other) const;
+};
+struct QUIC_EXPORT_PRIVATE RouteAdvertisementCapsule {
+ std::vector<IpAddressRange> ip_address_ranges;
+ bool operator==(const RouteAdvertisementCapsule& other) const;
+};
// Capsule from draft-ietf-masque-h3-datagram.
// IMPORTANT NOTE: Capsule does not own any of the absl::string_view memory it
@@ -55,11 +84,15 @@
static Capsule CloseWebTransportSession(
WebTransportSessionError error_code = 0,
absl::string_view error_message = "");
+ static Capsule AddressRequest();
+ static Capsule AddressAssign();
+ static Capsule RouteAdvertisement();
static Capsule Unknown(
uint64_t capsule_type,
absl::string_view unknown_capsule_data = absl::string_view());
explicit Capsule(CapsuleType capsule_type);
+ ~Capsule();
Capsule(const Capsule& other);
Capsule& operator=(const Capsule& other);
bool operator==(const Capsule& other) const;
@@ -96,27 +129,61 @@
QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::CLOSE_WEBTRANSPORT_SESSION);
return close_web_transport_session_capsule_;
}
+ AddressRequestCapsule& address_request_capsule() {
+ QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::ADDRESS_REQUEST);
+ return *address_request_capsule_;
+ }
+ const AddressRequestCapsule& address_request_capsule() const {
+ QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::ADDRESS_REQUEST);
+ return *address_request_capsule_;
+ }
+ AddressAssignCapsule& address_assign_capsule() {
+ QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::ADDRESS_ASSIGN);
+ return *address_assign_capsule_;
+ }
+ const AddressAssignCapsule& address_assign_capsule() const {
+ QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::ADDRESS_ASSIGN);
+ return *address_assign_capsule_;
+ }
+ RouteAdvertisementCapsule& route_advertisement_capsule() {
+ QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::ROUTE_ADVERTISEMENT);
+ return *route_advertisement_capsule_;
+ }
+ const RouteAdvertisementCapsule& route_advertisement_capsule() const {
+ QUICHE_DCHECK_EQ(capsule_type_, CapsuleType::ROUTE_ADVERTISEMENT);
+ return *route_advertisement_capsule_;
+ }
absl::string_view& unknown_capsule_data() {
QUICHE_DCHECK(capsule_type_ != CapsuleType::LEGACY_DATAGRAM &&
capsule_type_ != CapsuleType::DATAGRAM_WITHOUT_CONTEXT &&
- capsule_type_ != CapsuleType::CLOSE_WEBTRANSPORT_SESSION)
+ capsule_type_ != CapsuleType::CLOSE_WEBTRANSPORT_SESSION &&
+ capsule_type_ != CapsuleType::ADDRESS_REQUEST &&
+ capsule_type_ != CapsuleType::ADDRESS_ASSIGN &&
+ capsule_type_ != CapsuleType::ROUTE_ADVERTISEMENT)
<< capsule_type_;
return unknown_capsule_data_;
}
const absl::string_view& unknown_capsule_data() const {
QUICHE_DCHECK(capsule_type_ != CapsuleType::LEGACY_DATAGRAM &&
capsule_type_ != CapsuleType::DATAGRAM_WITHOUT_CONTEXT &&
- capsule_type_ != CapsuleType::CLOSE_WEBTRANSPORT_SESSION)
+ capsule_type_ != CapsuleType::CLOSE_WEBTRANSPORT_SESSION &&
+ capsule_type_ != CapsuleType::ADDRESS_REQUEST &&
+ capsule_type_ != CapsuleType::ADDRESS_ASSIGN &&
+ capsule_type_ != CapsuleType::ROUTE_ADVERTISEMENT)
<< capsule_type_;
return unknown_capsule_data_;
}
private:
+ void Free();
CapsuleType capsule_type_;
union {
LegacyDatagramCapsule legacy_datagram_capsule_;
DatagramWithoutContextCapsule datagram_without_context_capsule_;
CloseWebTransportSessionCapsule close_web_transport_session_capsule_;
+ AddressRequestCapsule* address_request_capsule_;
+ AddressAssignCapsule* address_assign_capsule_;
+ RouteAdvertisementCapsule* route_advertisement_capsule_;
absl::string_view unknown_capsule_data_;
};
};
diff --git a/quiche/quic/core/http/capsule_test.cc b/quiche/quic/core/http/capsule_test.cc
index 1791bb6..a686095 100644
--- a/quiche/quic/core/http/capsule_test.cc
+++ b/quiche/quic/core/http/capsule_test.cc
@@ -13,6 +13,7 @@
#include "absl/strings/string_view.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/common/quiche_ip_address.h"
#include "quiche/common/test_tools/quiche_test_utils.h"
using ::testing::_;
@@ -116,6 +117,123 @@
TestSerialization(expected_capsule, capsule_fragment);
}
+TEST_F(CapsuleTest, AddressAssignCapsule) {
+ std::string capsule_fragment = absl::HexStringToBytes(
+ "9ECA6A00" // ADDRESS_ASSIGN capsule type
+ "1A" // capsule length = 26
+ // first assigned address
+ "00" // request ID = 0
+ "04" // IP version = 4
+ "C000022A" // 192.0.2.42
+ "1F" // prefix length = 31
+ // second assigned address
+ "01" // request ID = 1
+ "06" // IP version = 6
+ "20010db8123456780000000000000000" // 2001:db8:1234:5678::
+ "40" // prefix length = 64
+ );
+ Capsule expected_capsule = Capsule::AddressAssign();
+ quiche::QuicheIpAddress ip_address1;
+ ip_address1.FromString("192.0.2.42");
+ PrefixWithId assigned_address1;
+ assigned_address1.request_id = 0;
+ assigned_address1.ip_prefix =
+ quiche::QuicheIpPrefix(ip_address1, /*prefix_length=*/31);
+ expected_capsule.address_assign_capsule().assigned_addresses.push_back(
+ assigned_address1);
+ quiche::QuicheIpAddress ip_address2;
+ ip_address2.FromString("2001:db8:1234:5678::");
+ PrefixWithId assigned_address2;
+ assigned_address2.request_id = 1;
+ assigned_address2.ip_prefix =
+ quiche::QuicheIpPrefix(ip_address2, /*prefix_length=*/64);
+ expected_capsule.address_assign_capsule().assigned_addresses.push_back(
+ assigned_address2);
+ {
+ EXPECT_CALL(visitor_, OnCapsule(expected_capsule));
+ ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment));
+ }
+ ValidateParserIsEmpty();
+ TestSerialization(expected_capsule, capsule_fragment);
+}
+
+TEST_F(CapsuleTest, AddressRequestCapsule) {
+ std::string capsule_fragment = absl::HexStringToBytes(
+ "9ECA6A01" // ADDRESS_REQUEST capsule type
+ "1A" // capsule length = 26
+ // first requested address
+ "00" // request ID = 0
+ "04" // IP version = 4
+ "C000022A" // 192.0.2.42
+ "1F" // prefix length = 31
+ // second requested address
+ "01" // request ID = 1
+ "06" // IP version = 6
+ "20010db8123456780000000000000000" // 2001:db8:1234:5678::
+ "40" // prefix length = 64
+ );
+ Capsule expected_capsule = Capsule::AddressRequest();
+ quiche::QuicheIpAddress ip_address1;
+ ip_address1.FromString("192.0.2.42");
+ PrefixWithId requested_address1;
+ requested_address1.request_id = 0;
+ requested_address1.ip_prefix =
+ quiche::QuicheIpPrefix(ip_address1, /*prefix_length=*/31);
+ expected_capsule.address_request_capsule().requested_addresses.push_back(
+ requested_address1);
+ quiche::QuicheIpAddress ip_address2;
+ ip_address2.FromString("2001:db8:1234:5678::");
+ PrefixWithId requested_address2;
+ requested_address2.request_id = 1;
+ requested_address2.ip_prefix =
+ quiche::QuicheIpPrefix(ip_address2, /*prefix_length=*/64);
+ expected_capsule.address_request_capsule().requested_addresses.push_back(
+ requested_address2);
+ {
+ EXPECT_CALL(visitor_, OnCapsule(expected_capsule));
+ ASSERT_TRUE(capsule_parser_.IngestCapsuleFragment(capsule_fragment));
+ }
+ ValidateParserIsEmpty();
+ TestSerialization(expected_capsule, capsule_fragment);
+}
+
+TEST_F(CapsuleTest, RouteAdvertisementCapsule) {
+ std::string capsule_fragment = absl::HexStringToBytes(
+ "9ECA6A02" // ROUTE_ADVERTISEMENT capsule type
+ "2C" // capsule length = 44
+ // first IP address range
+ "04" // IP version = 4
+ "C0000218" // 192.0.2.24
+ "C000022A" // 192.0.2.42
+ "00" // ip protocol = 0
+ // second IP address range
+ "06" // IP version = 6
+ "00000000000000000000000000000000" // ::
+ "ffffffffffffffffffffffffffffffff" // all ones IPv6 address
+ "01" // ip protocol = 1 (ICMP)
+ );
+ Capsule expected_capsule = Capsule::RouteAdvertisement();
+ IpAddressRange ip_address_range1;
+ ip_address_range1.start_ip_address.FromString("192.0.2.24");
+ ip_address_range1.end_ip_address.FromString("192.0.2.42");
+ ip_address_range1.ip_protocol = 0;
+ expected_capsule.route_advertisement_capsule().ip_address_ranges.push_back(
+ ip_address_range1);
+ IpAddressRange ip_address_range2;
+ ip_address_range2.start_ip_address.FromString("::");
+ ip_address_range2.end_ip_address.FromString(
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+ ip_address_range2.ip_protocol = 1;
+ expected_capsule.route_advertisement_capsule().ip_address_ranges.push_back(
+ ip_address_range2);
+ {
+ 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
diff --git a/quiche/quic/core/http/quic_spdy_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc
index c2c269a..7b580be 100644
--- a/quiche/quic/core/http/quic_spdy_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -292,6 +292,10 @@
}
}
+ if (connect_ip_visitor_ != nullptr) {
+ connect_ip_visitor_->OnHeadersWritten();
+ }
+
return bytes_written;
}
@@ -1361,6 +1365,24 @@
capsule.close_web_transport_session_capsule().error_code,
capsule.close_web_transport_session_capsule().error_message);
} break;
+ case CapsuleType::ADDRESS_ASSIGN:
+ if (connect_ip_visitor_ == nullptr) {
+ return true;
+ }
+ return connect_ip_visitor_->OnAddressAssignCapsule(
+ capsule.address_assign_capsule());
+ case CapsuleType::ADDRESS_REQUEST:
+ if (connect_ip_visitor_ == nullptr) {
+ return true;
+ }
+ return connect_ip_visitor_->OnAddressRequestCapsule(
+ capsule.address_request_capsule());
+ case CapsuleType::ROUTE_ADVERTISEMENT:
+ if (connect_ip_visitor_ == nullptr) {
+ return true;
+ }
+ return connect_ip_visitor_->OnRouteAdvertisementCapsule(
+ capsule.route_advertisement_capsule());
}
return true;
}
@@ -1441,6 +1463,43 @@
datagram_visitor_ = visitor;
}
+void QuicSpdyStream::RegisterConnectIpVisitor(ConnectIpVisitor* visitor) {
+ if (visitor == nullptr) {
+ QUIC_BUG(null connect - ip visitor)
+ << ENDPOINT << "Null connect-ip visitor for stream ID " << id();
+ return;
+ }
+ QUIC_DLOG(INFO) << ENDPOINT
+ << "Registering CONNECT-IP visitor with stream ID " << id();
+
+ if (connect_ip_visitor_ != nullptr) {
+ QUIC_BUG(connect - ip double registration)
+ << ENDPOINT << "Attempted to doubly register CONNECT-IP with stream ID "
+ << id();
+ return;
+ }
+ connect_ip_visitor_ = visitor;
+}
+
+void QuicSpdyStream::UnregisterConnectIpVisitor() {
+ if (connect_ip_visitor_ == nullptr) {
+ QUIC_BUG(connect - ip visitor empty during unregistration)
+ << ENDPOINT << "Cannot unregister CONNECT-IP visitor for stream ID "
+ << id();
+ return;
+ }
+ QUIC_DLOG(INFO) << ENDPOINT
+ << "Unregistering CONNECT-IP visitor for stream ID " << id();
+ connect_ip_visitor_ = nullptr;
+}
+
+void QuicSpdyStream::ReplaceConnectIpVisitor(ConnectIpVisitor* visitor) {
+ QUIC_BUG_IF(connect - ip unknown move, connect_ip_visitor_ == nullptr)
+ << "Attempted to move missing CONNECT-IP visitor on HTTP/3 stream ID "
+ << id();
+ connect_ip_visitor_ = visitor;
+}
+
void QuicSpdyStream::SetMaxDatagramTimeInQueue(
QuicTime::Delta max_time_in_queue) {
spdy_session_->SetMaxDatagramTimeInQueueForStreamId(id(), max_time_in_queue);
diff --git a/quiche/quic/core/http/quic_spdy_stream.h b/quiche/quic/core/http/quic_spdy_stream.h
index 62b0eea..b5b6e9e 100644
--- a/quiche/quic/core/http/quic_spdy_stream.h
+++ b/quiche/quic/core/http/quic_spdy_stream.h
@@ -279,6 +279,31 @@
// Mainly meant to be used by the visitors' move operators.
void ReplaceHttp3DatagramVisitor(Http3DatagramVisitor* visitor);
+ class QUIC_EXPORT_PRIVATE ConnectIpVisitor {
+ public:
+ virtual ~ConnectIpVisitor() {}
+
+ virtual bool OnAddressAssignCapsule(
+ const AddressAssignCapsule& capsule) = 0;
+ virtual bool OnAddressRequestCapsule(
+ const AddressRequestCapsule& capsule) = 0;
+ virtual bool OnRouteAdvertisementCapsule(
+ const RouteAdvertisementCapsule& capsule) = 0;
+ virtual void OnHeadersWritten() = 0;
+ };
+
+ // Registers |visitor| to receive CONNECT-IP capsules. |visitor| must be
+ // valid until a corresponding call to UnregisterConnectIpVisitor.
+ void RegisterConnectIpVisitor(ConnectIpVisitor* visitor);
+
+ // Unregisters a CONNECT-IP visitor. Must only be called after a call to
+ // RegisterConnectIpVisitor.
+ void UnregisterConnectIpVisitor();
+
+ // Replaces the current CONNECT-IP visitor with a different visitor.
+ // Mainly meant to be used by the visitors' move operators.
+ void ReplaceConnectIpVisitor(ConnectIpVisitor* visitor);
+
// Sets max datagram time in queue.
void SetMaxDatagramTimeInQueue(QuicTime::Delta max_time_in_queue);
@@ -449,6 +474,8 @@
// HTTP/3 Datagram support.
Http3DatagramVisitor* datagram_visitor_ = nullptr;
+ // CONNECT-IP support.
+ ConnectIpVisitor* connect_ip_visitor_ = nullptr;
};
} // namespace quic
diff --git a/quiche/quic/core/http/quic_spdy_stream_test.cc b/quiche/quic/core/http/quic_spdy_stream_test.cc
index 4695f25..c6cd92c 100644
--- a/quiche/quic/core/http/quic_spdy_stream_test.cc
+++ b/quiche/quic/core/http/quic_spdy_stream_test.cc
@@ -15,6 +15,7 @@
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/core/crypto/null_encrypter.h"
+#include "quiche/quic/core/http/capsule.h"
#include "quiche/quic/core/http/http_encoder.h"
#include "quiche/quic/core/http/quic_spdy_session.h"
#include "quiche/quic/core/http/spdy_utils.h"
@@ -36,6 +37,7 @@
#include "quiche/quic/test_tools/quic_spdy_stream_peer.h"
#include "quiche/quic/test_tools/quic_stream_peer.h"
#include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/common/quiche_ip_address.h"
#include "quiche/common/quiche_mem_slice_storage.h"
#include "quiche/common/simple_buffer_allocator.h"
@@ -3161,6 +3163,64 @@
EXPECT_GT(stream_->GetMaxDatagramSize(), 512u);
}
+TEST_P(QuicSpdyStreamTest, Capsules) {
+ if (!UsesHttp3()) {
+ return;
+ }
+ Initialize(kShouldProcessData);
+ session_->set_local_http_datagram_support(HttpDatagramSupport::kDraft09);
+ QuicSpdySessionPeer::SetHttpDatagramSupport(session_.get(),
+ HttpDatagramSupport::kDraft09);
+ SavingHttp3DatagramVisitor h3_datagram_visitor;
+ stream_->RegisterHttp3DatagramVisitor(&h3_datagram_visitor);
+ SavingConnectIpVisitor connect_ip_visitor;
+ stream_->RegisterConnectIpVisitor(&connect_ip_visitor);
+ headers_[":method"] = "CONNECT";
+ headers_[":protocol"] = "fake-capsule-protocol";
+ ProcessHeaders(/*fin=*/false, headers_);
+ // Datagram capsule.
+ std::string http_datagram_payload = {1, 2, 3, 4, 5, 6};
+ stream_->OnCapsule(Capsule::DatagramWithoutContext(http_datagram_payload));
+ EXPECT_THAT(h3_datagram_visitor.received_h3_datagrams(),
+ ElementsAre(SavingHttp3DatagramVisitor::SavedHttp3Datagram{
+ stream_->id(), http_datagram_payload}));
+ // Address assign capsule.
+ PrefixWithId ip_prefix_with_id;
+ ip_prefix_with_id.request_id = 1;
+ quiche::QuicheIpAddress ip_address;
+ ip_address.FromString("::");
+ ip_prefix_with_id.ip_prefix =
+ quiche::QuicheIpPrefix(ip_address, /*prefix_length=*/96);
+ Capsule address_assign_capsule = Capsule::AddressAssign();
+ address_assign_capsule.address_assign_capsule().assigned_addresses.push_back(
+ ip_prefix_with_id);
+ stream_->OnCapsule(address_assign_capsule);
+ EXPECT_THAT(connect_ip_visitor.received_address_assign_capsules(),
+ ElementsAre(address_assign_capsule.address_assign_capsule()));
+ // Address request capsule.
+ Capsule address_request_capsule = Capsule::AddressRequest();
+ address_request_capsule.address_request_capsule()
+ .requested_addresses.push_back(ip_prefix_with_id);
+ stream_->OnCapsule(address_request_capsule);
+ EXPECT_THAT(connect_ip_visitor.received_address_request_capsules(),
+ ElementsAre(address_request_capsule.address_request_capsule()));
+ // Route advertisement capsule.
+ Capsule route_advertisement_capsule = Capsule::RouteAdvertisement();
+ IpAddressRange ip_address_range;
+ ip_address_range.start_ip_address.FromString("192.0.2.24");
+ ip_address_range.end_ip_address.FromString("192.0.2.42");
+ ip_address_range.ip_protocol = 0;
+ route_advertisement_capsule.route_advertisement_capsule()
+ .ip_address_ranges.push_back(ip_address_range);
+ stream_->OnCapsule(route_advertisement_capsule);
+ EXPECT_THAT(
+ connect_ip_visitor.received_route_advertisement_capsules(),
+ ElementsAre(route_advertisement_capsule.route_advertisement_capsule()));
+ // Cleanup.
+ stream_->UnregisterHttp3DatagramVisitor();
+ stream_->UnregisterConnectIpVisitor();
+}
+
TEST_P(QuicSpdyStreamTest,
QUIC_TEST_DISABLED_IN_CHROME(HeadersAccumulatorNullptr)) {
if (!UsesHttp3()) {
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index 7981335..9808498 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -2100,6 +2100,46 @@
std::vector<SavedHttp3Datagram> received_h3_datagrams_;
};
+// Implementation of ConnectIpVisitor which saves all received capsules.
+class SavingConnectIpVisitor : public QuicSpdyStream::ConnectIpVisitor {
+ public:
+ const std::vector<AddressAssignCapsule>& received_address_assign_capsules()
+ const {
+ return received_address_assign_capsules_;
+ }
+ const std::vector<AddressRequestCapsule>& received_address_request_capsules()
+ const {
+ return received_address_request_capsules_;
+ }
+ const std::vector<RouteAdvertisementCapsule>&
+ received_route_advertisement_capsules() const {
+ return received_route_advertisement_capsules_;
+ }
+ bool headers_written() const { return headers_written_; }
+
+ // From QuicSpdyStream::ConnectIpVisitor.
+ bool OnAddressAssignCapsule(const AddressAssignCapsule& capsule) override {
+ received_address_assign_capsules_.push_back(capsule);
+ return true;
+ }
+ bool OnAddressRequestCapsule(const AddressRequestCapsule& capsule) override {
+ received_address_request_capsules_.push_back(capsule);
+ return true;
+ }
+ bool OnRouteAdvertisementCapsule(
+ const RouteAdvertisementCapsule& capsule) override {
+ received_route_advertisement_capsules_.push_back(capsule);
+ return true;
+ }
+ void OnHeadersWritten() override { headers_written_ = true; }
+
+ private:
+ std::vector<AddressAssignCapsule> received_address_assign_capsules_;
+ std::vector<AddressRequestCapsule> received_address_request_capsules_;
+ std::vector<RouteAdvertisementCapsule> received_route_advertisement_capsules_;
+ bool headers_written_ = false;
+};
+
inline std::string EscapeTestParamName(absl::string_view name) {
std::string result(name);
// Escape all characters that are not allowed by gtest ([a-zA-Z0-9_]).