Open source Binary Http.

Currently unused in production.
cl/470499594 tried to do this but needs source_file_patterns update, which this CL does.

Startblock:
   has LGTM from everyone
PiperOrigin-RevId: 471034502
diff --git a/build/source_list.bzl b/build/source_list.bzl
index deb4866..55336fa 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -1469,6 +1469,13 @@
     "quic/load_balancer/load_balancer_server_id_map_test.cc",
     "quic/load_balancer/load_balancer_server_id_test.cc",
 ]
+binary_http_hdrs = [
+    "binary_http/binary_http_message.h",
+]
+binary_http_srcs = [
+    "binary_http/binary_http_message.cc",
+    "binary_http/binary_http_message_test.cc",
+]
 qbone_hdrs = [
     "quic/qbone/bonnet/icmp_reachable.h",
     "quic/qbone/bonnet/icmp_reachable_interface.h",
diff --git a/build/source_list.gni b/build/source_list.gni
index fbcb0c4..802b493 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -1469,6 +1469,13 @@
     "src/quiche/quic/load_balancer/load_balancer_server_id_map_test.cc",
     "src/quiche/quic/load_balancer/load_balancer_server_id_test.cc",
 ]
+binary_http_hdrs = [
+    "src/quiche/binary_http/binary_http_message.h",
+]
+binary_http_srcs = [
+    "src/quiche/binary_http/binary_http_message.cc",
+    "src/quiche/binary_http/binary_http_message_test.cc",
+]
 qbone_hdrs = [
     "src/quiche/quic/qbone/bonnet/icmp_reachable.h",
     "src/quiche/quic/qbone/bonnet/icmp_reachable_interface.h",
diff --git a/build/source_list.json b/build/source_list.json
index dffd3bd..ea2cf5e 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -1468,6 +1468,13 @@
     "quiche/quic/load_balancer/load_balancer_server_id_map_test.cc",
     "quiche/quic/load_balancer/load_balancer_server_id_test.cc"
   ],
