michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef mozilla_net_Http2Session_h michael@0: #define mozilla_net_Http2Session_h michael@0: michael@0: // HTTP/2 michael@0: michael@0: #include "ASpdySession.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsAHttpConnection.h" michael@0: #include "nsClassHashtable.h" michael@0: #include "nsDataHashtable.h" michael@0: #include "nsDeque.h" michael@0: #include "nsHashKeys.h" michael@0: michael@0: #include "Http2Compression.h" michael@0: michael@0: class nsISocketTransport; michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: class Http2PushedStream; michael@0: class Http2Stream; michael@0: michael@0: class Http2Session MOZ_FINAL : public ASpdySession michael@0: , public nsAHttpConnection michael@0: , public nsAHttpSegmentReader michael@0: , public nsAHttpSegmentWriter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSAHTTPTRANSACTION michael@0: NS_DECL_NSAHTTPCONNECTION(mConnection) michael@0: NS_DECL_NSAHTTPSEGMENTREADER michael@0: NS_DECL_NSAHTTPSEGMENTWRITER michael@0: michael@0: Http2Session(nsAHttpTransaction *, nsISocketTransport *, int32_t); michael@0: ~Http2Session(); michael@0: michael@0: bool AddStream(nsAHttpTransaction *, int32_t); michael@0: bool CanReuse() { return !mShouldGoAway && !mClosed; } michael@0: bool RoomForMoreStreams(); michael@0: michael@0: // When the connection is active this is called up to once every 1 second michael@0: // return the interval (in seconds) that the connection next wants to michael@0: // have this invoked. It might happen sooner depending on the needs of michael@0: // other connections. michael@0: uint32_t ReadTimeoutTick(PRIntervalTime now); michael@0: michael@0: // Idle time represents time since "goodput".. e.g. a data or header frame michael@0: PRIntervalTime IdleTime(); michael@0: michael@0: // Registering with a newID of 0 means pick the next available odd ID michael@0: uint32_t RegisterStreamID(Http2Stream *, uint32_t aNewID = 0); michael@0: michael@0: /* michael@0: HTTP/2 framing michael@0: michael@0: 0 1 2 3 michael@0: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 michael@0: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ michael@0: | Length (16) | Type (8) | Flags (8) | michael@0: +-+-------------+---------------+-------------------------------+ michael@0: |R| Stream Identifier (31) | michael@0: +-+-------------------------------------------------------------+ michael@0: | Frame Data (0...) ... michael@0: +---------------------------------------------------------------+ michael@0: */ michael@0: michael@0: enum frameType { michael@0: FRAME_TYPE_DATA = 0, michael@0: FRAME_TYPE_HEADERS = 1, michael@0: FRAME_TYPE_PRIORITY = 2, michael@0: FRAME_TYPE_RST_STREAM = 3, michael@0: FRAME_TYPE_SETTINGS = 4, michael@0: FRAME_TYPE_PUSH_PROMISE = 5, michael@0: FRAME_TYPE_PING = 6, michael@0: FRAME_TYPE_GOAWAY = 7, michael@0: FRAME_TYPE_WINDOW_UPDATE = 8, michael@0: FRAME_TYPE_CONTINUATION = 9, michael@0: FRAME_TYPE_LAST = 10 michael@0: }; michael@0: michael@0: // NO_ERROR is a macro defined on windows, so we'll name the HTTP2 goaway michael@0: // code NO_ERROR to be NO_HTTP_ERROR michael@0: enum errorType { michael@0: NO_HTTP_ERROR = 0, michael@0: PROTOCOL_ERROR = 1, michael@0: INTERNAL_ERROR = 2, michael@0: FLOW_CONTROL_ERROR = 3, michael@0: SETTINGS_TIMEOUT_ERROR = 4, michael@0: STREAM_CLOSED_ERROR = 5, michael@0: FRAME_SIZE_ERROR = 6, michael@0: REFUSED_STREAM_ERROR = 7, michael@0: CANCEL_ERROR = 8, michael@0: COMPRESSION_ERROR = 9, michael@0: CONNECT_ERROR = 10, michael@0: ENHANCE_YOUR_CALM = 11, michael@0: INADEQUATE_SECURITY = 12 michael@0: }; michael@0: michael@0: // These are frame flags. If they, or other undefined flags, are michael@0: // used on frames other than the comments indicate they MUST be ignored. michael@0: const static uint8_t kFlag_END_STREAM = 0x01; // data, headers michael@0: const static uint8_t kFlag_END_HEADERS = 0x04; // headers, continuation michael@0: const static uint8_t kFlag_PRIORITY = 0x08; //headers michael@0: const static uint8_t kFlag_END_PUSH_PROMISE = 0x04; // push promise michael@0: const static uint8_t kFlag_ACK = 0x01; // ping and settings michael@0: const static uint8_t kFlag_END_SEGMENT = 0x02; // data michael@0: const static uint8_t kFlag_PAD_LOW = 0x10; // data, headers, continuation michael@0: const static uint8_t kFlag_PAD_HIGH = 0x20; // data, headers, continuation michael@0: michael@0: enum { michael@0: SETTINGS_TYPE_HEADER_TABLE_SIZE = 1, // compression table size michael@0: SETTINGS_TYPE_ENABLE_PUSH = 2, // can be used to disable push michael@0: SETTINGS_TYPE_MAX_CONCURRENT = 3, // streams recvr allowed to initiate michael@0: SETTINGS_TYPE_INITIAL_WINDOW = 4 // bytes for flow control default michael@0: }; michael@0: michael@0: // This should be big enough to hold all of your control packets, michael@0: // but if it needs to grow for huge headers it can do so dynamically. michael@0: const static uint32_t kDefaultBufferSize = 2048; michael@0: michael@0: // kDefaultQueueSize must be >= other queue size constants michael@0: const static uint32_t kDefaultQueueSize = 32768; michael@0: const static uint32_t kQueueMinimumCleanup = 24576; michael@0: const static uint32_t kQueueTailRoom = 4096; michael@0: const static uint32_t kQueueReserved = 1024; michael@0: michael@0: const static uint32_t kDefaultMaxConcurrent = 100; michael@0: const static uint32_t kMaxStreamID = 0x7800000; michael@0: michael@0: // This is a sentinel for a deleted stream. It is not a valid michael@0: // 31 bit stream ID. michael@0: const static uint32_t kDeadStreamID = 0xffffdead; michael@0: michael@0: // below the emergency threshold of local window we ack every received michael@0: // byte. Above that we coalesce bytes into the MinimumToAck size. michael@0: const static int32_t kEmergencyWindowThreshold = 256 * 1024; michael@0: const static uint32_t kMinimumToAck = 4 * 1024 * 1024; michael@0: michael@0: // The default rwin is 64KB - 1 unless updated by a settings frame michael@0: const static uint32_t kDefaultRwin = 65535; michael@0: michael@0: // Frames with HTTP semantics are limited to 2^14 - 1 bytes of length in michael@0: // order to preserve responsiveness michael@0: const static uint32_t kMaxFrameData = 16383; michael@0: michael@0: static nsresult RecvHeaders(Http2Session *); michael@0: static nsresult RecvPriority(Http2Session *); michael@0: static nsresult RecvRstStream(Http2Session *); michael@0: static nsresult RecvSettings(Http2Session *); michael@0: static nsresult RecvPushPromise(Http2Session *); michael@0: static nsresult RecvPing(Http2Session *); michael@0: static nsresult RecvGoAway(Http2Session *); michael@0: static nsresult RecvWindowUpdate(Http2Session *); michael@0: static nsresult RecvContinuation(Http2Session *); michael@0: michael@0: template michael@0: static void EnsureBuffer(nsAutoArrayPtr &, michael@0: uint32_t, uint32_t, uint32_t &); michael@0: char *EnsureOutputBuffer(uint32_t needed); michael@0: michael@0: template michael@0: void CreateFrameHeader(charType dest, uint16_t frameLength, michael@0: uint8_t frameType, uint8_t frameFlags, michael@0: uint32_t streamID); michael@0: michael@0: // For writing the data stream to LOG4 michael@0: static void LogIO(Http2Session *, Http2Stream *, const char *, michael@0: const char *, uint32_t); michael@0: michael@0: // an overload of nsAHttpConnection michael@0: void TransactionHasDataToWrite(nsAHttpTransaction *); michael@0: michael@0: // a similar version for Http2Stream michael@0: void TransactionHasDataToWrite(Http2Stream *); michael@0: michael@0: // an overload of nsAHttpSegementReader michael@0: virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment); michael@0: nsresult BufferOutput(const char *, uint32_t, uint32_t *); michael@0: void FlushOutputQueue(); michael@0: uint32_t AmountOfOutputBuffered() { return mOutputQueueUsed - mOutputQueueSent; } michael@0: michael@0: uint32_t GetServerInitialStreamWindow() { return mServerInitialStreamWindow; } michael@0: michael@0: void ConnectPushedStream(Http2Stream *stream); michael@0: void MaybeDecrementConcurrent(Http2Stream *stream); michael@0: michael@0: nsresult ConfirmTLSProfile(); michael@0: michael@0: uint64_t Serial() { return mSerial; } michael@0: michael@0: void PrintDiagnostics (nsCString &log); michael@0: michael@0: // Streams need access to these michael@0: uint32_t SendingChunkSize() { return mSendingChunkSize; } michael@0: uint32_t PushAllowance() { return mPushAllowance; } michael@0: Http2Compressor *Compressor() { return &mCompressor; } michael@0: nsISocketTransport *SocketTransport() { return mSocketTransport; } michael@0: int64_t ServerSessionWindow() { return mServerSessionWindow; } michael@0: void DecrementServerSessionWindow (uint32_t bytes) { mServerSessionWindow -= bytes; } michael@0: michael@0: private: michael@0: michael@0: // These internal states do not correspond to the states of the HTTP/2 specification michael@0: enum internalStateType { michael@0: BUFFERING_OPENING_SETTINGS, michael@0: BUFFERING_FRAME_HEADER, michael@0: BUFFERING_CONTROL_FRAME, michael@0: PROCESSING_DATA_FRAME_PADDING_CONTROL, michael@0: PROCESSING_DATA_FRAME, michael@0: DISCARDING_DATA_FRAME_PADDING, michael@0: DISCARDING_DATA_FRAME, michael@0: PROCESSING_COMPLETE_HEADERS, michael@0: PROCESSING_CONTROL_RST_STREAM michael@0: }; michael@0: michael@0: static const uint8_t kMagicHello[24]; michael@0: michael@0: nsresult ResponseHeadersComplete(); michael@0: uint32_t GetWriteQueueSize(); michael@0: void ChangeDownstreamState(enum internalStateType); michael@0: void ResetDownstreamState(); michael@0: nsresult ReadyToProcessDataFrame(enum internalStateType); michael@0: nsresult UncompressAndDiscard(); michael@0: void GeneratePing(bool); michael@0: void GenerateSettingsAck(); michael@0: void GeneratePriority(uint32_t, uint32_t); michael@0: void GenerateRstStream(uint32_t, uint32_t); michael@0: void GenerateGoAway(uint32_t); michael@0: void CleanupStream(Http2Stream *, nsresult, errorType); michael@0: void CloseStream(Http2Stream *, nsresult); michael@0: void SendHello(); michael@0: void RemoveStreamFromQueues(Http2Stream *); michael@0: nsresult ParsePadding(uint8_t &, uint16_t &); michael@0: michael@0: void SetWriteCallbacks(); michael@0: void RealignOutputQueue(); michael@0: michael@0: bool RoomForMoreConcurrent(); michael@0: void ActivateStream(Http2Stream *); michael@0: void ProcessPending(); michael@0: nsresult SetInputFrameDataStream(uint32_t); michael@0: bool VerifyStream(Http2Stream *, uint32_t); michael@0: void SetNeedsCleanup(); michael@0: michael@0: void UpdateLocalRwin(Http2Stream *stream, uint32_t bytes); michael@0: void UpdateLocalStreamWindow(Http2Stream *stream, uint32_t bytes); michael@0: void UpdateLocalSessionWindow(uint32_t bytes); michael@0: michael@0: // a wrapper for all calls to the nshttpconnection level segment writer. Used michael@0: // to track network I/O for timeout purposes michael@0: nsresult NetworkRead(nsAHttpSegmentWriter *, char *, uint32_t, uint32_t *); michael@0: michael@0: static PLDHashOperator ShutdownEnumerator(nsAHttpTransaction *, michael@0: nsAutoPtr &, michael@0: void *); michael@0: michael@0: static PLDHashOperator GoAwayEnumerator(nsAHttpTransaction *, michael@0: nsAutoPtr &, michael@0: void *); michael@0: michael@0: static PLDHashOperator UpdateServerRwinEnumerator(nsAHttpTransaction *, michael@0: nsAutoPtr &, michael@0: void *); michael@0: michael@0: static PLDHashOperator RestartBlockedOnRwinEnumerator(nsAHttpTransaction *, michael@0: nsAutoPtr &, michael@0: void *); michael@0: michael@0: // This is intended to be nsHttpConnectionMgr:nsConnectionHandle taken michael@0: // from the first transaction on this session. That object contains the michael@0: // pointer to the real network-level nsHttpConnection object. michael@0: nsRefPtr mConnection; michael@0: michael@0: // The underlying socket transport object is needed to propogate some events michael@0: nsISocketTransport *mSocketTransport; michael@0: michael@0: // These are temporary state variables to hold the argument to michael@0: // Read/WriteSegments so it can be accessed by On(read/write)segment michael@0: // further up the stack. michael@0: nsAHttpSegmentReader *mSegmentReader; michael@0: nsAHttpSegmentWriter *mSegmentWriter; michael@0: michael@0: uint32_t mSendingChunkSize; /* the transmission chunk size */ michael@0: uint32_t mNextStreamID; /* 24 bits */ michael@0: uint32_t mConcurrentHighWater; /* max parallelism on session */ michael@0: uint32_t mPushAllowance; /* rwin for unmatched pushes */ michael@0: michael@0: internalStateType mDownstreamState; /* in frame, between frames, etc.. */ michael@0: michael@0: // Maintain 2 indexes - one by stream ID, one by transaction pointer. michael@0: // There are also several lists of streams: ready to write, queued due to michael@0: // max parallelism, streams that need to force a read for push, and the full michael@0: // set of pushed streams. michael@0: // The objects are not ref counted - they get destroyed michael@0: // by the nsClassHashtable implementation when they are removed from michael@0: // the transaction hash. michael@0: nsDataHashtable mStreamIDHash; michael@0: nsClassHashtable, michael@0: Http2Stream> mStreamTransactionHash; michael@0: michael@0: nsDeque mReadyForWrite; michael@0: nsDeque mQueuedStreams; michael@0: nsDeque mReadyForRead; michael@0: nsTArray mPushedStreams; michael@0: michael@0: // Compression contexts for header transport. michael@0: // HTTP/2 compresses only HTTP headers and does not reset the context in between michael@0: // frames. Even data that is not associated with a stream (e.g invalid michael@0: // stream ID) is passed through these contexts to keep the compression michael@0: // context correct. michael@0: Http2Compressor mCompressor; michael@0: Http2Decompressor mDecompressor; michael@0: nsCString mDecompressBuffer; michael@0: michael@0: // mInputFrameBuffer is used to store received control packets and the 8 bytes michael@0: // of header on data packets michael@0: uint32_t mInputFrameBufferSize; // buffer allocation michael@0: uint32_t mInputFrameBufferUsed; // amt of allocation used michael@0: nsAutoArrayPtr mInputFrameBuffer; michael@0: michael@0: // mInputFrameDataSize/Read are used for tracking the amount of data consumed michael@0: // in a frame after the 8 byte header. Control frames are always fully buffered michael@0: // and the fixed 8 byte leading header is at mInputFrameBuffer + 0, the first michael@0: // data byte (i.e. the first settings/goaway/etc.. specific byte) is at michael@0: // mInputFrameBuffer + 8 michael@0: // The frame size is mInputFrameDataSize + the constant 8 byte header michael@0: uint32_t mInputFrameDataSize; michael@0: uint32_t mInputFrameDataRead; michael@0: bool mInputFrameFinal; // This frame was marked FIN michael@0: uint8_t mInputFrameType; michael@0: uint8_t mInputFrameFlags; michael@0: uint32_t mInputFrameID; michael@0: uint16_t mPaddingLength; michael@0: michael@0: // When a frame has been received that is addressed to a particular stream michael@0: // (e.g. a data frame after the stream-id has been decoded), this points michael@0: // to the stream. michael@0: Http2Stream *mInputFrameDataStream; michael@0: michael@0: // mNeedsCleanup is a state variable to defer cleanup of a closed stream michael@0: // If needed, It is set in session::OnWriteSegments() and acted on and michael@0: // cleared when the stack returns to session::WriteSegments(). The stream michael@0: // cannot be destroyed directly out of OnWriteSegments because michael@0: // stream::writeSegments() is on the stack at that time. michael@0: Http2Stream *mNeedsCleanup; michael@0: michael@0: // This reason code in the last processed RESET frame michael@0: uint32_t mDownstreamRstReason; michael@0: michael@0: // When HEADERS/PROMISE are chained together, this is the expected ID of the next michael@0: // recvd frame which must be the same type michael@0: uint32_t mExpectedHeaderID; michael@0: uint32_t mExpectedPushPromiseID; michael@0: uint32_t mContinuedPromiseStream; michael@0: michael@0: // for the conversion of downstream http headers into http/2 formatted headers michael@0: // The data here does not persist between frames michael@0: nsCString mFlatHTTPResponseHeaders; michael@0: uint32_t mFlatHTTPResponseHeadersOut; michael@0: michael@0: // when set, the session will go away when it reaches 0 streams. This flag michael@0: // is set when: the stream IDs are running out (at either the client or the michael@0: // server), when DontReuse() is called, a RST that is not specific to a michael@0: // particular stream is received, a GOAWAY frame has been received from michael@0: // the server. michael@0: bool mShouldGoAway; michael@0: michael@0: // the session has received a nsAHttpTransaction::Close() call michael@0: bool mClosed; michael@0: michael@0: // the session received a GoAway frame with a valid GoAwayID michael@0: bool mCleanShutdown; michael@0: michael@0: // The TLS comlpiance checks are not done in the ctor beacuse of bad michael@0: // exception handling - so we do them at IO time and cache the result michael@0: bool mTLSProfileConfirmed; michael@0: michael@0: // A specifc reason code for the eventual GoAway frame. If set to NO_HTTP_ERROR michael@0: // only NO_HTTP_ERROR, PROTOCOL_ERROR, or INTERNAL_ERROR will be sent. michael@0: errorType mGoAwayReason; michael@0: michael@0: // If a GoAway message was received this is the ID of the last valid michael@0: // stream. 0 otherwise. (0 is never a valid stream id.) michael@0: uint32_t mGoAwayID; michael@0: michael@0: // The last stream processed ID we will send in our GoAway frame. michael@0: uint32_t mOutgoingGoAwayID; michael@0: michael@0: // The limit on number of concurrent streams for this session. Normally it michael@0: // is basically unlimited, but the SETTINGS control message from the michael@0: // server might bring it down. michael@0: uint32_t mMaxConcurrent; michael@0: michael@0: // The actual number of concurrent streams at this moment. Generally below michael@0: // mMaxConcurrent, but the max can be lowered in real time to a value michael@0: // below the current value michael@0: uint32_t mConcurrent; michael@0: michael@0: // The number of server initiated promises, tracked for telemetry michael@0: uint32_t mServerPushedResources; michael@0: michael@0: // The server rwin for new streams as determined from a SETTINGS frame michael@0: uint32_t mServerInitialStreamWindow; michael@0: michael@0: // The Local Session window is how much data the server is allowed to send michael@0: // (across all streams) without getting a window update to stream 0. It is michael@0: // signed because asynchronous changes via SETTINGS can drive it negative. michael@0: int64_t mLocalSessionWindow; michael@0: michael@0: // The Remote Session Window is how much data the client is allowed to send michael@0: // (across all streams) without receiving a window update to stream 0. It is michael@0: // signed because asynchronous changes via SETTINGS can drive it negative. michael@0: int64_t mServerSessionWindow; michael@0: michael@0: // This is a output queue of bytes ready to be written to the SSL stream. michael@0: // When that streams returns WOULD_BLOCK on direct write the bytes get michael@0: // coalesced together here. This results in larger writes to the SSL layer. michael@0: // The buffer is not dynamically grown to accomodate stream writes, but michael@0: // does expand to accept infallible session wide frames like GoAway and RST. michael@0: uint32_t mOutputQueueSize; michael@0: uint32_t mOutputQueueUsed; michael@0: uint32_t mOutputQueueSent; michael@0: nsAutoArrayPtr mOutputQueueBuffer; michael@0: michael@0: PRIntervalTime mPingThreshold; michael@0: PRIntervalTime mLastReadEpoch; // used for ping timeouts michael@0: PRIntervalTime mLastDataReadEpoch; // used for IdleTime() michael@0: PRIntervalTime mPingSentEpoch; michael@0: michael@0: // used as a temporary buffer while enumerating the stream hash during GoAway michael@0: nsDeque mGoAwayStreamsToRestart; michael@0: michael@0: // Each session gets a unique serial number because the push cache is correlated michael@0: // by the load group and the serial number can be used as part of the cache key michael@0: // to make sure streams aren't shared across sessions. michael@0: uint64_t mSerial; michael@0: }; michael@0: michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla michael@0: michael@0: #endif // mozilla_net_Http2Session_h