Add PRIORITY_UPDATE frame and implement serialization.

PiperOrigin-RevId: 347611775
Change-Id: I8856aba25403f133b2c8e089506daa8d13484e10
diff --git a/spdy/core/spdy_framer.cc b/spdy/core/spdy_framer.cc
index d425bdd..44bea4c 100644
--- a/spdy/core/spdy_framer.cc
+++ b/spdy/core/spdy_framer.cc
@@ -765,6 +765,21 @@
   return builder.take();
 }
 
+SpdySerializedFrame SpdyFramer::SerializePriorityUpdate(
+    const SpdyPriorityUpdateIR& priority_update) const {
+  const size_t total_size = kPriorityUpdateFrameMinimumSize +
+                            priority_update.priority_field_value().size();
+  SpdyFrameBuilder builder(total_size);
+  builder.BeginNewFrame(SpdyFrameType::PRIORITY_UPDATE, kNoFlags,
+                        priority_update.stream_id());
+
+  builder.WriteUInt32(priority_update.prioritized_stream_id());
+  builder.WriteBytes(priority_update.priority_field_value().data(),
+                     priority_update.priority_field_value().size());
+  DCHECK_EQ(total_size, builder.length());
+  return builder.take();
+}
+
 SpdySerializedFrame SpdyFramer::SerializeUnknown(
     const SpdyUnknownIR& unknown) const {
   const size_t total_size = kFrameHeaderSize + unknown.payload().size();
@@ -818,6 +833,10 @@
   void VisitPriority(const SpdyPriorityIR& priority) override {
     frame_ = framer_->SerializePriority(priority);
   }
+  void VisitPriorityUpdate(
+      const SpdyPriorityUpdateIR& priority_update) override {
+    frame_ = framer_->SerializePriorityUpdate(priority_update);
+  }
   void VisitUnknown(const SpdyUnknownIR& unknown) override {
     frame_ = framer_->SerializeUnknown(unknown);
   }
@@ -904,6 +923,11 @@
     flags_ = kNoFlags;
   }
 
+  void VisitPriorityUpdate(
+      const SpdyPriorityUpdateIR& /*priority_update*/) override {
+    flags_ = kNoFlags;
+  }
+
   uint8_t flags() const { return flags_; }
 
  private:
@@ -1191,6 +1215,22 @@
   return ok;
 }
 
+bool SpdyFramer::SerializePriorityUpdate(
+    const SpdyPriorityUpdateIR& priority_update,
+    ZeroCopyOutputBuffer* output) const {
+  const size_t total_size = kPriorityUpdateFrameMinimumSize +
+                            priority_update.priority_field_value().size();
+  SpdyFrameBuilder builder(total_size, output);
+  bool ok = builder.BeginNewFrame(SpdyFrameType::PRIORITY_UPDATE, kNoFlags,
+                                  priority_update.stream_id());
+
+  ok = ok && builder.WriteUInt32(priority_update.prioritized_stream_id());
+  ok = ok && builder.WriteBytes(priority_update.priority_field_value().data(),
+                                priority_update.priority_field_value().size());
+  DCHECK_EQ(total_size, builder.length());
+  return ok;
+}
+
 bool SpdyFramer::SerializeUnknown(const SpdyUnknownIR& unknown,
                                   ZeroCopyOutputBuffer* output) const {
   const size_t total_size = kFrameHeaderSize + unknown.payload().size();
@@ -1246,6 +1286,10 @@
   void VisitPriority(const SpdyPriorityIR& priority) override {
     result_ = framer_->SerializePriority(priority, output_);
   }
+  void VisitPriorityUpdate(
+      const SpdyPriorityUpdateIR& priority_update) override {
+    result_ = framer_->SerializePriorityUpdate(priority_update, output_);
+  }
   void VisitUnknown(const SpdyUnknownIR& unknown) override {
     result_ = framer_->SerializeUnknown(unknown, output_);
   }
diff --git a/spdy/core/spdy_framer.h b/spdy/core/spdy_framer.h
index 99c2037..d2ba757 100644
--- a/spdy/core/spdy_framer.h
+++ b/spdy/core/spdy_framer.h
@@ -124,6 +124,11 @@
   // the relative priority of the given stream.
   SpdySerializedFrame SerializePriority(const SpdyPriorityIR& priority) const;
 
+  // Serializes a PRIORITY_UPDATE frame.
+  // See https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html.
+  SpdySerializedFrame SerializePriorityUpdate(
+      const SpdyPriorityUpdateIR& priority_update) const;
+
   // Serializes an unknown frame given a frame header and payload.
   SpdySerializedFrame SerializeUnknown(const SpdyUnknownIR& unknown) const;
 
@@ -192,6 +197,11 @@
   bool SerializePriority(const SpdyPriorityIR& priority,
                          ZeroCopyOutputBuffer* output) const;
 
+  // Serializes a PRIORITY_UPDATE frame.
+  // See https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html.
+  bool SerializePriorityUpdate(const SpdyPriorityUpdateIR& priority_update,
+                               ZeroCopyOutputBuffer* output) const;
+
   // Serializes an unknown frame given a frame header and payload.
   bool SerializeUnknown(const SpdyUnknownIR& unknown,
                         ZeroCopyOutputBuffer* output) const;
diff --git a/spdy/core/spdy_framer_test.cc b/spdy/core/spdy_framer_test.cc
index 6a14d0c..a1d7a79 100644
--- a/spdy/core/spdy_framer_test.cc
+++ b/spdy/core/spdy_framer_test.cc
@@ -2515,6 +2515,29 @@
   CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData));
 }
 
