blob: aeb370e4fddf5a3ab5dc24c3dbfcfdb1a871ffbd [file] [log] [blame] [edit]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/spdy/spdy_stream.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/stringprintf.h"
#include "base/values.h"
#include "net/spdy/spdy_http_utils.h"
#include "net/spdy/spdy_session.h"
namespace net {
namespace {
Value* NetLogSpdyStreamErrorCallback(SpdyStreamId stream_id,
int status,
const std::string* description,
NetLog::LogLevel /* log_level */) {
DictionaryValue* dict = new DictionaryValue();
dict->SetInteger("stream_id", static_cast<int>(stream_id));
dict->SetInteger("status", status);
dict->SetString("description", *description);
return dict;
}
Value* NetLogSpdyStreamWindowUpdateCallback(SpdyStreamId stream_id,
int32 delta,
int32 window_size,
NetLog::LogLevel /* log_level */) {
DictionaryValue* dict = new DictionaryValue();
dict->SetInteger("stream_id", stream_id);
dict->SetInteger("delta", delta);
dict->SetInteger("window_size", window_size);
return dict;
}
bool ContainsUpperAscii(const std::string& str) {
for (std::string::const_iterator i(str.begin()); i != str.end(); ++i) {
if (*i >= 'A' && *i <= 'Z') {
return true;
}
}
return false;
}
} // namespace
SpdyStream::SpdyStream(SpdySession* session,
bool pushed,
const BoundNetLog& net_log)
: continue_buffering_data_(true),
stream_id_(0),
priority_(HIGHEST),
slot_(0),
stalled_by_flow_control_(false),
send_window_size_(kSpdyStreamInitialWindowSize),
recv_window_size_(kSpdyStreamInitialWindowSize),
unacked_recv_window_bytes_(0),
pushed_(pushed),
response_received_(false),
session_(session),
delegate_(NULL),
request_time_(base::Time::Now()),
response_(new SpdyHeaderBlock),
io_state_(STATE_NONE),
response_status_(OK),
cancelled_(false),
has_upload_data_(false),
net_log_(net_log),
send_bytes_(0),
recv_bytes_(0),
domain_bound_cert_type_(CLIENT_CERT_INVALID_TYPE),
domain_bound_cert_request_handle_(NULL) {
}
class SpdyStream::SpdyStreamIOBufferProducer
: public SpdySession::SpdyIOBufferProducer {
public:
SpdyStreamIOBufferProducer(SpdyStream* stream) : stream_(stream) {}
// SpdyFrameProducer
virtual RequestPriority GetPriority() const OVERRIDE {
return stream_->priority();
}
virtual SpdyIOBuffer* ProduceNextBuffer(SpdySession* session) OVERRIDE {
if (stream_->cancelled())
return NULL;
if (stream_->stream_id() == 0)
SpdySession::SpdyIOBufferProducer::ActivateStream(session, stream_);
frame_.reset(stream_->ProduceNextFrame());
return frame_ == NULL ? NULL :
SpdySession::SpdyIOBufferProducer::CreateIOBuffer(
frame_.get(), GetPriority(), stream_);
}
private:
scoped_refptr<SpdyStream> stream_;
scoped_ptr<SpdyFrame> frame_;
};
void SpdyStream::SetHasWriteAvailable() {
session_->SetStreamHasWriteAvailable(this,
new SpdyStreamIOBufferProducer(this));
}
SpdyFrame* SpdyStream::ProduceNextFrame() {
if (io_state_ == STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE) {
CHECK(request_.get());
CHECK_GT(stream_id_, 0u);
std::string origin = GetUrl().GetOrigin().spec();
DCHECK(origin[origin.length() - 1] == '/');
origin.erase(origin.length() - 1); // Trim trailing slash.
SpdyCredentialControlFrame* frame = session_->CreateCredentialFrame(
origin, domain_bound_cert_type_, domain_bound_private_key_,
domain_bound_cert_, priority_);
return frame;
} else if (io_state_ == STATE_SEND_HEADERS_COMPLETE) {
CHECK(request_.get());
CHECK_GT(stream_id_, 0u);
SpdyControlFlags flags =
has_upload_data_ ? CONTROL_FLAG_NONE : CONTROL_FLAG_FIN;
SpdySynStreamControlFrame* frame = session_->CreateSynStream(
stream_id_, priority_, slot_, flags, *request_);
send_time_ = base::TimeTicks::Now();
return frame;
} else {
CHECK(!cancelled());
// We must need to write stream data.
// Until the headers have been completely sent, we can not be sure
// that our stream_id is correct.
DCHECK_GT(io_state_, STATE_SEND_HEADERS_COMPLETE);
DCHECK_GT(stream_id_, 0u);
DCHECK(!pending_frames_.empty());
PendingFrame frame = pending_frames_.front();
pending_frames_.pop_front();
waiting_completions_.push_back(frame.type);
if (frame.type == TYPE_DATA) {
// Send queued data frame.
return frame.data_frame;
} else {
DCHECK(frame.type == TYPE_HEADERS);
// Create actual HEADERS frame just in time because it depends on
// compression context and should not be reordered after the creation.
SpdyFrame* header_frame = session_->CreateHeadersFrame(
stream_id_, *frame.header_block, SpdyControlFlags());
delete frame.header_block;
return header_frame;
}
}
NOTREACHED();
}
SpdyStream::~SpdyStream() {
UpdateHistograms();
while (!pending_frames_.empty()) {
PendingFrame frame = pending_frames_.back();
pending_frames_.pop_back();
if (frame.type == TYPE_DATA)
delete frame.data_frame;
else
delete frame.header_block;
}
}
void SpdyStream::SetDelegate(Delegate* delegate) {
CHECK(delegate);
delegate_ = delegate;
if (pushed_) {
CHECK(response_received());
MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(&SpdyStream::PushedStreamReplayData, this));
} else {
continue_buffering_data_ = false;
}
}
void SpdyStream::PushedStreamReplayData() {
if (cancelled_ || !delegate_)
return;
continue_buffering_data_ = false;
int rv = delegate_->OnResponseReceived(*response_, response_time_, OK);
if (rv == ERR_INCOMPLETE_SPDY_HEADERS) {
// We don't have complete headers. Assume we're waiting for another
// HEADERS frame. Since we don't have headers, we had better not have
// any pending data frames.
if (pending_buffers_.size() != 0U) {
LogStreamError(ERR_SPDY_PROTOCOL_ERROR,
"HEADERS incomplete headers, but pending data frames.");
session_->CloseStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
}
return;
}
std::vector<scoped_refptr<IOBufferWithSize> > buffers;
buffers.swap(pending_buffers_);
for (size_t i = 0; i < buffers.size(); ++i) {
// It is always possible that a callback to the delegate results in
// the delegate no longer being available.
if (!delegate_)
break;
if (buffers[i]) {
delegate_->OnDataReceived(buffers[i]->data(), buffers[i]->size());
} else {
delegate_->OnDataReceived(NULL, 0);
session_->CloseStream(stream_id_, net::OK);
// Note: |this| may be deleted after calling CloseStream.
DCHECK_EQ(buffers.size() - 1, i);
}
}
}
void SpdyStream::DetachDelegate() {
delegate_ = NULL;
if (!closed())
Cancel();
}
const SpdyHeaderBlock& SpdyStream::spdy_headers() const {
DCHECK(request_ != NULL);
return *request_.get();
}
void SpdyStream::set_spdy_headers(scoped_ptr<SpdyHeaderBlock> headers) {
request_.reset(headers.release());
}
void SpdyStream::set_initial_recv_window_size(int32 window_size) {
session_->set_initial_recv_window_size(window_size);
}
void SpdyStream::PossiblyResumeIfStalled() {
if (send_window_size_ > 0 && stalled_by_flow_control_) {
stalled_by_flow_control_ = false;
io_state_ = STATE_SEND_BODY;
DoLoop(OK);
}
}
void SpdyStream::AdjustSendWindowSize(int32 delta_window_size) {
send_window_size_ += delta_window_size;
PossiblyResumeIfStalled();
}
void SpdyStream::IncreaseSendWindowSize(int32 delta_window_size) {
DCHECK(session_->is_flow_control_enabled());
DCHECK_GE(delta_window_size, 1);
// Ignore late WINDOW_UPDATEs.
if (closed())
return;
int32 new_window_size = send_window_size_ + delta_window_size;
// It's valid for send_window_size_ to become negative (via an incoming
// SETTINGS), in which case incoming WINDOW_UPDATEs will eventually make
// it positive; however, if send_window_size_ is positive and incoming
// WINDOW_UPDATE makes it negative, we have an overflow.
if (send_window_size_ > 0 && new_window_size < 0) {
std::string desc = base::StringPrintf(
"Received WINDOW_UPDATE [delta: %d] for stream %d overflows "
"send_window_size_ [current: %d]", delta_window_size, stream_id_,
send_window_size_);
session_->ResetStream(stream_id_, FLOW_CONTROL_ERROR, desc);
return;
}
send_window_size_ = new_window_size;
net_log_.AddEvent(
NetLog::TYPE_SPDY_STREAM_UPDATE_SEND_WINDOW,
base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
stream_id_, delta_window_size, send_window_size_));
PossiblyResumeIfStalled();
}
void SpdyStream::DecreaseSendWindowSize(int32 delta_window_size) {
// we only call this method when sending a frame, therefore
// |delta_window_size| should be within the valid frame size range.
DCHECK(session_->is_flow_control_enabled());
DCHECK_GE(delta_window_size, 1);
DCHECK_LE(delta_window_size, kMaxSpdyFrameChunkSize);
// |send_window_size_| should have been at least |delta_window_size| for
// this call to happen.
DCHECK_GE(send_window_size_, delta_window_size);
send_window_size_ -= delta_window_size;
net_log_.AddEvent(
NetLog::TYPE_SPDY_STREAM_UPDATE_SEND_WINDOW,
base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
stream_id_, -delta_window_size, send_window_size_));
}
void SpdyStream::IncreaseRecvWindowSize(int32 delta_window_size) {
DCHECK_GE(delta_window_size, 1);
// By the time a read is isued, stream may become inactive.
if (!session_->IsStreamActive(stream_id_))
return;
if (!session_->is_flow_control_enabled())
return;
int32 new_window_size = recv_window_size_ + delta_window_size;
if (recv_window_size_ > 0)
DCHECK(new_window_size > 0);
recv_window_size_ = new_window_size;
net_log_.AddEvent(
NetLog::TYPE_SPDY_STREAM_UPDATE_RECV_WINDOW,
base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
stream_id_, delta_window_size, recv_window_size_));
unacked_recv_window_bytes_ += delta_window_size;
if (unacked_recv_window_bytes_ > session_->initial_recv_window_size() / 2) {
session_->SendWindowUpdate(stream_id_, unacked_recv_window_bytes_);
unacked_recv_window_bytes_ = 0;
}
}
void SpdyStream::DecreaseRecvWindowSize(int32 delta_window_size) {
DCHECK_GE(delta_window_size, 1);
if (!session_->is_flow_control_enabled())
return;
recv_window_size_ -= delta_window_size;
net_log_.AddEvent(
NetLog::TYPE_SPDY_STREAM_UPDATE_RECV_WINDOW,
base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
stream_id_, -delta_window_size, recv_window_size_));
// Since we never decrease the initial window size, we should never hit
// a negative |recv_window_size_|, if we do, it's a client side bug, so we use
// PROTOCOL_ERROR for lack of a better error code.
if (recv_window_size_ < 0) {
session_->ResetStream(stream_id_, PROTOCOL_ERROR,
"Negative recv window size");
NOTREACHED();
}
}
int SpdyStream::GetPeerAddress(IPEndPoint* address) const {
return session_->GetPeerAddress(address);
}
int SpdyStream::GetLocalAddress(IPEndPoint* address) const {
return session_->GetLocalAddress(address);
}
bool SpdyStream::WasEverUsed() const {
return session_->WasEverUsed();
}
base::Time SpdyStream::GetRequestTime() const {
return request_time_;
}
void SpdyStream::SetRequestTime(base::Time t) {
request_time_ = t;
}
int SpdyStream::OnResponseReceived(const SpdyHeaderBlock& response) {
int rv = OK;
metrics_.StartStream();
DCHECK(response_->empty());
*response_ = response; // TODO(ukai): avoid copy.
recv_first_byte_time_ = base::TimeTicks::Now();
response_time_ = base::Time::Now();
// If we receive a response before we are in STATE_WAITING_FOR_RESPONSE, then
// the server has sent the SYN_REPLY too early.
if (!pushed_ && io_state_ != STATE_WAITING_FOR_RESPONSE)
return ERR_SPDY_PROTOCOL_ERROR;
if (pushed_)
CHECK(io_state_ == STATE_NONE);
io_state_ = STATE_OPEN;
// Append all the headers into the response header block.
for (SpdyHeaderBlock::const_iterator it = response.begin();
it != response.end(); ++it) {
// Disallow uppercase headers.
if (ContainsUpperAscii(it->first)) {
session_->ResetStream(stream_id_, PROTOCOL_ERROR,
"Upper case characters in header: " + it->first);
response_status_ = ERR_SPDY_PROTOCOL_ERROR;
return ERR_SPDY_PROTOCOL_ERROR;
}
}
if ((*response_).find("transfer-encoding") != (*response_).end()) {
session_->ResetStream(stream_id_, PROTOCOL_ERROR,
"Received transfer-encoding header");
return ERR_SPDY_PROTOCOL_ERROR;
}
if (delegate_)
rv = delegate_->OnResponseReceived(*response_, response_time_, rv);
// If delegate_ is not yet attached, we'll call OnResponseReceived after the
// delegate gets attached to the stream.
return rv;
}
int SpdyStream::OnHeaders(const SpdyHeaderBlock& headers) {
DCHECK(!response_->empty());
// Append all the headers into the response header block.
for (SpdyHeaderBlock::const_iterator it = headers.begin();
it != headers.end(); ++it) {
// Disallow duplicate headers. This is just to be conservative.
if ((*response_).find(it->first) != (*response_).end()) {
LogStreamError(ERR_SPDY_PROTOCOL_ERROR, "HEADERS duplicate header");
response_status_ = ERR_SPDY_PROTOCOL_ERROR;
return ERR_SPDY_PROTOCOL_ERROR;
}
// Disallow uppercase headers.
if (ContainsUpperAscii(it->first)) {
session_->ResetStream(stream_id_, PROTOCOL_ERROR,
"Upper case characters in header: " + it->first);
response_status_ = ERR_SPDY_PROTOCOL_ERROR;
return ERR_SPDY_PROTOCOL_ERROR;
}
(*response_)[it->first] = it->second;
}
if ((*response_).find("transfer-encoding") != (*response_).end()) {
session_->ResetStream(stream_id_, PROTOCOL_ERROR,
"Received transfer-encoding header");
return ERR_SPDY_PROTOCOL_ERROR;
}
int rv = OK;
if (delegate_) {
rv = delegate_->OnResponseReceived(*response_, response_time_, rv);
// ERR_INCOMPLETE_SPDY_HEADERS means that we are waiting for more
// headers before the response header block is complete.
if (rv == ERR_INCOMPLETE_SPDY_HEADERS)
rv = OK;
}
return rv;
}
void SpdyStream::OnDataReceived(const char* data, int length) {
DCHECK_GE(length, 0);
// If we don't have a response, then the SYN_REPLY did not come through.
// We cannot pass data up to the caller unless the reply headers have been
// received.
if (!response_received()) {
LogStreamError(ERR_SYN_REPLY_NOT_RECEIVED, "Didn't receive a response.");
session_->CloseStream(stream_id_, ERR_SYN_REPLY_NOT_RECEIVED);
return;
}
if (!delegate_ || continue_buffering_data_) {
// It should be valid for this to happen in the server push case.
// We'll return received data when delegate gets attached to the stream.
if (length > 0) {
IOBufferWithSize* buf = new IOBufferWithSize(length);
memcpy(buf->data(), data, length);
pending_buffers_.push_back(make_scoped_refptr(buf));
} else {
pending_buffers_.push_back(NULL);
metrics_.StopStream();
// Note: we leave the stream open in the session until the stream
// is claimed.
}
return;
}
CHECK(!closed());
// A zero-length read means that the stream is being closed.
if (!length) {
metrics_.StopStream();
session_->CloseStream(stream_id_, net::OK);
// Note: |this| may be deleted after calling CloseStream.
return;
}
DecreaseRecvWindowSize(length);
// Track our bandwidth.
metrics_.RecordBytes(length);
recv_bytes_ += length;
recv_last_byte_time_ = base::TimeTicks::Now();
if (!delegate_) {
// It should be valid for this to happen in the server push case.
// We'll return received data when delegate gets attached to the stream.
IOBufferWithSize* buf = new IOBufferWithSize(length);
memcpy(buf->data(), data, length);
pending_buffers_.push_back(make_scoped_refptr(buf));
return;
}
if (delegate_->OnDataReceived(data, length) != net::OK) {
// |delegate_| rejected the data.
LogStreamError(ERR_SPDY_PROTOCOL_ERROR, "Delegate rejected the data");
session_->CloseStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
return;
}
}
// This function is only called when an entire frame is written.
void SpdyStream::OnWriteComplete(int bytes) {
DCHECK_LE(0, bytes);
send_bytes_ += bytes;
if (cancelled() || closed())
return;
DoLoop(bytes);
}
int SpdyStream::GetProtocolVersion() const {
return session_->GetProtocolVersion();
}
void SpdyStream::LogStreamError(int status, const std::string& description) {
net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ERROR,
base::Bind(&NetLogSpdyStreamErrorCallback,
stream_id_, status, &description));
}
void SpdyStream::OnClose(int status) {
io_state_ = STATE_DONE;
response_status_ = status;
Delegate* delegate = delegate_;
delegate_ = NULL;
if (delegate)
delegate->OnClose(status);
}
void SpdyStream::Cancel() {
if (cancelled())
return;
cancelled_ = true;
if (session_->IsStreamActive(stream_id_))
session_->ResetStream(stream_id_, CANCEL, "");
else if (stream_id_ == 0)
session_->CloseCreatedStream(this, CANCEL);
}
void SpdyStream::Close() {
if (stream_id_ != 0)
session_->CloseStream(stream_id_, net::OK);
else
session_->CloseCreatedStream(this, OK);
}
int SpdyStream::SendRequest(bool has_upload_data) {
// Pushed streams do not send any data, and should always be in STATE_OPEN or
// STATE_DONE. However, we still want to return IO_PENDING to mimic non-push
// behavior.
has_upload_data_ = has_upload_data;
if (pushed_) {
send_time_ = base::TimeTicks::Now();
DCHECK(!has_upload_data_);
DCHECK(response_received());
return ERR_IO_PENDING;
}
CHECK_EQ(STATE_NONE, io_state_);
io_state_ = STATE_GET_DOMAIN_BOUND_CERT;
return DoLoop(OK);
}
int SpdyStream::WriteHeaders(SpdyHeaderBlock* headers) {
// Until the first headers by SYN_STREAM have been completely sent, we can
// not be sure that our stream_id is correct.
DCHECK_GT(io_state_, STATE_SEND_HEADERS_COMPLETE);
CHECK_GT(stream_id_, 0u);
PendingFrame frame;
frame.type = TYPE_HEADERS;
frame.header_block = headers;
pending_frames_.push_back(frame);
SetHasWriteAvailable();
return ERR_IO_PENDING;
}
int SpdyStream::WriteStreamData(IOBuffer* data,
int length,
SpdyDataFlags flags) {
// Until the headers have been completely sent, we can not be sure
// that our stream_id is correct.
DCHECK_GT(io_state_, STATE_SEND_HEADERS_COMPLETE);
CHECK_GT(stream_id_, 0u);
SpdyDataFrame* data_frame = session_->CreateDataFrame(
stream_id_, data, length, flags);
if (!data_frame)
return ERR_IO_PENDING;
PendingFrame frame;
frame.type = TYPE_DATA;
frame.data_frame = data_frame;
pending_frames_.push_back(frame);
SetHasWriteAvailable();
return ERR_IO_PENDING;
}
bool SpdyStream::GetSSLInfo(SSLInfo* ssl_info,
bool* was_npn_negotiated,
NextProto* protocol_negotiated) {
return session_->GetSSLInfo(
ssl_info, was_npn_negotiated, protocol_negotiated);
}
bool SpdyStream::GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) {
return session_->GetSSLCertRequestInfo(cert_request_info);
}
bool SpdyStream::HasUrl() const {
if (pushed_)
return response_received();
return request_.get() != NULL;
}
GURL SpdyStream::GetUrl() const {
DCHECK(HasUrl());
const SpdyHeaderBlock& headers = (pushed_) ? *response_ : *request_;
return GetUrlFromHeaderBlock(headers, GetProtocolVersion(), pushed_);
}
void SpdyStream::OnGetDomainBoundCertComplete(int result) {
DCHECK_EQ(STATE_GET_DOMAIN_BOUND_CERT_COMPLETE, io_state_);
DoLoop(result);
}
int SpdyStream::DoLoop(int result) {
do {
State state = io_state_;
io_state_ = STATE_NONE;
switch (state) {
// State machine 1: Send headers and body.
case STATE_GET_DOMAIN_BOUND_CERT:
CHECK_EQ(OK, result);
result = DoGetDomainBoundCert();
break;
case STATE_GET_DOMAIN_BOUND_CERT_COMPLETE:
result = DoGetDomainBoundCertComplete(result);
break;
case STATE_SEND_DOMAIN_BOUND_CERT:
CHECK_EQ(OK, result);
result = DoSendDomainBoundCert();
break;
case STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE:
result = DoSendDomainBoundCertComplete(result);
break;
case STATE_SEND_HEADERS:
CHECK_EQ(OK, result);
result = DoSendHeaders();
break;
case STATE_SEND_HEADERS_COMPLETE:
result = DoSendHeadersComplete(result);
break;
case STATE_SEND_BODY:
CHECK_EQ(OK, result);
result = DoSendBody();
break;
case STATE_SEND_BODY_COMPLETE:
result = DoSendBodyComplete(result);
break;
// This is an intermediary waiting state. This state is reached when all
// data has been sent, but no data has been received.
case STATE_WAITING_FOR_RESPONSE:
io_state_ = STATE_WAITING_FOR_RESPONSE;
result = ERR_IO_PENDING;
break;
// State machine 2: connection is established.
// In STATE_OPEN, OnResponseReceived has already been called.
// OnDataReceived, OnClose and OnWriteCompelte can be called.
// Only OnWriteComplete calls DoLoop(().
//
// For HTTP streams, no data is sent from the client while in the OPEN
// state, so OnWriteComplete is never called here. The HTTP body is
// handled in the OnDataReceived callback, which does not call into
// DoLoop.
//
// For WebSocket streams, which are bi-directional, we'll send and
// receive data once the connection is established. Received data is
// handled in OnDataReceived. Sent data is handled in OnWriteComplete,
// which calls DoOpen().
case STATE_OPEN:
result = DoOpen(result);
break;
case STATE_DONE:
DCHECK(result != ERR_IO_PENDING);
break;
default:
NOTREACHED() << io_state_;
break;
}
} while (result != ERR_IO_PENDING && io_state_ != STATE_NONE &&
io_state_ != STATE_OPEN);
return result;
}
int SpdyStream::DoGetDomainBoundCert() {
CHECK(request_.get());
if (!session_->NeedsCredentials()) {
// Proceed directly to sending headers
io_state_ = STATE_SEND_HEADERS;
return OK;
}
slot_ = session_->credential_state()->FindCredentialSlot(GetUrl());
if (slot_ != SpdyCredentialState::kNoEntry) {
// Proceed directly to sending headers
io_state_ = STATE_SEND_HEADERS;
return OK;
}
io_state_ = STATE_GET_DOMAIN_BOUND_CERT_COMPLETE;
ServerBoundCertService* sbc_service = session_->GetServerBoundCertService();
DCHECK(sbc_service != NULL);
std::vector<uint8> requested_cert_types;
requested_cert_types.push_back(CLIENT_CERT_ECDSA_SIGN);
int rv = sbc_service->GetDomainBoundCert(
GetUrl().GetOrigin().spec(), requested_cert_types,
&domain_bound_cert_type_, &domain_bound_private_key_, &domain_bound_cert_,
base::Bind(&SpdyStream::OnGetDomainBoundCertComplete,
base::Unretained(this)),
&domain_bound_cert_request_handle_);
return rv;
}
int SpdyStream::DoGetDomainBoundCertComplete(int result) {
if (result != OK)
return result;
io_state_ = STATE_SEND_DOMAIN_BOUND_CERT;
slot_ = session_->credential_state()->SetHasCredential(GetUrl());
return OK;
}
int SpdyStream::DoSendDomainBoundCert() {
io_state_ = STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE;
CHECK(request_.get());
SetHasWriteAvailable();
return ERR_IO_PENDING;
}
int SpdyStream::DoSendDomainBoundCertComplete(int result) {
if (result < 0)
return result;
io_state_ = STATE_SEND_HEADERS;
return OK;
}
int SpdyStream::DoSendHeaders() {
CHECK(!cancelled_);
SetHasWriteAvailable();
io_state_ = STATE_SEND_HEADERS_COMPLETE;
return ERR_IO_PENDING;
}
int SpdyStream::DoSendHeadersComplete(int result) {
if (result < 0)
return result;
CHECK_GT(result, 0);
if (!delegate_)
return ERR_UNEXPECTED;
// There is no body, skip that state.
if (delegate_->OnSendHeadersComplete(result)) {
io_state_ = STATE_WAITING_FOR_RESPONSE;
return OK;
}
io_state_ = STATE_SEND_BODY;
return OK;
}
// DoSendBody is called to send the optional body for the request. This call
// will also be called as each write of a chunk of the body completes.
int SpdyStream::DoSendBody() {
// If we're already in the STATE_SEND_BODY state, then we've already
// sent a portion of the body. In that case, we need to first consume
// the bytes written in the body stream. Note that the bytes written is
// the number of bytes in the frame that were written, only consume the
// data portion, of course.
io_state_ = STATE_SEND_BODY_COMPLETE;
if (!delegate_)
return ERR_UNEXPECTED;
return delegate_->OnSendBody();
}
int SpdyStream::DoSendBodyComplete(int result) {
if (result < 0)
return result;
if (!delegate_)
return ERR_UNEXPECTED;
bool eof = false;
result = delegate_->OnSendBodyComplete(result, &eof);
if (!eof)
io_state_ = STATE_SEND_BODY;
else
io_state_ = STATE_WAITING_FOR_RESPONSE;
return result;
}
int SpdyStream::DoOpen(int result) {
if (delegate_) {
FrameType type = waiting_completions_.front();
waiting_completions_.pop_front();
if (type == TYPE_DATA) {
delegate_->OnDataSent(result);
} else {
DCHECK(type == TYPE_HEADERS);
delegate_->OnHeadersSent();
}
}
io_state_ = STATE_OPEN;
return result;
}
void SpdyStream::UpdateHistograms() {
// We need all timers to be filled in, otherwise metrics can be bogus.
if (send_time_.is_null() || recv_first_byte_time_.is_null() ||
recv_last_byte_time_.is_null())
return;
UMA_HISTOGRAM_TIMES("Net.SpdyStreamTimeToFirstByte",
recv_first_byte_time_ - send_time_);
UMA_HISTOGRAM_TIMES("Net.SpdyStreamDownloadTime",
recv_last_byte_time_ - recv_first_byte_time_);
UMA_HISTOGRAM_TIMES("Net.SpdyStreamTime",
recv_last_byte_time_ - send_time_);
UMA_HISTOGRAM_COUNTS("Net.SpdySendBytes", send_bytes_);
UMA_HISTOGRAM_COUNTS("Net.SpdyRecvBytes", recv_bytes_);
}
} // namespace net