Support padding of binary http request and response messages.

PiperOrigin-RevId: 486635936
diff --git a/quiche/binary_http/binary_http_message.cc b/quiche/binary_http/binary_http_message.cc
index 24feedf..6d333ac 100644
--- a/quiche/binary_http/binary_http_message.cc
+++ b/quiche/binary_http/binary_http_message.cc
@@ -33,6 +33,11 @@
   return true;
 }
 
+bool IsValidPadding(absl::string_view data) {
+  return std::all_of(data.begin(), data.end(),
+                     [](char c) { return c == '\0'; });
+}
+
 absl::StatusOr<BinaryHttpRequest::ControlData> DecodeControlData(
     quiche::QuicheDataReader& reader) {
   BinaryHttpRequest::ControlData control_data;
@@ -107,6 +112,10 @@
       !status.ok()) {
     return status;
   }
+  if (!IsValidPadding(reader.PeekRemainingPayload())) {
+    return absl::InvalidArgumentError("Non-zero padding.");
+  }
+  request.set_num_padding_bytes(reader.BytesRemaining());
   return request;
 }
 
@@ -148,13 +157,16 @@
       !status.ok()) {
     return status;
   }
+  if (!IsValidPadding(reader.PeekRemainingPayload())) {
+    return absl::InvalidArgumentError("Non-zero padding.");
+  }
+  response.set_num_padding_bytes(reader.BytesRemaining());
   return response;
 }
 
 uint64_t StringPieceVarInt62Len(absl::string_view s) {
   return quiche::QuicheDataWriter::GetVarInt62Len(s.length()) + s.length();
 }
-
 }  // namespace
 
 void BinaryHttpMessage::Fields::AddField(BinaryHttpMessage::Field field) {
@@ -179,13 +191,13 @@
   return absl::OkStatus();
 }
 
