Branches the nghttp2_adapter interface from //net/http2.
PiperOrigin-RevId: 361667825
Change-Id: I94cab47ce471f23eb872a39173efbc9c159eb76b
diff --git a/http2/adapter/http2_adapter.h b/http2/adapter/http2_adapter.h
new file mode 100644
index 0000000..cccc269
--- /dev/null
+++ b/http2/adapter/http2_adapter.h
@@ -0,0 +1,114 @@
+#ifndef QUICHE_HTTP2_ADAPTER_HTTP2_ADAPTER_H_
+#define QUICHE_HTTP2_ADAPTER_HTTP2_ADAPTER_H_
+
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "http2/adapter/http2_protocol.h"
+#include "http2/adapter/http2_session.h"
+#include "http2/adapter/http2_visitor_interface.h"
+
+namespace http2 {
+namespace adapter {
+
+// Http2Adapter is an HTTP/2-processing class that exposes an interface similar
+// to the nghttp2 library for processing the HTTP/2 wire format. As nghttp2
+// parses HTTP/2 frames and invokes callbacks on Http2Adapter, Http2Adapter then
+// invokes corresponding callbacks on its passed-in Http2VisitorInterface.
+// Http2Adapter is a base class shared between client-side and server-side
+// implementations.
+class Http2Adapter {
+ public:
+ Http2Adapter(const Http2Adapter&) = delete;
+ Http2Adapter& operator=(const Http2Adapter&) = delete;
+
+ // Processes the incoming |bytes| as HTTP/2 and invokes callbacks on the
+ // |visitor_| as appropriate.
+ ssize_t ProcessBytes(absl::string_view bytes);
+
+ // Submits the |settings| to be written to the peer, e.g., as part of the
+ // HTTP/2 connection preface.
+ void SubmitSettings(absl::Span<const Http2Setting> settings);
+
+ // Submits a PRIORITY frame for the given stream.
+ void SubmitPriorityForStream(Http2StreamId stream_id,
+ Http2StreamId parent_stream_id, int weight,
+ bool exclusive);
+
+ // Submits a PING on the connection. Note that nghttp2 automatically submits
+ // PING acks upon receiving non-ack PINGs from the peer, so callers only use
+ // this method to originate PINGs. See nghttp2_option_set_no_auto_ping_ack().
+ void SubmitPing(Http2PingId ping_id);
+
+ // Submits a GOAWAY on the connection. Note that |last_accepted_stream_id|
+ // refers to stream IDs initiated by the peer. For client-side, this last
+ // stream ID must be even (or 0); for server-side, this last stream ID must be
+ // odd (or 0). To submit a GOAWAY with |last_accepted_stream_id| with the
+ // maximum stream ID, signaling imminent connection termination, call
+ // SubmitShutdownNotice() instead (though this is only possible server-side).
+ void SubmitGoAway(Http2StreamId last_accepted_stream_id,
+ Http2ErrorCode error_code, absl::string_view opaque_data);
+
+ // Submits a WINDOW_UPDATE for the given stream (a |stream_id| of 0 indicates
+ // a connection-level WINDOW_UPDATE).
+ void SubmitWindowUpdate(Http2StreamId stream_id, int window_increment);
+
+ // Submits a METADATA frame for the given stream (a |stream_id| of 0 indicates
+ // connection-level METADATA). If |fin|, the frame will also have the
+ // END_METADATA flag set.
+ void SubmitMetadata(Http2StreamId stream_id, bool fin);
+
+ // Returns serialized bytes for writing to the wire.
+ // Writes should be submitted to Http2Adapter first, so that Http2Adapter
+ // has data to serialize and return in this method.
+ std::string GetBytesToWrite();
+
+ // Returns the connection-level flow control window for the peer.
+ int GetPeerConnectionWindow() const;
+
+ // Marks the given amount of data as consumed for the given stream, which
+ // enables the nghttp2 layer to trigger WINDOW_UPDATEs as appropriate.
+ void MarkDataConsumedForStream(Http2StreamId stream_id, size_t num_bytes);
+
+ protected:
+ // Subclasses should expose a public factory method for constructing and
+ // initializing (via Initialize()) adapter instances.
+ explicit Http2Adapter(Http2VisitorInterface& visitor) : visitor_(visitor) {}
+ virtual ~Http2Adapter();
+
+ // Initializes the underlying HTTP/2 session and submits initial SETTINGS.
+ // Users must call Initialize() before doing other work with Http2Adapter.
+ // Because Initialize() calls virtual methods, this method cannot be called in
+ // the constructor. Factory methods may instead construct and initialize
+ // Http2Adapter instances to hide this implementation detail from users.
+ void Initialize();
+
+ // Creates the callbacks that will be used to initialize the |session_|.
+ virtual std::unique_ptr<Http2SessionCallbacks> CreateCallbacks();
+
+ // Creates with the given |callbacks| and |visitor| as context.
+ virtual std::unique_ptr<Http2Session> CreateSession(
+ std::unique_ptr<Http2SessionCallbacks> callbacks,
+ std::unique_ptr<Http2Options> options,
+ std::unique_ptr<Http2VisitorInterface> visitor) = 0;
+
+ // Accessors. Do not transfer ownership.
+ Http2VisitorInterface& visitor() { return visitor_; }
+
+ private:
+ // Creates the connection-level configuration options for the |session_|.
+ std::unique_ptr<Http2Options> CreateOptions();
+
+ // Http2Adapter will invoke callbacks upon the |visitor_| while processing.
+ Http2VisitorInterface& visitor_;
+
+ // Http2Adapter creates a |session_| for use with the underlying library.
+ std::unique_ptr<Http2Session> session_;
+
+ // Http2Adapter creates the |options_| for use with the |session_|.
+ std::unique_ptr<Http2Options> options_;
+};
+
+} // namespace adapter
+} // namespace http2
+
+#endif // QUICHE_HTTP2_ADAPTER_HTTP2_ADAPTER_H_
diff --git a/http2/adapter/http2_protocol.cc b/http2/adapter/http2_protocol.cc
new file mode 100644
index 0000000..18e8985
--- /dev/null
+++ b/http2/adapter/http2_protocol.cc
@@ -0,0 +1,68 @@
+#include "http2/adapter/http2_protocol.h"
+
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+
+namespace http2 {
+namespace adapter {
+
+const char kHttp2MethodPseudoHeader[] = ":method";
+const char kHttp2SchemePseudoHeader[] = ":scheme";
+const char kHttp2AuthorityPseudoHeader[] = ":authority";
+const char kHttp2PathPseudoHeader[] = ":path";
+const char kHttp2StatusPseudoHeader[] = ":status";
+
+absl::string_view Http2SettingsIdToString(uint16_t id) {
+ switch (id) {
+ case Http2KnownSettingsId::HEADER_TABLE_SIZE:
+ return "SETTINGS_HEADER_TABLE_SIZE";
+ case Http2KnownSettingsId::ENABLE_PUSH:
+ return "SETTINGS_ENABLE_PUSH";
+ case Http2KnownSettingsId::MAX_CONCURRENT_STREAMS:
+ return "SETTINGS_MAX_CONCURRENT_STREAMS";
+ case Http2KnownSettingsId::INITIAL_WINDOW_SIZE:
+ return "SETTINGS_INITIAL_WINDOW_SIZE";
+ case Http2KnownSettingsId::MAX_FRAME_SIZE:
+ return "SETTINGS_MAX_FRAME_SIZE";
+ case Http2KnownSettingsId::MAX_HEADER_LIST_SIZE:
+ return "SETTINGS_MAX_HEADER_LIST_SIZE";
+ }
+ return "SETTINGS_UNKNOWN";
+}
+
+absl::string_view Http2ErrorCodeToString(Http2ErrorCode error_code) {
+ switch (error_code) {
+ case Http2ErrorCode::NO_ERROR:
+ return "NO_ERROR";
+ case Http2ErrorCode::PROTOCOL_ERROR:
+ return "PROTOCOL_ERROR";
+ case Http2ErrorCode::INTERNAL_ERROR:
+ return "INTERNAL_ERROR";
+ case Http2ErrorCode::FLOW_CONTROL_ERROR:
+ return "FLOW_CONTROL_ERROR";
+ case Http2ErrorCode::SETTINGS_TIMEOUT:
+ return "SETTINGS_TIMEOUT";
+ case Http2ErrorCode::STREAM_CLOSED:
+ return "STREAM_CLOSED";
+ case Http2ErrorCode::FRAME_SIZE_ERROR:
+ return "FRAME_SIZE_ERROR";
+ case Http2ErrorCode::REFUSED_STREAM:
+ return "REFUSED_STREAM";
+ case Http2ErrorCode::CANCEL:
+ return "CANCEL";
+ case Http2ErrorCode::COMPRESSION_ERROR:
+ return "COMPRESSION_ERROR";
+ case Http2ErrorCode::CONNECT_ERROR:
+ return "CONNECT_ERROR";
+ case Http2ErrorCode::ENHANCE_YOUR_CALM:
+ return "ENHANCE_YOUR_CALM";
+ case Http2ErrorCode::INADEQUATE_SECURITY:
+ return "INADEQUATE_SECURITY";
+ case Http2ErrorCode::HTTP_1_1_REQUIRED:
+ return "HTTP_1_1_REQUIRED";
+ }
+ return "UNKNOWN_ERROR";
+}
+
+} // namespace adapter
+} // namespace http2
diff --git a/http2/adapter/http2_protocol.h b/http2/adapter/http2_protocol.h
new file mode 100644
index 0000000..49e5f16
--- /dev/null
+++ b/http2/adapter/http2_protocol.h
@@ -0,0 +1,105 @@
+#ifndef QUICHE_HTTP2_ADAPTER_HTTP2_PROTOCOL_H_
+#define QUICHE_HTTP2_ADAPTER_HTTP2_PROTOCOL_H_
+
+#include <cstdint>
+#include <string>
+#include <utility>
+
+#include "base/integral_types.h"
+#include "absl/base/attributes.h"
+#include "absl/strings/string_view.h"
+
+namespace http2 {
+namespace adapter {
+
+// Represents an HTTP/2 stream ID, consistent with nghttp2.
+using Http2StreamId = int32_t;
+
+// Represents an HTTP/2 SETTINGS parameter as specified in RFC 7540 Section 6.5.
+using Http2SettingsId = uint16_t;
+
+// Represents the payload of an HTTP/2 PING frame.
+using Http2PingId = uint64_t;
+
+// Represents an HTTP/2 header field. A header field is a key-value pair with
+// lowercase keys (as specified in RFC 7540 Section 8.1.2).
+using Header = std::pair<std::string, std::string>;
+
+// Represents an HTTP/2 SETTINGS key-value parameter.
+struct Http2Setting {
+ Http2SettingsId id;
+ uint32_t value;
+};
+
+// The maximum possible stream ID.
+const Http2StreamId kMaxStreamId = 0x7FFFFFFF;
+
+// The stream ID that represents the connection (e.g., for connection-level flow
+// control updates).
+const Http2StreamId kConnectionStreamId = 0;
+
+// The default value for the size of the largest frame payload, according to RFC
+// 7540 Section 6.5.2 (SETTINGS_MAX_FRAME_SIZE).
+const int kDefaultFramePayloadSizeLimit = 16 * 1024;
+
+// The default value for the initial stream flow control window size, according
+// to RFC 7540 Section 6.9.2.
+const int kDefaultInitialStreamWindowSize = 64 * 1024 - 1;
+
+// The pseudo-header fields as specified in RFC 7540 Section 8.1.2.3 (request)
+// and Section 8.1.2.4 (response).
+ABSL_CONST_INIT extern const char kHttp2MethodPseudoHeader[];
+ABSL_CONST_INIT extern const char kHttp2SchemePseudoHeader[];
+ABSL_CONST_INIT extern const char kHttp2AuthorityPseudoHeader[];
+ABSL_CONST_INIT extern const char kHttp2PathPseudoHeader[];
+ABSL_CONST_INIT extern const char kHttp2StatusPseudoHeader[];
+
+// HTTP/2 error codes as specified in RFC 7540 Section 7.
+enum class Http2ErrorCode {
+ NO_ERROR = 0x0,
+ PROTOCOL_ERROR = 0x1,
+ INTERNAL_ERROR = 0x2,
+ FLOW_CONTROL_ERROR = 0x3,
+ SETTINGS_TIMEOUT = 0x4,
+ STREAM_CLOSED = 0x5,
+ FRAME_SIZE_ERROR = 0x6,
+ REFUSED_STREAM = 0x7,
+ CANCEL = 0x8,
+ COMPRESSION_ERROR = 0x9,
+ CONNECT_ERROR = 0xA,
+ ENHANCE_YOUR_CALM = 0xB,
+ INADEQUATE_SECURITY = 0xC,
+ HTTP_1_1_REQUIRED = 0xD,
+ MAX_ERROR_CODE = HTTP_1_1_REQUIRED,
+};
+
+// The SETTINGS parameters defined in RFC 7540 Section 6.5.2. Endpoints may send
+// SETTINGS parameters outside of these definitions as per RFC 7540 Section 5.5.
+// This is explicitly an enum instead of an enum class for ease of implicit
+// conversion to the underlying Http2SettingsId type and use with non-standard
+// extension SETTINGS parameters.
+enum Http2KnownSettingsId : Http2SettingsId {
+ HEADER_TABLE_SIZE = 0x1,
+ MIN_SETTING = HEADER_TABLE_SIZE,
+ ENABLE_PUSH = 0x2,
+ MAX_CONCURRENT_STREAMS = 0x3,
+ INITIAL_WINDOW_SIZE = 0x4,
+ MAX_FRAME_SIZE = 0x5,
+ MAX_HEADER_LIST_SIZE = 0x6,
+ MAX_SETTING = MAX_HEADER_LIST_SIZE
+};
+
+// Returns a human-readable string representation of the given SETTINGS |id| for
+// logging/debugging. Returns "SETTINGS_UNKNOWN" for IDs outside of the RFC 7540
+// Section 6.5.2 definitions.
+absl::string_view Http2SettingsIdToString(uint16_t id);
+
+// Returns a human-readable string representation of the given |error_code| for
+// logging/debugging. Returns "UNKNOWN_ERROR" for errors outside of RFC 7540
+// Section 7 definitions.
+absl::string_view Http2ErrorCodeToString(Http2ErrorCode error_code);
+
+} // namespace adapter
+} // namespace http2
+
+#endif // QUICHE_HTTP2_ADAPTER_HTTP2_PROTOCOL_H_
diff --git a/http2/adapter/http2_session.h b/http2/adapter/http2_session.h
new file mode 100644
index 0000000..831cf73
--- /dev/null
+++ b/http2/adapter/http2_session.h
@@ -0,0 +1,41 @@
+#ifndef QUICHE_HTTP2_ADAPTER_HTTP2_SESSION_H_
+#define QUICHE_HTTP2_ADAPTER_HTTP2_SESSION_H_
+
+#include <cstdint>
+
+#include "absl/strings/string_view.h"
+#include "http2/adapter/http2_protocol.h"
+
+namespace http2 {
+namespace adapter {
+
+struct Http2SessionCallbacks {};
+
+// A class to represent the state of a single HTTP/2 connection.
+class Http2Session {
+ public:
+ Http2Session() = default;
+ virtual ~Http2Session() {}
+
+ virtual ssize_t ProcessBytes(absl::string_view bytes) = 0;
+
+ virtual int Consume(Http2StreamId stream_id, size_t num_bytes) = 0;
+
+ virtual bool want_read() = 0;
+ virtual bool want_write() = 0;
+ virtual int GetRemoteWindowSize() = 0;
+};
+
+class Http2Options {
+ public:
+ Http2Options() = default;
+ virtual ~Http2Options() {}
+
+ // This method returns an opaque reference to the underlying type.
+ virtual void* GetOptions() = 0;
+};
+
+} // namespace adapter
+} // namespace http2
+
+#endif // QUICHE_HTTP2_ADAPTER_HTTP2_SESSION_H_
diff --git a/http2/adapter/http2_visitor_interface.h b/http2/adapter/http2_visitor_interface.h
new file mode 100644
index 0000000..765e9f4
--- /dev/null
+++ b/http2/adapter/http2_visitor_interface.h
@@ -0,0 +1,178 @@
+#ifndef QUICHE_HTTP2_ADAPTER_HTTP2_VISITOR_INTERFACE_H_
+#define QUICHE_HTTP2_ADAPTER_HTTP2_VISITOR_INTERFACE_H_
+
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "http2/adapter/http2_protocol.h"
+
+namespace http2 {
+namespace adapter {
+
+// Http2VisitorInterface contains callbacks for receiving HTTP/2-level events. A
+// processor like NghttpAdapter parses HTTP/2 frames and invokes the callbacks
+// on an instance of this interface. Prefer a void return type for these
+// callbacks, instead setting output parameters as needed.
+//
+// Example sequences of calls/events:
+// GET:
+// - OnBeginHeadersForStream()
+// - OnHeaderForStream()
+// - OnEndHeadersForStream()
+// - OnEndStream()
+//
+// POST:
+// - OnBeginHeadersForStream()
+// - OnHeaderForStream()
+// - OnEndHeadersForStream()
+// - OnBeginDataForStream()
+// - OnDataForStream()
+// - OnEndStream()
+//
+// Request canceled mid-stream, e.g, with error code CANCEL:
+// - OnBeginHeadersForStream()
+// - OnHeaderForStream()
+// - OnEndHeadersForStream()
+// - OnRstStream()
+// - OnAbortStream()
+//
+// Request closed mid-stream, e.g., with error code NO_ERROR:
+// - OnBeginHeadersForStream()
+// - OnHeaderForStream()
+// - OnEndHeadersForStream()
+// - OnRstStream()
+// - OnCloseStream()
+//
+// More details are at RFC 7540 (go/http2spec), and more examples are at
+// http://google3/net/http2/server/lib/internal/h2/nghttp2/nghttp2_server_adapter_test.cc.
+class Http2VisitorInterface {
+ public:
+ Http2VisitorInterface(const Http2VisitorInterface&) = delete;
+ Http2VisitorInterface& operator=(const Http2VisitorInterface&) = delete;
+ virtual ~Http2VisitorInterface() = default;
+
+ // Called when a connection-level processing error has been encountered.
+ virtual void OnConnectionError() = 0;
+
+ // Called when a non-ack SETTINGS frame is received.
+ virtual void OnSettingsStart() = 0;
+
+ // Called for each SETTINGS id-value pair.
+ virtual void OnSetting(Http2Setting setting) = 0;
+
+ // Called at the end of a non-ack SETTINGS frame.
+ virtual void OnSettingsEnd() = 0;
+
+ // Called when a SETTINGS ack frame is received.
+ virtual void OnSettingsAck() = 0;
+
+ // Called when the connection receives the header block for a HEADERS frame on
+ // a stream but has not yet parsed individual headers.
+ virtual void OnBeginHeadersForStream(Http2StreamId stream_id) = 0;
+
+ // Called when the connection receives the header |key| and |value| for a
+ // stream. The HTTP/2 pseudo-headers defined in RFC 7540 Sections 8.1.2.3 and
+ // 8.1.2.4 are also conveyed in this callback. This method is called after
+ // OnBeginHeadersForStream().
+ virtual void OnHeaderForStream(Http2StreamId stream_id, absl::string_view key,
+ absl::string_view value) = 0;
+
+ // Called when the connection has received the complete header block for a
+ // logical HEADERS frame on a stream (which may contain CONTINUATION frames,
+ // transparent to the user).
+ virtual void OnEndHeadersForStream(Http2StreamId stream_id) = 0;
+
+ // Called when the connection receives the beginning of a DATA frame. The data
+ // payload will be provided via subsequent calls to OnDataForStream().
+ virtual void OnBeginDataForStream(Http2StreamId stream_id,
+ size_t payload_length) = 0;
+
+ // Called when the connection receives some |data| (as part of a DATA frame
+ // payload) for a stream.
+ virtual void OnDataForStream(Http2StreamId stream_id,
+ absl::string_view data) = 0;
+
+ // Called when the peer sends the END_STREAM flag on a stream, indicating that
+ // the peer will not send additional headers or data for that stream.
+ virtual void OnEndStream(Http2StreamId stream_id) = 0;
+
+ // Called when the connection receives a RST_STREAM for a stream. This call
+ // will be followed by either OnCloseStream() or OnAbortStream().
+ virtual void OnRstStream(Http2StreamId stream_id,
+ Http2ErrorCode error_code) = 0;
+
+ // Called when a stream is closed with error code NO_ERROR. Compare with
+ // OnAbortStream().
+ virtual void OnCloseStream(Http2StreamId stream_id) = 0;
+
+ // Called when a stream is aborted, i.e., closed for the reason indicated by
+ // the given |error_code|, where error_code != NO_ERROR. Compare with
+ // OnCloseStream().
+ virtual void OnAbortStream(Http2StreamId stream_id,
+ Http2ErrorCode error_code) = 0;
+
+ // Called when the connection receives a PRIORITY frame.
+ virtual void OnPriorityForStream(Http2StreamId stream_id,
+ Http2StreamId parent_stream_id, int weight,
+ bool exclusive) = 0;
+
+ // Called when the connection receives a PING frame.
+ virtual void OnPing(Http2PingId ping_id, bool is_ack) = 0;
+
+ // Called when the connection receives a PUSH_PROMISE frame. The server push
+ // request headers follow in calls to OnHeaderForStream() with |stream_id|.
+ virtual void OnPushPromiseForStream(Http2StreamId stream_id,
+ Http2StreamId promised_stream_id) = 0;
+
+ // Called when the connection receives a GOAWAY frame.
+ virtual void OnGoAway(Http2StreamId last_accepted_stream_id,
+ Http2ErrorCode error_code,
+ absl::string_view opaque_data) = 0;
+
+ // Called when the connection receives a WINDOW_UPDATE frame. For
+ // connection-level window updates, the |stream_id| will be 0.
+ virtual void OnWindowUpdate(Http2StreamId stream_id,
+ int window_increment) = 0;
+
+ // Called when the connection is ready to send data for a stream. The
+ // implementation should write at most |length| bytes of the data payload to
+ // the |destination_buffer| and set |end_stream| to true IFF there will be no
+ // more data sent on this stream. Sets |written| to the number of bytes
+ // written to the |destination_buffer| or a negative value if an error occurs.
+ virtual void OnReadyToSendDataForStream(Http2StreamId stream_id,
+ char* destination_buffer,
+ size_t length,
+ ssize_t* written,
+ bool* end_stream) = 0;
+
+ // Called when the connection is ready to write metadata for |stream_id| to
+ // the wire. The implementation should write at most |length| bytes of the
+ // serialized metadata payload to the |buffer| and set |written| to the number
+ // of bytes written or a negative value if there was an error.
+ virtual void OnReadyToSendMetadataForStream(Http2StreamId stream_id,
+ char* buffer, size_t length,
+ ssize_t* written) = 0;
+
+ // Called when the connection receives the beginning of a METADATA frame
+ // (which may itself be the middle of a logical metadata block). The metadata
+ // payload will be provided via subsequent calls to OnMetadataForStream().
+ virtual void OnBeginMetadataForStream(Http2StreamId stream_id,
+ size_t payload_length) = 0;
+
+ // Called when the connection receives |metadata| as part of a METADATA frame
+ // payload for a stream.
+ virtual void OnMetadataForStream(Http2StreamId stream_id,
+ absl::string_view metadata) = 0;
+
+ // Called when the connection has finished receiving a logical metadata block
+ // for a stream. Note that there may be multiple metadata blocks for a stream.
+ virtual void OnMetadataEndForStream(Http2StreamId stream_id) = 0;
+
+ protected:
+ Http2VisitorInterface() = default;
+};
+
+} // namespace adapter
+} // namespace http2
+
+#endif // QUICHE_HTTP2_ADAPTER_HTTP2_VISITOR_INTERFACE_H_