Framer/Parser for MOQT PUBLISH_SKIPPED.

This is named PUBLISH_BLOCKED in draft-18 but will be renamed.

We do not yet support SUBSCRIBE_TRACKS, so there is no use for this message yet.

PiperOrigin-RevId: 994719010
diff --git a/quiche/quic/moqt/moqt_framer.cc b/quiche/quic/moqt/moqt_framer.cc
index d52ac07..e02f090 100644
--- a/quiche/quic/moqt/moqt_framer.cc
+++ b/quiche/quic/moqt/moqt_framer.cc
@@ -586,6 +586,12 @@
       WireTrackNamespace(message.track_namespace_suffix));
 }
 
+quiche::QuicheBuffer MoqtFramer::SerializePublishSkipped(
+    const MoqtPublishSkipped& message) {
+  return SerializeControlMessage(MoqtMessageType::kPublishSkipped,
+                                 WireFullTrackName(message.name));
+}
+
 quiche::QuicheBuffer MoqtFramer::SerializeTrackStatus(
     const MoqtTrackStatus& message) {
   return SerializeSubscribe(message, MoqtMessageType::kTrackStatus);
diff --git a/quiche/quic/moqt/moqt_framer.h b/quiche/quic/moqt/moqt_framer.h
index 282104d..c5a1ac4 100644
--- a/quiche/quic/moqt/moqt_framer.h
+++ b/quiche/quic/moqt/moqt_framer.h
@@ -60,6 +60,8 @@
       const MoqtPublishNamespace& message);
   quiche::QuicheBuffer SerializeNamespace(const MoqtNamespace& message);
   quiche::QuicheBuffer SerializeNamespaceDone(const MoqtNamespaceDone& message);