-uint64_t BinaryHttpMessage::Fields::EncodedSize() const {
-  uint64_t size = EncodedFieldsSize();
+size_t BinaryHttpMessage::Fields::EncodedSize() const {
+  const size_t size = EncodedFieldsSize();
   return size + quiche::QuicheDataWriter::GetVarInt62Len(size);
 }
 
-uint64_t BinaryHttpMessage::Fields::EncodedFieldsSize() const {
-  uint64_t size = 0;
+size_t BinaryHttpMessage::Fields::EncodedFieldsSize() const {
+  size_t size = 0;
   for (const BinaryHttpMessage::Field& field : fields_) {
     size += StringPieceVarInt62Len(field.name) +
             StringPieceVarInt62Len(field.value);
@@ -217,7 +229,7 @@
   return absl::OkStatus();
 }
 
-uint64_t BinaryHttpMessage::EncodedKnownLengthFieldsAndBodySize() const {
+size_t BinaryHttpMessage::EncodedKnownLengthFieldsAndBodySize() const {
   return header_fields_.EncodedSize() + StringPieceVarInt62Len(body_);
 }
 
@@ -263,17 +275,18 @@
       !status.ok()) {
     return status;
   }
-  QUICHE_DCHECK_EQ(writer.remaining(), 0u);
+  QUICHE_DCHECK_EQ(writer.remaining(), num_padding_bytes());
+  writer.WritePadding();
   return data;
 }
 
-uint64_t BinaryHttpResponse::EncodedSize() const {
-  uint64_t size = sizeof(kKnownLengthResponseFraming);
+size_t BinaryHttpResponse::EncodedSize() const {
+  size_t size = sizeof(kKnownLengthResponseFraming);
   for (const auto& informational : informational_response_control_data_) {
     size += informational.EncodedSize();
   }
   return size + quiche::QuicheDataWriter::GetVarInt62Len(status_code_) +
-         EncodedKnownLengthFieldsAndBodySize();
+         EncodedKnownLengthFieldsAndBodySize() + num_padding_bytes();
 }
 
 void BinaryHttpResponse::InformationalResponse::AddField(absl::string_view name,
@@ -288,7 +301,7 @@
   return fields_.Encode(writer);
 }
 
-uint64_t BinaryHttpResponse::InformationalResponse::EncodedSize() const {
+size_t BinaryHttpResponse::InformationalResponse::EncodedSize() const {
   return quiche::QuicheDataWriter::GetVarInt62Len(status_code_) +
          fields_.EncodedSize();
 }
@@ -325,10 +338,10 @@
   return absl::OkStatus();
 }
 
-uint64_t BinaryHttpRequest::EncodedControlDataSize() const {
-  uint64_t size = StringPieceVarInt62Len(control_data_.method) +
-                  StringPieceVarInt62Len(control_data_.scheme) +
-                  StringPieceVarInt62Len(control_data_.path);
+size_t BinaryHttpRequest::EncodedControlDataSize() const {
+  size_t size = StringPieceVarInt62Len(control_data_.method) +
+                StringPieceVarInt62Len(control_data_.scheme) +
+                StringPieceVarInt62Len(control_data_.path);
   if (!has_host()) {
     size += StringPieceVarInt62Len(control_data_.authority);
   } else {
@@ -337,9 +350,9 @@
   return size;
 }
 
-uint64_t BinaryHttpRequest::EncodedSize() const {
+size_t BinaryHttpRequest::EncodedSize() const {
   return sizeof(kKnownLengthRequestFraming) + EncodedControlDataSize() +
-         EncodedKnownLengthFieldsAndBodySize();
+         EncodedKnownLengthFieldsAndBodySize() + num_padding_bytes();
 }
 
 // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-known-length-messages
@@ -357,7 +370,8 @@
       !status.ok()) {
     return status;
   }
-  QUICHE_DCHECK_EQ(writer.remaining(), 0u);
+  QUICHE_DCHECK_EQ(writer.remaining(), num_padding_bytes());
+  writer.WritePadding();
   return data;
 }
 
diff --git a/quiche/binary_http/binary_http_message.h b/quiche/binary_http/binary_http_message.h
index 70b8106..8f1f8f8 100644
--- a/quiche/binary_http/binary_http_message.h
+++ b/quiche/binary_http/binary_http_message.h
@@ -1,6 +1,7 @@
 #ifndef QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_
 #define QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_
 
+#include <cstddef>
 #include <cstdint>
 #include <functional>
 #include <memory>
@@ -51,9 +52,16 @@
   }
 
   void swap_body(std::string& body) { body_.swap(body); }
+  void set_num_padding_bytes(size_t num_padding_bytes) {
+    num_padding_bytes_ = num_padding_bytes;
+  }
+  size_t num_padding_bytes() const { return num_padding_bytes_; }
 
   absl::string_view body() const { return body_; }
 
+  // Returns the number of bytes `Serialize` will return, including padding.
+  virtual size_t EncodedSize() const = 0;
+
   // Returns the Binary Http formatted message.
   virtual absl::StatusOr<std::string> Serialize() const = 0;
   // TODO(bschneider): Add AddTrailerField for chunked messages
@@ -81,17 +89,18 @@
 
     // The number of returned by EncodedFieldsSize
     // plus the number of bytes used in the varint holding that value.
-    uint64_t EncodedSize() const;
+    size_t EncodedSize() const;
 
    private:
     // Number of bytes of just the set of fields.
-    uint64_t EncodedFieldsSize() const;
+    size_t EncodedFieldsSize() const;
 
     // Fields in insertion order.
     std::vector<BinaryHttpMessage::Field> fields_;
   };
 
-  bool operator==(const BinaryHttpMessage& rhs) const {
+  // Checks equality excluding padding.
+  bool IsPayloadEqual(const BinaryHttpMessage& rhs) const {
     // `has_host_` is derived from `header_fields_` so it doesn't need to be
     // tested directly.
     return body_ == rhs.body_ && header_fields_ == rhs.header_fields_;
@@ -99,13 +108,14 @@
 
   absl::Status EncodeKnownLengthFieldsAndBody(
       quiche::QuicheDataWriter& writer) const;
-  uint64_t EncodedKnownLengthFieldsAndBodySize() const;
+  size_t EncodedKnownLengthFieldsAndBodySize() const;
   bool has_host() const { return has_host_; }
 
  private:
   std::string body_;
   Fields header_fields_;
   bool has_host_ = false;
+  size_t num_padding_bytes_ = 0;
 };
 
 void QUICHE_EXPORT PrintTo(const BinaryHttpMessage::Field& msg,
@@ -113,7 +123,10 @@
 
 class QUICHE_EXPORT BinaryHttpRequest : public BinaryHttpMessage {
  public:
-  // HTTP request must have all of the following fields.
+  // HTTP request must have method, scheme, and path fields.
+  // The `authority` field is required unless a `host` header field is added.
+  // If a `host` header field is added, `authority` is serialized as the empty
+  // string.
   // Some examples are:
   //   scheme: HTTP
   //   authority: www.example.com
@@ -137,14 +150,21 @@
   // Deserialize
   static absl::StatusOr<BinaryHttpRequest> Create(absl::string_view data);
 
+  size_t EncodedSize() const override;
   absl::StatusOr<std::string> Serialize() const override;
   const ControlData& control_data() const { return control_data_; }
 
   virtual std::string DebugString() const override;
 
-  bool operator==(const BinaryHttpRequest& rhs) const {
+  // Returns true if the contents of the requests are equal, excluding padding.
+  bool IsPayloadEqual(const BinaryHttpRequest& rhs) const {
     return control_data_ == rhs.control_data_ &&
-           BinaryHttpMessage::operator==(rhs);
+           BinaryHttpMessage::IsPayloadEqual(rhs);
+  }
+
+  bool operator==(const BinaryHttpRequest& rhs) const {
+    return IsPayloadEqual(rhs) &&
+           num_padding_bytes() == rhs.num_padding_bytes();
   }
 
   bool operator!=(const BinaryHttpRequest& rhs) const {
@@ -154,9 +174,7 @@
  private:
   absl::Status EncodeControlData(quiche::QuicheDataWriter& writer) const;
 
-  uint64_t EncodedControlDataSize() const;
-
-  uint64_t EncodedSize() const;
+  size_t EncodedControlDataSize() const;
 
   // Returns Binary Http known length request formatted request.
   absl::StatusOr<std::string> EncodeAsKnownLength() const;
@@ -210,7 +228,7 @@
     // Give BinaryHttpResponse access to Encoding functionality.
     friend class BinaryHttpResponse;
 
-    uint64_t EncodedSize() const;
+    size_t EncodedSize() const;
 
     // Appends the encoded fields and body to `writer`.
     absl::Status Encode(quiche::QuicheDataWriter& writer) const;
@@ -225,6 +243,7 @@
   // Deserialize
   static absl::StatusOr<BinaryHttpResponse> Create(absl::string_view data);
 
+  size_t EncodedSize() const override;
   absl::StatusOr<std::string> Serialize() const override;
 
   // Informational status codes must be between 100 and 199 inclusive.
@@ -241,12 +260,19 @@
 
   virtual std::string DebugString() const override;
 
-  bool operator==(const BinaryHttpResponse& rhs) const {
+  // Returns true if the contents of the requests are equal, excluding padding.
+  bool IsPayloadEqual(const BinaryHttpResponse& rhs) const {
     return informational_response_control_data_ ==
                rhs.informational_response_control_data_ &&
            status_code_ == rhs.status_code_ &&
-           BinaryHttpMessage::operator==(rhs);
+           BinaryHttpMessage::IsPayloadEqual(rhs);
   }
+
+  bool operator==(const BinaryHttpResponse& rhs) const {
+    return IsPayloadEqual(rhs) &&
+           num_padding_bytes() == rhs.num_padding_bytes();
+  }
+
   bool operator!=(const BinaryHttpResponse& rhs) const {
     return !(*this == rhs);
   }
@@ -255,8 +281,6 @@
   // Returns Binary Http known length request formatted response.
   absl::StatusOr<std::string> EncodeAsKnownLength() const;
 
-  uint64_t EncodedSize() const;
-
   std::vector<InformationalResponse> informational_response_control_data_;
   const uint16_t status_code_;
 };
diff --git a/quiche/binary_http/binary_http_message_test.cc b/quiche/binary_http/binary_http_message_test.cc
index 1ecd9f9..df8c036 100644
--- a/quiche/binary_http/binary_http_message_test.cc
+++ b/quiche/binary_http/binary_http_message_test.cc
@@ -707,4 +707,80 @@
   EXPECT_NE(response, no_informational);
 }
 
+MATCHER_P(HasEqPayload, value, "Payloads of messages are equivalent.") {
+  return arg.IsPayloadEqual(value);
+}
+
+template <typename T>
+void TestPadding(T& message) {
+  const auto data_so = message.Serialize();
+  ASSERT_TRUE(data_so.ok());
+  auto data = *data_so;
+  ASSERT_EQ(data.size(), message.EncodedSize());
+
+  message.set_num_padding_bytes(10);
+  const auto padded_data_so = message.Serialize();
+  ASSERT_TRUE(padded_data_so.ok());
+  const auto padded_data = *padded_data_so;
+  ASSERT_EQ(padded_data.size(), message.EncodedSize());
+
+  // Check padding size output.
+  ASSERT_EQ(data.size() + 10, padded_data.size());
+  // Check for valid null byte padding output
+  data.resize(data.size() + 10);
+  ASSERT_EQ(data, padded_data);
+
+  // Deserialize padded and not padded, and verify they are the same.
+  const auto deserialized_padded_message_so = T::Create(data);
+  ASSERT_TRUE(deserialized_padded_message_so.ok());
+  const auto deserialized_padded_message = *deserialized_padded_message_so;
+  ASSERT_EQ(deserialized_padded_message, message);
+  ASSERT_EQ(deserialized_padded_message.num_padding_bytes(), size_t(10));
+
+  // Invalid padding
+  data[data.size() - 1] = 'a';
+  const auto bad_so = T::Create(data);
+  ASSERT_FALSE(bad_so.ok());
+
+  // Check that padding does not impact equality.
+  data.resize(data.size() - 10);
+  const auto deserialized_message_so = T::Create(data);
+  ASSERT_TRUE(deserialized_message_so.ok());
+  const auto deserialized_message = *deserialized_message_so;
+  ASSERT_EQ(deserialized_message.num_padding_bytes(), size_t(0));
+  // Confirm that the message payloads are equal, but not fully equivalent due
+  // to padding.
+  ASSERT_THAT(deserialized_message, HasEqPayload(deserialized_padded_message));
+  ASSERT_NE(deserialized_message, deserialized_padded_message);
+}
+
+TEST(BinaryHttpRequest, Padding) {
+  /*
+    GET /hello.txt HTTP/1.1
+    User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
+    Host: www.example.com
+    Accept-Language: en, mi
+  */
+  BinaryHttpRequest request({"GET", "https", "", "/hello.txt"});
+  request
+      .AddHeaderField({"User-Agent",
+                       "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3"})
+      ->AddHeaderField({"Host", "www.example.com"})
+      ->AddHeaderField({"Accept-Language", "en, mi"});
+  TestPadding(request);
+}
+
+TEST(BinaryHttpResponse, Padding) {
+  /*
+    HTTP/1.1 200 OK
+    Server: Apache
+
+    Hello, world!
+  */
+  BinaryHttpResponse response(200);
+  response.AddHeaderField({"Server", "Apache"});
+  response.set_body("Hello, world!\r\n");
+  TestPadding(response);
+}
+
 }  // namespace quiche