Move Datagram-Flow-Id header to SpdyUtils

This code is not used in production.

PiperOrigin-RevId: 359912077
Change-Id: Ifdf163e92b6f184f0e5f8bc9c6629d3c2805d9c3
diff --git a/quic/core/http/spdy_utils.cc b/quic/core/http/spdy_utils.cc
index a3d800a..d3a3c92 100644
--- a/quic/core/http/spdy_utils.cc
+++ b/quic/core/http/spdy_utils.cc
@@ -12,6 +12,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_split.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
 #include "quic/platform/api/quic_flag_utils.h"
 #include "quic/platform/api/quic_flags.h"
 #include "quic/platform/api/quic_logging.h"
@@ -153,4 +154,45 @@
   return true;
 }
 
+// static
+absl::optional<QuicDatagramFlowId> SpdyUtils::ParseDatagramFlowIdHeader(
+    const spdy::SpdyHeaderBlock& headers) {
+  auto flow_id_pair = headers.find("datagram-flow-id");
+  if (flow_id_pair == headers.end()) {
+    return absl::nullopt;
+  }
+  std::vector<absl::string_view> flow_id_strings =
+      absl::StrSplit(flow_id_pair->second, ',');
+  absl::optional<QuicDatagramFlowId> first_named_flow_id;
+  for (absl::string_view flow_id_string : flow_id_strings) {
+    std::vector<absl::string_view> flow_id_components =
+        absl::StrSplit(flow_id_string, ';');
+    if (flow_id_components.empty()) {
+      continue;
+    }
+    absl::string_view flow_id_value_string = flow_id_components[0];
+    quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(
+        &flow_id_value_string);
+    QuicDatagramFlowId flow_id;
+    if (!absl::SimpleAtoi(flow_id_value_string, &flow_id)) {
+      continue;
+    }
+    if (flow_id_components.size() == 1) {
+      // This flow ID is unnamed, return this one.
+      return flow_id;
+    }
+    // Otherwise this is a named flow ID.
+    if (!first_named_flow_id.has_value()) {
+      first_named_flow_id = flow_id;
+    }
+  }
+  return first_named_flow_id;
+}
+
+// static
+void SpdyUtils::AddDatagramFlowIdHeader(spdy::SpdyHeaderBlock* headers,
+                                        QuicDatagramFlowId flow_id) {
+  (*headers)["datagram-flow-id"] = absl::StrCat(flow_id);
+}
+
 }  // namespace quic
diff --git a/quic/core/http/spdy_utils.h b/quic/core/http/spdy_utils.h
index a3b3134..e3a8a97 100644
--- a/quic/core/http/spdy_utils.h
+++ b/quic/core/http/spdy_utils.h
@@ -9,6 +9,7 @@
 #include <cstdint>
 #include <string>
 
+#include "absl/types/optional.h"
 #include "quic/core/http/http_constants.h"
 #include "quic/core/http/quic_header_list.h"
 #include "quic/core/quic_packets.h"
@@ -51,6 +52,15 @@
   // which must be fully-qualified.
   static bool PopulateHeaderBlockFromUrl(const std::string url,
                                          spdy::SpdyHeaderBlock* headers);
+
+  // Parses the "datagram-flow-id" header, returns the flow ID on success, or
+  // returns absl::nullopt if the header was not present or failed to parse.
+  static absl::optional<QuicDatagramFlowId> ParseDatagramFlowIdHeader(
+      const spdy::SpdyHeaderBlock& headers);
+
+  // Adds the "datagram-flow-id" header.
+  static void AddDatagramFlowIdHeader(spdy::SpdyHeaderBlock* headers,
+                                      QuicDatagramFlowId flow_id);
 };
 
 }  // namespace quic
diff --git a/quic/core/http/spdy_utils_test.cc b/quic/core/http/spdy_utils_test.cc
index 5cb391d..c7207f3 100644
--- a/quic/core/http/spdy_utils_test.cc
+++ b/quic/core/http/spdy_utils_test.cc
@@ -33,6 +33,14 @@
   return headers;
 }
 
+static void ValidateDatagramFlowId(
+    const std::string& header_value,
+    absl::optional<QuicDatagramFlowId> expected_flow_id) {
+  SpdyHeaderBlock headers;
+  headers["datagram-flow-id"] = header_value;
+  ASSERT_EQ(SpdyUtils::ParseDatagramFlowIdHeader(headers), expected_flow_id);
+}
+
 }  // anonymous namespace
 
 using CopyAndValidateHeaders = QuicTest;
@@ -377,5 +385,29 @@
       SpdyUtils::PopulateHeaderBlockFromUrl("www.google.com/", &headers));
 }
 
