Add ACCEPT_CH frame and implement serialization.

PiperOrigin-RevId: 355853425
Change-Id: Iec612eaf5b10fdbb80a0f6c1a098c003354a5973
diff --git a/spdy/core/spdy_framer.cc b/spdy/core/spdy_framer.cc
index 22d3fff..e34b068 100644
--- a/spdy/core/spdy_framer.cc
+++ b/spdy/core/spdy_framer.cc
@@ -780,6 +780,24 @@
   return builder.take();
 }
 
+SpdySerializedFrame SpdyFramer::SerializeAcceptCh(
+    const SpdyAcceptChIR& accept_ch) const {
+  const size_t total_size = accept_ch.size();
+  SpdyFrameBuilder builder(total_size);
+  builder.BeginNewFrame(SpdyFrameType::ACCEPT_CH, kNoFlags,
+                        accept_ch.stream_id());
+
+  for (const SpdyAcceptChIR::OriginValuePair& entry : accept_ch.entries()) {
+    builder.WriteUInt16(entry.origin.size());
+    builder.WriteBytes(entry.origin.data(), entry.origin.size());
+    builder.WriteUInt16(entry.value.size());
+    builder.WriteBytes(entry.value.data(), entry.value.size());
+  }
+
+  QUICHE_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();
@@ -837,6 +855,9 @@
       const SpdyPriorityUpdateIR& priority_update) override {
     frame_ = framer_->SerializePriorityUpdate(priority_update);
   }
+  void VisitAcceptCh(const SpdyAcceptChIR& accept_ch) override {
+    frame_ = framer_->SerializeAcceptCh(accept_ch);
+  }
   void VisitUnknown(const SpdyUnknownIR& unknown) override {
     frame_ = framer_->SerializeUnknown(unknown);
   }
@@ -928,6 +949,10 @@
     flags_ = kNoFlags;
   }
 
+  void VisitAcceptCh(const SpdyAcceptChIR& /*accept_ch*/) override {
+    flags_ = kNoFlags;
+  }
+
   uint8_t flags() const { return flags_; }
 
  private:
@@ -1231,6 +1256,24 @@
   return ok;
 }
 
+bool SpdyFramer::SerializeAcceptCh(const SpdyAcceptChIR& accept_ch,
+                                   ZeroCopyOutputBuffer* output) const {
+  const size_t total_size = accept_ch.size();
+  SpdyFrameBuilder builder(total_size, output);
+  bool ok = builder.BeginNewFrame(SpdyFrameType::ACCEPT_CH, kNoFlags,
+                                  accept_ch.stream_id());
+
+  for (const SpdyAcceptChIR::OriginValuePair& entry : accept_ch.entries()) {
+    ok = ok && builder.WriteUInt16(entry.origin.size());
+    ok = ok && builder.WriteBytes(entry.origin.data(), entry.origin.size());
+    ok = ok && builder.WriteUInt16(entry.value.size());
+    ok = ok && builder.WriteBytes(entry.value.data(), entry.value.size());
+  }
+
+  QUICHE_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();
@@ -1290,6 +1333,10 @@
       const SpdyPriorityUpdateIR& priority_update) override {
     result_ = framer_->SerializePriorityUpdate(priority_update, output_);
   }
+  void VisitAcceptCh(const SpdyAcceptChIR& accept_ch) override {
+    result_ = framer_->SerializeAcceptCh(accept_ch, 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 260a5df..d7a81c1 100644
--- a/spdy/core/spdy_framer.h
+++ b/spdy/core/spdy_framer.h
@@ -129,6 +129,10 @@
   SpdySerializedFrame SerializePriorityUpdate(
       const SpdyPriorityUpdateIR& priority_update) const;
 
+  // Serializes an ACCEPT_CH frame.  See
+  // https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02.
+  SpdySerializedFrame SerializeAcceptCh(const SpdyAcceptChIR& accept_ch) const;
+
   // Serializes an unknown frame given a frame header and payload.
   SpdySerializedFrame SerializeUnknown(const SpdyUnknownIR& unknown) const;
 
@@ -202,6 +206,11 @@
   bool SerializePriorityUpdate(const SpdyPriorityUpdateIR& priority_update,
                                ZeroCopyOutputBuffer* output) const;
 
+  // Serializes an ACCEPT_CH frame.  See
+  // https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02.
+  bool SerializeAcceptCh(const SpdyAcceptChIR& accept_ch,
+                         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 53bfc2c..96b9f4c 100644
--- a/spdy/core/spdy_framer_test.cc
+++ b/spdy/core/spdy_framer_test.cc
@@ -2544,6 +2544,36 @@
   CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData));
 }
 
