#ifndef SRC_NODE_HTTP2_H_ #define SRC_NODE_HTTP2_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS // FIXME(joyeecheung): nghttp2.h needs stdint.h to compile on Windows #include #include "nghttp2/nghttp2.h" #include "allocated_buffer.h" #include "aliased_struct.h" #include "node_http2_state.h" #include "node_http_common.h" #include "node_mem.h" #include "node_perf.h" #include "stream_base.h" #include "string_bytes.h" #include #include namespace node { namespace http2 { // Constants in all caps are exported as user-facing constants // in JavaScript. Constants using the kName pattern are internal // only. // We strictly limit the number of outstanding unacknowledged PINGS a user // may send in order to prevent abuse. The current default cap is 10. The // user may set a different limit using a per Http2Session configuration // option. constexpr size_t kDefaultMaxPings = 10; // Also strictly limit the number of outstanding SETTINGS frames a user sends constexpr size_t kDefaultMaxSettings = 10; // Default maximum total memory cap for Http2Session. constexpr uint64_t kDefaultMaxSessionMemory = 10000000; // These are the standard HTTP/2 defaults as specified by the RFC constexpr uint32_t DEFAULT_SETTINGS_HEADER_TABLE_SIZE = 4096; constexpr uint32_t DEFAULT_SETTINGS_ENABLE_PUSH = 1; constexpr uint32_t DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS = 0xffffffffu; constexpr uint32_t DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE = 65535; constexpr uint32_t DEFAULT_SETTINGS_MAX_FRAME_SIZE = 16384; constexpr uint32_t DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE = 65535; constexpr uint32_t DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0; constexpr uint32_t MAX_MAX_FRAME_SIZE = 16777215; constexpr uint32_t MIN_MAX_FRAME_SIZE = DEFAULT_SETTINGS_MAX_FRAME_SIZE; constexpr uint32_t MAX_INITIAL_WINDOW_SIZE = 2147483647; // Stream is not going to have any DATA frames constexpr int STREAM_OPTION_EMPTY_PAYLOAD = 0x1; // Stream might have trailing headers constexpr int STREAM_OPTION_GET_TRAILERS = 0x2; // Http2Stream internal states constexpr int kStreamStateNone = 0x0; constexpr int kStreamStateShut = 0x1; constexpr int kStreamStateReadStart = 0x2; constexpr int kStreamStateReadPaused = 0x4; constexpr int kStreamStateClosed = 0x8; constexpr int kStreamStateDestroyed = 0x10; constexpr int kStreamStateTrailers = 0x20; // Http2Session internal states constexpr int kSessionStateNone = 0x0; constexpr int kSessionStateHasScope = 0x1; constexpr int kSessionStateWriteScheduled = 0x2; constexpr int kSessionStateClosed = 0x4; constexpr int kSessionStateClosing = 0x8; constexpr int kSessionStateSending = 0x10; constexpr int kSessionStateWriteInProgress = 0x20; constexpr int kSessionStateReadingStopped = 0x40; constexpr int kSessionStateReceivePaused = 0x80; // The Padding Strategy determines the method by which extra padding is // selected for HEADERS and DATA frames. These are configurable via the // options passed in to a Http2Session object. enum PaddingStrategy { // No padding strategy. This is the default. PADDING_STRATEGY_NONE, // Attempts to ensure that the frame is 8-byte aligned PADDING_STRATEGY_ALIGNED, // Padding will ensure all data frames are maxFrameSize PADDING_STRATEGY_MAX, // Removed and turned into an alias because it is unreasonably expensive for // very little benefit. PADDING_STRATEGY_CALLBACK = PADDING_STRATEGY_ALIGNED }; enum SessionType { NGHTTP2_SESSION_SERVER, NGHTTP2_SESSION_CLIENT }; template struct Nghttp2Deleter { void operator()(T* ptr) const noexcept { fn(ptr); } }; using Nghttp2OptionPointer = std::unique_ptr>; using Nghttp2SessionPointer = std::unique_ptr>; using Nghttp2SessionCallbacksPointer = std::unique_ptr>; struct Http2HeadersTraits { typedef nghttp2_nv nv_t; }; struct Http2RcBufferPointerTraits { typedef nghttp2_rcbuf rcbuf_t; typedef nghttp2_vec vector_t; static void inc(rcbuf_t* buf) { CHECK_NOT_NULL(buf); nghttp2_rcbuf_incref(buf); } static void dec(rcbuf_t* buf) { CHECK_NOT_NULL(buf); nghttp2_rcbuf_decref(buf); } static vector_t get_vec(rcbuf_t* buf) { CHECK_NOT_NULL(buf); return nghttp2_rcbuf_get_buf(buf); } static bool is_static(const rcbuf_t* buf) { CHECK_NOT_NULL(buf); return nghttp2_rcbuf_is_static(buf); } }; using Http2Headers = NgHeaders; using Http2RcBufferPointer = NgRcBufPointer; struct NgHttp2StreamWrite : public MemoryRetainer { BaseObjectPtr req_wrap; uv_buf_t buf; inline explicit NgHttp2StreamWrite(uv_buf_t buf_) : buf(buf_) {} inline NgHttp2StreamWrite(BaseObjectPtr req_wrap, uv_buf_t buf_) : req_wrap(std::move(req_wrap)), buf(buf_) {} void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(NgHttp2StreamWrite) SET_SELF_SIZE(NgHttp2StreamWrite) }; typedef uint32_t(*get_setting)(nghttp2_session* session, nghttp2_settings_id id); class Http2Ping; class Http2Session; class Http2Settings; class Http2Stream; class Origins; // This scope should be present when any call into nghttp2 that may schedule // data to be written to the underlying transport is made, and schedules // such a write automatically once the scope is exited. class Http2Scope { public: explicit Http2Scope(Http2Stream* stream); explicit Http2Scope(Http2Session* session); ~Http2Scope(); private: BaseObjectPtr session_; }; // The Http2Options class is used to parse the options object passed in to // a Http2Session object and convert those into an appropriate nghttp2_option // struct. This is the primary mechanism by which the Http2Session object is // configured. class Http2Options { public: Http2Options(Http2State* http2_state, SessionType type); ~Http2Options() = default; nghttp2_option* operator*() const { return options_.get(); } void set_max_header_pairs(uint32_t max) { max_header_pairs_ = max; } uint32_t max_header_pairs() const { return max_header_pairs_; } void set_padding_strategy(PaddingStrategy val) { padding_strategy_ = val; } PaddingStrategy padding_strategy() const { return padding_strategy_; } void set_max_outstanding_pings(size_t max) { max_outstanding_pings_ = max; } size_t max_outstanding_pings() const { return max_outstanding_pings_; } void set_max_outstanding_settings(size_t max) { max_outstanding_settings_ = max; } size_t max_outstanding_settings() const { return max_outstanding_settings_; } void set_max_session_memory(uint64_t max) { max_session_memory_ = max; } uint64_t max_session_memory() const { return max_session_memory_; } private: Nghttp2OptionPointer options_; uint64_t max_session_memory_ = kDefaultMaxSessionMemory; uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS; PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE; size_t max_outstanding_pings_ = kDefaultMaxPings; size_t max_outstanding_settings_ = kDefaultMaxSettings; }; struct Http2Priority : public nghttp2_priority_spec { Http2Priority(Environment* env, v8::Local parent, v8::Local weight, v8::Local exclusive); }; class Http2StreamListener : public StreamListener { public: uv_buf_t OnStreamAlloc(size_t suggested_size) override; void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; }; struct Http2HeaderTraits { typedef Http2RcBufferPointer rcbufferpointer_t; typedef Http2Session allocator_t; // HTTP/2 does not support identifying header names by token id. // HTTP/3 will, however, so we prepare for that now. static const char* ToHttpHeaderName(int32_t token) { return nullptr; } }; using Http2Header = NgHeader; class Http2Stream : public AsyncWrap, public StreamBase { public: static Http2Stream* New( Http2Session* session, int32_t id, nghttp2_headers_category category = NGHTTP2_HCAT_HEADERS, int options = 0); ~Http2Stream() override; nghttp2_stream* operator*() const; nghttp2_stream* stream() const; Http2Session* session() { return session_.get(); } const Http2Session* session() const { return session_.get(); } // Required for StreamBase int ReadStart() override; // Required for StreamBase int ReadStop() override; // Required for StreamBase ShutdownWrap* CreateShutdownWrap(v8::Local object) override; int DoShutdown(ShutdownWrap* req_wrap) override; bool HasWantsWrite() const override { return true; } // Initiate a response on this stream. int SubmitResponse(const Http2Headers& headers, int options); // Submit informational headers for this stream int SubmitInfo(const Http2Headers& headers); // Submit trailing headers for this stream int SubmitTrailers(const Http2Headers& headers); void OnTrailers(); // Submit a PRIORITY frame for this stream int SubmitPriority(const Http2Priority& priority, bool silent = false); // Submits an RST_STREAM frame using the given code void SubmitRstStream(const uint32_t code); void FlushRstStream(); // Submits a PUSH_PROMISE frame with this stream as the parent. Http2Stream* SubmitPushPromise( const Http2Headers& headers, int32_t* ret, int options = 0); void Close(int32_t code); // Destroy this stream instance and free all held memory. void Destroy(); bool is_destroyed() const { return flags_ & kStreamStateDestroyed; } bool is_writable() const { return !(flags_ & kStreamStateShut); } bool is_paused() const { return flags_ & kStreamStateReadPaused; } bool is_closed() const { return flags_ & kStreamStateClosed; } bool has_trailers() const { return flags_ & kStreamStateTrailers; } void set_has_trailers(bool on = true) { if (on) flags_ |= kStreamStateTrailers; else flags_ &= ~kStreamStateTrailers; } void set_closed() { flags_ |= kStreamStateClosed; } void set_destroyed() { flags_ |= kStreamStateDestroyed; } void set_not_writable() { flags_ |= kStreamStateShut; } void set_reading(bool on = true) { if (on) { flags_ |= kStreamStateReadStart; set_paused(false); } else {} } void set_paused(bool on = true) { if (on) flags_ |= kStreamStateReadPaused; else flags_ &= ~kStreamStateReadPaused; } // Returns true if this stream is in the reading state, which occurs when // the kStreamStateReadStart flag has been set and the // kStreamStateReadPaused flag is *not* set. bool is_reading() const { return flags_ & kStreamStateReadStart && !is_paused(); } // Returns the RST_STREAM code used to close this stream int32_t code() const { return code_; } // Returns the stream identifier for this stream int32_t id() const { return id_; } void IncrementAvailableOutboundLength(size_t amount); void DecrementAvailableOutboundLength(size_t amount); bool AddHeader(nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags); template void TransferHeaders(Fn&& fn) { size_t i = 0; for (const auto& header : current_headers_ ) fn(header, i++); current_headers_.clear(); } size_t headers_count() const { return current_headers_.size(); } nghttp2_headers_category headers_category() const { return current_headers_category_; } void StartHeaders(nghttp2_headers_category category); // Required for StreamBase bool IsAlive() override { return true; } // Required for StreamBase bool IsClosing() override { return false; } AsyncWrap* GetAsyncWrap() override { return this; } int DoWrite(WriteWrap* w, uv_buf_t* bufs, size_t count, uv_stream_t* send_handle) override; void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(Http2Stream) SET_SELF_SIZE(Http2Stream) std::string diagnostic_name() const override; // JavaScript API static void GetID(const v8::FunctionCallbackInfo& args); static void Destroy(const v8::FunctionCallbackInfo& args); static void Priority(const v8::FunctionCallbackInfo& args); static void PushPromise(const v8::FunctionCallbackInfo& args); static void RefreshState(const v8::FunctionCallbackInfo& args); static void Info(const v8::FunctionCallbackInfo& args); static void Trailers(const v8::FunctionCallbackInfo& args); static void Respond(const v8::FunctionCallbackInfo& args); static void RstStream(const v8::FunctionCallbackInfo& args); class Provider; struct Statistics { uint64_t start_time; uint64_t end_time; uint64_t first_header; // Time first header was received uint64_t first_byte; // Time first DATA frame byte was received uint64_t first_byte_sent; // Time first DATA frame byte was sent uint64_t sent_bytes; uint64_t received_bytes; }; Statistics statistics_ = {}; private: Http2Stream(Http2Session* session, v8::Local obj, int32_t id, nghttp2_headers_category category, int options); void EmitStatistics(); BaseObjectWeakPtr session_; // The Parent HTTP/2 Session int32_t id_ = 0; // The Stream Identifier int32_t code_ = NGHTTP2_NO_ERROR; // The RST_STREAM code (if any) int flags_ = kStreamStateNone; // Internal state flags uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS; uint32_t max_header_length_ = DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE; // The Current Headers block... As headers are received for this stream, // they are temporarily stored here until the OnFrameReceived is called // signalling the end of the HEADERS frame nghttp2_headers_category current_headers_category_ = NGHTTP2_HCAT_HEADERS; uint32_t current_headers_length_ = 0; // total number of octets std::vector current_headers_; // This keeps track of the amount of data read from the socket while the // socket was in paused mode. When `ReadStart()` is called (and not before // then), we tell nghttp2 that we consumed that data to get proper // backpressure handling. size_t inbound_consumed_data_while_paused_ = 0; // Outbound Data... This is the data written by the JS layer that is // waiting to be written out to the socket. std::queue queue_; size_t available_outbound_length_ = 0; Http2StreamListener stream_listener_; friend class Http2Session; }; class Http2Stream::Provider { public: Provider(Http2Stream* stream, int options); explicit Provider(int options); virtual ~Provider(); nghttp2_data_provider* operator*() { return !empty_ ? &provider_ : nullptr; } class FD; class Stream; protected: nghttp2_data_provider provider_; private: bool empty_ = false; }; class Http2Stream::Provider::Stream : public Http2Stream::Provider { public: Stream(Http2Stream* stream, int options); explicit Stream(int options); static ssize_t OnRead(nghttp2_session* session, int32_t id, uint8_t* buf, size_t length, uint32_t* flags, nghttp2_data_source* source, void* user_data); }; struct SessionJSFields { uint8_t bitfield; uint8_t priority_listener_count; uint8_t frame_error_listener_count; uint32_t max_invalid_frames = 1000; uint32_t max_rejected_streams = 100; }; // Indices for js_fields_, which serves as a way to communicate data with JS // land fast. In particular, we store information about the number/presence // of certain event listeners in JS, and skip calls from C++ into JS if they // are missing. enum SessionUint8Fields { kBitfield = offsetof(SessionJSFields, bitfield), // See below kSessionPriorityListenerCount = offsetof(SessionJSFields, priority_listener_count), kSessionFrameErrorListenerCount = offsetof(SessionJSFields, frame_error_listener_count), kSessionMaxInvalidFrames = offsetof(SessionJSFields, max_invalid_frames), kSessionMaxRejectedStreams = offsetof(SessionJSFields, max_rejected_streams), kSessionUint8FieldCount = sizeof(SessionJSFields) }; enum SessionBitfieldFlags { kSessionHasRemoteSettingsListeners, kSessionRemoteSettingsIsUpToDate, kSessionHasPingListeners, kSessionHasAltsvcListeners }; class Http2Session : public AsyncWrap, public StreamListener, public mem::NgLibMemoryManager { public: Http2Session(Http2State* http2_state, v8::Local wrap, SessionType type = NGHTTP2_SESSION_SERVER); ~Http2Session() override; StreamBase* underlying_stream() { return static_cast(stream_); } void Close(uint32_t code = NGHTTP2_NO_ERROR, bool socket_closed = false); void Consume(v8::Local stream); void Goaway(uint32_t code, int32_t lastStreamID, const uint8_t* data, size_t len); void AltSvc(int32_t id, uint8_t* origin, size_t origin_len, uint8_t* value, size_t value_len); void Origin(const Origins& origins); uint8_t SendPendingData(); // Submits a new request. If the request is a success, assigned // will be a pointer to the Http2Stream instance assigned. // This only works if the session is a client session. Http2Stream* SubmitRequest( const Http2Priority& priority, const Http2Headers& headers, int32_t* ret, int options = 0); SessionType type() const { return session_type_; } nghttp2_session* session() const { return session_.get(); } nghttp2_session* operator*() { return session_.get(); } uint32_t max_header_pairs() const { return max_header_pairs_; } const char* TypeName() const; bool is_destroyed() { return (flags_ & kSessionStateClosed) || session_ == nullptr; } void set_destroyed() { flags_ |= kSessionStateClosed; } #define IS_FLAG(name, flag) \ bool is_##name() const { return flags_ & flag; } \ void set_##name(bool on = true) { \ if (on) \ flags_ |= flag; \ else \ flags_ &= ~flag; \ } IS_FLAG(in_scope, kSessionStateHasScope) IS_FLAG(write_scheduled, kSessionStateWriteScheduled) IS_FLAG(closing, kSessionStateClosing) IS_FLAG(sending, kSessionStateSending) IS_FLAG(write_in_progress, kSessionStateWriteInProgress) IS_FLAG(reading_stopped, kSessionStateReadingStopped) IS_FLAG(receive_paused, kSessionStateReceivePaused) #undef IS_FLAG // Schedule a write if nghttp2 indicates it wants to write to the socket. void MaybeScheduleWrite(); // Stop reading if nghttp2 doesn't want to anymore. void MaybeStopReading(); // Returns pointer to the stream, or nullptr if stream does not exist BaseObjectPtr FindStream(int32_t id); bool CanAddStream(); // Adds a stream instance to this session void AddStream(Http2Stream* stream); // Removes a stream instance from this session BaseObjectPtr RemoveStream(int32_t id); // Indicates whether there currently exist outgoing buffers for this stream. bool HasWritesOnSocketForStream(Http2Stream* stream); // Write data from stream_buf_ to the session ssize_t ConsumeHTTP2Data(); void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(Http2Session) SET_SELF_SIZE(Http2Session) std::string diagnostic_name() const override; // Schedule an RstStream for after the current write finishes. void AddPendingRstStream(int32_t stream_id) { pending_rst_streams_.emplace_back(stream_id); } bool has_pending_rststream(int32_t stream_id) { return pending_rst_streams_.end() != std::find(pending_rst_streams_.begin(), pending_rst_streams_.end(), stream_id); } // Handle reads/writes from the underlying network transport. uv_buf_t OnStreamAlloc(size_t suggested_size) override; void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; void OnStreamAfterWrite(WriteWrap* w, int status) override; // Implementation for mem::NgLibMemoryManager void CheckAllocatedSize(size_t previous_size) const; void IncreaseAllocatedSize(size_t size); void DecreaseAllocatedSize(size_t size); // The JavaScript API static void New(const v8::FunctionCallbackInfo& args); static void Consume(const v8::FunctionCallbackInfo& args); static void Receive(const v8::FunctionCallbackInfo& args); static void Destroy(const v8::FunctionCallbackInfo& args); static void Settings(const v8::FunctionCallbackInfo& args); static void Request(const v8::FunctionCallbackInfo& args); static void SetNextStreamID(const v8::FunctionCallbackInfo& args); static void SetLocalWindowSize( const v8::FunctionCallbackInfo& args); static void Goaway(const v8::FunctionCallbackInfo& args); static void UpdateChunksSent(const v8::FunctionCallbackInfo& args); static void RefreshState(const v8::FunctionCallbackInfo& args); static void Ping(const v8::FunctionCallbackInfo& args); static void AltSvc(const v8::FunctionCallbackInfo& args); static void Origin(const v8::FunctionCallbackInfo& args); template static void RefreshSettings(const v8::FunctionCallbackInfo& args); uv_loop_t* event_loop() const { return env()->event_loop(); } Http2State* http2_state() const { return http2_state_.get(); } BaseObjectPtr PopPing(); bool AddPing(const uint8_t* data, v8::Local callback); BaseObjectPtr PopSettings(); bool AddSettings(v8::Local callback); void IncrementCurrentSessionMemory(uint64_t amount) { current_session_memory_ += amount; } void DecrementCurrentSessionMemory(uint64_t amount) { DCHECK_LE(amount, current_session_memory_); current_session_memory_ -= amount; } // Tell our custom memory allocator that this rcbuf is independent of // this session now, and may outlive it. void StopTrackingRcbuf(nghttp2_rcbuf* buf); // Returns the current session memory including memory allocated by nghttp2, // the current outbound storage queue, and pending writes. uint64_t current_session_memory() const { uint64_t total = current_session_memory_ + sizeof(Http2Session); total += current_nghttp2_memory_; total += outgoing_storage_.size(); return total; } // Return true if current_session_memory + amount is less than the max bool has_available_session_memory(uint64_t amount) const { return current_session_memory() + amount <= max_session_memory_; } struct Statistics { uint64_t start_time; uint64_t end_time; uint64_t ping_rtt; uint64_t data_sent; uint64_t data_received; uint32_t frame_count; uint32_t frame_sent; int32_t stream_count; size_t max_concurrent_streams; double stream_average_duration; }; Statistics statistics_ = {}; private: void EmitStatistics(); // Frame Padding Strategies ssize_t OnDWordAlignedPadding(size_t frameLength, size_t maxPayloadLen); ssize_t OnMaxFrameSizePadding(size_t frameLength, size_t maxPayloadLen); // Frame Handler int HandleDataFrame(const nghttp2_frame* frame); void HandleGoawayFrame(const nghttp2_frame* frame); void HandleHeadersFrame(const nghttp2_frame* frame); void HandlePriorityFrame(const nghttp2_frame* frame); void HandleSettingsFrame(const nghttp2_frame* frame); void HandlePingFrame(const nghttp2_frame* frame); void HandleAltSvcFrame(const nghttp2_frame* frame); void HandleOriginFrame(const nghttp2_frame* frame); // nghttp2 callbacks static int OnBeginHeadersCallback( nghttp2_session* session, const nghttp2_frame* frame, void* user_data); static int OnHeaderCallback( nghttp2_session* session, const nghttp2_frame* frame, nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags, void* user_data); static int OnFrameReceive( nghttp2_session* session, const nghttp2_frame* frame, void* user_data); static int OnFrameNotSent( nghttp2_session* session, const nghttp2_frame* frame, int error_code, void* user_data); static int OnFrameSent( nghttp2_session* session, const nghttp2_frame* frame, void* user_data); static int OnStreamClose( nghttp2_session* session, int32_t id, uint32_t code, void* user_data); static int OnInvalidHeader( nghttp2_session* session, const nghttp2_frame* frame, nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags, void* user_data); static int OnDataChunkReceived( nghttp2_session* session, uint8_t flags, int32_t id, const uint8_t* data, size_t len, void* user_data); static ssize_t OnSelectPadding( nghttp2_session* session, const nghttp2_frame* frame, size_t maxPayloadLen, void* user_data); static int OnNghttpError( nghttp2_session* session, const char* message, size_t len, void* user_data); static int OnSendData( nghttp2_session* session, nghttp2_frame* frame, const uint8_t* framehd, size_t length, nghttp2_data_source* source, void* user_data); static int OnInvalidFrame( nghttp2_session* session, const nghttp2_frame* frame, int lib_error_code, void* user_data); struct Callbacks { explicit Callbacks(bool kHasGetPaddingCallback); Nghttp2SessionCallbacksPointer callbacks; }; /* Use callback_struct_saved[kHasGetPaddingCallback ? 1 : 0] */ static const Callbacks callback_struct_saved[2]; // The underlying nghttp2_session handle Nghttp2SessionPointer session_; // JS-accessible numeric fields, as indexed by SessionUint8Fields. AliasedStruct js_fields_; // The session type: client or server SessionType session_type_; // The maximum number of header pairs permitted for streams on this session uint32_t max_header_pairs_ = DEFAULT_MAX_HEADER_LIST_PAIRS; // The maximum amount of memory allocated for this session uint64_t max_session_memory_ = kDefaultMaxSessionMemory; uint64_t current_session_memory_ = 0; // The amount of memory allocated by nghttp2 internals uint64_t current_nghttp2_memory_ = 0; // The collection of active Http2Streams associated with this session std::unordered_map> streams_; int flags_ = kSessionStateNone; // The StreamBase instance being used for i/o PaddingStrategy padding_strategy_ = PADDING_STRATEGY_NONE; // use this to allow timeout tracking during long-lasting writes uint32_t chunks_sent_since_last_write_ = 0; uv_buf_t stream_buf_ = uv_buf_init(nullptr, 0); // When processing input data, either stream_buf_ab_ or stream_buf_allocation_ // will be set. stream_buf_ab_ is lazily created from stream_buf_allocation_. v8::Global stream_buf_ab_; AllocatedBuffer stream_buf_allocation_; size_t stream_buf_offset_ = 0; size_t max_outstanding_pings_ = kDefaultMaxPings; std::queue> outstanding_pings_; size_t max_outstanding_settings_ = kDefaultMaxSettings; std::queue> outstanding_settings_; std::vector outgoing_buffers_; std::vector outgoing_storage_; size_t outgoing_length_ = 0; std::vector pending_rst_streams_; // Count streams that have been rejected while being opened. Exceeding a fixed // limit will result in the session being destroyed, as an indication of a // misbehaving peer. This counter is reset once new streams are being // accepted again. uint32_t rejected_stream_count_ = 0; // Also use the invalid frame count as a measure for rejecting input frames. uint32_t invalid_frame_count_ = 0; void PushOutgoingBuffer(NgHttp2StreamWrite&& write); BaseObjectPtr http2_state_; void CopyDataIntoOutgoing(const uint8_t* src, size_t src_length); void ClearOutgoing(int status); friend class Http2Scope; friend class Http2StreamListener; }; class Http2SessionPerformanceEntry : public performance::PerformanceEntry { public: Http2SessionPerformanceEntry( Http2State* http2_state, const Http2Session::Statistics& stats, SessionType type) : performance::PerformanceEntry( http2_state->env(), "Http2Session", "http2", stats.start_time, stats.end_time), ping_rtt_(stats.ping_rtt), data_sent_(stats.data_sent), data_received_(stats.data_received), frame_count_(stats.frame_count), frame_sent_(stats.frame_sent), stream_count_(stats.stream_count), max_concurrent_streams_(stats.max_concurrent_streams), stream_average_duration_(stats.stream_average_duration), session_type_(type), http2_state_(http2_state) { } uint64_t ping_rtt() const { return ping_rtt_; } uint64_t data_sent() const { return data_sent_; } uint64_t data_received() const { return data_received_; } uint32_t frame_count() const { return frame_count_; } uint32_t frame_sent() const { return frame_sent_; } int32_t stream_count() const { return stream_count_; } size_t max_concurrent_streams() const { return max_concurrent_streams_; } double stream_average_duration() const { return stream_average_duration_; } SessionType type() const { return session_type_; } Http2State* http2_state() const { return http2_state_.get(); } void Notify(v8::Local obj) { performance::PerformanceEntry::Notify(env(), kind(), obj); } private: uint64_t ping_rtt_; uint64_t data_sent_; uint64_t data_received_; uint32_t frame_count_; uint32_t frame_sent_; int32_t stream_count_; size_t max_concurrent_streams_; double stream_average_duration_; SessionType session_type_; BaseObjectPtr http2_state_; }; class Http2StreamPerformanceEntry : public performance::PerformanceEntry { public: Http2StreamPerformanceEntry( Http2State* http2_state, int32_t id, const Http2Stream::Statistics& stats) : performance::PerformanceEntry( http2_state->env(), "Http2Stream", "http2", stats.start_time, stats.end_time), id_(id), first_header_(stats.first_header), first_byte_(stats.first_byte), first_byte_sent_(stats.first_byte_sent), sent_bytes_(stats.sent_bytes), received_bytes_(stats.received_bytes), http2_state_(http2_state) { } int32_t id() const { return id_; } uint64_t first_header() const { return first_header_; } uint64_t first_byte() const { return first_byte_; } uint64_t first_byte_sent() const { return first_byte_sent_; } uint64_t sent_bytes() const { return sent_bytes_; } uint64_t received_bytes() const { return received_bytes_; } Http2State* http2_state() const { return http2_state_.get(); } void Notify(v8::Local obj) { performance::PerformanceEntry::Notify(env(), kind(), obj); } private: int32_t id_; uint64_t first_header_; uint64_t first_byte_; uint64_t first_byte_sent_; uint64_t sent_bytes_; uint64_t received_bytes_; BaseObjectPtr http2_state_; }; class Http2Ping : public AsyncWrap { public: explicit Http2Ping( Http2Session* session, v8::Local obj, v8::Local callback); void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(Http2Ping) SET_SELF_SIZE(Http2Ping) void Send(const uint8_t* payload); void Done(bool ack, const uint8_t* payload = nullptr); void DetachFromSession(); v8::Local callback() const; private: BaseObjectWeakPtr session_; v8::Global callback_; uint64_t startTime_; }; // The Http2Settings class is used to parse the settings passed in for // an Http2Session, converting those into an array of nghttp2_settings_entry // structs. class Http2Settings : public AsyncWrap { public: Http2Settings(Http2Session* session, v8::Local obj, v8::Local callback, uint64_t start_time = uv_hrtime()); void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(Http2Settings) SET_SELF_SIZE(Http2Settings) void Send(); void Done(bool ack); v8::Local callback() const; // Returns a Buffer instance with the serialized SETTINGS payload v8::Local Pack(); static v8::Local Pack(Http2State* state); // Resets the default values in the settings buffer static void RefreshDefaults(Http2State* http2_state); // Update the local or remote settings for the given session static void Update(Http2Session* session, get_setting fn); private: static size_t Init( Http2State* http2_state, nghttp2_settings_entry* entries); static v8::Local Pack( Environment* env, size_t count, const nghttp2_settings_entry* entries); BaseObjectWeakPtr session_; v8::Global callback_; uint64_t startTime_; size_t count_ = 0; nghttp2_settings_entry entries_[IDX_SETTINGS_COUNT]; }; class Origins { public: Origins(Environment* env, v8::Local origin_string, size_t origin_count); ~Origins() = default; const nghttp2_origin_entry* operator*() const { return reinterpret_cast(buf_.data()); } size_t length() const { return count_; } private: size_t count_; AllocatedBuffer buf_; }; #define HTTP2_HIDDEN_CONSTANTS(V) \ V(NGHTTP2_HCAT_REQUEST) \ V(NGHTTP2_HCAT_RESPONSE) \ V(NGHTTP2_HCAT_PUSH_RESPONSE) \ V(NGHTTP2_HCAT_HEADERS) \ V(NGHTTP2_NV_FLAG_NONE) \ V(NGHTTP2_NV_FLAG_NO_INDEX) \ V(NGHTTP2_ERR_DEFERRED) \ V(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE) \ V(NGHTTP2_ERR_INVALID_ARGUMENT) \ V(NGHTTP2_ERR_STREAM_CLOSED) \ V(NGHTTP2_ERR_NOMEM) \ V(STREAM_OPTION_EMPTY_PAYLOAD) \ V(STREAM_OPTION_GET_TRAILERS) #define HTTP2_ERROR_CODES(V) \ V(NGHTTP2_NO_ERROR) \ V(NGHTTP2_PROTOCOL_ERROR) \ V(NGHTTP2_INTERNAL_ERROR) \ V(NGHTTP2_FLOW_CONTROL_ERROR) \ V(NGHTTP2_SETTINGS_TIMEOUT) \ V(NGHTTP2_STREAM_CLOSED) \ V(NGHTTP2_FRAME_SIZE_ERROR) \ V(NGHTTP2_REFUSED_STREAM) \ V(NGHTTP2_CANCEL) \ V(NGHTTP2_COMPRESSION_ERROR) \ V(NGHTTP2_CONNECT_ERROR) \ V(NGHTTP2_ENHANCE_YOUR_CALM) \ V(NGHTTP2_INADEQUATE_SECURITY) \ V(NGHTTP2_HTTP_1_1_REQUIRED) \ #define HTTP2_CONSTANTS(V) \ V(NGHTTP2_ERR_FRAME_SIZE_ERROR) \ V(NGHTTP2_SESSION_SERVER) \ V(NGHTTP2_SESSION_CLIENT) \ V(NGHTTP2_STREAM_STATE_IDLE) \ V(NGHTTP2_STREAM_STATE_OPEN) \ V(NGHTTP2_STREAM_STATE_RESERVED_LOCAL) \ V(NGHTTP2_STREAM_STATE_RESERVED_REMOTE) \ V(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL) \ V(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE) \ V(NGHTTP2_STREAM_STATE_CLOSED) \ V(NGHTTP2_FLAG_NONE) \ V(NGHTTP2_FLAG_END_STREAM) \ V(NGHTTP2_FLAG_END_HEADERS) \ V(NGHTTP2_FLAG_ACK) \ V(NGHTTP2_FLAG_PADDED) \ V(NGHTTP2_FLAG_PRIORITY) \ V(DEFAULT_SETTINGS_HEADER_TABLE_SIZE) \ V(DEFAULT_SETTINGS_ENABLE_PUSH) \ V(DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS) \ V(DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE) \ V(DEFAULT_SETTINGS_MAX_FRAME_SIZE) \ V(DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE) \ V(DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL) \ V(MAX_MAX_FRAME_SIZE) \ V(MIN_MAX_FRAME_SIZE) \ V(MAX_INITIAL_WINDOW_SIZE) \ V(NGHTTP2_SETTINGS_HEADER_TABLE_SIZE) \ V(NGHTTP2_SETTINGS_ENABLE_PUSH) \ V(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) \ V(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE) \ V(NGHTTP2_SETTINGS_MAX_FRAME_SIZE) \ V(NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE) \ V(NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL) \ V(PADDING_STRATEGY_NONE) \ V(PADDING_STRATEGY_ALIGNED) \ V(PADDING_STRATEGY_MAX) \ V(PADDING_STRATEGY_CALLBACK) \ HTTP2_ERROR_CODES(V) #define HTTP2_SETTINGS(V) \ V(HEADER_TABLE_SIZE) \ V(ENABLE_PUSH) \ V(MAX_CONCURRENT_STREAMS) \ V(INITIAL_WINDOW_SIZE) \ V(MAX_FRAME_SIZE) \ V(MAX_HEADER_LIST_SIZE) \ V(ENABLE_CONNECT_PROTOCOL) \ } // namespace http2 } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_NODE_HTTP2_H_