blob: 19eb31e2c9bb9667b5130a2af84856d90686d56f [file] [log] [blame]
// 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_