blob: fd75c7054ff113d07f87d4302e1f678fde2e3be1 [file] [log] [blame]
#ifndef QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_
#define QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <ostream>
#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/string_view.h"
#include "absl/types/span.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/quiche_data_reader.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 BinaryHttpMessage {
public:
// A view of a field name and value. Used to pass around a field without
// owning or copying the backing data.
struct QUICHE_EXPORT FieldView {
absl::string_view name;
absl::string_view value;
};
// Name value pair of either a header or trailer field.
struct QUICHE_EXPORT Field {
std::string name;
std::string value;
bool operator==(const BinaryHttpMessage::Field& rhs) const {
return name == rhs.name && value == rhs.value;
}
bool operator!=(const BinaryHttpMessage::Field& rhs) const {
return !(*this == rhs);
}
std::string DebugString() const;
};
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;
}
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
// TODO(bschneider): Add SetBodyCallback() for chunked messages
virtual std::string DebugString() const;
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_;
}
bool operator==(const BinaryHttpMessage::Fields& rhs) const {
return fields_ == rhs.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.
size_t EncodedSize() const;
private:
// Number of bytes of just the set of fields.
size_t EncodedFieldsSize() const;
// Fields in insertion order.
std::vector<BinaryHttpMessage::Field> fields_;
};
// 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_;
}
absl::Status EncodeKnownLengthFieldsAndBody(
quiche::QuicheDataWriter& writer) 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,
std::ostream* os);
class QUICHE_EXPORT BinaryHttpRequest : public BinaryHttpMessage {
public:
// 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
// path: /index.html
struct QUICHE_EXPORT 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;
}
bool operator!=(const BinaryHttpRequest::ControlData& rhs) const {
return !(*this == rhs);
}
};
explicit BinaryHttpRequest(ControlData control_data)
: control_data_(std::move(control_data)) {}
// 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;
// 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::IsPayloadEqual(rhs);
}
bool operator==(const BinaryHttpRequest& rhs) const {
return IsPayloadEqual(rhs) &&
num_padding_bytes() == rhs.num_padding_bytes();
}
bool operator!=(const BinaryHttpRequest& rhs) const {
return !(*this == rhs);
}
// Provides a Decode method that can be called multiple times as data is
// received. The relevant MessageSectionHandler method will be called when
// its corresponding section is successfully decoded.
class QUICHE_EXPORT IndeterminateLengthDecoder {
public:
// The handler to invoke when a section is decoded successfully. The
// handler can return an error if the decoded data cannot be processed
// successfully.
class QUICHE_EXPORT MessageSectionHandler {
public:
virtual ~MessageSectionHandler() = default;
virtual absl::Status OnControlData(const ControlData& control_data) = 0;
virtual absl::Status OnHeader(absl::string_view name,
absl::string_view value) = 0;
virtual absl::Status OnHeadersDone() = 0;
virtual absl::Status OnBodyChunk(absl::string_view body_chunk) = 0;
virtual absl::Status OnBodyChunksDone() = 0;
virtual absl::Status OnTrailer(absl::string_view name,
absl::string_view value) = 0;
virtual absl::Status OnTrailersDone() = 0;
};
explicit IndeterminateLengthDecoder(
MessageSectionHandler& message_section_handler)
: message_section_handler_(message_section_handler) {}
// Decodes an Indeterminate-Length BHTTP request. As the caller receives
// portions of the request, the caller can call this method with the request
// portion. The class keeps track of the current message section that is
// being decoded and buffers data if the section is not fully decoded so
// that the next call can continue decoding from where it left off. It will
// also invoke the appropriate MessageSectionHandler method when a section
// is decoded successfully.
// `end_stream` indicates that no more data will be provided to the decoder.
// This is used to determine if a valid message was decoded properly given
// the last piece of data provided, handling both complete messages and
// truncated messages.
absl::Status Decode(absl::string_view data, bool end_stream);
private:
// The sections of an Indeterminate-Length BHTTP request.
enum class MessageSection {
kControlData,
kHeader,
kBody,
kTrailer,
kPadding,
// The decoder is set to end after end_stream is received or when an error
// occurs while decoding.
kEnd,
};
// Initializes the checkpoint with the provided data and any buffered data.
void InitializeCheckpoint(absl::string_view data);
// Carries out the decode logic from the checkpoint. Returns
// OutOfRangeError if there is not enough data to decode the current
// section. When a section is fully decoded, the checkpoint is updated.
absl::Status DecodeCheckpointData(bool end_stream);
// Saves the checkpoint based on the current position of the reader.
void SaveCheckpoint(const QuicheDataReader& reader) {
checkpoint_view_ = reader.PeekRemainingPayload();
}
// Buffers the checkpoint.
void BufferCheckpoint() { buffer_ = std::string(checkpoint_view_); }
// Decodes a section 0 or more times until a content terminator is
// encountered.
absl::Status DecodeContentTerminatedSection(QuicheDataReader& reader);
MessageSectionHandler& message_section_handler_;
// Stores the data that could not be processed due to missing data.
std::string buffer_;
// Tracks the remaining data to be processed or buffered.
// When decoding fails due to missing data, we buffer based on this
// checkpoint and return. When decoding succeeds, we update the checkpoint
// to not buffer the already processed data.
absl::string_view checkpoint_view_;
// The current section that is being decoded.
MessageSection current_section_ = MessageSection::kControlData;
// Upon initial entry of the body or trailer section, the message is assumed
// to be truncated. This will be set to `false` upon the detection of data,
// and the state remains consistent for the remainder of the section. This
// serves to differentiate between true truncation and an `end_stream`
// occurring after partial processing of the section's content but before
// its content terminator.
bool maybe_truncated_ = true;
};
private:
absl::Status EncodeControlData(quiche::QuicheDataWriter& writer) const;
size_t EncodedControlDataSize() const;
// Returns Binary Http known length request formatted request.
absl::StatusOr<std::string> EncodeAsKnownLength() const;
const ControlData control_data_;
};
void QUICHE_EXPORT PrintTo(const BinaryHttpRequest& msg, std::ostream* os);
class QUICHE_EXPORT BinaryHttpResponse : public BinaryHttpMessage {
public:
// https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-response-control-data
// A response can contain 0 to N informational responses. Each informational
// response contains a status code followed by a header field. Valid status
// codes are [100,199].
class QUICHE_EXPORT InformationalResponse {
public:
explicit InformationalResponse(uint16_t status_code)
: status_code_(status_code) {}
InformationalResponse(uint16_t status_code,
const std::vector<BinaryHttpMessage::Field>& fields)
: status_code_(status_code) {
for (const BinaryHttpMessage::Field& field : fields) {
AddField(field.name, field.value);
}
}
bool operator==(
const BinaryHttpResponse::InformationalResponse& rhs) const {
return status_code_ == rhs.status_code_ && fields_ == rhs.fields_;
}
bool operator!=(
const BinaryHttpResponse::InformationalResponse& rhs) const {
return !(*this == rhs);
}
// Adds a field with the provided name, converted to lower case.
// Fields are in the order they are added.
void AddField(absl::string_view name, std::string value);
const std::vector<BinaryHttpMessage::Field>& fields() const {
return fields_.fields();
}
uint16_t status_code() const { return status_code_; }
std::string DebugString() const;
private:
// Give BinaryHttpResponse access to Encoding functionality.
friend class BinaryHttpResponse;
size_t EncodedSize() const;
// Appends the encoded fields and body to `writer`.
absl::Status Encode(quiche::QuicheDataWriter& writer) const;
const uint16_t status_code_;
BinaryHttpMessage::Fields fields_;
};
explicit BinaryHttpResponse(uint16_t status_code)
: status_code_(status_code) {}
// 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.
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.
const std::vector<InformationalResponse>& informational_responses() const {
return informational_response_control_data_;
}
virtual std::string DebugString() const override;
// 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::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);
}
// Provides encoding methods for an Indeterminate-Length BHTTP response. The
// encoder keeps track of what has been encoded so far to ensure sections are
// encoded in the correct order, this means it can only be used for a single
// BHTTP response message.
class QUICHE_EXPORT IndeterminateLengthEncoder {
public:
// Encodes the specified informational response status code, fields, and its
// content terminator.
absl::StatusOr<std::string> EncodeInformationalResponse(
uint16_t status_code, absl::Span<FieldView> fields);
// Encodes the specified status code, headers, and its content terminator.
absl::StatusOr<std::string> EncodeHeaders(uint16_t status_code,
absl::Span<FieldView> headers);
// Encodes the specified body chunks. If 'body_chunks_done' is true, the
// encoded body chunks are followed by the content terminator.
absl::StatusOr<std::string> EncodeBodyChunks(
absl::Span<absl::string_view> body_chunks, bool body_chunks_done);
// Encodes the specified trailers and its content terminator.
absl::StatusOr<std::string> EncodeTrailers(absl::Span<FieldView> trailers);
private:
enum class MessageSection {
kInformationalResponseOrHeader,
kBody,
kTrailer,
kEnd,
};
absl::StatusOr<std::string> EncodeFieldSection(
std::optional<uint16_t> status_code, absl::Span<FieldView> fields);
std::string GetMessageSectionString(MessageSection section) const;
MessageSection current_section_ =
MessageSection::kInformationalResponseOrHeader;
bool framing_indicator_encoded_ = false;
};
private:
// Returns Binary Http known length request formatted response.
absl::StatusOr<std::string> EncodeAsKnownLength() const;
std::vector<InformationalResponse> informational_response_control_data_;
const uint16_t status_code_;
};
void QUICHE_EXPORT PrintTo(const BinaryHttpResponse& msg, std::ostream* os);
} // namespace quiche
#endif // QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_