+using DatagramFlowIdTest = QuicTest;
+
+TEST_F(DatagramFlowIdTest, DatagramFlowId) {
+  // Test missing header.
+  SpdyHeaderBlock headers;
+  EXPECT_EQ(SpdyUtils::ParseDatagramFlowIdHeader(headers), absl::nullopt);
+  // Add header and verify it parses.
+  QuicDatagramFlowId flow_id = 123;
+  SpdyUtils::AddDatagramFlowIdHeader(&headers, flow_id);
+  EXPECT_EQ(SpdyUtils::ParseDatagramFlowIdHeader(headers), flow_id);
+  // Test empty header.
+  ValidateDatagramFlowId("", absl::nullopt);
+  // Test invalid header.
+  ValidateDatagramFlowId("M4SQU3", absl::nullopt);
+  // Test simple header.
+  ValidateDatagramFlowId("42", 42);
+  // Test with parameter.
+  ValidateDatagramFlowId("42; abc=def", 42);
+  // Test list.
+  ValidateDatagramFlowId("42, 44; ecn-ect0, 46; ecn-ect1, 48; ecn-ce", 42);
+  // Test reordered list.
+  ValidateDatagramFlowId("44; ecn-ect0, 42, 48; ecn-ce, 46; ecn-ect1", 42);
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/masque/masque_client_session.cc b/quic/masque/masque_client_session.cc
index e158125..79ab509 100644
--- a/quic/masque/masque_client_session.cc
+++ b/quic/masque/masque_client_session.cc
@@ -4,6 +4,7 @@
 
 #include "quic/masque/masque_client_session.h"
 #include "absl/algorithm/container.h"
+#include "quic/core/http/spdy_utils.h"
 #include "quic/core/quic_data_reader.h"
 #include "common/platform/api/quiche_text_utils.h"
 
@@ -99,7 +100,7 @@
   headers[":scheme"] = "masque";
   headers[":path"] = "/";
   headers[":authority"] = target_server_address.ToString();
-  headers["datagram-flow-id"] = absl::StrCat(flow_id);
+  SpdyUtils::AddDatagramFlowIdHeader(&headers, flow_id);
   size_t bytes_sent =
       stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/false);
   if (bytes_sent == 0) {
diff --git a/quic/masque/masque_server_session.cc b/quic/masque/masque_server_session.cc
index 1e420b9..4f065ee 100644
--- a/quic/masque/masque_server_session.cc
+++ b/quic/masque/masque_server_session.cc
@@ -8,6 +8,8 @@
 
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "quic/core/http/spdy_utils.h"
 #include "quic/core/quic_data_reader.h"
 #include "quic/core/quic_udp_socket.h"
 #include "quic/tools/quic_url.h"
@@ -174,12 +176,10 @@
     auto path_pair = request_headers.find(":path");
     auto scheme_pair = request_headers.find(":scheme");
     auto method_pair = request_headers.find(":method");
-    auto flow_id_pair = request_headers.find("datagram-flow-id");
     auto authority_pair = request_headers.find(":authority");
     if (path_pair == request_headers.end() ||
         scheme_pair == request_headers.end() ||
         method_pair == request_headers.end() ||
-        flow_id_pair == request_headers.end() ||
         authority_pair == request_headers.end()) {
       QUIC_DLOG(ERROR) << "MASQUE request is missing required headers";
       return CreateBackendErrorResponse("400", "Missing required headers");
@@ -187,7 +187,6 @@
     absl::string_view path = path_pair->second;
     absl::string_view scheme = scheme_pair->second;
     absl::string_view method = method_pair->second;
-    absl::string_view flow_id_str = flow_id_pair->second;
     absl::string_view authority = authority_pair->second;
     if (path.empty()) {
       QUIC_DLOG(ERROR) << "MASQUE request with empty path";
@@ -201,11 +200,13 @@
       QUIC_DLOG(ERROR) << "MASQUE request with bad method \"" << method << "\"";
       return CreateBackendErrorResponse("400", "Bad method");
     }
-    QuicDatagramFlowId flow_id;
-    if (!absl::SimpleAtoi(flow_id_str, &flow_id)) {
-      QUIC_DLOG(ERROR) << "MASQUE request with bad flow_id \"" << flow_id_str
-                       << "\"";
-      return CreateBackendErrorResponse("400", "Bad flow ID");
+    absl::optional<QuicDatagramFlowId> flow_id =
+        SpdyUtils::ParseDatagramFlowIdHeader(request_headers);
+    if (!flow_id.has_value()) {
+      QUIC_DLOG(ERROR)
+          << "MASQUE request with bad or missing DatagramFlowId header";
+      return CreateBackendErrorResponse("400",
+                                        "Bad or missing DatagramFlowId header");
     }
     QuicUrl url(absl::StrCat("https://", authority));
     if (!url.IsValid() || url.PathParamsQuery() != "/") {
@@ -232,7 +233,7 @@
         info_list, freeaddrinfo);
     QuicSocketAddress target_server_address(info_list->ai_addr,
                                             info_list->ai_addrlen);
-    QUIC_DLOG(INFO) << "Got CONNECT_UDP request flow_id=" << flow_id
+    QUIC_DLOG(INFO) << "Got CONNECT_UDP request flow_id=" << *flow_id
                     << " target_server_address=\"" << target_server_address
                     << "\"";
 
@@ -250,12 +251,12 @@
     epoll_server_->RegisterFDForRead(fd_wrapper.fd(), this);
 
     connect_udp_server_states_.emplace_back(ConnectUdpServerState(
-        flow_id, request_handler->stream_id(), target_server_address,
+        *flow_id, request_handler->stream_id(), target_server_address,
         fd_wrapper.extract_fd(), this));
 
     spdy::Http2HeaderBlock response_headers;
     response_headers[":status"] = "200";
-    response_headers["datagram-flow-id"] = absl::StrCat(flow_id);
+    SpdyUtils::AddDatagramFlowIdHeader(&response_headers, *flow_id);
     auto response = std::make_unique<QuicBackendResponse>();
     response->set_response_type(QuicBackendResponse::INCOMPLETE_RESPONSE);
     response->set_headers(std::move(response_headers));