+  "binary_http_hdrs": [
+    "quiche/binary_http/binary_http_message.h"
+  ],
+  "binary_http_srcs": [
+    "quiche/binary_http/binary_http_message.cc",
+    "quiche/binary_http/binary_http_message_test.cc"
+  ],
   "qbone_hdrs": [
     "quiche/quic/qbone/bonnet/icmp_reachable.h",
     "quiche/quic/qbone/bonnet/icmp_reachable_interface.h",
diff --git a/quiche/binary_http/binary_http_message.cc b/quiche/binary_http/binary_http_message.cc
new file mode 100644
index 0000000..360c56d
--- /dev/null
+++ b/quiche/binary_http/binary_http_message.cc
@@ -0,0 +1,402 @@
+#include "quiche/binary_http/binary_http_message.h"
+
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "quiche/common/quiche_data_reader.h"
+#include "quiche/common/quiche_data_writer.h"
+
+namespace quiche {
+namespace {
+
+constexpr uint8_t kKnownLengthRequestFraming = 0;
+constexpr uint8_t kKnownLengthResponseFraming = 1;
+
+bool ReadStringValue(quiche::QuicheDataReader& reader, std::string& data) {
+  absl::string_view data_view;
+  if (!reader.ReadStringPieceVarInt62(&data_view)) {
+    return false;
+  }
+  data = std::string(data_view);
+  return true;
+}
+
+absl::StatusOr<BinaryHttpRequest::ControlData> DecodeControlData(
+    quiche::QuicheDataReader& reader) {
+  BinaryHttpRequest::ControlData control_data;
+  if (!ReadStringValue(reader, control_data.method)) {
+    return absl::InvalidArgumentError("Failed to read method.");
+  }
+  if (!ReadStringValue(reader, control_data.scheme)) {
+    return absl::InvalidArgumentError("Failed to read scheme.");
+  }
+  if (!ReadStringValue(reader, control_data.authority)) {
+    return absl::InvalidArgumentError("Failed to read authority.");
+  }
+  if (!ReadStringValue(reader, control_data.path)) {
+    return absl::InvalidArgumentError("Failed to read path.");
+  }
+  return control_data;
+}
+
+absl::Status DecodeFields(
+    quiche::QuicheDataReader& reader,
+    const std::function<void(absl::string_view name, absl::string_view value)>&
+        callback) {
+  absl::string_view fields;
+  if (!reader.ReadStringPieceVarInt62(&fields)) {
+    return absl::InvalidArgumentError("Failed to read fields.");
+  }
+  quiche::QuicheDataReader fields_reader(fields);
+  while (!fields_reader.IsDoneReading()) {
+    absl::string_view name;
+    if (!fields_reader.ReadStringPieceVarInt62(&name)) {
+      return absl::InvalidArgumentError("Failed to read field name.");
+    }
+    absl::string_view value;
+    if (!fields_reader.ReadStringPieceVarInt62(&value)) {
+      return absl::InvalidArgumentError("Failed to read field value.");
+    }
+    callback(name, value);
+  }
+  return absl::OkStatus();
+}
+
+absl::Status DecodeFieldsAndBody(quiche::QuicheDataReader& reader,
+                                 BinaryHttpMessage& message) {
+  if (const absl::Status status = DecodeFields(
+          reader,
+          [&message](absl::string_view name, absl::string_view value) {
+            message.AddHeaderField({std::string(name), std::string(value)});
+          });
+      !status.ok()) {
+    return status;
+  }
+  // TODO(bschneider): Handle case where remaining message is truncated.
+  // Skip it on encode as well.
+  // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-padding-and-truncation
+  absl::string_view body;
+  if (!reader.ReadStringPieceVarInt62(&body)) {
+    return absl::InvalidArgumentError("Failed to read body.");
+  }
+  message.set_body(std::string(body));
+  // TODO(bschneider): Check for / read-in any trailer-fields
+  return absl::OkStatus();
+}
+
+absl::StatusOr<BinaryHttpRequest> DecodeKnownLengthRequest(
+    quiche::QuicheDataReader& reader) {
+  const auto control_data = DecodeControlData(reader);
+  if (!control_data.ok()) {
+    return control_data.status();
+  }
+  BinaryHttpRequest request(std::move(*control_data));
+  if (const absl::Status status = DecodeFieldsAndBody(reader, request);
+      !status.ok()) {
+    return status;
+  }
+  return request;
+}
+
+absl::StatusOr<BinaryHttpResponse> DecodeKnownLengthResponse(
+    quiche::QuicheDataReader& reader) {
+  std::vector<std::pair<uint16_t, std::vector<BinaryHttpMessage::Field>>>
+      informational_responses;
+  uint64_t status_code;
+  bool reading_response_control_data = true;
+  while (reading_response_control_data) {
+    if (!reader.ReadVarInt62(&status_code)) {
+      return absl::InvalidArgumentError("Failed to read status code.");
+    }
+    if (status_code >= 100 && status_code <= 199) {
+      std::vector<BinaryHttpMessage::Field> fields;
+      if (const absl::Status status = DecodeFields(
+              reader,
+              [&fields](absl::string_view name, absl::string_view value) {
+                fields.push_back({std::string(name), std::string(value)});
+              });
+          !status.ok()) {
+        return status;
+      }
+      informational_responses.emplace_back(status_code, std::move(fields));
+    } else {
+      reading_response_control_data = false;
+    }
+  }
+  BinaryHttpResponse response(status_code);
+  for (const auto& informational_response : informational_responses) {
+    if (const absl::Status status = response.AddInformationalResponse(
+            informational_response.first,
+            std::move(informational_response.second));
+        !status.ok()) {
+      return status;
+    }
+  }
+  if (const absl::Status status = DecodeFieldsAndBody(reader, response);
+      !status.ok()) {
+    return status;
+  }
+  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) {
+  fields_.push_back(std::move(field));
+}
+
+// Encode fields in the order they were initially inserted.
+// Updates do not change order.
+absl::Status BinaryHttpMessage::Fields::Encode(
+    quiche::QuicheDataWriter& writer) const {
+  if (!writer.WriteVarInt62(EncodedFieldsSize())) {
+    return absl::InvalidArgumentError("Failed to write encoded field size.");
+  }
+  for (const BinaryHttpMessage::Field& field : fields_) {
+    if (!writer.WriteStringPieceVarInt62(field.name)) {
+      return absl::InvalidArgumentError("Failed to write field name.");
+    }
+    if (!writer.WriteStringPieceVarInt62(field.value)) {
+      return absl::InvalidArgumentError("Failed to write field value.");
+    }
+  }
+  return absl::OkStatus();
+}
+
+uint64_t BinaryHttpMessage::Fields::EncodedSize() const {
+  uint64_t size = EncodedFieldsSize();
+  return size + quiche::QuicheDataWriter::GetVarInt62Len(size);
+}
+
+uint64_t BinaryHttpMessage::Fields::EncodedFieldsSize() const {
+  uint64_t size = 0;
+  for (const BinaryHttpMessage::Field& field : fields_) {
+    size += StringPieceVarInt62Len(field.name) +
+            StringPieceVarInt62Len(field.value);
+  }
+  return size;
+}
+
+BinaryHttpMessage* BinaryHttpMessage::AddHeaderField(
+    BinaryHttpMessage::Field field) {
+  const std::string lower_name = absl::AsciiStrToLower(field.name);
+  if (lower_name == "host") {
+    has_host_ = true;
+  }
+  header_fields_.AddField({std::move(lower_name), std::move(field.value)});
+  return this;
+}
+
+// Appends the encoded fields and body to data.
+absl::Status BinaryHttpMessage::EncodeKnownLengthFieldsAndBody(
+    quiche::QuicheDataWriter& writer) const {
+  if (const absl::Status status = header_fields_.Encode(writer); !status.ok()) {
+    return status;
+  }
+  if (!writer.WriteStringPieceVarInt62(body_)) {
+    return absl::InvalidArgumentError("Failed to encode body.");
+  }
+  // TODO(bschneider): Consider support for trailer fields on known-length
+  // requests. Trailers are atypical for a known-length request.
+  return absl::OkStatus();
+}
+
+uint64_t BinaryHttpMessage::EncodedKnownLengthFieldsAndBodySize() const {
+  return header_fields_.EncodedSize() + StringPieceVarInt62Len(body_);
+}
+
+absl::Status BinaryHttpResponse::AddInformationalResponse(
+    uint16_t status_code, std::vector<Field> header_fields) {
+  if (status_code < 100) {
+    return absl::InvalidArgumentError("status code < 100");
+  }
+  if (status_code > 199) {
+    return absl::InvalidArgumentError("status code > 199");
+  }
+  InformationalResponseInternal data(status_code);
+  for (Field& header : header_fields) {
+    data.AddField(header.name, std::move(header.value));
+  }
+  informational_response_control_data_.push_back(std::move(data));
+  return absl::OkStatus();
+}
+
+absl::StatusOr<std::string> BinaryHttpResponse::Serialize() const {
+  // Only supporting known length requests so far.
+  return EncodeAsKnownLength();
+}
+
+std::vector<BinaryHttpResponse::InformationalResponse>
+BinaryHttpResponse::GetInformationalResponse() const {
+  std::vector<InformationalResponse> data;
+  data.reserve(informational_response_control_data_.size());
+  for (const auto& control_data : informational_response_control_data_) {
+    data.push_back({control_data.response_code(), control_data.fields()});
+  }
+  return data;
+}
+
+absl::StatusOr<std::string> BinaryHttpResponse::EncodeAsKnownLength() const {
+  std::string data;
+  data.resize(EncodedSize());
+  quiche::QuicheDataWriter writer(data.size(), data.data());
+  if (!writer.WriteUInt8(kKnownLengthResponseFraming)) {
+    return absl::InvalidArgumentError("Failed to write framing indicator");
+  }
+  // Informational response
+  for (const auto& informational : informational_response_control_data_) {
+    if (const absl::Status status = informational.Encode(writer);
+        !status.ok()) {
+      return status;
+    }
+  }
+  if (!writer.WriteVarInt62(status_code_)) {
+    return absl::InvalidArgumentError("Failed to write status code");
+  }
+  if (const absl::Status status = EncodeKnownLengthFieldsAndBody(writer);
+      !status.ok()) {
+    return status;
+  }
+  QUICHE_DCHECK_EQ(writer.remaining(), 0u);
+  return data;
+}
+
+uint64_t BinaryHttpResponse::EncodedSize() const {
+  uint64_t size = sizeof(kKnownLengthResponseFraming);
+  for (const auto& informational : informational_response_control_data_) {
+    size += informational.EncodedSize();
+  }
+  return size + quiche::QuicheDataWriter::GetVarInt62Len(status_code_) +
+         EncodedKnownLengthFieldsAndBodySize();
+}
+
+void BinaryHttpResponse::InformationalResponseInternal::AddField(
+    absl::string_view name, std::string value) {
+  fields_.AddField({absl::AsciiStrToLower(name), std::move(value)});
+}
+
+// Appends the encoded fields and body to data.
+absl::Status BinaryHttpResponse::InformationalResponseInternal::Encode(
+    quiche::QuicheDataWriter& writer) const {
+  writer.WriteVarInt62(response_code_);
+  return fields_.Encode(writer);
+}
+
+uint64_t BinaryHttpResponse::InformationalResponseInternal::EncodedSize()
+    const {
+  return quiche::QuicheDataWriter::GetVarInt62Len(response_code_) +
+         fields_.EncodedSize();
+}
+
+absl::StatusOr<std::string> BinaryHttpRequest::Serialize() const {
+  // Only supporting known length requests so far.
+  return EncodeAsKnownLength();
+}
+
+// https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-request-control-data
+absl::Status BinaryHttpRequest::EncodeControlData(
+    quiche::QuicheDataWriter& writer) const {
+  if (!writer.WriteStringPieceVarInt62(control_data_.method)) {
+    return absl::InvalidArgumentError("Failed to encode method.");
+  }
+  if (!writer.WriteStringPieceVarInt62(control_data_.scheme)) {
+    return absl::InvalidArgumentError("Failed to encode scheme.");
+  }
+  // the Host header field is not replicated in the :authority field, as is
+  // required for ensuring that the request is reproduced accurately; see
+  // Section 8.1.2.3 of [H2].
+  if (!has_host()) {
+    if (!writer.WriteStringPieceVarInt62(control_data_.authority)) {
+      return absl::InvalidArgumentError("Failed to encode authority.");
+    }
+  } else {
+    if (!writer.WriteStringPieceVarInt62("")) {
+      return absl::InvalidArgumentError("Failed to encode authority.");
+    }
+  }
+  if (!writer.WriteStringPieceVarInt62(control_data_.path)) {
+    return absl::InvalidArgumentError("Failed to encode path.");
+  }
+  return absl::OkStatus();
+}
+
+uint64_t BinaryHttpRequest::EncodedControlDataSize() const {
+  uint64_t size = StringPieceVarInt62Len(control_data_.method) +
+                  StringPieceVarInt62Len(control_data_.scheme) +
+                  StringPieceVarInt62Len(control_data_.path);
+  if (!has_host()) {
+    size += StringPieceVarInt62Len(control_data_.authority);
+  } else {
+    size += StringPieceVarInt62Len("");
+  }
+  return size;
+}
+
+uint64_t BinaryHttpRequest::EncodedSize() const {
+  return sizeof(kKnownLengthRequestFraming) + EncodedControlDataSize() +
+         EncodedKnownLengthFieldsAndBodySize();
+}
+
+// https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-known-length-messages
+absl::StatusOr<std::string> BinaryHttpRequest::EncodeAsKnownLength() const {
+  std::string data;
+  data.resize(EncodedSize());
+  quiche::QuicheDataWriter writer(data.size(), data.data());
+  if (!writer.WriteUInt8(kKnownLengthRequestFraming)) {
+    return absl::InvalidArgumentError("Failed to encode framing indicator.");
+  }
+  if (const absl::Status status = EncodeControlData(writer); !status.ok()) {
+    return status;
+  }
+  if (const absl::Status status = EncodeKnownLengthFieldsAndBody(writer);
+      !status.ok()) {
+    return status;
+  }
+  QUICHE_DCHECK_EQ(writer.remaining(), 0u);
+  return data;
+}
+
+absl::StatusOr<BinaryHttpRequest> BinaryHttpRequest::Create(
+    absl::string_view data) {
+  quiche::QuicheDataReader reader(data);
+  uint8_t framing;
+  if (!reader.ReadUInt8(&framing)) {
+    return absl::InvalidArgumentError("Missing framing indicator.");
+  }
+  if (framing == kKnownLengthRequestFraming) {
+    return DecodeKnownLengthRequest(reader);
+  }
+  return absl::UnimplementedError(
+      absl::StrCat("Unsupported framing type ", framing));
+}
+
+absl::StatusOr<BinaryHttpResponse> BinaryHttpResponse::Create(
+    absl::string_view data) {
+  quiche::QuicheDataReader reader(data);
+  uint8_t framing;
+  if (!reader.ReadUInt8(&framing)) {
+    return absl::InvalidArgumentError("Missing framing indicator.");
+  }
+  if (framing == kKnownLengthResponseFraming) {
+    return DecodeKnownLengthResponse(reader);
+  }
+  return absl::UnimplementedError(
+      absl::StrCat("Unsupported framing type ", framing));
+}
+
+}  // namespace quiche
diff --git a/quiche/binary_http/binary_http_message.h b/quiche/binary_http/binary_http_message.h
new file mode 100644
index 0000000..843b860
--- /dev/null
+++ b/quiche/binary_http/binary_http_message.h
@@ -0,0 +1,198 @@
+#ifndef QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_
+#define QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/quiche_data_writer.h"
+
+namespace quiche {
+
+// Supports encoding and decoding Binary Http messages.
+// Currently limited to known-length messages.
+// https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html
+class QUICHE_EXPORT_PRIVATE BinaryHttpMessage {
+ public:
+  // Name value pair of either a header or trailer field.
+  struct Field {
+    std::string name;
+    std::string value;
+    bool operator==(const BinaryHttpMessage::Field& rhs) const {
+      return name == rhs.name && value == rhs.value;
+    }
+  };
+  virtual ~BinaryHttpMessage() = default;
+
+  // TODO(bschneider): Switch to use existing Http2HeaderBlock
+  BinaryHttpMessage* AddHeaderField(Field header_field);
+
+  const std::vector<Field>& GetHeaderFields() const {
+    return header_fields_.fields();
+  }
+
+  BinaryHttpMessage* set_body(std::string body) {
+    body_ = std::move(body);
+    return this;
+  }
+
+  absl::string_view body() const { return body_; }
+
+  // Returns the Binary Http formatted message.
+  virtual absl::StatusOr<std::string> Serialize() const = 0;
+  // TODO(bschneider): Add AddTrailerField for chunked messages
+  // TODO(bschneider): Add SetBodyCallback() for chunked messages
+ protected:
+  class Fields {
+   public:
+    // Appends `field` to list of fields.  Can contain duplicates.
+    void AddField(BinaryHttpMessage::Field field);
+
+    const std::vector<BinaryHttpMessage::Field>& fields() const {
+      return fields_;
+    }
+
+    // Encode fields in insertion order.
+    // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-header-and-trailer-field-li
+    absl::Status Encode(quiche::QuicheDataWriter& writer) const;
+
+    // The number of returned by EncodedFieldsSize
+    // plus the number of bytes used in the varint holding that value.
+    uint64_t EncodedSize() const;
+
+   private:
+    // Number of bytes of just the set of fields.
+    uint64_t EncodedFieldsSize() const;
+
+    // Fields in insertion order.
+    std::vector<BinaryHttpMessage::Field> fields_;
+  };
+
+  absl::Status EncodeKnownLengthFieldsAndBody(
+      quiche::QuicheDataWriter& writer) const;
+  uint64_t EncodedKnownLengthFieldsAndBodySize() const;
+  bool has_host() const { return has_host_; }
+
+ private:
+  std::string body_;
+  Fields header_fields_;
+  bool has_host_ = false;
+};
+
+class QUICHE_EXPORT_PRIVATE BinaryHttpRequest : public BinaryHttpMessage {
+ public:
+  // HTTP request must have all of the following fields.
+  // Some examples are:
+  //   scheme: HTTP
+  //   authority: www.example.com
+  //   path: /index.html
+  struct ControlData {
+    std::string method;
+    std::string scheme;
+    std::string authority;
+    std::string path;
+    bool operator==(const BinaryHttpRequest::ControlData& rhs) const {
+      return method == rhs.method && scheme == rhs.scheme &&
+             authority == rhs.authority && path == rhs.path;
+    }
+  };
+  explicit BinaryHttpRequest(ControlData control_data)
+      : control_data_(std::move(control_data)) {}
+
+  // Deserialize
+  static absl::StatusOr<BinaryHttpRequest> Create(absl::string_view data);
+
+  absl::StatusOr<std::string> Serialize() const override;
+  const ControlData& control_data() const { return control_data_; }
+
+ private:
+  absl::Status EncodeControlData(quiche::QuicheDataWriter& writer) const;
+
+  uint64_t EncodedControlDataSize() const;
+
+  uint64_t EncodedSize() const;
+
+  // Returns Binary Http known length request formatted request.
+  absl::StatusOr<std::string> EncodeAsKnownLength() const;
+
+  const ControlData control_data_;
+};
+
+class QUICHE_EXPORT_PRIVATE BinaryHttpResponse : public BinaryHttpMessage {
+ public:
+  // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-response-control-data
+  struct InformationalResponse {
+    uint16_t status_code;
+    const std::vector<Field>& header_fields;
+    bool operator==(
+        const BinaryHttpResponse::InformationalResponse& rhs) const {
+      return status_code == rhs.status_code &&
+             header_fields == rhs.header_fields;
+    }
+  };
+
+  explicit BinaryHttpResponse(uint16_t status_code)
+      : status_code_(status_code) {}
+
+  // Deserialize
+  static absl::StatusOr<BinaryHttpResponse> Create(absl::string_view data);
+
+  absl::StatusOr<std::string> Serialize() const override;
+
+  // Informational status codes must be between 100 and 199 inclusive.
+  absl::Status AddInformationalResponse(uint16_t status_code,
+                                        std::vector<Field> header_fields);
+
+  uint16_t status_code() const { return status_code_; }
+  // References in the returned `ResponseControlData` are invalidated on
+  // `BinaryHttpResponse` object mutations.
+  std::vector<InformationalResponse> GetInformationalResponse() const;
+
+ private:
+  //
+  // Valid response codes are [100,199].
+  // TODO(bschneider): Collapse this inner class into the public
+  // `InformationalResponse` struct.
+  class InformationalResponseInternal {
+   public:
+    explicit InformationalResponseInternal(uint16_t response_code)
+        : response_code_(response_code) {}
+
+    void AddField(absl::string_view name, std::string value);
+
+    // Appends the encoded fields and body to `writer`.
+    absl::Status Encode(quiche::QuicheDataWriter& writer) const;
+
+    uint64_t EncodedSize() const;
+
+    uint16_t response_code() const { return response_code_; }
+
+    const std::vector<BinaryHttpMessage::Field>& fields() const {
+      return fields_.fields();
+    }
+
+   private:
+    const uint16_t response_code_;
+    BinaryHttpMessage::Fields fields_;
+  };
+
+  // Returns Binary Http known length request formatted response.
+  absl::StatusOr<std::string> EncodeAsKnownLength() const;
+
+  uint64_t EncodedSize() const;
+
+  std::vector<InformationalResponseInternal>
+      informational_response_control_data_;
+  const uint16_t status_code_;
+};
+}  // namespace quiche
+
+#endif  // QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_
diff --git a/quiche/binary_http/binary_http_message_test.cc b/quiche/binary_http/binary_http_message_test.cc
new file mode 100644
index 0000000..581848e
--- /dev/null
+++ b/quiche/binary_http/binary_http_message_test.cc
@@ -0,0 +1,489 @@
+#include "quiche/binary_http/binary_http_message.h"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+using ::testing::ContainerEq;
+using ::testing::FieldsAre;
+
+namespace quiche {
+namespace {
+
+std::string WordToBytes(uint32_t word) {
+  return std::string(
+      {static_cast<uint8_t>(word >> 24), static_cast<uint8_t>(word >> 16),
+       static_cast<uint8_t>(word >> 8), static_cast<uint8_t>(word)});
+}
+
+}  // namespace
+// Test examples from
+// https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html
+
+TEST(BinaryHttpRequest, EncodeGetNoBody) {
+  /*
+    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", "www.example.com", "/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"});
+  /*
+      00000000: 00034745 54056874 74707300 0a2f6865  ..GET.https../he
+      00000010: 6c6c6f2e 74787440 6c0a7573 65722d61  llo.txt@l.user-a
+      00000020: 67656e74 34637572 6c2f372e 31362e33  gent4curl/7.16.3
+      00000030: 206c6962 6375726c 2f372e31 362e3320   libcurl/7.16.3
+      00000040: 4f70656e 53534c2f 302e392e 376c207a  OpenSSL/0.9.7l z
+      00000050: 6c69622f 312e322e 3304686f 73740f77  lib/1.2.3.host.w
+      00000060: 77772e65 78616d70 6c652e63 6f6d0f61  ww.example.com.a
+      00000070: 63636570 742d6c61 6e677561 67650665  ccept-language.e
+      00000080: 6e2c206d 6900                        n, mi..
+  */
+  const uint32_t expected_words[] = {
+      0x00034745, 0x54056874, 0x74707300, 0x0a2f6865, 0x6c6c6f2e, 0x74787440,
+      0x6c0a7573, 0x65722d61, 0x67656e74, 0x34637572, 0x6c2f372e, 0x31362e33,
+      0x206c6962, 0x6375726c, 0x2f372e31, 0x362e3320, 0x4f70656e, 0x53534c2f,
+      0x302e392e, 0x376c207a, 0x6c69622f, 0x312e322e, 0x3304686f, 0x73740f77,
+      0x77772e65, 0x78616d70, 0x6c652e63, 0x6f6d0f61, 0x63636570, 0x742d6c61,
+      0x6e677561, 0x67650665, 0x6e2c206d, 0x69000000};
+  std::string expected;
+  for (const auto& word : expected_words) {
+    expected += WordToBytes(word);
+  }
+  // Remove padding.
+  expected.resize(expected.size() - 2);
+
+  const auto result = request.Serialize();
+  ASSERT_TRUE(result.ok());
+  ASSERT_EQ(*result, expected);
+}
+
+TEST(BinaryHttpRequest, DecodeGetNoBody) {
+  const uint32_t words[] = {
+      0x00034745, 0x54056874, 0x74707300, 0x0a2f6865, 0x6c6c6f2e, 0x74787440,
+      0x6c0a7573, 0x65722d61, 0x67656e74, 0x34637572, 0x6c2f372e, 0x31362e33,
+      0x206c6962, 0x6375726c, 0x2f372e31, 0x362e3320, 0x4f70656e, 0x53534c2f,
+      0x302e392e, 0x376c207a, 0x6c69622f, 0x312e322e, 0x3304686f, 0x73740f77,
+      0x77772e65, 0x78616d70, 0x6c652e63, 0x6f6d0f61, 0x63636570, 0x742d6c61,
+      0x6e677561, 0x67650665, 0x6e2c206d, 0x69000000};
+  std::string data;
+  for (const auto& word : words) {
+    data += WordToBytes(word);
+  }
+  const auto request_so = BinaryHttpRequest::Create(data);
+  ASSERT_TRUE(request_so.ok());
+  const BinaryHttpRequest request = *request_so;
+  ASSERT_THAT(request.control_data(),
+              FieldsAre("GET", "https", "", "/hello.txt"));
+  std::vector<BinaryHttpMessage::Field> expected_fields = {
+      {"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"}};
+  ASSERT_THAT(request.GetHeaderFields(), ContainerEq(expected_fields));
+  ASSERT_EQ(request.body(), "");
+}
+
+TEST(BinaryHttpRequest, EncodeGetWithAuthority) {
+  /*
+    GET https://www.example.com/hello.txt HTTP/1.1
+    User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
+    Accept-Language: en, mi
+  */
+  BinaryHttpRequest request({"GET", "https", "www.example.com", "/hello.txt"});
+  request
+      .AddHeaderField({"User-Agent",
+                       "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3"})
+      ->AddHeaderField({"Accept-Language", "en, mi"});
+  /*
+    00000000: 00034745 54056874 7470730f 7777772e  ..GET.https.www.
+    00000010: 6578616d 706c652e 636f6d0a 2f68656c  example.com./hel
+    00000020: 6c6f2e74 78744057 0a757365 722d6167  lo.txt@W.user-ag
+    00000030: 656e7434 6375726c 2f372e31 362e3320  ent4curl/7.16.3
+    00000040: 6c696263 75726c2f 372e3136 2e33204f  libcurl/7.16.3 O
+    00000050: 70656e53 534c2f30 2e392e37 6c207a6c  penSSL/0.9.7l zl
+    00000060: 69622f31 2e322e33 0f616363 6570742d  ib/1.2.3.accept-
+    00000070: 6c616e67 75616765 06656e2c 206d6900  language.en, mi.
+  */
+
+  const uint32_t expected_words[] = {
+      0x00034745, 0x54056874, 0x7470730f, 0x7777772e, 0x6578616d, 0x706c652e,
+      0x636f6d0a, 0x2f68656c, 0x6c6f2e74, 0x78744057, 0x0a757365, 0x722d6167,
+      0x656e7434, 0x6375726c, 0x2f372e31, 0x362e3320, 0x6c696263, 0x75726c2f,
+      0x372e3136, 0x2e33204f, 0x70656e53, 0x534c2f30, 0x2e392e37, 0x6c207a6c,
+      0x69622f31, 0x2e322e33, 0x0f616363, 0x6570742d, 0x6c616e67, 0x75616765,
+      0x06656e2c, 0x206d6900};
+  std::string expected;
+  for (const auto& word : expected_words) {
+    expected += WordToBytes(word);
+  }
+  const auto result = request.Serialize();
+  ASSERT_TRUE(result.ok());
+  ASSERT_EQ(*result, expected);
+}
+
+TEST(BinaryHttpRequest, DecodeGetWithAuthority) {
+  const uint32_t words[] = {
+      0x00034745, 0x54056874, 0x7470730f, 0x7777772e, 0x6578616d, 0x706c652e,
+      0x636f6d0a, 0x2f68656c, 0x6c6f2e74, 0x78744057, 0x0a757365, 0x722d6167,
+      0x656e7434, 0x6375726c, 0x2f372e31, 0x362e3320, 0x6c696263, 0x75726c2f,
+      0x372e3136, 0x2e33204f, 0x70656e53, 0x534c2f30, 0x2e392e37, 0x6c207a6c,
+      0x69622f31, 0x2e322e33, 0x0f616363, 0x6570742d, 0x6c616e67, 0x75616765,
+      0x06656e2c, 0x206d6900, 0x00};
+  std::string data;
+  for (const auto& word : words) {
+    data += WordToBytes(word);
+  }
+  const auto request_so = BinaryHttpRequest::Create(data);
+  ASSERT_TRUE(request_so.ok());
+  const BinaryHttpRequest request = *request_so;
+  ASSERT_THAT(request.control_data(),
+              FieldsAre("GET", "https", "www.example.com", "/hello.txt"));
+  std::vector<BinaryHttpMessage::Field> expected_fields = {
+      {"user-agent", "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3"},
+      {"accept-language", "en, mi"}};
+  ASSERT_THAT(request.GetHeaderFields(), ContainerEq(expected_fields));
+  ASSERT_EQ(request.body(), "");
+}
+
+TEST(BinaryHttpRequest, EncodePostBody) {
+  /*
+  POST /hello.txt HTTP/1.1
+  User-Agent: not/telling
+  Host: www.example.com
+  Accept-Language: en
+
+  Some body that I used to post.
+  */
+  BinaryHttpRequest request({"POST", "https", "www.example.com", "/hello.txt"});
+  request.AddHeaderField({"User-Agent", "not/telling"})
+      ->AddHeaderField({"Host", "www.example.com"})
+      ->AddHeaderField({"Accept-Language", "en"})
+      ->set_body({"Some body that I used to post.\r\n"});
+  /*
+    00000000: 0004504f 53540568 74747073 000a2f68  ..POST.https../h
+    00000010: 656c6c6f 2e747874 3f0a7573 65722d61  ello.txt?.user-a
+    00000020: 67656e74 0b6e6f74 2f74656c 6c696e67  gent.not/telling
+    00000030: 04686f73 740f7777 772e6578 616d706c  .host.www.exampl
+    00000040: 652e636f 6d0f6163 63657074 2d6c616e  e.com.accept-lan
+    00000050: 67756167 6502656e 20536f6d 6520626f  guage.en Some bo
+    00000060: 64792074 68617420 49207573 65642074  dy that I used t
+    00000070: 6f20706f 73742e0d 0a                 o post....
+  */
+  const uint32_t expected_words[] = {
+      0x0004504f, 0x53540568, 0x74747073, 0x000a2f68, 0x656c6c6f, 0x2e747874,
+      0x3f0a7573, 0x65722d61, 0x67656e74, 0x0b6e6f74, 0x2f74656c, 0x6c696e67,
+      0x04686f73, 0x740f7777, 0x772e6578, 0x616d706c, 0x652e636f, 0x6d0f6163,
+      0x63657074, 0x2d6c616e, 0x67756167, 0x6502656e, 0x20536f6d, 0x6520626f,
+      0x64792074, 0x68617420, 0x49207573, 0x65642074, 0x6f20706f, 0x73742e0d,
+      0x0a000000};
+  std::string expected;
+  for (const auto& word : expected_words) {
+    expected += WordToBytes(word);
+  }
+  // Remove padding.
+  expected.resize(expected.size() - 3);
+  const auto result = request.Serialize();
+  ASSERT_TRUE(result.ok());
+  ASSERT_EQ(*result, expected);
+}
+
+TEST(BinaryHttpRequest, DecodePostBody) {
+  const uint32_t words[] = {
+      0x0004504f, 0x53540568, 0x74747073, 0x000a2f68, 0x656c6c6f, 0x2e747874,
+      0x3f0a7573, 0x65722d61, 0x67656e74, 0x0b6e6f74, 0x2f74656c, 0x6c696e67,
+      0x04686f73, 0x740f7777, 0x772e6578, 0x616d706c, 0x652e636f, 0x6d0f6163,
+      0x63657074, 0x2d6c616e, 0x67756167, 0x6502656e, 0x20536f6d, 0x6520626f,
+      0x64792074, 0x68617420, 0x49207573, 0x65642074, 0x6f20706f, 0x73742e0d,
+      0x0a000000};
+  std::string data;
+  for (const auto& word : words) {
+    data += WordToBytes(word);
+  }
+  const auto request_so = BinaryHttpRequest::Create(data);
+  ASSERT_TRUE(request_so.ok());
+  BinaryHttpRequest request = *request_so;
+  ASSERT_THAT(request.control_data(),
+              FieldsAre("POST", "https", "", "/hello.txt"));
+  std::vector<BinaryHttpMessage::Field> expected_fields = {
+      {"user-agent", "not/telling"},
+      {"host", "www.example.com"},
+      {"accept-language", "en"}};
+  ASSERT_THAT(request.GetHeaderFields(), ContainerEq(expected_fields));
+  ASSERT_EQ(request.body(), "Some body that I used to post.\r\n");
+}
+
+TEST(BinaryHttpResponse, EncodeNoBody) {
+  /*
+    HTTP/1.1 404 Not Found
+    Server: Apache
+  */
+  BinaryHttpResponse response(404);
+  response.AddHeaderField({"Server", "Apache"});
+  /*
+    0141940e 06736572 76657206 41706163  .A...server.Apac
+    686500                               he..
+  */
+  const uint32_t expected_words[] = {0x0141940e, 0x06736572, 0x76657206,
+                                     0x41706163, 0x68650000};
+  std::string expected;
+  for (const auto& word : expected_words) {
+    expected += WordToBytes(word);
+  }
+  // Remove padding.
+  expected.resize(expected.size() - 1);
+  const auto result = response.Serialize();
+  ASSERT_TRUE(result.ok());
+  ASSERT_EQ(*result, expected);
+}
+
+TEST(BinaryHttpResponse, DecodeNoBody) {
+  /*
+    HTTP/1.1 404 Not Found
+    Server: Apache
+  */
+  const uint32_t words[] = {0x0141940e, 0x06736572, 0x76657206, 0x41706163,
+                            0x68650000};
+  std::string data;
+  for (const auto& word : words) {
+    data += WordToBytes(word);
+  }
+  const auto response_so = BinaryHttpResponse::Create(data);
+  ASSERT_TRUE(response_so.ok());
+  const BinaryHttpResponse response = *response_so;
+  ASSERT_EQ(response.status_code(), 404);
+  std::vector<BinaryHttpMessage::Field> expected_fields = {
+      {"server", "Apache"}};
+  ASSERT_THAT(response.GetHeaderFields(), ContainerEq(expected_fields));
+  ASSERT_EQ(response.body(), "");
+  ASSERT_TRUE(response.GetInformationalResponse().empty());
+}
+
+TEST(BinaryHttpResponse, EncodeBody) {
+  /*
+    HTTP/1.1 200 OK
+    Server: Apache
+
+    Hello, world!
+  */
+  BinaryHttpResponse response(200);
+  response.AddHeaderField({"Server", "Apache"});
+  response.set_body("Hello, world!\r\n");
+  /*
+    0140c80e 06736572 76657206 41706163  .@...server.Apac
+    68650f48 656c6c6f 2c20776f 726c6421  he.Hello, world!
+    0d0a                                 ....
+  */
+  const uint32_t expected_words[] = {0x0140c80e, 0x06736572, 0x76657206,
+                                     0x41706163, 0x68650f48, 0x656c6c6f,
+                                     0x2c20776f, 0x726c6421, 0x0d0a0000};
+  std::string expected;
+  for (const auto& word : expected_words) {
+    expected += WordToBytes(word);
+  }
+  // Remove padding.
+  expected.resize(expected.size() - 2);
+
+  const auto result = response.Serialize();
+  ASSERT_TRUE(result.ok());
+  ASSERT_EQ(*result, expected);
+}
+
+TEST(BinaryHttpResponse, DecodeBody) {
+  /*
+    HTTP/1.1 200 OK
+
+    Hello, world!
+  */
+  const uint32_t words[] = {0x0140c80e, 0x06736572, 0x76657206,
+                            0x41706163, 0x68650f48, 0x656c6c6f,
+                            0x2c20776f, 0x726c6421, 0x0d0a0000};
+  std::string data;
+  for (const auto& word : words) {
+    data += WordToBytes(word);
+  }
+  const auto response_so = BinaryHttpResponse::Create(data);
+  ASSERT_TRUE(response_so.ok());
+  const BinaryHttpResponse response = *response_so;
+  ASSERT_EQ(response.status_code(), 200);
+  std::vector<BinaryHttpMessage::Field> expected_fields = {
+      {"server", "Apache"}};
+  ASSERT_THAT(response.GetHeaderFields(), ContainerEq(expected_fields));
+  ASSERT_EQ(response.body(), "Hello, world!\r\n");
+  ASSERT_TRUE(response.GetInformationalResponse().empty());
+}
+
+TEST(BHttpResponse, AddBadInformationalResponseCode) {
+  BinaryHttpResponse response(200);
+  ASSERT_FALSE(response.AddInformationalResponse(50, {}).ok());
+  ASSERT_FALSE(response.AddInformationalResponse(300, {}).ok());
+}
+
+TEST(BinaryHttpResponse, EncodeMultiInformationalWithBody) {
+  /*
+    HTTP/1.1 102 Processing
+    Running: "sleep 15"
+
+    HTTP/1.1 103 Early Hints
+    Link: </style.css>; rel=preload; as=style
+    Link: </script.js>; rel=preload; as=script
+
+    HTTP/1.1 200 OK
+    Date: Mon, 27 Jul 2009 12:28:53 GMT
+    Server: Apache
+    Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
+    ETag: "34aa387-d-1568eb00"
+    Accept-Ranges: bytes
+    Content-Length: 51
+    Vary: Accept-Encoding
+    Content-Type: text/plain
+
+    Hello World! My content includes a trailing CRLF.
+  */
+  BinaryHttpResponse response(200);
+  response.AddHeaderField({"Date", "Mon, 27 Jul 2009 12:28:53 GMT"})
+      ->AddHeaderField({"Server", "Apache"})
+      ->AddHeaderField({"Last-Modified", "Wed, 22 Jul 2009 19:15:56 GMT"})
+      ->AddHeaderField({"ETag", "\"34aa387-d-1568eb00\""})
+      ->AddHeaderField({"Accept-Ranges", "bytes"})
+      ->AddHeaderField({"Content-Length", "51"})
+      ->AddHeaderField({"Vary", "Accept-Encoding"})
+      ->AddHeaderField({"Content-Type", "text/plain"});
+  response.set_body("Hello World! My content includes a trailing CRLF.\r\n");
+  ASSERT_TRUE(
+      response.AddInformationalResponse(102, {{"Running", "\"sleep 15\""}})
+          .ok());
+  ASSERT_TRUE(response
+                  .AddInformationalResponse(
+                      103, {{"Link", "</style.css>; rel=preload; as=style"},
+                            {"Link", "</script.js>; rel=preload; as=script"}})
+                  .ok());
+
+  /*
+      01406613 0772756e 6e696e67 0a22736c  .@f..running."sl
+      65657020 31352240 67405304 6c696e6b  eep 15"@g@S.link
+      233c2f73 74796c65 2e637373 3e3b2072  #</style.css>; r
+      656c3d70 72656c6f 61643b20 61733d73  el=preload; as=s
+      74796c65 046c696e 6b243c2f 73637269  tyle.link$</scri
+      70742e6a 733e3b20 72656c3d 7072656c  pt.js>; rel=prel
+      6f61643b 2061733d 73637269 707440c8  oad; as=script@.
+      40ca0464 6174651d 4d6f6e2c 20323720  @..date.Mon, 27
+      4a756c20 32303039 2031323a 32383a35  Jul 2009 12:28:5
+      3320474d 54067365 72766572 06417061  3 GMT.server.Apa
+      6368650d 6c617374 2d6d6f64 69666965  che.last-modifie
+      641d5765 642c2032 32204a75 6c203230  d.Wed, 22 Jul 20
+      30392031 393a3135 3a353620 474d5404  09 19:15:56 GMT.
+      65746167 14223334 61613338 372d642d  etag."34aa387-d-
+      31353638 65623030 220d6163 63657074  1568eb00".accept
+      2d72616e 67657305 62797465 730e636f  -ranges.bytes.co
+      6e74656e 742d6c65 6e677468 02353104  ntent-length.51.
+      76617279 0f416363 6570742d 456e636f  vary.Accept-Enco
+      64696e67 0c636f6e 74656e74 2d747970  ding.content-typ
+      650a7465 78742f70 6c61696e 3348656c  e.text/plain3Hel
+      6c6f2057 6f726c64 21204d79 20636f6e  lo World! My con
+      74656e74 20696e63 6c756465 73206120  tent includes a
+      74726169 6c696e67 2043524c 462e0d0a  trailing CRLF...
+  */
+  const uint32_t expected_words[] = {
+      0x01406613, 0x0772756e, 0x6e696e67, 0x0a22736c, 0x65657020, 0x31352240,
+      0x67405304, 0x6c696e6b, 0x233c2f73, 0x74796c65, 0x2e637373, 0x3e3b2072,
+      0x656c3d70, 0x72656c6f, 0x61643b20, 0x61733d73, 0x74796c65, 0x046c696e,
+      0x6b243c2f, 0x73637269, 0x70742e6a, 0x733e3b20, 0x72656c3d, 0x7072656c,
+      0x6f61643b, 0x2061733d, 0x73637269, 0x707440c8, 0x40ca0464, 0x6174651d,
+      0x4d6f6e2c, 0x20323720, 0x4a756c20, 0x32303039, 0x2031323a, 0x32383a35,
+      0x3320474d, 0x54067365, 0x72766572, 0x06417061, 0x6368650d, 0x6c617374,
+      0x2d6d6f64, 0x69666965, 0x641d5765, 0x642c2032, 0x32204a75, 0x6c203230,
+      0x30392031, 0x393a3135, 0x3a353620, 0x474d5404, 0x65746167, 0x14223334,
+      0x61613338, 0x372d642d, 0x31353638, 0x65623030, 0x220d6163, 0x63657074,
+      0x2d72616e, 0x67657305, 0x62797465, 0x730e636f, 0x6e74656e, 0x742d6c65,
+      0x6e677468, 0x02353104, 0x76617279, 0x0f416363, 0x6570742d, 0x456e636f,
+      0x64696e67, 0x0c636f6e, 0x74656e74, 0x2d747970, 0x650a7465, 0x78742f70,
+      0x6c61696e, 0x3348656c, 0x6c6f2057, 0x6f726c64, 0x21204d79, 0x20636f6e,
+      0x74656e74, 0x20696e63, 0x6c756465, 0x73206120, 0x74726169, 0x6c696e67,
+      0x2043524c, 0x462e0d0a};
+  std::string expected;
+  for (const auto& word : expected_words) {
+    expected += WordToBytes(word);
+  }
+  const auto result = response.Serialize();
+  ASSERT_TRUE(result.ok());
+  ASSERT_EQ(*result, expected);
+}
+
+TEST(BinaryHttpResponse, DecodeMultiInformationalWithBody) {
+  /*
+    HTTP/1.1 102 Processing
+    Running: "sleep 15"
+
+    HTTP/1.1 103 Early Hints
+    Link: </style.css>; rel=preload; as=style
+    Link: </script.js>; rel=preload; as=script
+
+    HTTP/1.1 200 OK
+    Date: Mon, 27 Jul 2009 12:28:53 GMT
+    Server: Apache
+    Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
+    ETag: "34aa387-d-1568eb00"
+    Accept-Ranges: bytes
+    Content-Length: 51
+    Vary: Accept-Encoding
+    Content-Type: text/plain
+
+    Hello World! My content includes a trailing CRLF.
+  */
+  const uint32_t words[] = {
+      0x01406613, 0x0772756e, 0x6e696e67, 0x0a22736c, 0x65657020, 0x31352240,
+      0x67405304, 0x6c696e6b, 0x233c2f73, 0x74796c65, 0x2e637373, 0x3e3b2072,
+      0x656c3d70, 0x72656c6f, 0x61643b20, 0x61733d73, 0x74796c65, 0x046c696e,
+      0x6b243c2f, 0x73637269, 0x70742e6a, 0x733e3b20, 0x72656c3d, 0x7072656c,
+      0x6f61643b, 0x2061733d, 0x73637269, 0x707440c8, 0x40ca0464, 0x6174651d,
+      0x4d6f6e2c, 0x20323720, 0x4a756c20, 0x32303039, 0x2031323a, 0x32383a35,
+      0x3320474d, 0x54067365, 0x72766572, 0x06417061, 0x6368650d, 0x6c617374,
+      0x2d6d6f64, 0x69666965, 0x641d5765, 0x642c2032, 0x32204a75, 0x6c203230,
+      0x30392031, 0x393a3135, 0x3a353620, 0x474d5404, 0x65746167, 0x14223334,
+      0x61613338, 0x372d642d, 0x31353638, 0x65623030, 0x220d6163, 0x63657074,
+      0x2d72616e, 0x67657305, 0x62797465, 0x730e636f, 0x6e74656e, 0x742d6c65,
+      0x6e677468, 0x02353104, 0x76617279, 0x0f416363, 0x6570742d, 0x456e636f,
+      0x64696e67, 0x0c636f6e, 0x74656e74, 0x2d747970, 0x650a7465, 0x78742f70,
+      0x6c61696e, 0x3348656c, 0x6c6f2057, 0x6f726c64, 0x21204d79, 0x20636f6e,
+      0x74656e74, 0x20696e63, 0x6c756465, 0x73206120, 0x74726169, 0x6c696e67,
+      0x2043524c, 0x462e0d0a, 0x00000000};
+  std::string data;
+  for (const auto& word : words) {
+    data += WordToBytes(word);
+  }
+  const auto response_so = BinaryHttpResponse::Create(data);
+  ASSERT_TRUE(response_so.ok());
+  const BinaryHttpResponse response = *response_so;
+  std::vector<BinaryHttpMessage::Field> expected_fields = {
+      {"date", "Mon, 27 Jul 2009 12:28:53 GMT"},
+      {"server", "Apache"},
+      {"last-modified", "Wed, 22 Jul 2009 19:15:56 GMT"},
+      {"etag", "\"34aa387-d-1568eb00\""},
+      {"accept-ranges", "bytes"},
+      {"content-length", "51"},
+      {"vary", "Accept-Encoding"},
+      {"content-type", "text/plain"}};
+  ASSERT_THAT(response.GetHeaderFields(), ContainerEq(expected_fields));
+  ASSERT_EQ(response.body(),
+            "Hello World! My content includes a trailing CRLF.\r\n");
+  std::vector<BinaryHttpMessage::Field> header102 = {
+      {"running", "\"sleep 15\""}};
+  std::vector<BinaryHttpMessage::Field> header103 = {
+      {"link", "</style.css>; rel=preload; as=style"},
+      {"link", "</script.js>; rel=preload; as=script"}};
+  std::vector<BinaryHttpResponse::InformationalResponse> expected_control = {
+      {102, header102}, {103, header103}};
+  ASSERT_THAT(response.GetInformationalResponse(),
+              ContainerEq(expected_control));
+}
+
+}  // namespace quiche