// The initial receive window size for both streams and sessions.
const int32 kDefaultInitialRecvWindowSize = 10 * 1024 * 1024; // 10MB
+// First and last valid stream IDs. As we always act as the client,
+// start at 1 for the first stream id.
+const SpdyStreamId kFirstStreamId = 1;
+const SpdyStreamId kLastStreamId = 0x7fffffff;
+
class BoundNetLog;
struct LoadTimingInfo;
class SpdyStream;
class SSLInfo;
+class TransportSecurityState;
// NOTE: There's an enum of the same name (also with numeric suffixes)
// in histograms.xml. Be sure to add new values there also.
SPDY_ERROR_RST_STREAM_FRAME_CORRUPT = 30,
SPDY_ERROR_INVALID_DATA_FRAME_FLAGS = 8,
SPDY_ERROR_INVALID_CONTROL_FRAME_FLAGS = 9,
+ SPDY_ERROR_UNEXPECTED_FRAME = 31,
// SpdyRstStreamStatus mappings.
// RST_STREAM_INVALID not mapped.
STATUS_CODE_PROTOCOL_ERROR = 11,
STATUS_CODE_STREAM_IN_USE = 18,
STATUS_CODE_STREAM_ALREADY_CLOSED = 19,
STATUS_CODE_INVALID_CREDENTIALS = 20,
- STATUS_CODE_FRAME_TOO_LARGE = 21,
+ STATUS_CODE_FRAME_SIZE_ERROR = 21,
+ STATUS_CODE_SETTINGS_TIMEOUT = 32,
+ STATUS_CODE_CONNECT_ERROR = 33,
+ STATUS_CODE_ENHANCE_YOUR_CALM = 34,
+
// SpdySession errors
PROTOCOL_ERROR_UNEXPECTED_PING = 22,
PROTOCOL_ERROR_RST_STREAM_FOR_NON_ACTIVE_STREAM = 23,
PROTOCOL_ERROR_RECEIVE_WINDOW_VIOLATION = 28,
// Next free value.
- NUM_SPDY_PROTOCOL_ERROR_DETAILS = 31,
+ NUM_SPDY_PROTOCOL_ERROR_DETAILS = 35,
};
-SpdyProtocolErrorDetails NET_EXPORT_PRIVATE MapFramerErrorToProtocolError(
- SpdyFramer::SpdyError);
-SpdyProtocolErrorDetails NET_EXPORT_PRIVATE MapRstStreamStatusToProtocolError(
- SpdyRstStreamStatus);
+SpdyProtocolErrorDetails NET_EXPORT_PRIVATE
+ MapFramerErrorToProtocolError(SpdyFramer::SpdyError error);
+Error NET_EXPORT_PRIVATE MapFramerErrorToNetError(SpdyFramer::SpdyError error);
+SpdyProtocolErrorDetails NET_EXPORT_PRIVATE
+ MapRstStreamStatusToProtocolError(SpdyRstStreamStatus status);
+SpdyGoAwayStatus NET_EXPORT_PRIVATE MapNetErrorToGoAwayStatus(Error err);
// If these compile asserts fail then SpdyProtocolErrorDetails needs
// to be updated with new values, as do the mapping functions above.
-COMPILE_ASSERT(11 == SpdyFramer::LAST_ERROR,
+COMPILE_ASSERT(12 == SpdyFramer::LAST_ERROR,
SpdyProtocolErrorDetails_SpdyErrors_mismatch);
-COMPILE_ASSERT(12 == RST_STREAM_NUM_STATUS_CODES,
+COMPILE_ASSERT(15 == RST_STREAM_NUM_STATUS_CODES,
SpdyProtocolErrorDetails_RstStreamStatus_mismatch);
+// Splits pushed |headers| into request and response parts. Request headers are
+// the headers specifying resource URL.
+void NET_EXPORT_PRIVATE
+ SplitPushedHeadersToRequestAndResponse(const SpdyHeaderBlock& headers,
+ SpdyMajorVersion protocol_version,
+ SpdyHeaderBlock* request_headers,
+ SpdyHeaderBlock* response_headers);
+
// A helper class used to manage a request to create a stream.
class NET_EXPORT_PRIVATE SpdyStreamRequest {
public:
void Reset();
- base::WeakPtrFactory<SpdyStreamRequest> weak_ptr_factory_;
SpdyStreamType type_;
base::WeakPtr<SpdySession> session_;
base::WeakPtr<SpdyStream> stream_;
BoundNetLog net_log_;
CompletionCallback callback_;
+ base::WeakPtrFactory<SpdyStreamRequest> weak_ptr_factory_;
+
DISALLOW_COPY_AND_ASSIGN(SpdyStreamRequest);
};
FLOW_CONTROL_STREAM_AND_SESSION
};
+ // Returns true if |hostname| can be pooled into an existing connection
+ // associated with |ssl_info|.
+ static bool CanPool(TransportSecurityState* transport_security_state,
+ const SSLInfo& ssl_info,
+ const std::string& old_hostname,
+ const std::string& new_hostname);
+
// Create a new SpdySession.
// |spdy_session_key| is the host/port that this session connects to, privacy
// and proxy configuration settings that it's using.
// network events to.
SpdySession(const SpdySessionKey& spdy_session_key,
const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ TransportSecurityState* transport_security_state,
bool verify_domain_authentication,
bool enable_sending_initial_data,
bool enable_compression,
const HostPortPair& trusted_spdy_proxy,
NetLog* net_log);
- virtual ~SpdySession();
+ ~SpdySession() override;
const HostPortPair& host_port_pair() const {
return spdy_session_key_.host_port_proxy_pair().first;
// |certificate_error_code| must either be OK or less than
// ERR_IO_PENDING.
//
- // Returns OK on success, or an error on failure. Never returns
- // ERR_IO_PENDING. If an error is returned, the session must be
- // destroyed immediately.
- Error InitializeWithSocket(scoped_ptr<ClientSocketHandle> connection,
- SpdySessionPool* pool,
- bool is_secure,
- int certificate_error_code);
+ // The session begins reading from |connection| on a subsequent event loop
+ // iteration, so the SpdySession may close immediately afterwards if the first
+ // read of |connection| fails.
+ void InitializeWithSocket(scoped_ptr<ClientSocketHandle> connection,
+ SpdySessionPool* pool,
+ bool is_secure,
+ int certificate_error_code);
// Returns the protocol used by this session. Always between
// kProtoSPDYMinimumVersion and kProtoSPDYMaximumVersion.
void SendStreamWindowUpdate(SpdyStreamId stream_id,
uint32 delta_window_size);
- // Whether the stream is closed, i.e. it has stopped processing data
- // and is about to be destroyed.
- //
- // TODO(akalin): This is only used in tests. Remove this function
- // and have tests test the WeakPtr instead.
- bool IsClosed() const { return availability_state_ == STATE_CLOSED; }
+ // Accessors for the session's availability state.
+ bool IsAvailable() const { return availability_state_ == STATE_AVAILABLE; }
+ bool IsGoingAway() const { return availability_state_ == STATE_GOING_AWAY; }
+ bool IsDraining() const { return availability_state_ == STATE_DRAINING; }
// Closes this session. This will close all active streams and mark
// the session as permanently closed. Callers must assume that the
// will not close any streams.
void MakeUnavailable();
+ // Closes all active streams with stream id's greater than
+ // |last_good_stream_id|, as well as any created or pending
+ // streams. Must be called only when |availability_state_| >=
+ // STATE_GOING_AWAY. After this function, DcheckGoingAway() will
+ // pass. May be called multiple times.
+ void StartGoingAway(SpdyStreamId last_good_stream_id, Error status);
+
+ // Must be called only when going away (i.e., DcheckGoingAway()
+ // passes). If there are no more active streams and the session
+ // isn't closed yet, close it.
+ void MaybeFinishGoingAway();
+
// Retrieves information on the current state of the SPDY session as a
// Value. Caller takes possession of the returned value.
base::Value* GetInfoAsValue() const;
// Indicates whether the session is being reused after having successfully
- // used to send/receive data in the past.
+ // used to send/receive data in the past or if the underlying socket was idle
+ // before being used for a SPDY session.
bool IsReused() const;
// Returns true if the underlying transport socket ever had any reads or
// available for testing and diagnostics.
size_t num_active_streams() const { return active_streams_.size(); }
size_t num_unclaimed_pushed_streams() const {
- return unclaimed_pushed_streams_.size();
+ return unclaimed_pushed_streams_.size();
}
size_t num_created_streams() const { return created_streams_.size(); }
+ size_t num_pushed_streams() const { return num_pushed_streams_; }
+ size_t num_active_pushed_streams() const {
+ return num_active_pushed_streams_;
+ }
+
size_t pending_create_stream_queue_size(RequestPriority priority) const {
DCHECK_GE(priority, MINIMUM_PRIORITY);
DCHECK_LE(priority, MAXIMUM_PRIORITY);
return buffered_spdy_framer_->GetDataFrameMaximumPayload();
}
+ // https://http2.github.io/http2-spec/#TLSUsage mandates minimum security
+ // standards for TLS.
+ bool HasAcceptableTransportSecurity() const;
+
// Must be used only by |pool_|.
base::WeakPtr<SpdySession> GetWeakPtr();
// HigherLayeredPool implementation:
- virtual bool CloseOneIdleConnection() OVERRIDE;
+ bool CloseOneIdleConnection() override;
private:
friend class base::RefCounted<SpdySession>;
FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlNoReceiveLeaks);
FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlNoSendLeaks);
FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlEndToEnd);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, StreamIdSpaceExhausted);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, UnstallRacesWithStreamCreation);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, GoAwayOnSessionFlowControlError);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest,
+ RejectPushedStreamExceedingConcurrencyLimit);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, IgnoreReservedRemoteStreamsCount);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest,
+ CancelReservedStreamOnHeadersReceived);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, RejectInvalidUnknownFrames);
typedef std::deque<base::WeakPtr<SpdyStreamRequest> >
PendingStreamRequestQueue;
// The session can process data on existing streams but will
// refuse to create new ones.
STATE_GOING_AWAY,
- // The session has been closed, is waiting to be deleted, and will
- // refuse to process any more data.
- STATE_CLOSED
+ // The session is draining its write queue in preparation of closing.
+ // Further writes will not be queued, and further reads will not be issued
+ // (though the remainder of a current read may be processed). The session
+ // will be destroyed by its write loop once the write queue is drained.
+ STATE_DRAINING,
};
enum ReadState {
WRITE_STATE_DO_WRITE_COMPLETE,
};
- // The return value of DoCloseSession() describing what was done.
- enum CloseSessionResult {
- // The session was already closed so nothing was done.
- SESSION_ALREADY_CLOSED,
- // The session was moved into the closed state but was not removed
- // from |pool_| (because we're in an IO loop).
- SESSION_CLOSED_BUT_NOT_REMOVED,
- // The session was moved into the closed state and removed from
- // |pool_|.
- SESSION_CLOSED_AND_REMOVED,
- };
-
// Checks whether a stream for the given |url| can be created or
// retrieved from the set of unclaimed push streams. Returns OK if
// so. Otherwise, the session is closed and an error <
// possible.
void ProcessPendingStreamRequests();
+ bool TryCreatePushStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ const SpdyHeaderBlock& headers);
+
// Close the stream pointed to by the given iterator. Note that that
// stream may hold the last reference to the session.
void CloseActiveStreamIterator(ActiveStreamMap::iterator it, int status);
SpdyRstStreamStatus status,
const std::string& description);
- // Calls DoReadLoop and then if |availability_state_| is
- // STATE_CLOSED, calls RemoveFromPool().
- //
- // Use this function instead of DoReadLoop when posting a task to
- // pump the read loop.
+ // Calls DoReadLoop. Use this function instead of DoReadLoop when
+ // posting a task to pump the read loop.
void PumpReadLoop(ReadState expected_read_state, int result);
// Advance the ReadState state machine. |expected_read_state| is the
// expected starting read state.
//
- // This function must always be called via PumpReadLoop() except for
- // from InitializeWithSocket().
+ // This function must always be called via PumpReadLoop().
int DoReadLoop(ReadState expected_read_state, int result);
// The implementations of the states of the ReadState state machine.
int DoRead();
int DoReadComplete(int result);
- // Calls DoWriteLoop and then if |availability_state_| is
- // STATE_CLOSED, calls RemoveFromPool().
+ // Calls DoWriteLoop. If |availability_state_| is STATE_DRAINING and no
+ // writes remain, the session is removed from the session pool and
+ // destroyed.
//
// Use this function instead of DoWriteLoop when posting a task to
// pump the write loop.
void PumpWriteLoop(WriteState expected_write_state, int result);
+ // Iff the write loop is not currently active, posts a callback into
+ // PumpWriteLoop().
+ void MaybePostWriteLoop();
+
// Advance the WriteState state machine. |expected_write_state| is
// the expected starting write state.
//
RequestPriority priority);
// Send the PING frame.
- void WritePingFrame(uint32 unique_id);
+ void WritePingFrame(uint32 unique_id, bool is_ack);
// Post a CheckPingStatus call after delay. Don't post if there is already
// CheckPingStatus running.
void CheckPingStatus(base::TimeTicks last_check_time);
// Get a new stream id.
- int GetNewStreamId();
+ SpdyStreamId GetNewStreamId();
// Pushes the given frame with the given priority into the write
// queue for the session.
void DcheckGoingAway() const;
// Calls DcheckGoingAway(), then DCHECKs that |availability_state_|
- // == STATE_CLOSED, |error_on_close_| has a valid value, that there
- // are no active streams or unclaimed pushed streams, and that the
- // write queue is empty.
- void DcheckClosed() const;
-
- // Closes all active streams with stream id's greater than
- // |last_good_stream_id|, as well as any created or pending
- // streams. Must be called only when |availability_state_| >=
- // STATE_GOING_AWAY. After this function, DcheckGoingAway() will
- // pass. May be called multiple times.
- void StartGoingAway(SpdyStreamId last_good_stream_id, Error status);
+ // == STATE_DRAINING, |error_on_close_| has a valid value, and that there
+ // are no active streams or unclaimed pushed streams.
+ void DcheckDraining() const;
- // Must be called only when going away (i.e., DcheckGoingAway()
- // passes). If there are no more active streams and the session
- // isn't closed yet, close it.
- void MaybeFinishGoingAway();
-
- // If the stream is already closed, does nothing. Otherwise, moves
- // the session to a closed state. Then, if we're in an IO loop,
- // returns (as the IO loop will do the pool removal itself when its
- // done). Otherwise, also removes |this| from |pool_|. The returned
- // result describes what was done.
- CloseSessionResult DoCloseSession(Error err, const std::string& description);
-
- // Remove this session from its pool, which must exist. Must be
- // called only when the session is closed.
- //
- // Must be called only via Pump{Read,Write}Loop() or
- // DoCloseSession().
- void RemoveFromPool();
+ // If the session is already draining, does nothing. Otherwise, moves
+ // the session to the draining state.
+ void DoDrainSession(Error err, const std::string& description);
// Called right before closing a (possibly-inactive) stream for a
// reason other than being requested to by the stream.
void DeleteExpiredPushedStreams();
// BufferedSpdyFramerVisitorInterface:
- virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE;
- virtual void OnStreamError(SpdyStreamId stream_id,
- const std::string& description) OVERRIDE;
- virtual void OnPing(SpdyPingId unique_id) OVERRIDE;
- virtual void OnRstStream(SpdyStreamId stream_id,
- SpdyRstStreamStatus status) OVERRIDE;
- virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
- SpdyGoAwayStatus status) OVERRIDE;
- virtual void OnDataFrameHeader(SpdyStreamId stream_id,
- size_t length,
- bool fin) OVERRIDE;
- virtual void OnStreamFrameData(SpdyStreamId stream_id,
- const char* data,
- size_t len,
- bool fin) OVERRIDE;
- virtual void OnSettings(bool clear_persisted) OVERRIDE;
- virtual void OnSetting(
- SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE;
- virtual void OnWindowUpdate(SpdyStreamId stream_id,
- uint32 delta_window_size) OVERRIDE;
- virtual void OnPushPromise(SpdyStreamId stream_id,
- SpdyStreamId promised_stream_id) OVERRIDE;
- virtual void OnSynStream(SpdyStreamId stream_id,
- SpdyStreamId associated_stream_id,
- SpdyPriority priority,
- bool fin,
- bool unidirectional,
- const SpdyHeaderBlock& headers) OVERRIDE;
- virtual void OnSynReply(
- SpdyStreamId stream_id,
- bool fin,
- const SpdyHeaderBlock& headers) OVERRIDE;
- virtual void OnHeaders(
- SpdyStreamId stream_id,
- bool fin,
- const SpdyHeaderBlock& headers) OVERRIDE;
+ void OnError(SpdyFramer::SpdyError error_code) override;
+ void OnStreamError(SpdyStreamId stream_id,
+ const std::string& description) override;
+ void OnPing(SpdyPingId unique_id, bool is_ack) override;
+ void OnRstStream(SpdyStreamId stream_id, SpdyRstStreamStatus status) override;
+ void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) override;
+ void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) override;
+ void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) override;
+ void OnSettings(bool clear_persisted) override;
+ void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) override;
+ void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) override;
+ void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ const SpdyHeaderBlock& headers) override;
+ void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) override;
+ void OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) override;
+ void OnHeaders(SpdyStreamId stream_id,
+ bool has_priority,
+ SpdyPriority priority,
+ bool fin,
+ const SpdyHeaderBlock& headers) override;
+ bool OnUnknownFrame(SpdyStreamId stream_id, int frame_type) override;
// SpdyFramerDebugVisitorInterface
- virtual void OnSendCompressedFrame(
- SpdyStreamId stream_id,
- SpdyFrameType type,
- size_t payload_len,
- size_t frame_len) OVERRIDE;
- virtual void OnReceiveCompressedFrame(
- SpdyStreamId stream_id,
- SpdyFrameType type,
- size_t frame_len) OVERRIDE;
+ void OnSendCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len) override;
+ void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len) override;
// Called when bytes are consumed from a SpdyBuffer for a DATA frame
// that is to be written or is being written. Increases the send
hung_interval_ = duration;
}
+ void set_max_concurrent_pushed_streams(size_t value) {
+ max_concurrent_pushed_streams_ = value;
+ }
+
int64 pings_in_flight() const { return pings_in_flight_; }
uint32 next_ping_id() const { return next_ping_id_; }
// or NULL, if the transport is not SSL.
SSLClientSocket* GetSSLClientSocket() const;
- // Used for posting asynchronous IO tasks. We use this even though
- // SpdySession is refcounted because we don't need to keep the SpdySession
- // alive if the last reference is within a RunnableMethod. Just revoke the
- // method.
- base::WeakPtrFactory<SpdySession> weak_factory_;
-
// Whether Do{Read,Write}Loop() is in the call stack. Useful for
// making sure we don't destroy ourselves prematurely in that case.
bool in_io_loop_;
SpdySessionPool* pool_;
const base::WeakPtr<HttpServerProperties> http_server_properties_;
+ TransportSecurityState* transport_security_state_;
+
// The socket handle for this session.
scoped_ptr<ClientSocketHandle> connection_;
// The read buffer used to read data from the socket.
scoped_refptr<IOBuffer> read_buffer_;
- int stream_hi_water_mark_; // The next stream id to use.
+ SpdyStreamId stream_hi_water_mark_; // The next stream id to use.
+
+ // Used to ensure the server increments push stream ids correctly.
+ SpdyStreamId last_accepted_push_stream_id_;
// Queue, for each priority, of pending stream requests that have
// not yet been satisfied.
// |created_streams_| owns all its SpdyStream objects.
CreatedStreamSet created_streams_;
+ // Number of pushed streams. All active streams are stored in
+ // |active_streams_|, but it's better to know the number of push streams
+ // without traversing the whole collection.
+ size_t num_pushed_streams_;
+
+ // Number of active pushed streams in |active_streams_|, i.e. not in reserved
+ // remote state. Streams in reserved state are not counted towards any
+ // concurrency limits.
+ size_t num_active_pushed_streams_;
+
// The write queue.
SpdyWriteQueue write_queue_;
ReadState read_state_;
WriteState write_state_;
- // If the session was closed (i.e., |availability_state_| is
- // STATE_CLOSED), then |error_on_close_| holds the error with which
- // it was closed, which is < ERR_IO_PENDING. Otherwise, it is set to
- // OK.
+ // If the session is closing (i.e., |availability_state_| is STATE_DRAINING),
+ // then |error_on_close_| holds the error with which it was closed, which
+ // may be OK (upon a polite GOAWAY) or an error < ERR_IO_PENDING otherwise.
+ // Initialized to OK.
Error error_on_close_;
// Limits
size_t max_concurrent_streams_; // 0 if no limit
size_t max_concurrent_streams_limit_;
+ size_t max_concurrent_pushed_streams_;
// Some statistics counters for the session.
int streams_initiated_count_;
HostPortPair trusted_spdy_proxy_;
TimeFunc time_func_;
+
+ // Used for posting asynchronous IO tasks. We use this even though
+ // SpdySession is refcounted because we don't need to keep the SpdySession
+ // alive if the last reference is within a RunnableMethod. Just revoke the
+ // method.
+ base::WeakPtrFactory<SpdySession> weak_factory_;
};
} // namespace net