| // 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 <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #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/ssl/ssl_client_cert_type.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. |
| 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. |
| 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<SpdyBuffer> 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<SpdySession>& 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. |
| 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<SpdyBuffer> 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<SpdyStream> 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_; } |
| 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. |
| void PushedStreamReplay(); |
| |
| // Produces the HEADERS frame for the stream. The stream must |
| // already be activated. |
| std::unique_ptr<spdy::SpdySerializedFrame> 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<SpdySession> session_; |
| |
| // The transaction should own the delegate. |
| raw_ptr<SpdyStream::Delegate> 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<DrainableIOBuffer> 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<std::unique_ptr<SpdyBuffer>> 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<SpdyStream> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace net |
| |
| #endif // NET_SPDY_SPDY_STREAM_H_ |