blob: 2f58a3f8aa7cee26d2e1e60bd9318b28314b82ca [file]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file contains some protocol structures for use with SPDY 3 and HTTP 2
// The SPDY 3 spec can be found at:
// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3
#ifndef QUICHE_HTTP2_CORE_SPDY_PROTOCOL_H_
#define QUICHE_HTTP2_CORE_SPDY_PROTOCOL_H_
#include <array>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iosfwd>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include "absl/container/inlined_vector.h"
#include "absl/strings/string_view.h"
#include "quiche/http2/core/spdy_alt_svc_wire_format.h"
#include "quiche/http2/core/spdy_bitmasks.h"
#include "quiche/common/http/http_header_block.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/platform/api/quiche_flags.h"
#include "quiche/common/platform/api/quiche_logging.h"
namespace spdy {
// A stream ID is a 31-bit entity.
using SpdyStreamId = uint32_t;
// A SETTINGS ID is a 16-bit entity.
using SpdySettingsId = uint16_t;
// Specifies the stream ID used to denote the current session (for
// flow control).
inline constexpr SpdyStreamId kSessionFlowControlStreamId = 0;
// 0 is not a valid stream ID for any other purpose than flow control.
inline constexpr SpdyStreamId kInvalidStreamId = 0;
// Max stream id.
inline constexpr SpdyStreamId kMaxStreamId = 0x7fffffff;
// The maximum possible frame payload size allowed by the spec.
inline constexpr uint32_t kSpdyMaxFrameSizeLimit = (1 << 24) - 1;
// The initial value for the maximum frame payload size as per the spec. This is
// the maximum control frame size we accept.
inline constexpr uint32_t kHttp2DefaultFramePayloadLimit = 1 << 14;
// The maximum size of the control frames that we send, including the size of
// the header. This limit is arbitrary. We can enforce it here or at the
// application layer. We chose the framing layer, but this can be changed (or
// removed) if necessary later down the line.
inline constexpr size_t kHttp2MaxControlFrameSendSize =
kHttp2DefaultFramePayloadLimit - 1;
// Number of octets in the frame header.
inline constexpr size_t kFrameHeaderSize = 9;
// The initial value for the maximum frame payload size as per the spec. This is
// the maximum control frame size we accept.
inline constexpr uint32_t kHttp2DefaultFrameSizeLimit =
kHttp2DefaultFramePayloadLimit + kFrameHeaderSize;
// The initial value for the maximum size of the header list, "unlimited" (max
// unsigned 32-bit int) as per the spec.
inline constexpr uint32_t kSpdyInitialHeaderListSizeLimit = 0xFFFFFFFF;
// Maximum window size for a Spdy stream or session.
inline constexpr int32_t kSpdyMaximumWindowSize =
0x7FFFFFFF; // Max signed 32bit int
// Maximum padding size in octets for one DATA or HEADERS or PUSH_PROMISE frame.
inline constexpr int32_t kPaddingSizePerFrame = 256;
// The HTTP/2 connection preface, which must be the first bytes sent by the
// client upon starting an HTTP/2 connection, and which must be followed by a
// SETTINGS frame. Note that even though |kHttp2ConnectionHeaderPrefix| is
// defined as a string literal with a null terminator, the actual connection
// preface is only the first |kHttp2ConnectionHeaderPrefixSize| bytes, which
// excludes the null terminator.
QUICHE_EXPORT extern const char* const kHttp2ConnectionHeaderPrefix;
inline constexpr int kHttp2ConnectionHeaderPrefixSize = 24;
// Wire values for HTTP2 frame types.
enum class SpdyFrameType : uint8_t {
DATA = 0x00,
HEADERS = 0x01,
PRIORITY = 0x02,
RST_STREAM = 0x03,
SETTINGS = 0x04,
PUSH_PROMISE = 0x05,
PING = 0x06,
GOAWAY = 0x07,
WINDOW_UPDATE = 0x08,
CONTINUATION = 0x09,
// ALTSVC is a public extension.
ALTSVC = 0x0a,
PRIORITY_UPDATE = 0x10,
ACCEPT_CH = 0x89,
};
// Flags on data packets.
enum SpdyDataFlags {
DATA_FLAG_NONE = 0x00,
DATA_FLAG_FIN = 0x01,
DATA_FLAG_PADDED = 0x08,
};
// Flags on control packets
enum SpdyControlFlags {
CONTROL_FLAG_NONE = 0x00,
CONTROL_FLAG_FIN = 0x01,
};
enum SpdyPingFlags {
PING_FLAG_ACK = 0x01,
};
// Used by HEADERS, PUSH_PROMISE, and CONTINUATION.
enum SpdyHeadersFlags {
HEADERS_FLAG_END_HEADERS = 0x04,
HEADERS_FLAG_PADDED = 0x08,
HEADERS_FLAG_PRIORITY = 0x20,
};
enum SpdyPushPromiseFlags {
PUSH_PROMISE_FLAG_END_PUSH_PROMISE = 0x04,
PUSH_PROMISE_FLAG_PADDED = 0x08,
};
enum Http2SettingsControlFlags {
SETTINGS_FLAG_ACK = 0x01,
};
// Wire values of HTTP/2 setting identifiers.
enum SpdyKnownSettingsId : SpdySettingsId {
// HPACK header table maximum size.
SETTINGS_HEADER_TABLE_SIZE = 0x1,
SETTINGS_MIN = SETTINGS_HEADER_TABLE_SIZE,
// Whether or not server push (PUSH_PROMISE) is enabled.
SETTINGS_ENABLE_PUSH = 0x2,
// The maximum number of simultaneous live streams in each direction.
SETTINGS_MAX_CONCURRENT_STREAMS = 0x3,
// Initial window size in bytes
SETTINGS_INITIAL_WINDOW_SIZE = 0x4,
// The size of the largest frame payload that a receiver is willing to accept.
SETTINGS_MAX_FRAME_SIZE = 0x5,
// The maximum size of header list that the sender is prepared to accept.
SETTINGS_MAX_HEADER_LIST_SIZE = 0x6,
// Enable Websockets over HTTP/2, see
// https://httpwg.org/specs/rfc8441.html
SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x8,
// Disable HTTP/2 priorities, see
// https://tools.ietf.org/html/draft-ietf-httpbis-priority-02.
SETTINGS_DEPRECATE_HTTP2_PRIORITIES = 0x9,
SETTINGS_MAX = SETTINGS_DEPRECATE_HTTP2_PRIORITIES,
// Experimental setting used to configure an alternative write scheduler.
SETTINGS_EXPERIMENT_SCHEDULER = 0xFF45,
};
// This explicit operator is needed, otherwise compiler finds
// overloaded operator to be ambiguous.
QUICHE_EXPORT std::ostream& operator<<(std::ostream& out,
SpdyKnownSettingsId id);
// This operator is needed, because SpdyFrameType is an enum class,
// therefore implicit conversion to underlying integer type is not allowed.
QUICHE_EXPORT std::ostream& operator<<(std::ostream& out,
SpdyFrameType frame_type);
using SettingsMap = std::map<SpdySettingsId, uint32_t>;
// HTTP/2 error codes, RFC 7540 Section 7.
enum SpdyErrorCode : uint32_t {
ERROR_CODE_NO_ERROR = 0x0,
ERROR_CODE_PROTOCOL_ERROR = 0x1,
ERROR_CODE_INTERNAL_ERROR = 0x2,
ERROR_CODE_FLOW_CONTROL_ERROR = 0x3,
ERROR_CODE_SETTINGS_TIMEOUT = 0x4,
ERROR_CODE_STREAM_CLOSED = 0x5,
ERROR_CODE_FRAME_SIZE_ERROR = 0x6,
ERROR_CODE_REFUSED_STREAM = 0x7,
ERROR_CODE_CANCEL = 0x8,
ERROR_CODE_COMPRESSION_ERROR = 0x9,
ERROR_CODE_CONNECT_ERROR = 0xa,
ERROR_CODE_ENHANCE_YOUR_CALM = 0xb,
ERROR_CODE_INADEQUATE_SECURITY = 0xc,
ERROR_CODE_HTTP_1_1_REQUIRED = 0xd,
ERROR_CODE_MAX = ERROR_CODE_HTTP_1_1_REQUIRED
};
// Type of priority write scheduler.
enum class WriteSchedulerType {
LIFO, // Last added stream has the highest priority.
SPDY, // Uses SPDY priorities described in
// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority.
HTTP2, // Uses HTTP2 (tree-style) priority described in
// https://tools.ietf.org/html/rfc7540#section-5.3.
FIFO, // Stream with the smallest stream ID has the highest priority.
};
// A SPDY priority is a number between 0 and 7 (inclusive).
typedef uint8_t SpdyPriority;
// Lowest and Highest here refer to SPDY priorities as described in
// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority
inline constexpr SpdyPriority kV3HighestPriority = 0;
inline constexpr SpdyPriority kV3LowestPriority = 7;
// Returns SPDY 3.x priority value clamped to the valid range of [0, 7].
QUICHE_EXPORT SpdyPriority ClampSpdy3Priority(SpdyPriority priority);
// HTTP/2 stream weights are integers in range [1, 256], as specified in RFC
// 7540 section 5.3.2. Default stream weight is defined in section 5.3.5.
inline constexpr int kHttp2MinStreamWeight = 1;
inline constexpr int kHttp2MaxStreamWeight = 256;
inline constexpr int kHttp2DefaultStreamWeight = 16;
// Returns HTTP/2 weight clamped to the valid range of [1, 256].
QUICHE_EXPORT int ClampHttp2Weight(int weight);
// Maps SPDY 3.x priority value in range [0, 7] to HTTP/2 weight value in range
// [1, 256], where priority 0 (i.e. highest precedence) corresponds to maximum
// weight 256 and priority 7 (lowest precedence) corresponds to minimum weight
// 1.
QUICHE_EXPORT int Spdy3PriorityToHttp2Weight(SpdyPriority priority);
// Maps HTTP/2 weight value in range [1, 256] to SPDY 3.x priority value in
// range [0, 7], where minimum weight 1 corresponds to priority 7 (lowest
// precedence) and maximum weight 256 corresponds to priority 0 (highest
// precedence).
QUICHE_EXPORT SpdyPriority Http2WeightToSpdy3Priority(int weight);
// Reserved ID for root stream of HTTP/2 stream dependency tree, as specified
// in RFC 7540 section 5.3.1.
const unsigned int kHttp2RootStreamId = 0;
typedef uint64_t SpdyPingId;
// Returns true if a given on-the-wire enumeration of a frame type is defined
// in a standardized HTTP/2 specification, false otherwise.
QUICHE_EXPORT bool IsDefinedFrameType(uint8_t frame_type_field);
// Parses a frame type from an on-the-wire enumeration.
// Behavior is undefined for invalid frame type fields; consumers should first
// use IsValidFrameType() to verify validity of frame type fields.
QUICHE_EXPORT SpdyFrameType ParseFrameType(uint8_t frame_type_field);
// Serializes a frame type to the on-the-wire value.
QUICHE_EXPORT uint8_t SerializeFrameType(SpdyFrameType frame_type);
// (HTTP/2) All standard frame types except WINDOW_UPDATE are
// (stream-specific xor connection-level). Returns false iff we know
// the given frame type does not align with the given streamID.
QUICHE_EXPORT bool IsValidHTTP2FrameStreamId(
SpdyStreamId current_frame_stream_id, SpdyFrameType frame_type_field);
// Serialize |frame_type| to string for logging/debugging.
QUICHE_EXPORT const char* FrameTypeToString(SpdyFrameType frame_type);
// If |wire_setting_id| is the on-the-wire representation of a defined SETTINGS
// parameter, parse it to |*setting_id| and return true.
QUICHE_EXPORT bool ParseSettingsId(SpdySettingsId wire_setting_id,
SpdyKnownSettingsId* setting_id);
// Returns a string representation of the |id| for logging/debugging. Returns
// the |id| prefixed with "SETTINGS_UNKNOWN_" for unknown SETTINGS IDs. To parse
// the |id| into a SpdyKnownSettingsId (if applicable), use ParseSettingsId().
QUICHE_EXPORT std::string SettingsIdToString(SpdySettingsId id);
// Parse |wire_error_code| to a SpdyErrorCode.
// Treat unrecognized error codes as INTERNAL_ERROR
// as recommended by the HTTP/2 specification.
QUICHE_EXPORT SpdyErrorCode ParseErrorCode(uint32_t wire_error_code);
// Serialize RST_STREAM or GOAWAY frame error code to string
// for logging/debugging.
QUICHE_EXPORT const char* ErrorCodeToString(SpdyErrorCode error_code);
// Serialize |type| to string for logging/debugging.
QUICHE_EXPORT const char* WriteSchedulerTypeToString(WriteSchedulerType type);
// Minimum size of a frame, in octets.
inline constexpr size_t kFrameMinimumSize = kFrameHeaderSize;
// Minimum frame size for variable size frame types (includes mandatory fields),
// frame size for fixed size frames, in octets.
inline constexpr size_t kDataFrameMinimumSize = kFrameHeaderSize;
inline constexpr size_t kHeadersFrameMinimumSize = kFrameHeaderSize;
// PRIORITY frame has stream_dependency (4 octets) and weight (1 octet) fields.
inline constexpr size_t kPriorityFrameSize = kFrameHeaderSize + 5;
// RST_STREAM frame has error_code (4 octets) field.
inline constexpr size_t kRstStreamFrameSize = kFrameHeaderSize + 4;
inline constexpr size_t kSettingsFrameMinimumSize = kFrameHeaderSize;
inline constexpr size_t kSettingsOneSettingSize =
sizeof(uint32_t) + sizeof(SpdySettingsId);
// PUSH_PROMISE frame has promised_stream_id (4 octet) field.
inline constexpr size_t kPushPromiseFrameMinimumSize = kFrameHeaderSize + 4;
// PING frame has opaque_bytes (8 octet) field.
inline constexpr size_t kPingFrameSize = kFrameHeaderSize + 8;
// GOAWAY frame has last_stream_id (4 octet) and error_code (4 octet) fields.
inline constexpr size_t kGoawayFrameMinimumSize = kFrameHeaderSize + 8;
// WINDOW_UPDATE frame has window_size_increment (4 octet) field.
inline constexpr size_t kWindowUpdateFrameSize = kFrameHeaderSize + 4;
inline constexpr size_t kContinuationFrameMinimumSize = kFrameHeaderSize;
// ALTSVC frame has origin_len (2 octets) field.
inline constexpr size_t kGetAltSvcFrameMinimumSize = kFrameHeaderSize + 2;
// PRIORITY_UPDATE frame has prioritized_stream_id (4 octets) field.
inline constexpr size_t kPriorityUpdateFrameMinimumSize = kFrameHeaderSize + 4;
// ACCEPT_CH frame may have empty payload.
inline constexpr size_t kAcceptChFrameMinimumSize = kFrameHeaderSize;
// Each ACCEPT_CH frame entry has a 16-bit origin length and a 16-bit value
// length.
inline constexpr size_t kAcceptChFramePerEntryOverhead = 4;
// Maximum possible configurable size of a frame in octets.
inline constexpr size_t kMaxFrameSizeLimit =
kSpdyMaxFrameSizeLimit + kFrameHeaderSize;
// Size of a header block size field.
inline constexpr size_t kSizeOfSizeField = sizeof(uint32_t);
// Initial window size for a stream in bytes.
inline constexpr int32_t kInitialStreamWindowSize = 64 * 1024 - 1;
// Initial window size for a session in bytes.
inline constexpr int32_t kInitialSessionWindowSize = 64 * 1024 - 1;
// The NPN string for HTTP2, "h2".
QUICHE_EXPORT extern const char* const kHttp2Npn;
// An estimate of the HPACK overhead for each header field in bytes, intended to
// be no smaller than actual overhead, based on the literal header field
// representation in RFC 7541 Section 6.2 (with or without indexing):
// - 1 byte for the opcode.
// - 2 bytes for the name length (assuming new name).
// - 3 bytes for the value length.
inline constexpr size_t kPerHeaderHpackOverhead = 6;
// Names of pseudo-headers defined for HTTP/2 requests.
QUICHE_EXPORT extern const char* const kHttp2AuthorityHeader;
QUICHE_EXPORT extern const char* const kHttp2MethodHeader;
QUICHE_EXPORT extern const char* const kHttp2PathHeader;
QUICHE_EXPORT extern const char* const kHttp2SchemeHeader;
QUICHE_EXPORT extern const char* const kHttp2ProtocolHeader;
// Name of pseudo-header defined for HTTP/2 responses.
QUICHE_EXPORT extern const char* const kHttp2StatusHeader;
QUICHE_EXPORT size_t GetNumberRequiredContinuationFrames(size_t size);
// Variant type that is either a SPDY 3.x priority value, or else an HTTP/2
// stream dependency tuple {parent stream ID, weight, exclusive bit}. Templated
// to allow for use by QUIC code; SPDY and HTTP/2 code should use the concrete
// type instantiation SpdyStreamPrecedence.
template <typename StreamIdType>
class QUICHE_EXPORT StreamPrecedence {
public:
// Constructs instance that is a SPDY 3.x priority. Clamps priority value to
// the valid range [0, 7].
explicit StreamPrecedence(SpdyPriority priority)
: precedence_(ClampSpdy3Priority(priority)) {}
// Constructs instance that is an HTTP/2 stream weight, parent stream ID, and
// exclusive bit. Clamps stream weight to the valid range [1, 256].
StreamPrecedence(StreamIdType parent_id, int weight, bool is_exclusive)
: precedence_(Http2StreamDependency{parent_id, ClampHttp2Weight(weight),
is_exclusive}) {}
// Intentionally copyable, to support pass by value.
StreamPrecedence(const StreamPrecedence& other) = default;
StreamPrecedence& operator=(const StreamPrecedence& other) = default;
// Returns true if this instance is a SPDY 3.x priority, or false if this
// instance is an HTTP/2 stream dependency.
bool is_spdy3_priority() const {
return std::holds_alternative<SpdyPriority>(precedence_);
}
// Returns SPDY 3.x priority value. If |is_spdy3_priority()| is true, this is
// the value provided at construction, clamped to the legal priority
// range. Otherwise, it is the HTTP/2 stream weight mapped to a SPDY 3.x
// priority value, where minimum weight 1 corresponds to priority 7 (lowest
// precedence) and maximum weight 256 corresponds to priority 0 (highest
// precedence).
SpdyPriority spdy3_priority() const {
return is_spdy3_priority()
? std::get<SpdyPriority>(precedence_)
: Http2WeightToSpdy3Priority(
std::get<Http2StreamDependency>(precedence_).weight);
}
// Returns HTTP/2 parent stream ID. If |is_spdy3_priority()| is false, this is
// the value provided at construction, otherwise it is |kHttp2RootStreamId|.
StreamIdType parent_id() const {
return is_spdy3_priority()
? kHttp2RootStreamId
: std::get<Http2StreamDependency>(precedence_).parent_id;
}
// Returns HTTP/2 stream weight. If |is_spdy3_priority()| is false, this is
// the value provided at construction, clamped to the legal weight
// range. Otherwise, it is the SPDY 3.x priority value mapped to an HTTP/2
// stream weight, where priority 0 (i.e. highest precedence) corresponds to
// maximum weight 256 and priority 7 (lowest precedence) corresponds to
// minimum weight 1.
int weight() const {
return is_spdy3_priority()
? Spdy3PriorityToHttp2Weight(std::get<SpdyPriority>(precedence_))
: std::get<Http2StreamDependency>(precedence_).weight;
}
// Returns HTTP/2 parent stream exclusivity. If |is_spdy3_priority()| is
// false, this is the value provided at construction, otherwise it is false.
bool is_exclusive() const {
return std::holds_alternative<Http2StreamDependency>(precedence_) &&
std::get<Http2StreamDependency>(precedence_).is_exclusive;
}
// Facilitates test assertions.
bool operator==(const StreamPrecedence& other) const {
return precedence_ == other.precedence_;
}
bool operator!=(const StreamPrecedence& other) const {
return !(*this == other);
}
private:
struct QUICHE_EXPORT Http2StreamDependency {
StreamIdType parent_id;
int weight;
bool is_exclusive;
bool operator==(const Http2StreamDependency& other) const {
return parent_id == other.parent_id && weight == other.weight &&
is_exclusive == other.is_exclusive;
}
};
std::variant<SpdyPriority, Http2StreamDependency> precedence_;
};
typedef StreamPrecedence<SpdyStreamId> SpdyStreamPrecedence;
// =============================================================================
// Value-Semantic POD Frame Structs
// =============================================================================
// Contains the fields specific to any priority-conveying frames.
struct QUICHE_EXPORT PriorityFields {
SpdyStreamId parent_stream_id = 0;
uint16_t weight = kHttp2DefaultStreamWeight;
bool exclusive = false;
constexpr bool operator==(const PriorityFields&) const = default;
};
// A container for a single SETTINGS field.
struct QUICHE_EXPORT SettingParameter {
SpdySettingsId id = 0;
uint32_t value = 0;
constexpr bool operator==(const SettingParameter&) const = default;
};
// A container for a single ACCEPT_CH entry.
struct QUICHE_EXPORT AcceptChEntryView {
absl::string_view origin;
absl::string_view value;
constexpr bool operator==(const AcceptChEntryView&) const = default;
};
struct QUICHE_EXPORT DataFrame {
SpdyStreamId stream_id = 0;
uint8_t flags = 0;
uint8_t padding_payload_len = 0;
absl::string_view data;
constexpr bool HasFin() const { return (flags & DATA_FLAG_FIN) != 0; }
ABSL_DEPRECATED("Use HasFin() instead.") constexpr bool fin() const {
return HasFin();
}
constexpr bool IsPadded() const {
return (flags & DATA_FLAG_PADDED) != 0 || padding_payload_len > 0;
}
ABSL_DEPRECATED("Use IsPadded() instead.") constexpr bool padded() const {
return IsPadded();
}
constexpr bool operator==(const DataFrame&) const = default;
};
static_assert(sizeof(DataFrame) == (sizeof(void*) == 8 ? 24 : 16));
struct QUICHE_EXPORT HeadersFrame {
SpdyStreamId stream_id = 0;
uint8_t flags = 0;
uint8_t padding_payload_len = 0;
bool has_priority = false;
PriorityFields priority{};
absl::string_view hpack_block;
constexpr bool HasFin() const { return (flags & CONTROL_FLAG_FIN) != 0; }
ABSL_DEPRECATED("Use HasFin() instead.") constexpr bool fin() const {
return HasFin();
}
constexpr bool HasEndHeaders() const {
return (flags & HEADERS_FLAG_END_HEADERS) != 0;
}
ABSL_DEPRECATED("Use HasEndHeaders() instead.")
constexpr bool end_headers() const { return HasEndHeaders(); }
constexpr bool IsPadded() const {
return (flags & HEADERS_FLAG_PADDED) != 0 || padding_payload_len > 0;
}
ABSL_DEPRECATED("Use IsPadded() instead.") constexpr bool padded() const {
return IsPadded();
}
constexpr bool operator==(const HeadersFrame&) const = default;
};
static_assert(sizeof(HeadersFrame) == (sizeof(void*) == 8 ? 32 : 24));
struct QUICHE_EXPORT PriorityFrame {
SpdyStreamId stream_id = 0;
PriorityFields priority{};
constexpr bool operator==(const PriorityFrame&) const = default;
};
static_assert(sizeof(PriorityFrame) == 12);
struct QUICHE_EXPORT RstStreamFrame {
SpdyStreamId stream_id = 0;
SpdyErrorCode error_code = ERROR_CODE_NO_ERROR;
constexpr bool operator==(const RstStreamFrame&) const = default;
};
static_assert(sizeof(RstStreamFrame) == 8);
struct QUICHE_EXPORT SettingsFrame {
static constexpr size_t kMaxInlineSettings = 5;
bool is_ack = false;
absl::InlinedVector<SettingParameter, kMaxInlineSettings> values;
bool operator==(const SettingsFrame&) const = default;
};
static_assert(sizeof(SettingsFrame) == (sizeof(void*) == 8 ? 56 : 48));
struct QUICHE_EXPORT PushPromiseFrame {
SpdyStreamId stream_id = 0;
SpdyStreamId promised_stream_id = 0;
uint8_t flags = 0;
uint8_t padding_payload_len = 0;
absl::string_view hpack_block;
constexpr bool HasEndHeaders() const {
return (flags & PUSH_PROMISE_FLAG_END_PUSH_PROMISE) != 0;
}
ABSL_DEPRECATED("Use HasEndHeaders() instead.")
constexpr bool end_headers() const { return HasEndHeaders(); }
constexpr bool IsPadded() const {
return (flags & PUSH_PROMISE_FLAG_PADDED) != 0 || padding_payload_len > 0;
}
ABSL_DEPRECATED("Use IsPadded() instead.") constexpr bool padded() const {
return IsPadded();
}
constexpr bool operator==(const PushPromiseFrame&) const = default;
};
static_assert(sizeof(PushPromiseFrame) == (sizeof(void*) == 8 ? 32 : 20));
struct QUICHE_EXPORT PingFrame {
uint64_t opaque_data = 0;
bool is_ack = false;
constexpr bool operator==(const PingFrame&) const = default;
};
static_assert(sizeof(PingFrame) == (alignof(uint64_t) == 8 ? 16 : 12));
struct QUICHE_EXPORT GoAwayFrame {
SpdyStreamId last_good_stream_id = 0;
SpdyErrorCode error_code = ERROR_CODE_NO_ERROR;
absl::string_view debug_data;
constexpr bool operator==(const GoAwayFrame&) const = default;
};
static_assert(sizeof(GoAwayFrame) == (sizeof(void*) == 8 ? 24 : 16));
struct QUICHE_EXPORT WindowUpdateFrame {
SpdyStreamId stream_id = 0;
uint32_t delta = 0;
constexpr bool operator==(const WindowUpdateFrame&) const = default;
};
static_assert(sizeof(WindowUpdateFrame) == 8);
struct QUICHE_EXPORT ContinuationFrame {
SpdyStreamId stream_id = 0;
uint8_t flags = 0;
absl::string_view hpack_block;
constexpr bool HasEndHeaders() const {
return (flags & HEADERS_FLAG_END_HEADERS) != 0;
}
ABSL_DEPRECATED("Use HasEndHeaders() instead.")
constexpr bool end_headers() const { return HasEndHeaders(); }
constexpr bool operator==(const ContinuationFrame&) const = default;
};
static_assert(sizeof(ContinuationFrame) == (sizeof(void*) == 8 ? 24 : 16));
struct QUICHE_EXPORT AltSvcFrame {
SpdyStreamId stream_id = 0;
absl::string_view origin;
absl::string_view value;
constexpr bool operator==(const AltSvcFrame&) const = default;
};
static_assert(sizeof(AltSvcFrame) == (sizeof(void*) == 8 ? 40 : 20));
struct QUICHE_EXPORT PriorityUpdateFrame {
SpdyStreamId prioritized_stream_id = 0;
absl::string_view priority_field_value;
constexpr bool operator==(const PriorityUpdateFrame&) const = default;
};
static_assert(sizeof(PriorityUpdateFrame) == (sizeof(void*) == 8 ? 24 : 12));
struct QUICHE_EXPORT AcceptChFrame {
static constexpr size_t kMaxInlineEntries = 1;
uint8_t num_entries = 0;
std::array<AcceptChEntryView, kMaxInlineEntries> entries{};
constexpr bool operator==(const AcceptChFrame&) const = default;
};
static_assert(sizeof(AcceptChFrame) == (sizeof(void*) == 8 ? 40 : 20));
struct QUICHE_EXPORT UnknownFrame {
SpdyStreamId stream_id = 0;
uint8_t type = 0;
uint8_t flags = 0;
absl::string_view payload;
constexpr bool operator==(const UnknownFrame&) const = default;
};
static_assert(sizeof(UnknownFrame) == (sizeof(void*) == 8 ? 24 : 16));
// Compile-Time Type Traits
template <typename T>
struct FrameTraits;
#define REGISTER_FRAME_TRAITS(Type, EnumVal, HasStreamId, HasFin, HasPadding, \
IsFixed, FixedSize, ConsumesFC) \
template <> \
struct FrameTraits<Type> { \
static constexpr SpdyFrameType frame_type = EnumVal; \
static constexpr bool has_stream_id = HasStreamId; \
static constexpr bool has_fin = HasFin; \
static constexpr bool has_padding = HasPadding; \
static constexpr bool is_fixed_size = IsFixed; \
static constexpr size_t fixed_payload_size = FixedSize; \
static constexpr bool consumes_flow_control = ConsumesFC; \
};
REGISTER_FRAME_TRAITS(DataFrame, SpdyFrameType::DATA, true, true, true, false,
0, true)
REGISTER_FRAME_TRAITS(HeadersFrame, SpdyFrameType::HEADERS, true, true, true,
false, 0, false)
REGISTER_FRAME_TRAITS(PriorityFrame, SpdyFrameType::PRIORITY, true, false,
false, true, 5, false)
REGISTER_FRAME_TRAITS(RstStreamFrame, SpdyFrameType::RST_STREAM, true, false,
false, true, 4, false)
REGISTER_FRAME_TRAITS(SettingsFrame, SpdyFrameType::SETTINGS, false, false,
false, false, 0, false)
REGISTER_FRAME_TRAITS(PushPromiseFrame, SpdyFrameType::PUSH_PROMISE, true,
false, true, false, 0, false)
REGISTER_FRAME_TRAITS(PingFrame, SpdyFrameType::PING, false, false, false, true,
8, false)
REGISTER_FRAME_TRAITS(GoAwayFrame, SpdyFrameType::GOAWAY, false, false, false,
false, 0, false)
REGISTER_FRAME_TRAITS(WindowUpdateFrame, SpdyFrameType::WINDOW_UPDATE, true,
false, false, true, 4, false)
REGISTER_FRAME_TRAITS(ContinuationFrame, SpdyFrameType::CONTINUATION, true,
false, false, false, 0, false)
REGISTER_FRAME_TRAITS(AltSvcFrame, SpdyFrameType::ALTSVC, true, false, false,
false, 0, false)
REGISTER_FRAME_TRAITS(PriorityUpdateFrame, SpdyFrameType::PRIORITY_UPDATE,
false, false, false, false, 0, false)
REGISTER_FRAME_TRAITS(AcceptChFrame, SpdyFrameType::ACCEPT_CH, false, false,
false, false, 0, false)
REGISTER_FRAME_TRAITS(UnknownFrame, SpdyFrameType::DATA, true, false, false,
false, 0, false)
#undef REGISTER_FRAME_TRAITS
template <typename T>
inline constexpr SpdyFrameType frame_type_v = FrameTraits<T>::frame_type;
template <typename T>
inline constexpr bool is_fixed_size_v = FrameTraits<T>::is_fixed_size;
template <typename T>
inline constexpr bool has_stream_id_v = FrameTraits<T>::has_stream_id;
template <typename T>
inline constexpr bool has_fin_v = FrameTraits<T>::has_fin;
template <typename T>
inline constexpr bool has_padding_v = FrameTraits<T>::has_padding;
template <typename T>
inline constexpr bool consumes_flow_control_v =
FrameTraits<T>::consumes_flow_control;
template <typename T>
concept Http2FrameConcept = requires {
{ FrameTraits<T>::frame_type } -> std::convertible_to<SpdyFrameType>;
{ FrameTraits<T>::is_fixed_size } -> std::convertible_to<bool>;
{ FrameTraits<T>::has_stream_id } -> std::convertible_to<bool>;
{ FrameTraits<T>::has_fin } -> std::convertible_to<bool>;
{ FrameTraits<T>::has_padding } -> std::convertible_to<bool>;
{ FrameTraits<T>::consumes_flow_control } -> std::convertible_to<bool>;
};
// Generic Size Calculation
template <Http2FrameConcept T>
constexpr size_t FrameSize(const T& frame) {
if constexpr (is_fixed_size_v<T>) {
return kFrameHeaderSize + FrameTraits<T>::fixed_payload_size;
} else if constexpr (std::is_same_v<T, DataFrame>) {
return kDataFrameMinimumSize + frame.data.size() +
(frame.IsPadded() ? (1 + frame.padding_payload_len) : 0);
} else if constexpr (std::is_same_v<T, HeadersFrame>) {
size_t size = kHeadersFrameMinimumSize + frame.hpack_block.size();
if (frame.IsPadded()) {
size += 1 + frame.padding_payload_len;
}
if (frame.has_priority) {
size += 5;
}
return size;
} else if constexpr (std::is_same_v<T, SettingsFrame>) {
if (frame.is_ack) {
return kSettingsFrameMinimumSize;
}
return kSettingsFrameMinimumSize +
(frame.values.size() * kSettingsOneSettingSize);
} else if constexpr (std::is_same_v<T, PushPromiseFrame>) {
size_t size = kPushPromiseFrameMinimumSize + frame.hpack_block.size();
if (frame.IsPadded()) {
size += 1 + frame.padding_payload_len;
}
return size;
} else if constexpr (std::is_same_v<T, GoAwayFrame>) {
return kGoawayFrameMinimumSize + frame.debug_data.size();
} else if constexpr (std::is_same_v<T, ContinuationFrame>) {
return kContinuationFrameMinimumSize + frame.hpack_block.size();
} else if constexpr (std::is_same_v<T, AltSvcFrame>) {
return kGetAltSvcFrameMinimumSize + frame.origin.size() +
frame.value.size();
} else if constexpr (std::is_same_v<T, PriorityUpdateFrame>) {
return kPriorityUpdateFrameMinimumSize + frame.priority_field_value.size();
} else if constexpr (std::is_same_v<T, AcceptChFrame>) {
size_t total_size = kAcceptChFrameMinimumSize;
for (size_t i = 0; i < frame.num_entries && i < frame.entries.size(); ++i) {
total_size += frame.entries[i].origin.size() +
frame.entries[i].value.size() +
kAcceptChFramePerEntryOverhead;
}
return total_size;
} else if constexpr (std::is_same_v<T, UnknownFrame>) {
return kFrameHeaderSize + frame.payload.size();
} else {
return kFrameHeaderSize;
}
}
// Forward declares SpdyFrameBuilder for SerializeFrame.
class SpdyFrameBuilder;
template <Http2FrameConcept T>
bool SerializeFrame(const T& frame, SpdyFrameBuilder& builder);
// Unified variant.
using SpdyFrame =
std::variant<DataFrame, HeadersFrame, PriorityFrame, RstStreamFrame,
SettingsFrame, PushPromiseFrame, PingFrame, GoAwayFrame,
WindowUpdateFrame, ContinuationFrame, AltSvcFrame,
PriorityUpdateFrame, AcceptChFrame, UnknownFrame>;
static_assert(sizeof(SpdyFrame) <= 64,
"SpdyFrame must fit within a 64-byte cache line");
inline SpdyStreamId GetFrameStreamId(const SpdyFrame& frame) {
return std::visit(
[](const auto& f) -> SpdyStreamId {
using T = std::decay_t<decltype(f)>;
if constexpr (has_stream_id_v<T>) {
return f.stream_id;
}
return 0;
},
frame);
}
inline SpdyFrameType GetFrameType(const SpdyFrame& frame) {
return std::visit(
[](const auto& f) -> SpdyFrameType {
using T = std::decay_t<decltype(f)>;
if constexpr (std::is_same_v<T, UnknownFrame>) {
return static_cast<SpdyFrameType>(f.type);
}
return frame_type_v<T>;
},
frame);
}
inline size_t GetFrameSize(const SpdyFrame& frame) {
return std::visit([](const auto& f) { return FrameSize(f); }, frame);
}
bool SerializeSpdyFrame(const SpdyFrame& frame, SpdyFrameBuilder& builder);
class SpdyFrameVisitor;
// Intermediate representation for HTTP2 frames.
class QUICHE_EXPORT SpdyFrameIR {
public:
virtual ~SpdyFrameIR() {}
virtual void Visit(SpdyFrameVisitor* visitor) const = 0;
virtual SpdyFrameType frame_type() const = 0;
SpdyStreamId stream_id() const { return stream_id_; }
virtual bool fin() const;
// Returns an estimate of the size of the serialized frame, without applying
// compression. May not be exact, but implementations should return the same
// value for a const frame.
virtual size_t size() const = 0;
// Returns the number of bytes of flow control window that would be consumed
// by this frame if written to the wire.
virtual int flow_control_window_consumed() const;
protected:
SpdyFrameIR() : stream_id_(0) {}
explicit SpdyFrameIR(SpdyStreamId stream_id) : stream_id_(stream_id) {}
SpdyFrameIR(const SpdyFrameIR&) = delete;
SpdyFrameIR& operator=(const SpdyFrameIR&) = delete;
private:
SpdyStreamId stream_id_;
};
// Abstract class intended to be inherited by IRs that have the option of a FIN
// flag.
class QUICHE_EXPORT SpdyFrameWithFinIR : public SpdyFrameIR {
public:
~SpdyFrameWithFinIR() override {}
bool fin() const override;
void set_fin(bool fin) { fin_ = fin; }
protected:
explicit SpdyFrameWithFinIR(SpdyStreamId stream_id)
: SpdyFrameIR(stream_id), fin_(false) {}
SpdyFrameWithFinIR(const SpdyFrameWithFinIR&) = delete;
SpdyFrameWithFinIR& operator=(const SpdyFrameWithFinIR&) = delete;
private:
bool fin_;
};
// Abstract class intended to be inherited by IRs that contain a header
// block. Implies SpdyFrameWithFinIR.
class QUICHE_EXPORT SpdyFrameWithHeaderBlockIR : public SpdyFrameWithFinIR {
public:
~SpdyFrameWithHeaderBlockIR() override;
const quiche::HttpHeaderBlock& header_block() const { return header_block_; }
void set_header_block(quiche::HttpHeaderBlock header_block) {
// Deep copy.
header_block_ = std::move(header_block);
}
void SetHeader(absl::string_view name, absl::string_view value) {
header_block_[name] = value;
}
protected:
SpdyFrameWithHeaderBlockIR(SpdyStreamId stream_id,
quiche::HttpHeaderBlock header_block);
SpdyFrameWithHeaderBlockIR(const SpdyFrameWithHeaderBlockIR&) = delete;
SpdyFrameWithHeaderBlockIR& operator=(const SpdyFrameWithHeaderBlockIR&) =
delete;
private:
quiche::HttpHeaderBlock header_block_;
};
class QUICHE_EXPORT SpdyDataIR : public SpdyFrameWithFinIR {
public:
// Makes a copy of `data` if passed a non-movable string.
SpdyDataIR(SpdyStreamId stream_id, std::string data);
template <typename S>
SpdyDataIR(SpdyStreamId stream_id, S data)
: SpdyDataIR(stream_id, std::string(data)) {}
// Use in conjunction with SetDataShallow() to set the DATA frame payload
// length.
explicit SpdyDataIR(SpdyStreamId stream_id);
SpdyDataIR(const SpdyDataIR&) = delete;
SpdyDataIR& operator=(const SpdyDataIR&) = delete;
~SpdyDataIR() override;
const char* data() const { return data_; }
size_t data_len() const { return data_len_; }
bool padded() const { return padded_; }
int padding_payload_len() const { return padding_payload_len_; }
void set_padding_len(int padding_len) {
QUICHE_DCHECK_GT(padding_len, 0);
QUICHE_DCHECK_LE(padding_len, kPaddingSizePerFrame);
padded_ = true;
// The pad field takes one octet on the wire.
padding_payload_len_ = padding_len - 1;
}
// Use this method if we don't have a contiguous buffer and only
// need a length.
void SetDataShallow(size_t len) {
data_store_ = std::nullopt;
data_ = nullptr;
data_len_ = len;
}
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
int flow_control_window_consumed() const override;
size_t size() const override;
private:
// Used to store data that this SpdyDataIR should own.
std::optional<std::string> data_store_;
const char* data_;
size_t data_len_;
bool padded_;
// padding_payload_len_ = desired padding length - len(padding length field).
int padding_payload_len_;
};
class QUICHE_EXPORT SpdyRstStreamIR : public SpdyFrameIR {
public:
SpdyRstStreamIR(SpdyStreamId stream_id, SpdyErrorCode error_code);
SpdyRstStreamIR(const SpdyRstStreamIR&) = delete;
SpdyRstStreamIR& operator=(const SpdyRstStreamIR&) = delete;
~SpdyRstStreamIR() override;
SpdyErrorCode error_code() const { return error_code_; }
void set_error_code(SpdyErrorCode error_code) { error_code_ = error_code; }
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
private:
SpdyErrorCode error_code_;
};
class QUICHE_EXPORT SpdySettingsIR : public SpdyFrameIR {
public:
SpdySettingsIR();
SpdySettingsIR(const SpdySettingsIR&) = delete;
SpdySettingsIR& operator=(const SpdySettingsIR&) = delete;
~SpdySettingsIR() override;
// Overwrites as appropriate.
const SettingsMap& values() const { return values_; }
void AddSetting(SpdySettingsId id, int32_t value) { values_[id] = value; }
bool is_ack() const { return is_ack_; }
void set_is_ack(bool is_ack) { is_ack_ = is_ack; }
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
private:
SettingsMap values_;
bool is_ack_;
};
class QUICHE_EXPORT SpdyPingIR : public SpdyFrameIR {
public:
explicit SpdyPingIR(SpdyPingId id) : id_(id), is_ack_(false) {}
SpdyPingIR(const SpdyPingIR&) = delete;
SpdyPingIR& operator=(const SpdyPingIR&) = delete;
SpdyPingId id() const { return id_; }
bool is_ack() const { return is_ack_; }
void set_is_ack(bool is_ack) { is_ack_ = is_ack; }
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
private:
SpdyPingId id_;
bool is_ack_;
};
class QUICHE_EXPORT SpdyGoAwayIR : public SpdyFrameIR {
public:
// References description, doesn't copy it, so description must outlast
// this SpdyGoAwayIR.
SpdyGoAwayIR(SpdyStreamId last_good_stream_id, SpdyErrorCode error_code,
absl::string_view description);
// References description, doesn't copy it, so description must outlast
// this SpdyGoAwayIR.
SpdyGoAwayIR(SpdyStreamId last_good_stream_id, SpdyErrorCode error_code,
const char* description);
// Moves description into description_store_, so caller doesn't need to
// keep description live after constructing this SpdyGoAwayIR.
SpdyGoAwayIR(SpdyStreamId last_good_stream_id, SpdyErrorCode error_code,
std::string description);
SpdyGoAwayIR(const SpdyGoAwayIR&) = delete;
SpdyGoAwayIR& operator=(const SpdyGoAwayIR&) = delete;
~SpdyGoAwayIR() override;
SpdyStreamId last_good_stream_id() const { return last_good_stream_id_; }
void set_last_good_stream_id(SpdyStreamId last_good_stream_id) {
QUICHE_DCHECK_EQ(0u, last_good_stream_id & ~kStreamIdMask);
last_good_stream_id_ = last_good_stream_id;
}
SpdyErrorCode error_code() const { return error_code_; }
void set_error_code(SpdyErrorCode error_code) {
// TODO(hkhalil): Check valid ranges of error_code?
error_code_ = error_code;
}
const absl::string_view& description() const { return description_; }
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
private:
SpdyStreamId last_good_stream_id_;
SpdyErrorCode error_code_;
const std::string description_store_;
const absl::string_view description_;
};
class QUICHE_EXPORT SpdyHeadersIR : public SpdyFrameWithHeaderBlockIR {
public:
explicit SpdyHeadersIR(SpdyStreamId stream_id)
: SpdyHeadersIR(stream_id, quiche::HttpHeaderBlock()) {}
SpdyHeadersIR(SpdyStreamId stream_id, quiche::HttpHeaderBlock header_block)
: SpdyFrameWithHeaderBlockIR(stream_id, std::move(header_block)) {}
SpdyHeadersIR(const SpdyHeadersIR&) = delete;
SpdyHeadersIR& operator=(const SpdyHeadersIR&) = delete;
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
bool has_priority() const { return has_priority_; }
void set_has_priority(bool has_priority) { has_priority_ = has_priority; }
int weight() const { return weight_; }
void set_weight(int weight) { weight_ = weight; }
SpdyStreamId parent_stream_id() const { return parent_stream_id_; }
void set_parent_stream_id(SpdyStreamId id) { parent_stream_id_ = id; }
bool exclusive() const { return exclusive_; }
void set_exclusive(bool exclusive) { exclusive_ = exclusive; }
bool padded() const { return padded_; }
int padding_payload_len() const { return padding_payload_len_; }
void set_padding_len(int padding_len) {
QUICHE_DCHECK_GT(padding_len, 0);
QUICHE_DCHECK_LE(padding_len, kPaddingSizePerFrame);
padded_ = true;
// The pad field takes one octet on the wire.
padding_payload_len_ = padding_len - 1;
}
private:
bool has_priority_ = false;
int weight_ = kHttp2DefaultStreamWeight;
SpdyStreamId parent_stream_id_ = 0;
bool exclusive_ = false;
bool padded_ = false;
int padding_payload_len_ = 0;
};
class QUICHE_EXPORT SpdyWindowUpdateIR : public SpdyFrameIR {
public:
SpdyWindowUpdateIR(SpdyStreamId stream_id, int32_t delta)
: SpdyFrameIR(stream_id) {
set_delta(delta);
}
SpdyWindowUpdateIR(const SpdyWindowUpdateIR&) = delete;
SpdyWindowUpdateIR& operator=(const SpdyWindowUpdateIR&) = delete;
int32_t delta() const { return delta_; }
void set_delta(int32_t delta) {
QUICHE_DCHECK_LE(0, delta);
QUICHE_DCHECK_LE(delta, kSpdyMaximumWindowSize);
delta_ = delta;
}
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
private:
int32_t delta_;
};
class QUICHE_EXPORT SpdyPushPromiseIR : public SpdyFrameWithHeaderBlockIR {
public:
SpdyPushPromiseIR(SpdyStreamId stream_id, SpdyStreamId promised_stream_id)
: SpdyPushPromiseIR(stream_id, promised_stream_id,
quiche::HttpHeaderBlock()) {}
SpdyPushPromiseIR(SpdyStreamId stream_id, SpdyStreamId promised_stream_id,
quiche::HttpHeaderBlock header_block)
: SpdyFrameWithHeaderBlockIR(stream_id, std::move(header_block)),
promised_stream_id_(promised_stream_id),
padded_(false),
padding_payload_len_(0) {}
SpdyPushPromiseIR(const SpdyPushPromiseIR&) = delete;
SpdyPushPromiseIR& operator=(const SpdyPushPromiseIR&) = delete;
SpdyStreamId promised_stream_id() const { return promised_stream_id_; }
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
bool padded() const { return padded_; }
int padding_payload_len() const { return padding_payload_len_; }
void set_padding_len(int padding_len) {
QUICHE_DCHECK_GT(padding_len, 0);
QUICHE_DCHECK_LE(padding_len, kPaddingSizePerFrame);
padded_ = true;
// The pad field takes one octet on the wire.
padding_payload_len_ = padding_len - 1;
}
private:
SpdyStreamId promised_stream_id_;
bool padded_;
int padding_payload_len_;
};
class QUICHE_EXPORT SpdyContinuationIR : public SpdyFrameIR {
public:
explicit SpdyContinuationIR(SpdyStreamId stream_id);
SpdyContinuationIR(const SpdyContinuationIR&) = delete;
SpdyContinuationIR& operator=(const SpdyContinuationIR&) = delete;
~SpdyContinuationIR() override;
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
bool end_headers() const { return end_headers_; }
void set_end_headers(bool end_headers) { end_headers_ = end_headers; }
const std::string& encoding() const { return encoding_; }
void take_encoding(std::string encoding) { encoding_ = std::move(encoding); }
size_t size() const override;
private:
std::string encoding_;
bool end_headers_;
};
class QUICHE_EXPORT SpdyAltSvcIR : public SpdyFrameIR {
public:
explicit SpdyAltSvcIR(SpdyStreamId stream_id);
SpdyAltSvcIR(const SpdyAltSvcIR&) = delete;
SpdyAltSvcIR& operator=(const SpdyAltSvcIR&) = delete;
~SpdyAltSvcIR() override;
std::string origin() const { return origin_; }
const SpdyAltSvcWireFormat::AlternativeServiceVector& altsvc_vector() const {
return altsvc_vector_;
}
void set_origin(std::string origin) { origin_ = std::move(origin); }
void add_altsvc(const SpdyAltSvcWireFormat::AlternativeService& altsvc) {
altsvc_vector_.push_back(altsvc);
}
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
private:
std::string origin_;
SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector_;
};
class QUICHE_EXPORT SpdyPriorityIR : public SpdyFrameIR {
public:
SpdyPriorityIR(SpdyStreamId stream_id, SpdyStreamId parent_stream_id,
int weight, bool exclusive)
: SpdyFrameIR(stream_id),
parent_stream_id_(parent_stream_id),
weight_(weight),
exclusive_(exclusive) {}
SpdyPriorityIR(const SpdyPriorityIR&) = delete;
SpdyPriorityIR& operator=(const SpdyPriorityIR&) = delete;
SpdyStreamId parent_stream_id() const { return parent_stream_id_; }
int weight() const { return weight_; }
bool exclusive() const { return exclusive_; }
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
private:
SpdyStreamId parent_stream_id_;
int weight_;
bool exclusive_;
};
class QUICHE_EXPORT SpdyPriorityUpdateIR : public SpdyFrameIR {
public:
SpdyPriorityUpdateIR(SpdyStreamId stream_id,
SpdyStreamId prioritized_stream_id,
std::string priority_field_value)
: SpdyFrameIR(stream_id),
prioritized_stream_id_(prioritized_stream_id),
priority_field_value_(std::move(priority_field_value)) {}
SpdyPriorityUpdateIR(const SpdyPriorityUpdateIR&) = delete;
SpdyPriorityUpdateIR& operator=(const SpdyPriorityUpdateIR&) = delete;
SpdyStreamId prioritized_stream_id() const { return prioritized_stream_id_; }
const std::string& priority_field_value() const {
return priority_field_value_;
}
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
private:
SpdyStreamId prioritized_stream_id_;
std::string priority_field_value_;
};
struct QUICHE_EXPORT AcceptChOriginValuePair {
std::string origin;
std::string value;
bool operator==(const AcceptChOriginValuePair& rhs) const {
return origin == rhs.origin && value == rhs.value;
}
};
class QUICHE_EXPORT SpdyAcceptChIR : public SpdyFrameIR {
public:
explicit SpdyAcceptChIR(std::vector<AcceptChOriginValuePair> entries)
: entries_(std::move(entries)) {}
SpdyAcceptChIR(const SpdyAcceptChIR&) = delete;
SpdyAcceptChIR& operator=(const SpdyAcceptChIR&) = delete;
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
size_t size() const override;
const std::vector<AcceptChOriginValuePair>& entries() const {
return entries_;
}
private:
std::vector<AcceptChOriginValuePair> entries_;
};
// Represents a frame of unrecognized type.
class QUICHE_EXPORT SpdyUnknownIR : public SpdyFrameIR {
public:
SpdyUnknownIR(SpdyStreamId stream_id, uint8_t type, uint8_t flags,
std::string payload)
: SpdyFrameIR(stream_id),
type_(type),
flags_(flags),
length_(payload.size()),
payload_(std::move(payload)) {}
SpdyUnknownIR(const SpdyUnknownIR&) = delete;
SpdyUnknownIR& operator=(const SpdyUnknownIR&) = delete;
uint8_t type() const { return type_; }
uint8_t flags() const { return flags_; }
size_t length() const { return length_; }
const std::string& payload() const { return payload_; }
void Visit(SpdyFrameVisitor* visitor) const override;
SpdyFrameType frame_type() const override;
int flow_control_window_consumed() const override;
size_t size() const override;
protected:
// Allows subclasses to overwrite the default payload length.
void set_length(size_t length) { length_ = length; }
private:
uint8_t type_;
uint8_t flags_;
size_t length_;
const std::string payload_;
};
class QUICHE_EXPORT SpdySerializedFrame {
public:
SpdySerializedFrame() : size_(0) {}
// Creates a valid SpdySerializedFrame using a pre-created buffer.
SpdySerializedFrame(std::unique_ptr<char[]> data, size_t size)
: frame_(std::move(data)), size_(size) {}
SpdySerializedFrame(SpdySerializedFrame&& other)
: frame_(std::move(other.frame_)), size_(other.size_) {}
SpdySerializedFrame(const SpdySerializedFrame&) = delete;
SpdySerializedFrame& operator=(const SpdySerializedFrame&) = delete;
SpdySerializedFrame& operator=(SpdySerializedFrame&& other) {
// Take over |other|.
frame_ = std::move(other.frame_);
size_ = other.size_;
return *this;
}
~SpdySerializedFrame() = default;
// Provides access to the frame bytes, which is a buffer containing the frame
// packed as expected for sending over the wire.
char* data() const { return frame_.get(); }
// Returns the actual size of the underlying buffer.
size_t size() const { return size_; }
char* begin() { return data(); }
char* end() { return data() + size(); }
operator absl::string_view() const {
return absl::string_view{frame_.get(), size_};
}
private:
std::unique_ptr<char[]> frame_;
size_t size_;
};
// This interface is for classes that want to process SpdyFrameIRs without
// having to know what type they are. An instance of this interface can be
// passed to a SpdyFrameIR's Visit method, and the appropriate type-specific
// method of this class will be called.
class QUICHE_EXPORT SpdyFrameVisitor {
public:
SpdyFrameVisitor() {}
SpdyFrameVisitor(const SpdyFrameVisitor&) = delete;
SpdyFrameVisitor& operator=(const SpdyFrameVisitor&) = delete;
virtual ~SpdyFrameVisitor() {}
virtual void VisitRstStream(const SpdyRstStreamIR& rst_stream) = 0;
virtual void VisitSettings(const SpdySettingsIR& settings) = 0;
virtual void VisitPing(const SpdyPingIR& ping) = 0;
virtual void VisitGoAway(const SpdyGoAwayIR& goaway) = 0;
virtual void VisitHeaders(const SpdyHeadersIR& headers) = 0;
virtual void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) = 0;
virtual void VisitPushPromise(const SpdyPushPromiseIR& push_promise) = 0;
virtual void VisitContinuation(const SpdyContinuationIR& continuation) = 0;
virtual void VisitAltSvc(const SpdyAltSvcIR& altsvc) = 0;
virtual void VisitPriority(const SpdyPriorityIR& priority) = 0;
virtual void VisitData(const SpdyDataIR& data) = 0;
virtual void VisitPriorityUpdate(
const SpdyPriorityUpdateIR& priority_update) = 0;
virtual void VisitAcceptCh(const SpdyAcceptChIR& accept_ch) = 0;
virtual void VisitUnknown(const SpdyUnknownIR& /*unknown*/) {
// TODO(birenroy): make abstract.
}
};
// Optionally, and in addition to SpdyFramerVisitorInterface, a class supporting
// SpdyFramerDebugVisitorInterface may be used in conjunction with SpdyFramer in
// order to extract debug/internal information about the SpdyFramer as it
// operates.
//
// Most HTTP2 implementations need not bother with this interface at all.
class QUICHE_EXPORT SpdyFramerDebugVisitorInterface {
public:
virtual ~SpdyFramerDebugVisitorInterface() {}
// Called after compressing a frame with a payload of
// a list of name-value pairs.
// |payload_len| is the uncompressed payload size.
// |frame_len| is the compressed frame size.
virtual void OnSendCompressedFrame(SpdyStreamId /*stream_id*/,
SpdyFrameType /*type*/,
size_t /*payload_len*/,
size_t /*frame_len*/) {}
// Called when a frame containing a compressed payload of
// name-value pairs is received.
// |frame_len| is the compressed frame size.
virtual void OnReceiveCompressedFrame(SpdyStreamId /*stream_id*/,
SpdyFrameType /*type*/,
size_t /*frame_len*/) {}
};
// Calculates the number of bytes required to serialize a SpdyHeadersIR, not
// including the bytes to be used for the encoded header set.
size_t GetHeaderFrameSizeSansBlock(const SpdyHeadersIR& header_ir);
// Calculates the number of bytes required to serialize a SpdyPushPromiseIR,
// not including the bytes to be used for the encoded header set.
size_t GetPushPromiseFrameSizeSansBlock(
const SpdyPushPromiseIR& push_promise_ir);
} // namespace spdy
#endif // QUICHE_HTTP2_CORE_SPDY_PROTOCOL_H_