+  quiche::QuicheBuffer SerializePublishSkipped(
+      const MoqtPublishSkipped& message);
   quiche::QuicheBuffer SerializeTrackStatus(const MoqtTrackStatus& message);
   quiche::QuicheBuffer SerializeGoAway(const MoqtGoAway& message);
   quiche::QuicheBuffer SerializeSubscribeNamespace(
diff --git a/quiche/quic/moqt/moqt_framer_test.cc b/quiche/quic/moqt/moqt_framer_test.cc
index 09f355c..941c286 100644
--- a/quiche/quic/moqt/moqt_framer_test.cc
+++ b/quiche/quic/moqt/moqt_framer_test.cc
@@ -43,23 +43,15 @@
 std::vector<MoqtFramerTestParams> GetMoqtFramerTestParams() {
   std::vector<MoqtFramerTestParams> params;
   std::vector<MoqtMessageType> message_types = {
-      MoqtMessageType::kRequestOk,
-      MoqtMessageType::kRequestError,
-      MoqtMessageType::kSubscribe,
-      MoqtMessageType::kSubscribeOk,
-      MoqtMessageType::kPublishDone,
-      MoqtMessageType::kPublishNamespace,
-      MoqtMessageType::kNamespace,
-      MoqtMessageType::kNamespaceDone,
-      MoqtMessageType::kTrackStatus,
-      MoqtMessageType::kGoAway,
-      MoqtMessageType::kSubscribeNamespace,
-      MoqtMessageType::kSubscribeTracks,
-      MoqtMessageType::kFetch,
-      MoqtMessageType::kFetchOk,
-      MoqtMessageType::kPublish,
-      MoqtMessageType::kObjectAck,
-      MoqtMessageType::kSetup,
+      MoqtMessageType::kRequestOk,       MoqtMessageType::kRequestError,
+      MoqtMessageType::kSubscribe,       MoqtMessageType::kSubscribeOk,
+      MoqtMessageType::kPublishDone,     MoqtMessageType::kPublishNamespace,
+      MoqtMessageType::kNamespace,       MoqtMessageType::kNamespaceDone,
+      MoqtMessageType::kPublishSkipped,  MoqtMessageType::kTrackStatus,
+      MoqtMessageType::kGoAway,          MoqtMessageType::kSubscribeNamespace,
+      MoqtMessageType::kSubscribeTracks, MoqtMessageType::kFetch,
+      MoqtMessageType::kFetchOk,         MoqtMessageType::kPublish,
+      MoqtMessageType::kObjectAck,       MoqtMessageType::kSetup,
   };
   for (const MoqtMessageType message_type : message_types) {
     if (message_type == MoqtMessageType::kSetup) {
@@ -169,6 +161,10 @@
         auto data = std::get<MoqtNamespaceDone>(structured_data);
         return framer_.SerializeNamespaceDone(data);
       }
+      case MoqtMessageType::kPublishSkipped: {
+        auto data = std::get<MoqtPublishSkipped>(structured_data);
+        return framer_.SerializePublishSkipped(data);
+      }
       case moqt::MoqtMessageType::kTrackStatus: {
         auto data = std::get<MoqtTrackStatus>(structured_data);
         return framer_.SerializeTrackStatus(data);
diff --git a/quiche/quic/moqt/moqt_messages.cc b/quiche/quic/moqt/moqt_messages.cc
index 6aeb9c5..f2f7652 100644
--- a/quiche/quic/moqt/moqt_messages.cc
+++ b/quiche/quic/moqt/moqt_messages.cc
@@ -91,6 +91,8 @@
       return "NAMESPACE";
     case MoqtMessageType::kNamespaceDone:
       return "NAMESPACE_DONE";
+    case MoqtMessageType::kPublishSkipped:
+      return "PUBLISH_SKIPPED";
     case MoqtMessageType::kRequestOk:
       return "REQUEST_OK";
     case MoqtMessageType::kGoAway:
diff --git a/quiche/quic/moqt/moqt_messages.h b/quiche/quic/moqt/moqt_messages.h
index 149fa69..8488fdb 100644
--- a/quiche/quic/moqt/moqt_messages.h
+++ b/quiche/quic/moqt/moqt_messages.h
@@ -213,6 +213,7 @@
   kPublishDone = 0x0b,
   kTrackStatus = 0x0d,
   kNamespaceDone = 0x0e,
+  kPublishSkipped = 0x0f,
   kGoAway = 0x10,
   kFetch = 0x16,
   kFetchOk = 0x18,
@@ -431,6 +432,10 @@
   TrackNamespace track_namespace_suffix;
 };
 
+struct QUICHE_EXPORT MoqtPublishSkipped {
+  FullTrackName name;
+};
+
 enum class QUICHE_EXPORT FetchType : uint64_t {
   kStandalone = 0x1,
   kRelativeJoining = 0x2,
diff --git a/quiche/quic/moqt/moqt_parser.cc b/quiche/quic/moqt/moqt_parser.cc
index d57ef11..7a17db0 100644
--- a/quiche/quic/moqt/moqt_parser.cc
+++ b/quiche/quic/moqt/moqt_parser.cc
@@ -778,6 +778,15 @@
   return namespace_done;
 }
 
+absl::StatusOr<MoqtPublishSkipped>
+MoqtControlMessageParser::ProcessPublishSkipped(absl::string_view data) const {
+  quic::QuicDataReader reader(data);
+  MoqtPublishSkipped publish_skipped;
+  QUICHE_RETURN_IF_ERROR(ReadFullTrackName(reader, publish_skipped.name));
+  QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader));
+  return publish_skipped;
+}
+
 absl::StatusOr<MoqtRequestOk> MoqtControlMessageParser::ProcessRequestOk(
     absl::string_view data) const {
   quic::QuicDataReader reader(data);
diff --git a/quiche/quic/moqt/moqt_parser.h b/quiche/quic/moqt/moqt_parser.h
index a9c4ce5..cda423a 100644
--- a/quiche/quic/moqt/moqt_parser.h
+++ b/quiche/quic/moqt/moqt_parser.h
@@ -164,6 +164,8 @@
   absl::StatusOr<MoqtNamespace> ProcessNamespace(absl::string_view data) const;
   absl::StatusOr<MoqtNamespaceDone> ProcessNamespaceDone(
       absl::string_view data) const;
+  absl::StatusOr<MoqtPublishSkipped> ProcessPublishSkipped(
+      absl::string_view data) const;
   absl::StatusOr<MoqtTrackStatus> ProcessTrackStatus(
       absl::string_view data) const;
   absl::StatusOr<MoqtGoAway> ProcessGoAway(absl::string_view data) const;
@@ -212,6 +214,8 @@
         return parse(&MoqtControlMessageParser::ProcessNamespace);
       case MoqtMessageType::kNamespaceDone:
         return parse(&MoqtControlMessageParser::ProcessNamespaceDone);
+      case MoqtMessageType::kPublishSkipped:
+        return parse(&MoqtControlMessageParser::ProcessPublishSkipped);
       case MoqtMessageType::kTrackStatus:
         return parse(&MoqtControlMessageParser::ProcessTrackStatus);
       case MoqtMessageType::kGoAway:
diff --git a/quiche/quic/moqt/moqt_parser_test.cc b/quiche/quic/moqt/moqt_parser_test.cc
index bb16687..78e7252 100644
--- a/quiche/quic/moqt/moqt_parser_test.cc
+++ b/quiche/quic/moqt/moqt_parser_test.cc
@@ -57,6 +57,7 @@
     MoqtMessageType::kPublishNamespace,
     MoqtMessageType::kNamespace,
     MoqtMessageType::kNamespaceDone,
+    MoqtMessageType::kPublishSkipped,
     MoqtMessageType::kGoAway,
     MoqtMessageType::kSubscribeNamespace,
     MoqtMessageType::kSubscribeTracks,
diff --git a/quiche/quic/moqt/test_tools/moqt_framer_utils.cc b/quiche/quic/moqt/test_tools/moqt_framer_utils.cc
index 1b11bfd..f301c3c 100644
--- a/quiche/quic/moqt/test_tools/moqt_framer_utils.cc
+++ b/quiche/quic/moqt/test_tools/moqt_framer_utils.cc
@@ -52,6 +52,9 @@
   quiche::QuicheBuffer operator()(const MoqtNamespaceDone& message) {
     return framer.SerializeNamespaceDone(message);
   }
+  quiche::QuicheBuffer operator()(const MoqtPublishSkipped& message) {
+    return framer.SerializePublishSkipped(message);
+  }
   quiche::QuicheBuffer operator()(const MoqtTrackStatus& message) {
     return framer.SerializeTrackStatus(message);
   }
diff --git a/quiche/quic/moqt/test_tools/moqt_framer_utils.h b/quiche/quic/moqt/test_tools/moqt_framer_utils.h
index 7fd5a9d..4a53a64 100644
--- a/quiche/quic/moqt/test_tools/moqt_framer_utils.h
+++ b/quiche/quic/moqt/test_tools/moqt_framer_utils.h
@@ -20,11 +20,13 @@
 
 namespace moqt::test {
 
-using AnyMoqtControlMessage = std::variant<
-    MoqtSetup, MoqtRequestOk, MoqtRequestError, MoqtSubscribe, MoqtSubscribeOk,
-    MoqtPublishDone, MoqtRequestUpdate, MoqtPublishNamespace, MoqtTrackStatus,
-    MoqtGoAway, MoqtSubscribeNamespace, MoqtSubscribeTracks, MoqtFetch,
-    MoqtFetchOk, MoqtPublish, MoqtNamespace, MoqtNamespaceDone, MoqtObjectAck>;
+using AnyMoqtControlMessage =
+    std::variant<MoqtSetup, MoqtRequestOk, MoqtRequestError, MoqtSubscribe,
+                 MoqtSubscribeOk, MoqtPublishDone, MoqtRequestUpdate,
+                 MoqtPublishNamespace, MoqtTrackStatus, MoqtGoAway,
+                 MoqtSubscribeNamespace, MoqtSubscribeTracks, MoqtFetch,
+                 MoqtFetchOk, MoqtPublish, MoqtNamespace, MoqtNamespaceDone,
+                 MoqtPublishSkipped, MoqtObjectAck>;
 
 std::string SerializeGenericMessage(const AnyMoqtControlMessage& frame,
                                     bool use_webtrans = false);
diff --git a/quiche/quic/moqt/test_tools/moqt_test_message.h b/quiche/quic/moqt/test_tools/moqt_test_message.h
index 6c023c7..5325c92 100644
--- a/quiche/quic/moqt/test_tools/moqt_test_message.h
+++ b/quiche/quic/moqt/test_tools/moqt_test_message.h
@@ -119,7 +119,7 @@
                    MoqtRequestUpdate, MoqtPublishNamespace, MoqtTrackStatus,
                    MoqtGoAway, MoqtSubscribeNamespace, MoqtSubscribeTracks,
                    MoqtFetch, MoqtFetchOk, MoqtPublish, MoqtNamespace,
-                   MoqtNamespaceDone, MoqtObjectAck>;
+                   MoqtNamespaceDone, MoqtPublishSkipped, MoqtObjectAck>;
 
   // The total actual size of the message.
   size_t total_message_size() const { return wire_image_size_; }
@@ -1068,6 +1068,39 @@
   };
 };
 
+class QUICHE_NO_EXPORT PublishSkippedMessage : public TestMessageBase {
+ public:
+  PublishSkippedMessage() : TestMessageBase() {
+    SetWireImage(raw_packet_, sizeof(raw_packet_));
+  }
+
+  bool EqualFieldValues(const MessageStructuredData& values) const override {
+    auto cast = std::get<MoqtPublishSkipped>(values);
+    if (cast.name != publish_skipped_.name) {
+      QUIC_LOG(INFO) << "PUBLISH_SKIPPED name mismatch";
+      return false;
+    }
+    return true;
+  }
+
+  void ExpandVarints() override { ExpandVarintsImpl("vv---v---"); }
+
+  MessageStructuredData structured_data() const override {
+    return TestMessageBase::MessageStructuredData(publish_skipped_);
+  }
+
+ private:
+  uint8_t raw_packet_[12] = {
+      0x0f, 0x00, 0x09, 0x01,
+      0x03, 0x66, 0x6f, 0x6f,  // track_namespace = "foo"
+      0x03, 0x62, 0x61, 0x72,  // track_name = "bar"
+  };
+
+  MoqtPublishSkipped publish_skipped_ = {
+      FullTrackName("foo", "bar"),
+  };
+};
+
 class QUICHE_NO_EXPORT RequestOkMessage : public TestMessageBase {
  public:
   RequestOkMessage() : TestMessageBase() {
@@ -1662,6 +1695,8 @@
       return std::make_unique<NamespaceMessage>();
     case MoqtMessageType::kNamespaceDone:
       return std::make_unique<NamespaceDoneMessage>();
+    case MoqtMessageType::kPublishSkipped:
+      return std::make_unique<PublishSkippedMessage>();
     case MoqtMessageType::kTrackStatus:
       return std::make_unique<TrackStatusMessage>();
     case MoqtMessageType::kGoAway: