CONNECT-IP payload parse/serialization
Implement the payload format from RFC 9484, Section 6.
PiperOrigin-RevId: 586798600
diff --git a/build/source_list.bzl b/build/source_list.bzl
index df02f45..ade85e5 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -13,6 +13,7 @@
"common/capsule.h",
"common/http/http_header_block.h",
"common/http/http_header_storage.h",
+ "common/masque/connect_ip_datagram_payload.h",
"common/masque/connect_udp_datagram_payload.h",
"common/platform/api/quiche_bug_tracker.h",
"common/platform/api/quiche_client_stats.h",
@@ -400,6 +401,7 @@
"common/capsule.cc",
"common/http/http_header_block.cc",
"common/http/http_header_storage.cc",
+ "common/masque/connect_ip_datagram_payload.cc",
"common/masque/connect_udp_datagram_payload.cc",
"common/platform/api/quiche_hostname_utils.cc",
"common/platform/api/quiche_mutex.cc",
@@ -1052,6 +1054,7 @@
"common/capsule_test.cc",
"common/http/http_header_block_test.cc",
"common/http/http_header_storage_test.cc",
+ "common/masque/connect_ip_datagram_payload_test.cc",
"common/masque/connect_udp_datagram_payload_test.cc",
"common/platform/api/quiche_file_utils_test.cc",
"common/platform/api/quiche_hostname_utils_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 16a7eea..c514ab2 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -13,6 +13,7 @@
"src/quiche/common/capsule.h",
"src/quiche/common/http/http_header_block.h",
"src/quiche/common/http/http_header_storage.h",
+ "src/quiche/common/masque/connect_ip_datagram_payload.h",
"src/quiche/common/masque/connect_udp_datagram_payload.h",
"src/quiche/common/platform/api/quiche_bug_tracker.h",
"src/quiche/common/platform/api/quiche_client_stats.h",
@@ -400,6 +401,7 @@
"src/quiche/common/capsule.cc",
"src/quiche/common/http/http_header_block.cc",
"src/quiche/common/http/http_header_storage.cc",
+ "src/quiche/common/masque/connect_ip_datagram_payload.cc",
"src/quiche/common/masque/connect_udp_datagram_payload.cc",
"src/quiche/common/platform/api/quiche_hostname_utils.cc",
"src/quiche/common/platform/api/quiche_mutex.cc",
@@ -1053,6 +1055,7 @@
"src/quiche/common/capsule_test.cc",
"src/quiche/common/http/http_header_block_test.cc",
"src/quiche/common/http/http_header_storage_test.cc",
+ "src/quiche/common/masque/connect_ip_datagram_payload_test.cc",
"src/quiche/common/masque/connect_udp_datagram_payload_test.cc",
"src/quiche/common/platform/api/quiche_file_utils_test.cc",
"src/quiche/common/platform/api/quiche_hostname_utils_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 41946db..68779fa 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -12,6 +12,7 @@
"quiche/common/capsule.h",
"quiche/common/http/http_header_block.h",
"quiche/common/http/http_header_storage.h",
+ "quiche/common/masque/connect_ip_datagram_payload.h",
"quiche/common/masque/connect_udp_datagram_payload.h",
"quiche/common/platform/api/quiche_bug_tracker.h",
"quiche/common/platform/api/quiche_client_stats.h",
@@ -399,6 +400,7 @@
"quiche/common/capsule.cc",
"quiche/common/http/http_header_block.cc",
"quiche/common/http/http_header_storage.cc",
+ "quiche/common/masque/connect_ip_datagram_payload.cc",
"quiche/common/masque/connect_udp_datagram_payload.cc",
"quiche/common/platform/api/quiche_hostname_utils.cc",
"quiche/common/platform/api/quiche_mutex.cc",
@@ -1052,6 +1054,7 @@
"quiche/common/capsule_test.cc",
"quiche/common/http/http_header_block_test.cc",
"quiche/common/http/http_header_storage_test.cc",
+ "quiche/common/masque/connect_ip_datagram_payload_test.cc",
"quiche/common/masque/connect_udp_datagram_payload_test.cc",
"quiche/common/platform/api/quiche_file_utils_test.cc",
"quiche/common/platform/api/quiche_hostname_utils_test.cc",
diff --git a/quiche/common/masque/connect_ip_datagram_payload.cc b/quiche/common/masque/connect_ip_datagram_payload.cc
new file mode 100644
index 0000000..cf97c21
--- /dev/null
+++ b/quiche/common/masque/connect_ip_datagram_payload.cc
@@ -0,0 +1,131 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/masque/connect_ip_datagram_payload.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#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"
+
+namespace quiche {
+
+// static
+std::unique_ptr<ConnectIpDatagramPayload> ConnectIpDatagramPayload::Parse(
+ absl::string_view datagram_payload) {
+ QuicheDataReader data_reader(datagram_payload);
+
+ uint64_t context_id;
+ if (!data_reader.ReadVarInt62(&context_id)) {
+ QUICHE_DVLOG(1) << "Could not parse malformed IP proxy payload";
+ return nullptr;
+ }
+
+ if (ContextId{context_id} == ConnectIpDatagramIpPacketPayload::kContextId) {
+ return std::make_unique<ConnectIpDatagramIpPacketPayload>(
+ data_reader.ReadRemainingPayload());
+ } else {
+ return std::make_unique<ConnectIpDatagramUnknownPayload>(
+ ContextId{context_id}, data_reader.ReadRemainingPayload());
+ }
+}
+
+std::string ConnectIpDatagramPayload::Serialize() const {
+ std::string buffer(SerializedLength(), '\0');
+ QuicheDataWriter writer(buffer.size(), buffer.data());
+
+ bool result = SerializeTo(writer);
+ QUICHE_DCHECK(result);
+ QUICHE_DCHECK_EQ(writer.remaining(), 0u);
+
+ return buffer;
+}
+
+ConnectIpDatagramIpPacketPayload::ConnectIpDatagramIpPacketPayload(
+ absl::string_view ip_packet)
+ : ip_packet_(ip_packet) {}
+
+ConnectIpDatagramPayload::ContextId
+ConnectIpDatagramIpPacketPayload::GetContextId() const {
+ return kContextId;
+}
+
+ConnectIpDatagramPayload::Type ConnectIpDatagramIpPacketPayload::GetType()
+ const {
+ return Type::kIpPacket;
+}
+
+absl::string_view ConnectIpDatagramIpPacketPayload::GetIpProxyingPayload()
+ const {
+ return ip_packet_;
+}
+
+size_t ConnectIpDatagramIpPacketPayload::SerializedLength() const {
+ return ip_packet_.size() +
+ QuicheDataWriter::GetVarInt62Len(uint64_t{kContextId});
+}
+
+bool ConnectIpDatagramIpPacketPayload::SerializeTo(
+ QuicheDataWriter& writer) const {
+ if (!writer.WriteVarInt62(uint64_t{kContextId})) {
+ return false;
+ }
+
+ if (!writer.WriteStringPiece(ip_packet_)) {
+ return false;
+ }
+
+ return true;
+}
+
+ConnectIpDatagramUnknownPayload::ConnectIpDatagramUnknownPayload(
+ ContextId context_id, absl::string_view ip_proxying_payload)
+ : context_id_(context_id), ip_proxying_payload_(ip_proxying_payload) {
+ if (context_id == ConnectIpDatagramIpPacketPayload::kContextId) {
+ QUICHE_BUG(ip_proxy_unknown_payload_ip_context)
+ << "ConnectIpDatagramUnknownPayload created with IP packet context "
+ "ID (0). Should instead create a "
+ "ConnectIpDatagramIpPacketPayload.";
+ }
+}
+
+ConnectIpDatagramPayload::ContextId
+ConnectIpDatagramUnknownPayload::GetContextId() const {
+ return context_id_;
+}
+
+ConnectIpDatagramPayload::Type ConnectIpDatagramUnknownPayload::GetType()
+ const {
+ return Type::kUnknown;
+}
+absl::string_view ConnectIpDatagramUnknownPayload::GetIpProxyingPayload()
+ const {
+ return ip_proxying_payload_;
+}
+
+size_t ConnectIpDatagramUnknownPayload::SerializedLength() const {
+ return ip_proxying_payload_.size() +
+ QuicheDataWriter::GetVarInt62Len(uint64_t{context_id_});
+}
+
+bool ConnectIpDatagramUnknownPayload::SerializeTo(
+ QuicheDataWriter& writer) const {
+ if (!writer.WriteVarInt62(uint64_t{context_id_})) {
+ return false;
+ }
+
+ if (!writer.WriteStringPiece(ip_proxying_payload_)) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace quiche
diff --git a/quiche/common/masque/connect_ip_datagram_payload.h b/quiche/common/masque/connect_ip_datagram_payload.h
new file mode 100644
index 0000000..978b74c
--- /dev/null
+++ b/quiche/common/masque/connect_ip_datagram_payload.h
@@ -0,0 +1,100 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_COMMON_MASQUE_CONNECT_IP_DATAGRAM_PAYLOAD_H_
+#define QUICHE_COMMON_MASQUE_CONNECT_IP_DATAGRAM_PAYLOAD_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/quiche_data_writer.h"
+
+namespace quiche {
+
+// IP-proxying HTTP Datagram payload for use with CONNECT-IP. See RFC 9484,
+// Section 6.
+class QUICHE_EXPORT ConnectIpDatagramPayload {
+ public:
+ using ContextId = uint64_t;
+ enum class Type { kIpPacket, kUnknown };
+
+ // Parse from `datagram_payload` (a wire-format IP-proxying HTTP datagram
+ // payload). Returns nullptr on error. The created ConnectIpDatagramPayload
+ // object may use absl::string_views pointing into `datagram_payload`, so the
+ // data pointed to by `datagram_payload` must outlive the created
+ // ConnectIpDatagramPayload object.
+ static std::unique_ptr<ConnectIpDatagramPayload> Parse(
+ absl::string_view datagram_payload);
+
+ ConnectIpDatagramPayload() = default;
+
+ ConnectIpDatagramPayload(const ConnectIpDatagramPayload&) = delete;
+ ConnectIpDatagramPayload& operator=(const ConnectIpDatagramPayload&) = delete;
+
+ virtual ~ConnectIpDatagramPayload() = default;
+
+ virtual ContextId GetContextId() const = 0;
+ virtual Type GetType() const = 0;
+ // Get the inner payload (the IP Proxying Payload).
+ virtual absl::string_view GetIpProxyingPayload() const = 0;
+
+ // Length of this IP-proxying HTTP datagram payload in wire format.
+ virtual size_t SerializedLength() const = 0;
+ // Write a wire-format buffer for the payload. Returns false on write failure
+ // (typically due to `writer` buffer being full).
+ virtual bool SerializeTo(QuicheDataWriter& writer) const = 0;
+
+ // Write a wire-format buffer.
+ std::string Serialize() const;
+};
+
+// IP-proxying HTTP Datagram payload that encodes an IP packet.
+class QUICHE_EXPORT ConnectIpDatagramIpPacketPayload final
+ : public ConnectIpDatagramPayload {
+ public:
+ static constexpr ContextId kContextId = 0;
+
+ // The string pointed to by `ip_packet` must outlive the created
+ // ConnectIpDatagramIpPacketPayload.
+ explicit ConnectIpDatagramIpPacketPayload(absl::string_view ip_packet);
+
+ ContextId GetContextId() const override;
+ Type GetType() const override;
+ absl::string_view GetIpProxyingPayload() const override;
+ size_t SerializedLength() const override;
+ bool SerializeTo(QuicheDataWriter& writer) const override;
+
+ absl::string_view ip_packet() const { return ip_packet_; }
+
+ private:
+ absl::string_view ip_packet_;
+};
+
+class QUICHE_EXPORT ConnectIpDatagramUnknownPayload final
+ : public ConnectIpDatagramPayload {
+ public:
+ // `ip_proxying_payload` represents the inner payload contained by the IP-
+ // proxying HTTP datagram payload. The string pointed to by `inner_payload`
+ // must outlive the created ConnectIpDatagramUnknownPayload.
+ ConnectIpDatagramUnknownPayload(ContextId context_id,
+ absl::string_view ip_proxying_payload);
+
+ ContextId GetContextId() const override;
+ Type GetType() const override;
+ absl::string_view GetIpProxyingPayload() const override;
+ size_t SerializedLength() const override;
+ bool SerializeTo(QuicheDataWriter& writer) const override;
+
+ private:
+ ContextId context_id_;
+ absl::string_view ip_proxying_payload_; // The inner payload.
+};
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_MASQUE_CONNECT_IP_DATAGRAM_PAYLOAD_H_
diff --git a/quiche/common/masque/connect_ip_datagram_payload_test.cc b/quiche/common/masque/connect_ip_datagram_payload_test.cc
new file mode 100644
index 0000000..2eb6f51
--- /dev/null
+++ b/quiche/common/masque/connect_ip_datagram_payload_test.cc
@@ -0,0 +1,62 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/masque/connect_ip_datagram_payload.h"
+
+#include <memory>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace quiche::test {
+namespace {
+
+TEST(ConnectIpDatagramPayloadTest, ParseIpPacket) {
+ static constexpr char kDatagramPayload[] = "\x00packet";
+
+ std::unique_ptr<ConnectIpDatagramPayload> parsed =
+ ConnectIpDatagramPayload::Parse(
+ absl::string_view(kDatagramPayload, sizeof(kDatagramPayload) - 1));
+ ASSERT_TRUE(parsed);
+
+ EXPECT_EQ(parsed->GetContextId(),
+ ConnectIpDatagramIpPacketPayload::kContextId);
+ EXPECT_EQ(parsed->GetType(), ConnectIpDatagramPayload::Type::kIpPacket);
+ EXPECT_EQ(parsed->GetIpProxyingPayload(), "packet");
+}
+
+TEST(ConnectIpDatagramPayloadTest, SerializeIpPacket) {
+ static constexpr absl::string_view kIpPacket = "packet";
+
+ ConnectIpDatagramIpPacketPayload payload(kIpPacket);
+ EXPECT_EQ(payload.GetIpProxyingPayload(), kIpPacket);
+
+ EXPECT_EQ(payload.Serialize(), std::string("\x00packet", 7));
+}
+
+TEST(ConnectIpDatagramPayloadTest, ParseUnknownPacket) {
+ static constexpr char kDatagramPayload[] = "\x05packet";
+
+ std::unique_ptr<ConnectIpDatagramPayload> parsed =
+ ConnectIpDatagramPayload::Parse(
+ absl::string_view(kDatagramPayload, sizeof(kDatagramPayload) - 1));
+ ASSERT_TRUE(parsed);
+
+ EXPECT_EQ(parsed->GetContextId(), 5);
+ EXPECT_EQ(parsed->GetType(), ConnectIpDatagramPayload::Type::kUnknown);
+ EXPECT_EQ(parsed->GetIpProxyingPayload(), "packet");
+}
+
+TEST(ConnectIpDatagramPayloadTest, SerializeUnknownPacket) {
+ static constexpr absl::string_view kInnerIpProxyingPayload = "packet";
+
+ ConnectIpDatagramUnknownPayload payload(4u, kInnerIpProxyingPayload);
+ EXPECT_EQ(payload.GetIpProxyingPayload(), kInnerIpProxyingPayload);
+
+ EXPECT_EQ(payload.Serialize(), std::string("\x04packet", 7));
+}
+
+} // namespace
+} // namespace quiche::test