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