+TEST_P(SpdyFramerTest, CreatePriorityUpdate) {
+  const char kDescription[] = "PRIORITY_UPDATE frame";
+  const unsigned char kType =
+      SerializeFrameType(SpdyFrameType::PRIORITY_UPDATE);
+  const unsigned char kFrameData[] = {
+      0x00,  0x00, 0x07,        // frame length
+      kType,                    // frame type
+      0x00,                     // flags
+      0x00,  0x00, 0x00, 0x00,  // stream ID, must be 0 for PRIORITY_UPDATE
+      0x00,  0x00, 0x00, 0x03,  // prioritized stream ID
+      'u',   '=',  '0'};        // priority field value
+  SpdyPriorityUpdateIR priority_update_ir(/* stream_id = */ 0,
+                                          /* prioritized_stream_id = */ 3,
+                                          /* priority_field_value = */ "u=0");
+  SpdySerializedFrame frame(framer_.SerializeFrame(priority_update_ir));
+  if (use_output_) {
+    EXPECT_EQ(framer_.SerializeFrame(priority_update_ir, &output_),
+              frame.size());
+    frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+  }
+  CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData));
+}
+
 TEST_P(SpdyFramerTest, CreateUnknown) {
   const char kDescription[] = "Unknown frame";
   const uint8_t kType = 0xaf;
diff --git a/spdy/core/spdy_protocol.cc b/spdy/core/spdy_protocol.cc
index b1d9f56..dfe3485 100644
--- a/spdy/core/spdy_protocol.cc
+++ b/spdy/core/spdy_protocol.cc
@@ -83,6 +83,8 @@
       return true;
     case SpdyFrameType::ALTSVC:
       return true;
+    case SpdyFrameType::PRIORITY_UPDATE:
+      return true;
   }
   return false;
 }
@@ -149,6 +151,8 @@
       return "PRIORITY";
     case SpdyFrameType::ALTSVC:
       return "ALTSVC";
+    case SpdyFrameType::PRIORITY_UPDATE:
+      return "PRIORITY_UPDATE";
   }
   return "UNKNOWN_FRAME_TYPE";
 }
@@ -559,6 +563,18 @@
   return kPriorityFrameSize;
 }
 
+void SpdyPriorityUpdateIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitPriorityUpdate(*this);
+}
+
+SpdyFrameType SpdyPriorityUpdateIR::frame_type() const {
+  return SpdyFrameType::PRIORITY_UPDATE;
+}
+
+size_t SpdyPriorityUpdateIR::size() const {
+  return kPriorityUpdateFrameMinimumSize + priority_field_value_.size();
+}
+
 void SpdyUnknownIR::Visit(SpdyFrameVisitor* visitor) const {
   return visitor->VisitUnknown(*this);
 }
diff --git a/spdy/core/spdy_protocol.h b/spdy/core/spdy_protocol.h
index 39b3daa..c05d7f6 100644
--- a/spdy/core/spdy_protocol.h
+++ b/spdy/core/spdy_protocol.h
@@ -99,6 +99,7 @@
   CONTINUATION = 0x09,
   // ALTSVC is a public extension.
   ALTSVC = 0x0a,
+  PRIORITY_UPDATE = 0x10,
 };
 
 // Flags on data packets.
@@ -307,6 +308,8 @@
 const size_t kContinuationFrameMinimumSize = kFrameHeaderSize;
 // ALTSVC frame has origin_len (2 octets) field.
 const size_t kGetAltSvcFrameMinimumSize = kFrameHeaderSize + 2;
+// PRIORITY_UPDATE frame has prioritized_stream_id (4 octets) field.
+const size_t kPriorityUpdateFrameMinimumSize = kFrameHeaderSize + 4;
 
 // Maximum possible configurable size of a frame in octets.
 const size_t kMaxFrameSizeLimit = kSpdyMaxFrameSizeLimit + kFrameHeaderSize;
@@ -883,6 +886,32 @@
   bool exclusive_;
 };
 
+class QUICHE_EXPORT_PRIVATE SpdyPriorityUpdateIR : public SpdyFrameIR {
+ public:
+  SpdyPriorityUpdateIR(SpdyStreamId stream_id,
+                       SpdyStreamId prioritized_stream_id,
+                       std::string priority_field_value)
+      : SpdyFrameIR(stream_id),
+        prioritized_stream_id_(prioritized_stream_id),
+        priority_field_value_(std::move(priority_field_value)) {}
+  SpdyPriorityUpdateIR(const SpdyPriorityUpdateIR&) = delete;
+  SpdyPriorityUpdateIR& operator=(const SpdyPriorityUpdateIR&) = delete;
+  SpdyStreamId prioritized_stream_id() const { return prioritized_stream_id_; }
+  const std::string& priority_field_value() const {
+    return priority_field_value_;
+  }
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+ private:
+  SpdyStreamId prioritized_stream_id_;
+  std::string priority_field_value_;
+};
+
 // Represents a frame of unrecognized type.
 class QUICHE_EXPORT_PRIVATE SpdyUnknownIR : public SpdyFrameIR {
  public:
@@ -1022,6 +1051,8 @@
   virtual void VisitAltSvc(const SpdyAltSvcIR& altsvc) = 0;
   virtual void VisitPriority(const SpdyPriorityIR& priority) = 0;
   virtual void VisitData(const SpdyDataIR& data) = 0;
+  virtual void VisitPriorityUpdate(
+      const SpdyPriorityUpdateIR& priority_update) = 0;
   virtual void VisitUnknown(const SpdyUnknownIR& /*unknown*/) {
     // TODO(birenroy): make abstract.
   }