// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef NET_SPDY_SPDY_STREAM_H_ #define NET_SPDY_SPDY_STREAM_H_ #include #include #include #include #include #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "net/base/io_buffer.h" #include "net/base/net_export.h" #include "net/base/request_priority.h" #include "net/log/net_log_source.h" #include "net/log/net_log_with_source.h" #include "net/socket/next_proto.h" #include "net/socket/ssl_client_socket.h" #include "net/spdy/spdy_buffer.h" #include "net/third_party/quiche/src/quiche/spdy/core/http2_header_block.h" #include "net/third_party/quiche/src/quiche/spdy/core/spdy_framer.h" #include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h" #include "net/traffic_annotation/network_traffic_annotation.h" #include "url/gurl.h" namespace net { namespace test { class SpdyStreamTest; } class IPEndPoint; struct LoadTimingInfo; class SSLInfo; class SpdySession; enum SpdyStreamType { // The most general type of stream; there are no restrictions on // when data can be sent and received. SPDY_BIDIRECTIONAL_STREAM, // A stream where the client sends a request with possibly a body, // and the server then sends a response with a body. SPDY_REQUEST_RESPONSE_STREAM, // A server-initiated stream where the server just sends a response // with a body and the client does not send anything. // TODO(https://crbug.com/1426477): Remove. SPDY_PUSH_STREAM }; // Passed to some SpdyStream functions to indicate whether there's // more data to send. enum SpdySendStatus { MORE_DATA_TO_SEND, NO_MORE_DATA_TO_SEND }; // SpdyStream is owned by SpdySession and is used to represent each stream known // on the SpdySession. This class provides interfaces for SpdySession to use. // Streams can be created either by the client or by the server. When they // are initiated by the client, both the SpdySession and client object (such as // a SpdyNetworkTransaction) will maintain a reference to the stream. When // initiated by the server, only the SpdySession will maintain any reference, // until such a time as a client object requests a stream for the path. class NET_EXPORT_PRIVATE SpdyStream { public: // Delegate handles protocol specific behavior of spdy stream. class NET_EXPORT_PRIVATE Delegate { public: Delegate() = default; Delegate(const Delegate&) = delete; Delegate& operator=(const Delegate&) = delete; // Called when the request headers have been sent. Never called // for push streams. Must not cause the stream to be closed. virtual void OnHeadersSent() = 0; // OnEarlyHintsReceived(), OnHeadersReceived(), OnDataReceived(), // OnTrailers(), and OnClose() are guaranteed to be called in the following // order: // - OnEarlyHintsReceived() zero or more times; // - OnHeadersReceived() exactly once; // - OnDataReceived() zero or more times; // - OnTrailers() zero or one times; // - OnClose() exactly once. // Called when a 103 Early Hints response is received. virtual void OnEarlyHintsReceived( const spdy::Http2HeaderBlock& headers) = 0; // Called when response headers have been received. In case of a pushed // stream, the pushed request headers are also passed. // TODO(https://crbug.com/1426477): Remove. virtual void OnHeadersReceived( const spdy::Http2HeaderBlock& response_headers, const spdy::Http2HeaderBlock* pushed_request_headers) = 0; // Called when data is received. |buffer| may be NULL, which signals EOF. // May cause the stream to be closed. virtual void OnDataReceived(std::unique_ptr buffer) = 0; // Called when data is sent. Must not cause the stream to be closed. virtual void OnDataSent() = 0; // Called when trailers are received. virtual void OnTrailers(const spdy::Http2HeaderBlock& trailers) = 0; // Called when SpdyStream is closed. No other delegate functions // will be called after this is called, and the delegate must not // access the stream after this is called. Must not cause the // stream to be (re-)closed. // // TODO(akalin): Allow this function to re-close the stream and // handle it gracefully. virtual void OnClose(int status) = 0; // Returns whether it is allowed to send greased (reserved type) frames on // the HTTP/2 stream. virtual bool CanGreaseFrameType() const = 0; virtual NetLogSource source_dependency() const = 0; protected: virtual ~Delegate() = default; }; // SpdyStream constructor SpdyStream(SpdyStreamType type, const base::WeakPtr& session, const GURL& url, RequestPriority priority, int32_t initial_send_window_size, int32_t max_recv_window_size, const NetLogWithSource& net_log, const NetworkTrafficAnnotationTag& traffic_annotation, bool detect_broken_connection); SpdyStream(const SpdyStream&) = delete; SpdyStream& operator=(const SpdyStream&) = delete; ~SpdyStream(); // Set the delegate, which must not be NULL. Must not be called more // than once. For push streams, calling this may cause buffered data // to be sent to the delegate (from a posted task). void SetDelegate(Delegate* delegate); // Detach the delegate from the stream, which must not yet be // closed, and cancel it. void DetachDelegate(); // The time at which the first bytes of the response were received // from the server, or null if the response hasn't been received // yet. base::Time response_time() const { return response_time_; } SpdyStreamType type() const { return type_; } spdy::SpdyStreamId stream_id() const { return stream_id_; } void set_stream_id(spdy::SpdyStreamId stream_id) { stream_id_ = stream_id; } const GURL& url() const { return url_; } RequestPriority priority() const { return priority_; } // Update priority and send PRIORITY frames on the wire if necessary. void SetPriority(RequestPriority priority); int32_t send_window_size() const { return send_window_size_; } int32_t recv_window_size() const { return recv_window_size_; } bool send_stalled_by_flow_control() const { return send_stalled_by_flow_control_; } void set_send_stalled_by_flow_control(bool stalled) { send_stalled_by_flow_control_ = stalled; } // Called by the session to adjust this stream's send window size by // |delta_window_size|, which is the difference between the // spdy::SETTINGS_INITIAL_WINDOW_SIZE in the most recent SETTINGS frame // and the previous initial send window size, possibly unstalling // this stream. Although |delta_window_size| may cause this stream's // send window size to go negative, it must not cause it to wrap // around in either direction. Does nothing if the stream is already // closed. // Returns true if successful. Returns false if |send_window_size_| // would exceed 2^31-1 after the update, see RFC7540 Section 6.9.2. // Note that |send_window_size_| should not possibly underflow. [[nodiscard]] bool AdjustSendWindowSize(int32_t delta_window_size); // Called when bytes are consumed from a SpdyBuffer for a DATA frame // that is to be written or is being written. Increases the send // window size accordingly if some or all of the SpdyBuffer is being // discarded. // // If stream flow control is turned off, this must not be called. void OnWriteBufferConsumed(size_t frame_payload_size, size_t consume_size, SpdyBuffer::ConsumeSource consume_source); // Called by the session to increase this stream's send window size // by |delta_window_size| (which must be at least 1) from a received // WINDOW_UPDATE frame or from a dropped DATA frame that was // intended to be sent, possibly unstalling this stream. If // |delta_window_size| would cause this stream's send window size to // overflow, calls into the session to reset this stream. Does // nothing if the stream is already closed. // // If stream flow control is turned off, this must not be called. void IncreaseSendWindowSize(int32_t delta_window_size); // If stream flow control is turned on, called by the session to // decrease this stream's send window size by |delta_window_size|, // which must be at least 0 and at most kMaxSpdyFrameChunkSize. // |delta_window_size| must not cause this stream's send window size // to go negative. Does nothing if the stream is already closed. // // If stream flow control is turned off, this must not be called. void DecreaseSendWindowSize(int32_t delta_window_size); // Called when bytes are consumed by the delegate from a SpdyBuffer // containing received data. Increases the receive window size // accordingly. // // If stream flow control is turned off, this must not be called. void OnReadBufferConsumed(size_t consume_size, SpdyBuffer::ConsumeSource consume_source); // Called by OnReadBufferConsume to increase this stream's receive // window size by |delta_window_size|, which must be at least 1 and // must not cause this stream's receive window size to overflow, // possibly also sending a WINDOW_UPDATE frame. Does nothing if the // stream is not active. // // If stream flow control is turned off, this must not be called. void IncreaseRecvWindowSize(int32_t delta_window_size); // Called by OnDataReceived or OnPaddingConsumed (which are in turn called by // the session) to decrease this stream's receive window size by // |delta_window_size|, which must be at least 1. May close the stream on // flow control error. // // If stream flow control is turned off or the stream is not active, // this must not be called. void DecreaseRecvWindowSize(int32_t delta_window_size); int GetPeerAddress(IPEndPoint* address) const; int GetLocalAddress(IPEndPoint* address) const; // Returns true if the underlying transport socket ever had any reads or // writes. bool WasEverUsed() const; const NetLogWithSource& net_log() const { return net_log_; } base::Time GetRequestTime() const; void SetRequestTime(base::Time t); // Called by SpdySession when headers are received for this stream. May close // the stream. void OnHeadersReceived(const spdy::Http2HeaderBlock& response_headers, base::Time response_time, base::TimeTicks recv_first_byte_time); // Called by the SpdySession when a frame carrying request headers opening a // push stream is received. Stream transits to STATE_RESERVED_REMOTE state. // TODO(https://crbug.com/1426477): Remove. void OnPushPromiseHeadersReceived(spdy::Http2HeaderBlock headers, GURL url); // Called by the SpdySession when response data has been received // for this stream. This callback may be called multiple times as // data arrives from the network, and will never be called prior to // OnResponseHeadersReceived. // // |buffer| contains the data received, or NULL if the stream is // being closed. The stream must copy any data from this // buffer before returning from this callback. // // |length| is the number of bytes received (at most 2^24 - 1) or 0 if // the stream is being closed. void OnDataReceived(std::unique_ptr buffer); // Called by the SpdySession when padding is consumed to allow for the stream // receiving window to be updated. void OnPaddingConsumed(size_t len); // Called by the SpdySession when a frame has been successfully and completely // written. |frame_size| is the total size of the logical frame in bytes, // including framing overhead. For fragmented headers, this is the total size // of the HEADERS or PUSH_PROMISE frame and subsequent CONTINUATION frames. void OnFrameWriteComplete(spdy::SpdyFrameType frame_type, size_t frame_size); // HEADERS-specific write handler invoked by OnFrameWriteComplete(). int OnHeadersSent(); // DATA-specific write handler invoked by OnFrameWriteComplete(). // If more data is already available to be written, the next write is // queued and ERR_IO_PENDING is returned. Returns OK otherwise. int OnDataSent(size_t frame_size); // Called by the SpdySession when the request is finished. This callback // will always be called at the end of the request and signals to the // stream that the stream has no more network events. No further callbacks // to the stream will be made after this call. Must be called before // SpdyStream is destroyed. // |status| is an error code or OK. void OnClose(int status); // Called by the SpdySession to log stream related errors. void LogStreamError(int error, base::StringPiece description); // If this stream is active, reset it, and close it otherwise. In // either case the stream is deleted. void Cancel(int error); // Close this stream without sending a RST_STREAM and delete // it. void Close(); // Must be used only by |session_|. base::WeakPtr GetWeakPtr(); // Interface for the delegate to use. // Only one send can be in flight at a time, except for push // streams, which must not send anything. // Sends the request headers. The delegate is called back via OnHeadersSent() // when the request headers have completed sending. |send_status| must be // MORE_DATA_TO_SEND for bidirectional streams; for request/response streams, // it must be MORE_DATA_TO_SEND if the request has data to upload, or // NO_MORE_DATA_TO_SEND if not. int SendRequestHeaders(spdy::Http2HeaderBlock request_headers, SpdySendStatus send_status); // Sends a DATA frame. The delegate will be notified via // OnDataSent() when the send is complete. |send_status| must be // MORE_DATA_TO_SEND for bidirectional streams; for request/response // streams, it must be MORE_DATA_TO_SEND if there is more data to // upload, or NO_MORE_DATA_TO_SEND if not. // Must not be called until Delegate::OnHeadersSent() is called. void SendData(IOBuffer* data, int length, SpdySendStatus send_status); // Fills SSL info in |ssl_info| and returns true when SSL is in use. bool GetSSLInfo(SSLInfo* ssl_info) const; // Returns true if ALPN was negotiated for the underlying socket. bool WasAlpnNegotiated() const; // Returns the protocol negotiated via ALPN for the underlying socket. NextProto GetNegotiatedProtocol() const; // If the stream is stalled on sending data, but the session is not // stalled on sending data and |send_window_size_| is positive, then // set |send_stalled_by_flow_control_| to false and unstall the data // sending. Called by the session or by the stream itself. Must be // called only when the stream is still open. enum ShouldRequeueStream { Requeue, DoNotRequeue }; ShouldRequeueStream PossiblyResumeIfSendStalled(); // Returns whether or not this stream is closed. Note that the only // time a stream is closed and not deleted is in its delegate's // OnClose() method. bool IsClosed() const; // Returns whether the streams local endpoint is closed. // The remote endpoint may still be active. bool IsLocallyClosed() const; // Returns whether this stream is IDLE: request and response headers // have neither been sent nor receieved. bool IsIdle() const; // Returns whether or not this stream is fully open: that request and // response headers are complete, and it is not in a half-closed state. bool IsOpen() const; // Returns whether the stream is reserved by remote endpoint: server has sent // intended request headers for a pushed stream, but haven't started response // yet. bool IsReservedRemote() const; void AddRawReceivedBytes(size_t received_bytes); void AddRawSentBytes(size_t sent_bytes); int64_t raw_received_bytes() const { return raw_received_bytes_; } int64_t raw_sent_bytes() const { return raw_sent_bytes_; } int recv_bytes() const { return recv_bytes_; } // TODO(https://crbug.com/1426477): Remove. bool ShouldRetryRSTPushStream() const; bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const; const spdy::Http2HeaderBlock& request_headers() const { return request_headers_; } const spdy::Http2HeaderBlock& response_headers() const { return response_headers_; } const NetworkTrafficAnnotationTag traffic_annotation() const { return traffic_annotation_; } bool detect_broken_connection() const { return detect_broken_connection_; } private: friend class test::SpdyStreamTest; class HeadersBufferProducer; // SpdyStream states and transitions are modeled // on the HTTP/2 stream state machine. All states and transitions // are modeled, with the exceptions of RESERVED_LOCAL (the client // cannot initate push streams), and the transition to OPEN due to // a remote HEADERS (the client can only initate streams). enum State { STATE_IDLE, STATE_OPEN, STATE_HALF_CLOSED_LOCAL_UNCLAIMED, STATE_HALF_CLOSED_LOCAL, STATE_HALF_CLOSED_REMOTE, STATE_RESERVED_REMOTE, STATE_CLOSED, }; // Per RFC 7540 Section 8.1, an HTTP response consists of: // * zero or more header blocks with informational (1xx) HTTP status, // * one header block, // * zero or more DATA frames, // * zero or one header block ("trailers"). // Each header block must have a ":status" header field. SpdyStream enforces // these requirements, and resets the stream if they are not met. enum ResponseState { READY_FOR_HEADERS, READY_FOR_DATA_OR_TRAILERS, TRAILERS_RECEIVED }; // When a server-push stream is claimed by SetDelegate(), this function is // posted on the current MessageLoop to replay everything the server has sent. // From the perspective of SpdyStream's state machine, headers, data, and // FIN states received prior to the delegate being attached have not yet been // read. While buffered by |pending_recv_data_| it's not until // PushedStreamReplay() is invoked that reads are considered // to have occurred, driving the state machine forward. // TODO(https://crbug.com/1426477): Remove. void PushedStreamReplay(); // Produces the HEADERS frame for the stream. The stream must // already be activated. std::unique_ptr ProduceHeadersFrame(); // Queues the send for next frame of the remaining data in // |pending_send_data_|. Must be called only when // |pending_send_data_| is set. void QueueNextDataFrame(); void OnEarlyHintsReceived(const spdy::Http2HeaderBlock& response_headers, base::TimeTicks recv_first_byte_time); // Saves the given headers into |response_headers_| and calls // OnHeadersReceived() on the delegate if attached. void SaveResponseHeaders(const spdy::Http2HeaderBlock& response_headers, int status); static std::string DescribeState(State state); const SpdyStreamType type_; spdy::SpdyStreamId stream_id_ = 0; const GURL url_; RequestPriority priority_; bool send_stalled_by_flow_control_ = false; // Current send window size. int32_t send_window_size_; // Maximum receive window size. Each time a WINDOW_UPDATE is sent, it // restores the receive window size to this value. int32_t max_recv_window_size_; // Sum of |session_unacked_recv_window_bytes_| and current receive window // size. // TODO(bnc): Rename or change semantics so that |window_size_| is actual // window size. int32_t recv_window_size_; // When bytes are consumed, SpdyIOBuffer destructor calls back to SpdySession, // and this member keeps count of them until the corresponding WINDOW_UPDATEs // are sent. int32_t unacked_recv_window_bytes_ = 0; // Time of the last WINDOW_UPDATE for the receive window base::TimeTicks last_recv_window_update_; const base::WeakPtr session_; // The transaction should own the delegate. raw_ptr delegate_ = nullptr; // The headers for the request to send. bool request_headers_valid_ = false; spdy::Http2HeaderBlock request_headers_; // Data waiting to be sent, and the close state of the local endpoint // after the data is fully written. scoped_refptr pending_send_data_; SpdySendStatus pending_send_status_ = MORE_DATA_TO_SEND; // Data waiting to be received, and the close state of the remote endpoint // after the data is fully read. Specifically, data received before the // delegate is attached must be buffered and later replayed. A remote FIN // is represented by a final, zero-length buffer. std::vector> pending_recv_data_; // The time at which the request was made that resulted in this response. // For cached responses, this time could be "far" in the past. base::Time request_time_; spdy::Http2HeaderBlock response_headers_; ResponseState response_state_ = READY_FOR_HEADERS; base::Time response_time_; State io_state_ = STATE_IDLE; NetLogWithSource net_log_; base::TimeTicks send_time_; // The time at which the first / last byte of the HTTP headers were received. // // These correspond to |LoadTimingInfo::receive_headers_start| and // |LoadTimingInfo::receive_headers_end|. See also comments there. base::TimeTicks recv_first_byte_time_; base::TimeTicks recv_last_byte_time_; // The time at which the first byte of the HTTP headers for the // non-informational response (non-1xx). This corresponds to // |LoadTimingInfo::receive_non_informational_headers_start|. See also // comments there. base::TimeTicks recv_first_byte_time_for_non_informational_response_; // The time at which the first 103 Early Hints response is received. base::TimeTicks first_early_hints_time_; // Number of bytes that have been received on this stream, including frame // overhead and headers. int64_t raw_received_bytes_ = 0; // Number of bytes that have been sent on this stream, including frame // overhead and headers. int64_t raw_sent_bytes_ = 0; // Number of data bytes that have been received on this stream, not including // frame overhead. Note that this does not count headers. int recv_bytes_ = 0; // Guards calls of delegate write handlers ensuring |this| is not destroyed. // TODO(jgraettinger): Consider removing after crbug.com/35511 is tracked // down. bool write_handler_guard_ = false; const NetworkTrafficAnnotationTag traffic_annotation_; // Used by SpdySession to remember if this stream requested broken connection // detection. bool detect_broken_connection_; base::WeakPtrFactory weak_ptr_factory_{this}; }; } // namespace net #endif // NET_SPDY_SPDY_STREAM_H_