Add a function to parse WebTransport subprotocol negotiation response.

I completely forgot that parsing response requires logic too.

PiperOrigin-RevId: 583947370
diff --git a/quiche/web_transport/web_transport_headers.cc b/quiche/web_transport/web_transport_headers.cc
index 9d4b4f8..fc46536 100644
--- a/quiche/web_transport/web_transport_headers.cc
+++ b/quiche/web_transport/web_transport_headers.cc
@@ -32,20 +32,23 @@
 using ::quiche::structured_headers::ParameterizedItem;
 using ::quiche::structured_headers::ParameterizedMember;
 
-absl::Status CheckItemType(const ParameterizedMember& member,
+absl::Status CheckItemType(const ParameterizedItem& item,
                            Item::ItemType expected_type) {
+  if (item.item.Type() != expected_type) {
+    return absl::InvalidArgumentError(absl::StrCat(
+        "Expected all members to be of type ", ItemTypeToString(expected_type),
+        ", found ", ItemTypeToString(item.item.Type()), " instead"));
+  }
+  return absl::OkStatus();
+}
+absl::Status CheckMemberType(const ParameterizedMember& member,
+                             Item::ItemType expected_type) {
   if (member.member_is_inner_list || member.member.size() != 1) {
     return absl::InvalidArgumentError(absl::StrCat(
         "Expected all members to be of type", ItemTypeToString(expected_type),
         ", found a nested list instead"));
   }
-  if (member.member[0].item.Type() != expected_type) {
-    return absl::InvalidArgumentError(absl::StrCat(
-        "Expected all members to be of type ", ItemTypeToString(expected_type),
-        ", found ", ItemTypeToString(member.member[0].item.Type()),
-        " instead"));
-  }
-  return absl::OkStatus();
+  return CheckItemType(member.member[0], expected_type);
 }
 
 ABSL_CONST_INIT std::array kInitHeaderFields{
@@ -66,7 +69,7 @@
   std::vector<std::string> result;
   result.reserve(parsed->size());
   for (ParameterizedMember& member : *parsed) {
-    QUICHE_RETURN_IF_ERROR(CheckItemType(member, Item::kTokenType));
+    QUICHE_RETURN_IF_ERROR(CheckMemberType(member, Item::kTokenType));
     result.push_back(std::move(member.member[0].item).TakeString());
   }
   return result;
@@ -84,6 +87,25 @@
   return absl::StrJoin(subprotocols, ", ");
 }
 
+absl::StatusOr<std::string> ParseSubprotocolResponseHeader(
+    absl::string_view value) {
+  std::optional<ParameterizedItem> parsed =
+      quiche::structured_headers::ParseItem(value);
+  if (!parsed.has_value()) {
+    return absl::InvalidArgumentError("Failed to parse sf-item");
+  }
+  QUICHE_RETURN_IF_ERROR(CheckItemType(*parsed, Item::kTokenType));
+  return std::move(parsed->item).TakeString();
+}
+
+absl::StatusOr<std::string> SerializeSubprotocolResponseHeader(
+    absl::string_view subprotocol) {
+  if (!quiche::structured_headers::IsValidToken(subprotocol)) {
+    return absl::InvalidArgumentError("Invalid token value supplied");
+  }
+  return std::string(subprotocol);
+}
+
 absl::StatusOr<WebTransportInitHeader> ParseInitHeader(
     absl::string_view header) {
   std::optional<Dictionary> parsed =
@@ -98,7 +120,7 @@
       if (field_name_a != field_name_b) {
         continue;
       }
-      QUICHE_RETURN_IF_ERROR(CheckItemType(field_value, Item::kIntegerType));
+      QUICHE_RETURN_IF_ERROR(CheckMemberType(field_value, Item::kIntegerType));
       int64_t value = field_value.member[0].item.GetInteger();
       if (value < 0) {
         return absl::InvalidArgumentError(
diff --git a/quiche/web_transport/web_transport_headers.h b/quiche/web_transport/web_transport_headers.h
index 10f6d2f..6cf16ee 100644
--- a/quiche/web_transport/web_transport_headers.h
+++ b/quiche/web_transport/web_transport_headers.h
@@ -25,6 +25,10 @@
 ParseSubprotocolRequestHeader(absl::string_view value);
 QUICHE_EXPORT absl::StatusOr<std::string> SerializeSubprotocolRequestHeader(
     absl::Span<const std::string> subprotocols);
+QUICHE_EXPORT absl::StatusOr<std::string> ParseSubprotocolResponseHeader(
+    absl::string_view value);
+QUICHE_EXPORT absl::StatusOr<std::string> SerializeSubprotocolResponseHeader(
+    absl::string_view subprotocol);
 
 inline constexpr absl::string_view kInitHeader = "WebTransport-Init";
 
diff --git a/quiche/web_transport/web_transport_headers_test.cc b/quiche/web_transport/web_transport_headers_test.cc
index 056303a..c41dc8d 100644
--- a/quiche/web_transport/web_transport_headers_test.cc
+++ b/quiche/web_transport/web_transport_headers_test.cc
@@ -57,6 +57,25 @@
       StatusIs(absl::StatusCode::kInvalidArgument, "Invalid token: 0123"));
 }
 
+TEST(WebTransportHeader, ParseSubprotocolResponseHeader) {
+  EXPECT_THAT(ParseSubprotocolResponseHeader("foo"), IsOkAndHolds("foo"));
+  EXPECT_THAT(ParseSubprotocolResponseHeader("foo; a=b"), IsOkAndHolds("foo"));
+  EXPECT_THAT(
+      ParseSubprotocolResponseHeader("1234"),
+      StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("found integer")));
+  EXPECT_THAT(
+      ParseSubprotocolResponseHeader("(a"),
+      StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("parse sf-item")));
+}
+
+TEST(WebTransportHeader, SerializeSubprotocolResponseHeader) {
+  EXPECT_THAT(SerializeSubprotocolResponseHeader("foo"), IsOkAndHolds("foo"));
+  EXPECT_THAT(SerializeSubprotocolResponseHeader("moqt-draft01"),
+              IsOkAndHolds("moqt-draft01"));
+  EXPECT_THAT(SerializeSubprotocolResponseHeader("123abc"),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
 TEST(WebTransportHeader, ParseInitHeader) {
   WebTransportInitHeader expected_header;
   expected_header.initial_unidi_limit = 100;