+TEST_P(SpdyFramerTest, CreateAcceptCh) {
+  const char kDescription[] = "ACCEPT_CH frame";
+  const unsigned char kType = SerializeFrameType(SpdyFrameType::ACCEPT_CH);
+  const unsigned char kFrameData[] = {
+      0x00,  0x00, 0x2d,                  // frame length
+      kType,                              // frame type
+      0x00,                               // flags
+      0x00,  0x00, 0x00, 0x00,            // stream ID, must be 0 for ACCEPT_CH
+      0x00,  0x0f,                        // origin length
+      'w',   'w',  'w',  '.',  'e', 'x',  // origin
+      'a',   'm',  'p',  'l',  'e', '.',  //
+      'c',   'o',  'm',                   //
+      0x00,  0x03,                        // value length
+      'f',   'o',  'o',                   // value
+      0x00,  0x10,                        // origin length
+      'm',   'a',  'i',  'l',  '.', 'e',  //
+      'x',   'a',  'm',  'p',  'l', 'e',  //
+      '.',   'c',  'o',  'm',             //
+      0x00,  0x03,                        // value length
+      'b',   'a',  'r'};                  // value
+  SpdyAcceptChIR accept_ch_ir(
+      {{"www.example.com", "foo"}, {"mail.example.com", "bar"}});
+  SpdySerializedFrame frame(framer_.SerializeFrame(accept_ch_ir));
+  if (use_output_) {
+    EXPECT_EQ(framer_.SerializeFrame(accept_ch_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 2d627d8..2f66ba7 100644
--- a/spdy/core/spdy_protocol.cc
+++ b/spdy/core/spdy_protocol.cc
@@ -85,6 +85,8 @@
       return true;
     case SpdyFrameType::PRIORITY_UPDATE:
       return true;
+    case SpdyFrameType::ACCEPT_CH:
+      return true;
   }
   return false;
 }
@@ -153,6 +155,8 @@
       return "ALTSVC";
     case SpdyFrameType::PRIORITY_UPDATE:
       return "PRIORITY_UPDATE";
+    case SpdyFrameType::ACCEPT_CH:
+      return "ACCEPT_CH";
   }
   return "UNKNOWN_FRAME_TYPE";
 }
@@ -578,6 +582,23 @@
   return kPriorityUpdateFrameMinimumSize + priority_field_value_.size();
 }
 
+void SpdyAcceptChIR::Visit(SpdyFrameVisitor* visitor) const {
+  return visitor->VisitAcceptCh(*this);
+}
+
+SpdyFrameType SpdyAcceptChIR::frame_type() const {
+  return SpdyFrameType::ACCEPT_CH;
+}
+
+size_t SpdyAcceptChIR::size() const {
+  size_t total_size = kAcceptChFrameMinimumSize;
+  for (const OriginValuePair& entry : entries_) {
+    total_size += entry.origin.size() + entry.value.size() +
+                  kAcceptChFramePerEntryOverhead;
+  }
+  return total_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 8d5d3d8..635439b 100644
--- a/spdy/core/spdy_protocol.h
+++ b/spdy/core/spdy_protocol.h
@@ -100,6 +100,7 @@
   // ALTSVC is a public extension.
   ALTSVC = 0x0a,
   PRIORITY_UPDATE = 0x10,
+  ACCEPT_CH = 0x89,
 };
 
 // Flags on data packets.
@@ -313,6 +314,11 @@
 const size_t kGetAltSvcFrameMinimumSize = kFrameHeaderSize + 2;
 // PRIORITY_UPDATE frame has prioritized_stream_id (4 octets) field.
 const size_t kPriorityUpdateFrameMinimumSize = kFrameHeaderSize + 4;
+// ACCEPT_CH frame may have empty payload.
+const size_t kAcceptChFrameMinimumSize = kFrameHeaderSize;
+// Each ACCEPT_CH frame entry has a 16-bit origin length and a 16-bit value
+// length.
+const size_t kAcceptChFramePerEntryOverhead = 4;
 
 // Maximum possible configurable size of a frame in octets.
 const size_t kMaxFrameSizeLimit = kSpdyMaxFrameSizeLimit + kFrameHeaderSize;
@@ -915,6 +921,30 @@
   std::string priority_field_value_;
 };
 
+class QUICHE_EXPORT_PRIVATE SpdyAcceptChIR : public SpdyFrameIR {
+ public:
+  struct OriginValuePair {
+    std::string origin;
+    std::string value;
+  };
+
+  SpdyAcceptChIR(std::vector<OriginValuePair> entries)
+      : entries_(std::move(entries)) {}
+  SpdyAcceptChIR(const SpdyAcceptChIR&) = delete;
+  SpdyAcceptChIR& operator=(const SpdyAcceptChIR&) = delete;
+
+  void Visit(SpdyFrameVisitor* visitor) const override;
+
+  SpdyFrameType frame_type() const override;
+
+  size_t size() const override;
+
+  const std::vector<OriginValuePair>& entries() const { return entries_; }
+
+ private:
+  std::vector<OriginValuePair> entries_;
+};
+
 // Represents a frame of unrecognized type.
 class QUICHE_EXPORT_PRIVATE SpdyUnknownIR : public SpdyFrameIR {
  public:
@@ -1056,6 +1086,7 @@
   virtual void VisitData(const SpdyDataIR& data) = 0;
   virtual void VisitPriorityUpdate(
       const SpdyPriorityUpdateIR& priority_update) = 0;
+  virtual void VisitAcceptCh(const SpdyAcceptChIR& accept_ch) = 0;
   virtual void VisitUnknown(const SpdyUnknownIR& /*unknown*/) {
     // TODO(birenroy): make abstract.
   }