| // 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_session.h" | 
 |  | 
 | #include <map> | 
 |  | 
 | #include "base/basictypes.h" | 
 | #include "base/bind.h" | 
 | #include "base/compiler_specific.h" | 
 | #include "base/logging.h" | 
 | #include "base/message_loop.h" | 
 | #include "base/metrics/field_trial.h" | 
 | #include "base/metrics/histogram.h" | 
 | #include "base/metrics/stats_counters.h" | 
 | #include "base/stl_util.h" | 
 | #include "base/string_number_conversions.h" | 
 | #include "base/string_util.h" | 
 | #include "base/stringprintf.h" | 
 | #include "base/time.h" | 
 | #include "base/utf_string_conversions.h" | 
 | #include "base/values.h" | 
 | #include "crypto/ec_private_key.h" | 
 | #include "crypto/ec_signature_creator.h" | 
 | #include "net/base/asn1_util.h" | 
 | #include "net/base/connection_type_histograms.h" | 
 | #include "net/base/net_log.h" | 
 | #include "net/base/net_util.h" | 
 | #include "net/base/server_bound_cert_service.h" | 
 | #include "net/http/http_network_session.h" | 
 | #include "net/http/http_server_properties.h" | 
 | #include "net/spdy/spdy_credential_builder.h" | 
 | #include "net/spdy/spdy_frame_builder.h" | 
 | #include "net/spdy/spdy_http_utils.h" | 
 | #include "net/spdy/spdy_protocol.h" | 
 | #include "net/spdy/spdy_session_pool.h" | 
 | #include "net/spdy/spdy_stream.h" | 
 |  | 
 | namespace net { | 
 |  | 
 | namespace { | 
 |  | 
 | const int kReadBufferSize = 8 * 1024; | 
 | const int kDefaultConnectionAtRiskOfLossSeconds = 10; | 
 | const int kHungIntervalSeconds = 10; | 
 |  | 
 | // Minimum seconds that unclaimed pushed streams will be kept in memory. | 
 | const int kMinPushedStreamLifetimeSeconds = 300; | 
 |  | 
 | Value* NetLogSpdySynCallback(const SpdyHeaderBlock* headers, | 
 |                              bool fin, | 
 |                              bool unidirectional, | 
 |                              SpdyStreamId stream_id, | 
 |                              SpdyStreamId associated_stream, | 
 |                              NetLog::LogLevel /* log_level */) { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |   ListValue* headers_list = new ListValue(); | 
 |   for (SpdyHeaderBlock::const_iterator it = headers->begin(); | 
 |        it != headers->end(); ++it) { | 
 |     headers_list->Append(new StringValue(base::StringPrintf( | 
 |         "%s: %s", it->first.c_str(), it->second.c_str()))); | 
 |   } | 
 |   dict->SetBoolean("fin", fin); | 
 |   dict->SetBoolean("unidirectional", unidirectional); | 
 |   dict->Set("headers", headers_list); | 
 |   dict->SetInteger("stream_id", stream_id); | 
 |   if (associated_stream) | 
 |     dict->SetInteger("associated_stream", associated_stream); | 
 |   return dict; | 
 | } | 
 |  | 
 | Value* NetLogSpdyCredentialCallback(size_t slot, | 
 |                                     const std::string* origin, | 
 |                                     NetLog::LogLevel /* log_level */) { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |   dict->SetInteger("slot", slot); | 
 |   dict->SetString("origin", *origin); | 
 |   return dict; | 
 | } | 
 |  | 
 | Value* NetLogSpdySessionCloseCallback(int net_error, | 
 |                                       const std::string* description, | 
 |                                       NetLog::LogLevel /* log_level */) { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |   dict->SetInteger("net_error", net_error); | 
 |   dict->SetString("description", *description); | 
 |   return dict; | 
 | } | 
 |  | 
 | Value* NetLogSpdySessionCallback(const HostPortProxyPair* host_pair, | 
 |                                  NetLog::LogLevel /* log_level */) { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |   dict->SetString("host", host_pair->first.ToString()); | 
 |   dict->SetString("proxy", host_pair->second.ToPacString()); | 
 |   return dict; | 
 | } | 
 |  | 
 | Value* NetLogSpdySettingCallback(SpdySettingsIds id, | 
 |                                  SpdySettingsFlags flags, | 
 |                                  uint32 value, | 
 |                                  NetLog::LogLevel /* log_level */) { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |   dict->SetInteger("id", id); | 
 |   dict->SetInteger("flags", flags); | 
 |   dict->SetInteger("value", value); | 
 |   return dict; | 
 | } | 
 |  | 
 | Value* NetLogSpdySettingsCallback(const SettingsMap* settings, | 
 |                                   NetLog::LogLevel /* log_level */) { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |   ListValue* settings_list = new ListValue(); | 
 |   for (SettingsMap::const_iterator it = settings->begin(); | 
 |        it != settings->end(); ++it) { | 
 |     const SpdySettingsIds id = it->first; | 
 |     const SpdySettingsFlags flags = it->second.first; | 
 |     const uint32 value = it->second.second; | 
 |     settings_list->Append(new StringValue( | 
 |         base::StringPrintf("[id:%u flags:%u value:%u]", id, flags, value))); | 
 |   } | 
 |   dict->Set("settings", settings_list); | 
 |   return dict; | 
 | } | 
 |  | 
 | Value* NetLogSpdyWindowUpdateCallback(SpdyStreamId stream_id, | 
 |                                       int32 delta, | 
 |                                       NetLog::LogLevel /* log_level */) { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |   dict->SetInteger("stream_id", static_cast<int>(stream_id)); | 
 |   dict->SetInteger("delta", delta); | 
 |   return dict; | 
 | } | 
 |  | 
 | Value* NetLogSpdyDataCallback(SpdyStreamId stream_id, | 
 |                               int size, | 
 |                               SpdyDataFlags flags, | 
 |                               NetLog::LogLevel /* log_level */) { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |   dict->SetInteger("stream_id", static_cast<int>(stream_id)); | 
 |   dict->SetInteger("size", size); | 
 |   dict->SetInteger("flags", static_cast<int>(flags)); | 
 |   return dict; | 
 | } | 
 |  | 
 | Value* NetLogSpdyRstCallback(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* NetLogSpdyPingCallback(uint32 unique_id, | 
 |                               const char* type, | 
 |                               NetLog::LogLevel /* log_level */) { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |   dict->SetInteger("unique_id", unique_id); | 
 |   dict->SetString("type", type); | 
 |   return dict; | 
 | } | 
 |  | 
 | Value* NetLogSpdyGoAwayCallback(SpdyStreamId last_stream_id, | 
 |                                 int active_streams, | 
 |                                 int unclaimed_streams, | 
 |                                 SpdyGoAwayStatus status, | 
 |                                 NetLog::LogLevel /* log_level */) { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |   dict->SetInteger("last_accepted_stream_id", | 
 |                    static_cast<int>(last_stream_id)); | 
 |   dict->SetInteger("active_streams", active_streams); | 
 |   dict->SetInteger("unclaimed_streams", unclaimed_streams); | 
 |   dict->SetInteger("status", static_cast<int>(status)); | 
 |   return dict; | 
 | } | 
 |  | 
 | // Maximum number of concurrent streams we will create, unless the server | 
 | // sends a SETTINGS frame with a different value. | 
 | const size_t kInitialMaxConcurrentStreams = 100; | 
 | // The maximum number of concurrent streams we will ever create.  Even if | 
 | // the server permits more, we will never exceed this limit. | 
 | const size_t kMaxConcurrentStreamLimit = 256; | 
 | const size_t kDefaultInitialRecvWindowSize = 10 * 1024 * 1024;  // 10MB | 
 |  | 
 | }  // namespace | 
 |  | 
 | // static | 
 | void SpdySession::SpdyIOBufferProducer::ActivateStream( | 
 |     SpdySession* spdy_session, | 
 |     SpdyStream* spdy_stream) { | 
 |   spdy_session->ActivateStream(spdy_stream); | 
 | } | 
 |  | 
 | // static | 
 | SpdyIOBuffer* SpdySession::SpdyIOBufferProducer::CreateIOBuffer( | 
 |     SpdyFrame* frame, | 
 |     RequestPriority priority, | 
 |     SpdyStream* stream) { | 
 |   size_t size = frame->length() + SpdyFrame::kHeaderSize; | 
 |   DCHECK_GT(size, 0u); | 
 |  | 
 |   // TODO(mbelshe): We have too much copying of data here. | 
 |   IOBufferWithSize* buffer = new IOBufferWithSize(size); | 
 |   memcpy(buffer->data(), frame->data(), size); | 
 |  | 
 |   return new SpdyIOBuffer(buffer, size, priority, stream); | 
 | } | 
 |  | 
 | SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair, | 
 |                          SpdySessionPool* spdy_session_pool, | 
 |                          HttpServerProperties* http_server_properties, | 
 |                          bool verify_domain_authentication, | 
 |                          bool enable_sending_initial_settings, | 
 |                          bool enable_credential_frames, | 
 |                          bool enable_compression, | 
 |                          bool enable_ping_based_connection_checking, | 
 |                          NextProto default_protocol, | 
 |                          size_t initial_recv_window_size, | 
 |                          size_t initial_max_concurrent_streams, | 
 |                          size_t max_concurrent_streams_limit, | 
 |                          TimeFunc time_func, | 
 |                          const HostPortPair& trusted_spdy_proxy, | 
 |                          NetLog* net_log) | 
 |     : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), | 
 |       host_port_proxy_pair_(host_port_proxy_pair), | 
 |       spdy_session_pool_(spdy_session_pool), | 
 |       http_server_properties_(http_server_properties), | 
 |       connection_(new ClientSocketHandle), | 
 |       read_buffer_(new IOBuffer(kReadBufferSize)), | 
 |       read_pending_(false), | 
 |       stream_hi_water_mark_(1),  // Always start at 1 for the first stream id. | 
 |       write_pending_(false), | 
 |       delayed_write_pending_(false), | 
 |       is_secure_(false), | 
 |       certificate_error_code_(OK), | 
 |       error_(OK), | 
 |       state_(IDLE), | 
 |       max_concurrent_streams_(initial_max_concurrent_streams == 0 ? | 
 |                               kInitialMaxConcurrentStreams : | 
 |                               initial_max_concurrent_streams), | 
 |       max_concurrent_streams_limit_(max_concurrent_streams_limit == 0 ? | 
 |                                     kMaxConcurrentStreamLimit : | 
 |                                     max_concurrent_streams_limit), | 
 |       streams_initiated_count_(0), | 
 |       streams_pushed_count_(0), | 
 |       streams_pushed_and_claimed_count_(0), | 
 |       streams_abandoned_count_(0), | 
 |       bytes_received_(0), | 
 |       sent_settings_(false), | 
 |       received_settings_(false), | 
 |       stalled_streams_(0), | 
 |       pings_in_flight_(0), | 
 |       next_ping_id_(1), | 
 |       last_activity_time_(base::TimeTicks::Now()), | 
 |       check_ping_status_pending_(false), | 
 |       flow_control_(false), | 
 |       initial_send_window_size_(kSpdyStreamInitialWindowSize), | 
 |       initial_recv_window_size_(initial_recv_window_size == 0 ? | 
 |                                 kDefaultInitialRecvWindowSize : | 
 |                                 initial_recv_window_size), | 
 |       net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SPDY_SESSION)), | 
 |       verify_domain_authentication_(verify_domain_authentication), | 
 |       enable_sending_initial_settings_(enable_sending_initial_settings), | 
 |       enable_credential_frames_(enable_credential_frames), | 
 |       enable_compression_(enable_compression), | 
 |       enable_ping_based_connection_checking_( | 
 |           enable_ping_based_connection_checking), | 
 |       default_protocol_(default_protocol), | 
 |       credential_state_(SpdyCredentialState::kDefaultNumSlots), | 
 |       connection_at_risk_of_loss_time_( | 
 |           base::TimeDelta::FromSeconds(kDefaultConnectionAtRiskOfLossSeconds)), | 
 |       hung_interval_( | 
 |           base::TimeDelta::FromSeconds(kHungIntervalSeconds)), | 
 |       trusted_spdy_proxy_(trusted_spdy_proxy), | 
 |       time_func_(time_func) { | 
 |   DCHECK(HttpStreamFactory::spdy_enabled()); | 
 |   net_log_.BeginEvent( | 
 |       NetLog::TYPE_SPDY_SESSION, | 
 |       base::Bind(&NetLogSpdySessionCallback, &host_port_proxy_pair_)); | 
 |   next_unclaimed_push_stream_sweep_time_ = time_func_() + | 
 |       base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds); | 
 |   // TODO(mbelshe): consider randomization of the stream_hi_water_mark. | 
 | } | 
 |  | 
 | SpdySession::PendingCreateStream::PendingCreateStream( | 
 |     const GURL& url, RequestPriority priority, | 
 |     scoped_refptr<SpdyStream>* spdy_stream, | 
 |     const BoundNetLog& stream_net_log, | 
 |     const CompletionCallback& callback) | 
 |     : url(&url), | 
 |       priority(priority), | 
 |       spdy_stream(spdy_stream), | 
 |       stream_net_log(&stream_net_log), | 
 |       callback(callback) { | 
 | } | 
 |  | 
 | SpdySession::PendingCreateStream::~PendingCreateStream() {} | 
 |  | 
 | SpdySession::CallbackResultPair::CallbackResultPair( | 
 |     const CompletionCallback& callback_in, int result_in) | 
 |     : callback(callback_in), | 
 |       result(result_in) { | 
 | } | 
 |  | 
 | SpdySession::CallbackResultPair::~CallbackResultPair() {} | 
 |  | 
 | SpdySession::~SpdySession() { | 
 |   if (state_ != CLOSED) { | 
 |     state_ = CLOSED; | 
 |  | 
 |     // Cleanup all the streams. | 
 |     CloseAllStreams(net::ERR_ABORTED); | 
 |   } | 
 |  | 
 |   if (connection_->is_initialized()) { | 
 |     // With SPDY we can't recycle sockets. | 
 |     connection_->socket()->Disconnect(); | 
 |   } | 
 |  | 
 |   // Streams should all be gone now. | 
 |   DCHECK_EQ(0u, num_active_streams()); | 
 |   DCHECK_EQ(0u, num_unclaimed_pushed_streams()); | 
 |  | 
 |   DCHECK(pending_callback_map_.empty()); | 
 |  | 
 |   RecordHistograms(); | 
 |  | 
 |   net_log_.EndEvent(NetLog::TYPE_SPDY_SESSION); | 
 | } | 
 |  | 
 | net::Error SpdySession::InitializeWithSocket( | 
 |     ClientSocketHandle* connection, | 
 |     bool is_secure, | 
 |     int certificate_error_code) { | 
 |   base::StatsCounter spdy_sessions("spdy.sessions"); | 
 |   spdy_sessions.Increment(); | 
 |  | 
 |   state_ = CONNECTED; | 
 |   connection_.reset(connection); | 
 |   is_secure_ = is_secure; | 
 |   certificate_error_code_ = certificate_error_code; | 
 |  | 
 |   NextProto protocol = default_protocol_; | 
 |   NextProto protocol_negotiated = connection->socket()->GetNegotiatedProtocol(); | 
 |   if (protocol_negotiated != kProtoUnknown) { | 
 |     protocol = protocol_negotiated; | 
 |   } | 
 |  | 
 |   SSLClientSocket* ssl_socket = GetSSLClientSocket(); | 
 |   if (ssl_socket && ssl_socket->WasChannelIDSent()) { | 
 |     // According to the SPDY spec, the credential associated with the TLS | 
 |     // connection is stored in slot[1]. | 
 |     credential_state_.SetHasCredential(GURL("https://" + | 
 |                                             host_port_pair().ToString())); | 
 |   } | 
 |  | 
 |   DCHECK(protocol >= kProtoSPDY2); | 
 |   DCHECK(protocol <= kProtoSPDY3); | 
 |   int version = (protocol == kProtoSPDY3) ? 3 : 2; | 
 |   flow_control_ = (protocol >= kProtoSPDY3); | 
 |  | 
 |   buffered_spdy_framer_.reset(new BufferedSpdyFramer(version, | 
 |                                                      enable_compression_)); | 
 |   buffered_spdy_framer_->set_visitor(this); | 
 |   SendInitialSettings(); | 
 |   UMA_HISTOGRAM_ENUMERATION("Net.SpdyVersion", protocol, kProtoMaximumVersion); | 
 |  | 
 |   // Write out any data that we might have to send, such as the settings frame. | 
 |   WriteSocketLater(); | 
 |   net::Error error = ReadSocket(); | 
 |   if (error == ERR_IO_PENDING) | 
 |     return OK; | 
 |   return error; | 
 | } | 
 |  | 
 | bool SpdySession::VerifyDomainAuthentication(const std::string& domain) { | 
 |   if (!verify_domain_authentication_) | 
 |     return true; | 
 |  | 
 |   if (state_ != CONNECTED) | 
 |     return false; | 
 |  | 
 |   SSLInfo ssl_info; | 
 |   bool was_npn_negotiated; | 
 |   NextProto protocol_negotiated = kProtoUnknown; | 
 |   if (!GetSSLInfo(&ssl_info, &was_npn_negotiated, &protocol_negotiated)) | 
 |     return true;   // This is not a secure session, so all domains are okay. | 
 |  | 
 |   return !ssl_info.client_cert_sent && | 
 |       (enable_credential_frames_ || !ssl_info.channel_id_sent || | 
 |        ServerBoundCertService::GetDomainForHost(domain) == | 
 |        ServerBoundCertService::GetDomainForHost( | 
 |            host_port_proxy_pair_.first.host())) && | 
 |       ssl_info.cert->VerifyNameMatch(domain); | 
 | } | 
 |  | 
 | void SpdySession::SetStreamHasWriteAvailable(SpdyStream* stream, | 
 |                                              SpdyIOBufferProducer* producer) { | 
 |   write_queue_.push(producer); | 
 |   stream_producers_[producer] = stream; | 
 |   WriteSocketLater(); | 
 | } | 
 |  | 
 | int SpdySession::GetPushStream( | 
 |     const GURL& url, | 
 |     scoped_refptr<SpdyStream>* stream, | 
 |     const BoundNetLog& stream_net_log) { | 
 |   CHECK_NE(state_, CLOSED); | 
 |  | 
 |   *stream = NULL; | 
 |  | 
 |   // Don't allow access to secure push streams over an unauthenticated, but | 
 |   // encrypted SSL socket. | 
 |   if (is_secure_ && certificate_error_code_ != OK && | 
 |       (url.SchemeIs("https") || url.SchemeIs("wss"))) { | 
 |     RecordProtocolErrorHistogram( | 
 |         PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION); | 
 |     CloseSessionOnError( | 
 |         static_cast<net::Error>(certificate_error_code_), | 
 |         true, | 
 |         "Tried to get SPDY stream for secure content over an unauthenticated " | 
 |         "session."); | 
 |     return ERR_SPDY_PROTOCOL_ERROR; | 
 |   } | 
 |  | 
 |   *stream = GetActivePushStream(url.spec()); | 
 |   if (stream->get()) { | 
 |     DCHECK(streams_pushed_and_claimed_count_ < streams_pushed_count_); | 
 |     streams_pushed_and_claimed_count_++; | 
 |     return OK; | 
 |   } | 
 |   return 0; | 
 | } | 
 |  | 
 | int SpdySession::CreateStream( | 
 |     const GURL& url, | 
 |     RequestPriority priority, | 
 |     scoped_refptr<SpdyStream>* spdy_stream, | 
 |     const BoundNetLog& stream_net_log, | 
 |     const CompletionCallback& callback) { | 
 |   if (!max_concurrent_streams_ || | 
 |       (active_streams_.size() + created_streams_.size() < | 
 |        max_concurrent_streams_)) { | 
 |     return CreateStreamImpl(url, priority, spdy_stream, stream_net_log); | 
 |   } | 
 |  | 
 |   stalled_streams_++; | 
 |   net_log().AddEvent(NetLog::TYPE_SPDY_SESSION_STALLED_MAX_STREAMS); | 
 |   pending_create_stream_queues_[priority].push( | 
 |       PendingCreateStream(url, priority, spdy_stream, | 
 |                           stream_net_log, callback)); | 
 |   return ERR_IO_PENDING; | 
 | } | 
 |  | 
 | void SpdySession::ProcessPendingCreateStreams() { | 
 |   while (!max_concurrent_streams_ || | 
 |          (active_streams_.size() + created_streams_.size() < | 
 |           max_concurrent_streams_)) { | 
 |     bool no_pending_create_streams = true; | 
 |     for (int i = NUM_PRIORITIES - 1; i >= MINIMUM_PRIORITY; --i) { | 
 |       if (!pending_create_stream_queues_[i].empty()) { | 
 |         PendingCreateStream pending_create = | 
 |             pending_create_stream_queues_[i].front(); | 
 |         pending_create_stream_queues_[i].pop(); | 
 |         no_pending_create_streams = false; | 
 |         int error = CreateStreamImpl(*pending_create.url, | 
 |                                      pending_create.priority, | 
 |                                      pending_create.spdy_stream, | 
 |                                      *pending_create.stream_net_log); | 
 |         scoped_refptr<SpdyStream>* stream = pending_create.spdy_stream; | 
 |         DCHECK(!ContainsKey(pending_callback_map_, stream)); | 
 |         pending_callback_map_.insert(std::make_pair(stream, | 
 |             CallbackResultPair(pending_create.callback, error))); | 
 |         MessageLoop::current()->PostTask( | 
 |             FROM_HERE, | 
 |             base::Bind(&SpdySession::InvokeUserStreamCreationCallback, | 
 |                        weak_factory_.GetWeakPtr(), stream)); | 
 |         break; | 
 |       } | 
 |     } | 
 |     if (no_pending_create_streams) | 
 |       return;  // there were no streams in any queue | 
 |   } | 
 | } | 
 |  | 
 | void SpdySession::CancelPendingCreateStreams( | 
 |     const scoped_refptr<SpdyStream>* spdy_stream) { | 
 |   PendingCallbackMap::iterator it = pending_callback_map_.find(spdy_stream); | 
 |   if (it != pending_callback_map_.end()) { | 
 |     pending_callback_map_.erase(it); | 
 |     return; | 
 |   } | 
 |  | 
 |   for (int i = 0; i < NUM_PRIORITIES; ++i) { | 
 |     PendingCreateStreamQueue tmp; | 
 |     // Make a copy removing this trans | 
 |     while (!pending_create_stream_queues_[i].empty()) { | 
 |       PendingCreateStream pending_create = | 
 |           pending_create_stream_queues_[i].front(); | 
 |       pending_create_stream_queues_[i].pop(); | 
 |       if (pending_create.spdy_stream != spdy_stream) | 
 |         tmp.push(pending_create); | 
 |     } | 
 |     // Now copy it back | 
 |     while (!tmp.empty()) { | 
 |       pending_create_stream_queues_[i].push(tmp.front()); | 
 |       tmp.pop(); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | int SpdySession::CreateStreamImpl( | 
 |     const GURL& url, | 
 |     RequestPriority priority, | 
 |     scoped_refptr<SpdyStream>* spdy_stream, | 
 |     const BoundNetLog& stream_net_log) { | 
 |   DCHECK_GE(priority, MINIMUM_PRIORITY); | 
 |   DCHECK_LT(priority, NUM_PRIORITIES); | 
 |  | 
 |   // Make sure that we don't try to send https/wss over an unauthenticated, but | 
 |   // encrypted SSL socket. | 
 |   if (is_secure_ && certificate_error_code_ != OK && | 
 |       (url.SchemeIs("https") || url.SchemeIs("wss"))) { | 
 |     RecordProtocolErrorHistogram( | 
 |         PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION); | 
 |     CloseSessionOnError( | 
 |         static_cast<net::Error>(certificate_error_code_), | 
 |         true, | 
 |         "Tried to create SPDY stream for secure content over an " | 
 |         "unauthenticated session."); | 
 |     return ERR_SPDY_PROTOCOL_ERROR; | 
 |   } | 
 |  | 
 |   const std::string& path = url.PathForRequest(); | 
 |  | 
 |   *spdy_stream = new SpdyStream(this, | 
 |                                 false, | 
 |                                 stream_net_log); | 
 |   const scoped_refptr<SpdyStream>& stream = *spdy_stream; | 
 |  | 
 |   stream->set_priority(priority); | 
 |   stream->set_path(path); | 
 |   stream->set_send_window_size(initial_send_window_size_); | 
 |   stream->set_recv_window_size(initial_recv_window_size_); | 
 |   created_streams_.insert(stream); | 
 |  | 
 |   UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyPriorityCount", | 
 |       static_cast<int>(priority), 0, 10, 11); | 
 |  | 
 |   // TODO(mbelshe): Optimize memory allocations | 
 |  | 
 |   return OK; | 
 | } | 
 |  | 
 | bool SpdySession::NeedsCredentials() const { | 
 |   if (!is_secure_) | 
 |     return false; | 
 |   SSLClientSocket* ssl_socket = GetSSLClientSocket(); | 
 |   if (ssl_socket->GetNegotiatedProtocol() < kProtoSPDY3) | 
 |     return false; | 
 |   return ssl_socket->WasChannelIDSent(); | 
 | } | 
 |  | 
 | void SpdySession::AddPooledAlias(const HostPortProxyPair& alias) { | 
 |   pooled_aliases_.insert(alias); | 
 | } | 
 |  | 
 | int SpdySession::GetProtocolVersion() const { | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   return buffered_spdy_framer_->protocol_version(); | 
 | } | 
 |  | 
 | SpdySynStreamControlFrame* SpdySession::CreateSynStream( | 
 |     SpdyStreamId stream_id, | 
 |     RequestPriority priority, | 
 |     uint8 credential_slot, | 
 |     SpdyControlFlags flags, | 
 |     const SpdyHeaderBlock& headers) { | 
 |   CHECK(IsStreamActive(stream_id)); | 
 |   const scoped_refptr<SpdyStream>& stream = active_streams_[stream_id]; | 
 |   CHECK_EQ(stream->stream_id(), stream_id); | 
 |  | 
 |   SendPrefacePingIfNoneInFlight(); | 
 |  | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   scoped_ptr<SpdySynStreamControlFrame> syn_frame( | 
 |       buffered_spdy_framer_->CreateSynStream( | 
 |           stream_id, 0, | 
 |           ConvertRequestPriorityToSpdyPriority(priority, GetProtocolVersion()), | 
 |           credential_slot, flags, true, &headers)); | 
 |  | 
 |   base::StatsCounter spdy_requests("spdy.requests"); | 
 |   spdy_requests.Increment(); | 
 |   streams_initiated_count_++; | 
 |  | 
 |   if (net_log().IsLoggingAllEvents()) { | 
 |     net_log().AddEvent( | 
 |         NetLog::TYPE_SPDY_SESSION_SYN_STREAM, | 
 |         base::Bind(&NetLogSpdySynCallback, &headers, | 
 |                    (flags & CONTROL_FLAG_FIN) != 0, | 
 |                    (flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0, | 
 |                    stream_id, 0)); | 
 |   } | 
 |  | 
 |   return syn_frame.release(); | 
 | } | 
 |  | 
 | SpdyCredentialControlFrame* SpdySession::CreateCredentialFrame( | 
 |     const std::string& origin, | 
 |     SSLClientCertType type, | 
 |     const std::string& key, | 
 |     const std::string& cert, | 
 |     RequestPriority priority) { | 
 |   DCHECK(is_secure_); | 
 |   SSLClientSocket* ssl_socket = GetSSLClientSocket(); | 
 |   DCHECK(ssl_socket); | 
 |   DCHECK(ssl_socket->WasChannelIDSent()); | 
 |  | 
 |   SpdyCredential credential; | 
 |   std::string tls_unique; | 
 |   ssl_socket->GetTLSUniqueChannelBinding(&tls_unique); | 
 |   size_t slot = credential_state_.SetHasCredential(GURL(origin)); | 
 |   int rv = SpdyCredentialBuilder::Build(tls_unique, type, key, cert, slot, | 
 |                                         &credential); | 
 |   DCHECK_EQ(OK, rv); | 
 |   if (rv != OK) | 
 |     return NULL; | 
 |  | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   scoped_ptr<SpdyCredentialControlFrame> credential_frame( | 
 |       buffered_spdy_framer_->CreateCredentialFrame(credential)); | 
 |  | 
 |   if (net_log().IsLoggingAllEvents()) { | 
 |     net_log().AddEvent( | 
 |         NetLog::TYPE_SPDY_SESSION_SEND_CREDENTIAL, | 
 |         base::Bind(&NetLogSpdyCredentialCallback, credential.slot, &origin)); | 
 |   } | 
 |   return credential_frame.release(); | 
 | } | 
 |  | 
 | SpdyHeadersControlFrame* SpdySession::CreateHeadersFrame( | 
 |     SpdyStreamId stream_id, | 
 |     const SpdyHeaderBlock& headers, | 
 |     SpdyControlFlags flags) { | 
 |   // Find our stream | 
 |   CHECK(IsStreamActive(stream_id)); | 
 |   scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; | 
 |   CHECK_EQ(stream->stream_id(), stream_id); | 
 |  | 
 |   // Create a HEADER frame. | 
 |   scoped_ptr<SpdyHeadersControlFrame> frame( | 
 |       buffered_spdy_framer_->CreateHeaders(stream_id, flags, true, &headers)); | 
 |  | 
 |   if (net_log().IsLoggingAllEvents()) { | 
 |     bool fin = flags & CONTROL_FLAG_FIN; | 
 |     net_log().AddEvent( | 
 |         NetLog::TYPE_SPDY_SESSION_SEND_HEADERS, | 
 |         base::Bind(&NetLogSpdySynCallback, | 
 |                    &headers, fin, /*unidirectional=*/false, | 
 |                    stream_id, 0)); | 
 |   } | 
 |   return frame.release(); | 
 | } | 
 |  | 
 | SpdyDataFrame* SpdySession::CreateDataFrame(SpdyStreamId stream_id, | 
 |                                             net::IOBuffer* data, int len, | 
 |                                             SpdyDataFlags flags) { | 
 |   // Find our stream | 
 |   CHECK(IsStreamActive(stream_id)); | 
 |   scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; | 
 |   CHECK_EQ(stream->stream_id(), stream_id); | 
 |  | 
 |   if (len > kMaxSpdyFrameChunkSize) { | 
 |     len = kMaxSpdyFrameChunkSize; | 
 |     flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_FIN); | 
 |   } | 
 |  | 
 |   // Obey send window size of the stream if flow control is enabled. | 
 |   if (flow_control_) { | 
 |     if (stream->send_window_size() <= 0) { | 
 |       // Because we queue frames onto the session, it is possible that | 
 |       // a stream was not flow controlled at the time it attempted the | 
 |       // write, but when we go to fulfill the write, it is now flow | 
 |       // controlled.  This is why we need the session to mark the stream | 
 |       // as stalled - because only the session knows for sure when the | 
 |       // stall occurs. | 
 |       stream->set_stalled_by_flow_control(true); | 
 |       net_log().AddEvent( | 
 |           NetLog::TYPE_SPDY_SESSION_STALLED_ON_SEND_WINDOW, | 
 |           NetLog::IntegerCallback("stream_id", stream_id)); | 
 |       return NULL; | 
 |     } | 
 |     int new_len = std::min(len, stream->send_window_size()); | 
 |     if (new_len < len) { | 
 |       len = new_len; | 
 |       flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_FIN); | 
 |     } | 
 |     stream->DecreaseSendWindowSize(len); | 
 |   } | 
 |  | 
 |   if (net_log().IsLoggingAllEvents()) { | 
 |     net_log().AddEvent( | 
 |         NetLog::TYPE_SPDY_SESSION_SEND_DATA, | 
 |         base::Bind(&NetLogSpdyDataCallback, stream_id, len, flags)); | 
 |   } | 
 |  | 
 |   // Send PrefacePing for DATA_FRAMEs with nonzero payload size. | 
 |   if (len > 0) | 
 |     SendPrefacePingIfNoneInFlight(); | 
 |  | 
 |   // TODO(mbelshe): reduce memory copies here. | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   scoped_ptr<SpdyDataFrame> frame( | 
 |       buffered_spdy_framer_->CreateDataFrame( | 
 |           stream_id, data->data(), len, flags)); | 
 |  | 
 |   return frame.release(); | 
 | } | 
 |  | 
 | void SpdySession::CloseStream(SpdyStreamId stream_id, int status) { | 
 |   DCHECK_NE(0u, stream_id); | 
 |   // TODO(mbelshe): We should send a RST_STREAM control frame here | 
 |   //                so that the server can cancel a large send. | 
 |  | 
 |   DeleteStream(stream_id, status); | 
 | } | 
 |  | 
 | void SpdySession::CloseCreatedStream(SpdyStream* stream, int status) { | 
 |   DCHECK_EQ(0u, stream->stream_id()); | 
 |   created_streams_.erase(scoped_refptr<SpdyStream>(stream)); | 
 |   ProcessPendingCreateStreams(); | 
 | } | 
 |  | 
 | void SpdySession::ResetStream(SpdyStreamId stream_id, | 
 |                               SpdyStatusCodes status, | 
 |                               const std::string& description) { | 
 |   net_log().AddEvent( | 
 |       NetLog::TYPE_SPDY_SESSION_SEND_RST_STREAM, | 
 |       base::Bind(&NetLogSpdyRstCallback, stream_id, status, &description)); | 
 |  | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   scoped_ptr<SpdyRstStreamControlFrame> rst_frame( | 
 |       buffered_spdy_framer_->CreateRstStream(stream_id, status)); | 
 |  | 
 |   // Default to lowest priority unless we know otherwise. | 
 |   RequestPriority priority = net::IDLE; | 
 |   if(IsStreamActive(stream_id)) { | 
 |     scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; | 
 |     priority = stream->priority(); | 
 |   } | 
 |   QueueFrame(rst_frame.release(), priority); | 
 |   RecordProtocolErrorHistogram( | 
 |       static_cast<SpdyProtocolErrorDetails>(status + STATUS_CODE_INVALID)); | 
 |   DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR); | 
 | } | 
 |  | 
 | bool SpdySession::IsStreamActive(SpdyStreamId stream_id) const { | 
 |   return ContainsKey(active_streams_, stream_id); | 
 | } | 
 |  | 
 | LoadState SpdySession::GetLoadState() const { | 
 |   // NOTE: The application only queries the LoadState via the | 
 |   //       SpdyNetworkTransaction, and details are only needed when | 
 |   //       we're in the process of connecting. | 
 |  | 
 |   // If we're connecting, defer to the connection to give us the actual | 
 |   // LoadState. | 
 |   if (state_ == CONNECTING) | 
 |     return connection_->GetLoadState(); | 
 |  | 
 |   // Just report that we're idle since the session could be doing | 
 |   // many things concurrently. | 
 |   return LOAD_STATE_IDLE; | 
 | } | 
 |  | 
 | void SpdySession::OnReadComplete(int bytes_read) { | 
 |   // Parse a frame.  For now this code requires that the frame fit into our | 
 |   // buffer (32KB). | 
 |   // TODO(mbelshe): support arbitrarily large frames! | 
 |  | 
 |   read_pending_ = false; | 
 |  | 
 |   if (bytes_read <= 0) { | 
 |     // Session is tearing down. | 
 |     net::Error error = static_cast<net::Error>(bytes_read); | 
 |     if (bytes_read == 0) | 
 |       error = ERR_CONNECTION_CLOSED; | 
 |     CloseSessionOnError(error, true, "bytes_read is <= 0."); | 
 |     return; | 
 |   } | 
 |  | 
 |   bytes_received_ += bytes_read; | 
 |  | 
 |   last_activity_time_ = base::TimeTicks::Now(); | 
 |  | 
 |   // The SpdyFramer will use callbacks onto |this| as it parses frames. | 
 |   // When errors occur, those callbacks can lead to teardown of all references | 
 |   // to |this|, so maintain a reference to self during this call for safe | 
 |   // cleanup. | 
 |   scoped_refptr<SpdySession> self(this); | 
 |  | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   char *data = read_buffer_->data(); | 
 |   while (bytes_read && | 
 |          buffered_spdy_framer_->error_code() == | 
 |              SpdyFramer::SPDY_NO_ERROR) { | 
 |     uint32 bytes_processed = | 
 |         buffered_spdy_framer_->ProcessInput(data, bytes_read); | 
 |     bytes_read -= bytes_processed; | 
 |     data += bytes_processed; | 
 |     if (buffered_spdy_framer_->state() == SpdyFramer::SPDY_DONE) | 
 |       buffered_spdy_framer_->Reset(); | 
 |   } | 
 |  | 
 |   if (state_ != CLOSED) | 
 |     ReadSocket(); | 
 | } | 
 |  | 
 | void SpdySession::OnWriteComplete(int result) { | 
 |   DCHECK(write_pending_); | 
 |   DCHECK(in_flight_write_.size()); | 
 |  | 
 |   last_activity_time_ = base::TimeTicks::Now(); | 
 |   write_pending_ = false; | 
 |  | 
 |   scoped_refptr<SpdyStream> stream = in_flight_write_.stream(); | 
 |  | 
 |   if (result >= 0) { | 
 |     // It should not be possible to have written more bytes than our | 
 |     // in_flight_write_. | 
 |     DCHECK_LE(result, in_flight_write_.buffer()->BytesRemaining()); | 
 |  | 
 |     in_flight_write_.buffer()->DidConsume(result); | 
 |  | 
 |     // We only notify the stream when we've fully written the pending frame. | 
 |     if (!in_flight_write_.buffer()->BytesRemaining()) { | 
 |       if (stream) { | 
 |         // Report the number of bytes written to the caller, but exclude the | 
 |         // frame size overhead.  NOTE: if this frame was compressed the | 
 |         // reported bytes written is the compressed size, not the original | 
 |         // size. | 
 |         if (result > 0) { | 
 |           result = in_flight_write_.buffer()->size(); | 
 |           DCHECK_GE(result, static_cast<int>(SpdyFrame::kHeaderSize)); | 
 |           result -= static_cast<int>(SpdyFrame::kHeaderSize); | 
 |         } | 
 |  | 
 |         // It is possible that the stream was cancelled while we were writing | 
 |         // to the socket. | 
 |         if (!stream->cancelled()) | 
 |           stream->OnWriteComplete(result); | 
 |       } | 
 |  | 
 |       // Cleanup the write which just completed. | 
 |       in_flight_write_.release(); | 
 |     } | 
 |  | 
 |     // Write more data.  We're already in a continuation, so we can | 
 |     // go ahead and write it immediately (without going back to the | 
 |     // message loop). | 
 |     WriteSocketLater(); | 
 |   } else { | 
 |     in_flight_write_.release(); | 
 |  | 
 |     // The stream is now errored.  Close it down. | 
 |     CloseSessionOnError( | 
 |         static_cast<net::Error>(result), true, "The stream has errored."); | 
 |   } | 
 | } | 
 |  | 
 | net::Error SpdySession::ReadSocket() { | 
 |   if (read_pending_) | 
 |     return OK; | 
 |  | 
 |   if (state_ == CLOSED) { | 
 |     NOTREACHED(); | 
 |     return ERR_UNEXPECTED; | 
 |   } | 
 |  | 
 |   CHECK(connection_.get()); | 
 |   CHECK(connection_->socket()); | 
 |   int bytes_read = connection_->socket()->Read( | 
 |       read_buffer_.get(), | 
 |       kReadBufferSize, | 
 |       base::Bind(&SpdySession::OnReadComplete, base::Unretained(this))); | 
 |   switch (bytes_read) { | 
 |     case 0: | 
 |       // Socket is closed! | 
 |       CloseSessionOnError(ERR_CONNECTION_CLOSED, true, "bytes_read is 0."); | 
 |       return ERR_CONNECTION_CLOSED; | 
 |     case net::ERR_IO_PENDING: | 
 |       // Waiting for data.  Nothing to do now. | 
 |       read_pending_ = true; | 
 |       return ERR_IO_PENDING; | 
 |     default: | 
 |       // Data was read, process it. | 
 |       // Schedule the work through the message loop to avoid recursive | 
 |       // callbacks. | 
 |       read_pending_ = true; | 
 |       MessageLoop::current()->PostTask( | 
 |           FROM_HERE, | 
 |           base::Bind(&SpdySession::OnReadComplete, | 
 |                      weak_factory_.GetWeakPtr(), bytes_read)); | 
 |       break; | 
 |   } | 
 |   return OK; | 
 | } | 
 |  | 
 | void SpdySession::WriteSocketLater() { | 
 |   if (delayed_write_pending_) | 
 |     return; | 
 |  | 
 |   if (state_ < CONNECTED) | 
 |     return; | 
 |  | 
 |   delayed_write_pending_ = true; | 
 |   MessageLoop::current()->PostTask( | 
 |       FROM_HERE, | 
 |       base::Bind(&SpdySession::WriteSocket, weak_factory_.GetWeakPtr())); | 
 | } | 
 |  | 
 | void SpdySession::WriteSocket() { | 
 |   // This function should only be called via WriteSocketLater. | 
 |   DCHECK(delayed_write_pending_); | 
 |   delayed_write_pending_ = false; | 
 |  | 
 |   // If the socket isn't connected yet, just wait; we'll get called | 
 |   // again when the socket connection completes.  If the socket is | 
 |   // closed, just return. | 
 |   if (state_ < CONNECTED || state_ == CLOSED) | 
 |     return; | 
 |  | 
 |   if (write_pending_)   // Another write is in progress still. | 
 |     return; | 
 |  | 
 |   // Loop sending frames until we've sent everything or until the write | 
 |   // returns error (or ERR_IO_PENDING). | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   while (in_flight_write_.buffer() || !write_queue_.empty()) { | 
 |     if (!in_flight_write_.buffer()) { | 
 |       // Grab the next SpdyBuffer to send. | 
 |       scoped_ptr<SpdyIOBufferProducer> producer(write_queue_.top()); | 
 |       write_queue_.pop(); | 
 |       scoped_ptr<SpdyIOBuffer> buffer(producer->ProduceNextBuffer(this)); | 
 |       stream_producers_.erase(producer.get()); | 
 |       // It is possible that a stream had data to write, but a | 
 |       // WINDOW_UPDATE frame has been received which made that | 
 |       // stream no longer writable. | 
 |       // TODO(rch): consider handling that case by removing the | 
 |       // stream from the writable queue? | 
 |       if (buffer == NULL) | 
 |         continue; | 
 |  | 
 |       in_flight_write_ = *buffer; | 
 |     } else { | 
 |       DCHECK(in_flight_write_.buffer()->BytesRemaining()); | 
 |     } | 
 |  | 
 |     write_pending_ = true; | 
 |     int rv = connection_->socket()->Write( | 
 |         in_flight_write_.buffer(), | 
 |         in_flight_write_.buffer()->BytesRemaining(), | 
 |         base::Bind(&SpdySession::OnWriteComplete, base::Unretained(this))); | 
 |     if (rv == net::ERR_IO_PENDING) | 
 |       break; | 
 |  | 
 |     // We sent the frame successfully. | 
 |     OnWriteComplete(rv); | 
 |  | 
 |     // TODO(mbelshe):  Test this error case.  Maybe we should mark the socket | 
 |     //                 as in an error state. | 
 |     if (rv < 0) | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | void SpdySession::CloseAllStreams(net::Error status) { | 
 |   base::StatsCounter abandoned_streams("spdy.abandoned_streams"); | 
 |   base::StatsCounter abandoned_push_streams( | 
 |       "spdy.abandoned_push_streams"); | 
 |  | 
 |   if (!active_streams_.empty()) | 
 |     abandoned_streams.Add(active_streams_.size()); | 
 |   if (!unclaimed_pushed_streams_.empty()) { | 
 |     streams_abandoned_count_ += unclaimed_pushed_streams_.size(); | 
 |     abandoned_push_streams.Add(unclaimed_pushed_streams_.size()); | 
 |     unclaimed_pushed_streams_.clear(); | 
 |   } | 
 |  | 
 |   for (int i = 0; i < NUM_PRIORITIES; ++i) { | 
 |     while (!pending_create_stream_queues_[i].empty()) { | 
 |       PendingCreateStream pending_create = | 
 |           pending_create_stream_queues_[i].front(); | 
 |       pending_create_stream_queues_[i].pop(); | 
 |       pending_create.callback.Run(ERR_ABORTED); | 
 |     } | 
 |   } | 
 |  | 
 |   while (!active_streams_.empty()) { | 
 |     ActiveStreamMap::iterator it = active_streams_.begin(); | 
 |     const scoped_refptr<SpdyStream>& stream = it->second; | 
 |     LogAbandonedStream(stream, status); | 
 |     DeleteStream(stream->stream_id(), status); | 
 |   } | 
 |  | 
 |   while (!created_streams_.empty()) { | 
 |     CreatedStreamSet::iterator it = created_streams_.begin(); | 
 |     const scoped_refptr<SpdyStream> stream = *it; | 
 |     created_streams_.erase(it); | 
 |     LogAbandonedStream(stream, status); | 
 |     stream->OnClose(status); | 
 |   } | 
 |  | 
 |   // We also need to drain the queue. | 
 |   while (!write_queue_.empty()) { | 
 |     scoped_ptr<SpdyIOBufferProducer> producer(write_queue_.top()); | 
 |     write_queue_.pop(); | 
 |     stream_producers_.erase(producer.get()); | 
 |   } | 
 | } | 
 |  | 
 | void SpdySession::LogAbandonedStream(const scoped_refptr<SpdyStream>& stream, | 
 |                                      net::Error status) { | 
 |   DCHECK(stream); | 
 |   std::string description = base::StringPrintf( | 
 |       "ABANDONED (stream_id=%d): ", stream->stream_id()) + stream->path(); | 
 |   stream->LogStreamError(status, description); | 
 | } | 
 |  | 
 | int SpdySession::GetNewStreamId() { | 
 |   int id = stream_hi_water_mark_; | 
 |   stream_hi_water_mark_ += 2; | 
 |   if (stream_hi_water_mark_ > 0x7fff) | 
 |     stream_hi_water_mark_ = 1; | 
 |   return id; | 
 | } | 
 |  | 
 | void SpdySession::CloseSessionOnError(net::Error err, | 
 |                                       bool remove_from_pool, | 
 |                                       const std::string& description) { | 
 |   // Closing all streams can have a side-effect of dropping the last reference | 
 |   // to |this|.  Hold a reference through this function. | 
 |   scoped_refptr<SpdySession> self(this); | 
 |  | 
 |   DCHECK_LT(err, OK); | 
 |   net_log_.AddEvent( | 
 |       NetLog::TYPE_SPDY_SESSION_CLOSE, | 
 |       base::Bind(&NetLogSpdySessionCloseCallback, err, &description)); | 
 |  | 
 |   // Don't close twice.  This can occur because we can have both | 
 |   // a read and a write outstanding, and each can complete with | 
 |   // an error. | 
 |   if (state_ != CLOSED) { | 
 |     state_ = CLOSED; | 
 |     error_ = err; | 
 |     if (remove_from_pool) | 
 |       RemoveFromPool(); | 
 |     CloseAllStreams(err); | 
 |   } | 
 | } | 
 |  | 
 | Value* SpdySession::GetInfoAsValue() const { | 
 |   DictionaryValue* dict = new DictionaryValue(); | 
 |  | 
 |   dict->SetInteger("source_id", net_log_.source().id); | 
 |  | 
 |   dict->SetString("host_port_pair", host_port_proxy_pair_.first.ToString()); | 
 |   if (!pooled_aliases_.empty()) { | 
 |     ListValue* alias_list = new ListValue(); | 
 |     for (std::set<HostPortProxyPair>::const_iterator it = | 
 |              pooled_aliases_.begin(); | 
 |          it != pooled_aliases_.end(); it++) { | 
 |       alias_list->Append(Value::CreateStringValue(it->first.ToString())); | 
 |     } | 
 |     dict->Set("aliases", alias_list); | 
 |   } | 
 |   dict->SetString("proxy", host_port_proxy_pair_.second.ToURI()); | 
 |  | 
 |   dict->SetInteger("active_streams", active_streams_.size()); | 
 |  | 
 |   dict->SetInteger("unclaimed_pushed_streams", | 
 |       unclaimed_pushed_streams_.size()); | 
 |  | 
 |   dict->SetBoolean("is_secure", is_secure_); | 
 |  | 
 |   dict->SetString("protocol_negotiated", | 
 |                   SSLClientSocket::NextProtoToString( | 
 |                       connection_->socket()->GetNegotiatedProtocol())); | 
 |  | 
 |   dict->SetInteger("error", error_); | 
 |   dict->SetInteger("max_concurrent_streams", max_concurrent_streams_); | 
 |  | 
 |   dict->SetInteger("streams_initiated_count", streams_initiated_count_); | 
 |   dict->SetInteger("streams_pushed_count", streams_pushed_count_); | 
 |   dict->SetInteger("streams_pushed_and_claimed_count", | 
 |       streams_pushed_and_claimed_count_); | 
 |   dict->SetInteger("streams_abandoned_count", streams_abandoned_count_); | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   dict->SetInteger("frames_received", buffered_spdy_framer_->frames_received()); | 
 |  | 
 |   dict->SetBoolean("sent_settings", sent_settings_); | 
 |   dict->SetBoolean("received_settings", received_settings_); | 
 |   return dict; | 
 | } | 
 |  | 
 | bool SpdySession::IsReused() const { | 
 |   return buffered_spdy_framer_->frames_received() > 0; | 
 | } | 
 |  | 
 | int SpdySession::GetPeerAddress(IPEndPoint* address) const { | 
 |   if (!connection_->socket()) | 
 |     return ERR_SOCKET_NOT_CONNECTED; | 
 |  | 
 |   return connection_->socket()->GetPeerAddress(address); | 
 | } | 
 |  | 
 | int SpdySession::GetLocalAddress(IPEndPoint* address) const { | 
 |   if (!connection_->socket()) | 
 |     return ERR_SOCKET_NOT_CONNECTED; | 
 |  | 
 |   return connection_->socket()->GetLocalAddress(address); | 
 | } | 
 |  | 
 | class SimpleSpdyIOBufferProducer : public SpdySession::SpdyIOBufferProducer { | 
 |  public: | 
 |   SimpleSpdyIOBufferProducer(SpdyFrame* frame, | 
 |                              RequestPriority priority) | 
 |       : frame_(frame), | 
 |         priority_(priority) { | 
 |   } | 
 |  | 
 |   virtual RequestPriority GetPriority() const OVERRIDE { | 
 |     return priority_; | 
 |   } | 
 |  | 
 |   virtual SpdyIOBuffer* ProduceNextBuffer(SpdySession* session) OVERRIDE { | 
 |     return SpdySession::SpdyIOBufferProducer::CreateIOBuffer( | 
 |         frame_.get(), priority_, NULL); | 
 |   } | 
 |  | 
 |  private: | 
 |   scoped_ptr<SpdyFrame> frame_; | 
 |   RequestPriority priority_; | 
 | }; | 
 |  | 
 | void SpdySession::QueueFrame(SpdyFrame* frame, | 
 |                              RequestPriority priority) { | 
 |   SimpleSpdyIOBufferProducer* producer = | 
 |       new SimpleSpdyIOBufferProducer(frame, priority); | 
 |   write_queue_.push(producer); | 
 |   WriteSocketLater(); | 
 | } | 
 |  | 
 | void SpdySession::ActivateStream(SpdyStream* stream) { | 
 |   if (stream->stream_id() == 0) { | 
 |     stream->set_stream_id(GetNewStreamId()); | 
 |     created_streams_.erase(scoped_refptr<SpdyStream>(stream)); | 
 |   } | 
 |   const SpdyStreamId id = stream->stream_id(); | 
 |   DCHECK(!IsStreamActive(id)); | 
 |  | 
 |   active_streams_[id] = stream; | 
 | } | 
 |  | 
 | void SpdySession::DeleteStream(SpdyStreamId id, int status) { | 
 |   // For push streams, if they are being deleted normally, we leave | 
 |   // the stream in the unclaimed_pushed_streams_ list.  However, if | 
 |   // the stream is errored out, clean it up entirely. | 
 |   if (status != OK) { | 
 |     PushedStreamMap::iterator it; | 
 |     for (it = unclaimed_pushed_streams_.begin(); | 
 |          it != unclaimed_pushed_streams_.end(); ++it) { | 
 |       scoped_refptr<SpdyStream> curr = it->second.first; | 
 |       if (id == curr->stream_id()) { | 
 |         unclaimed_pushed_streams_.erase(it); | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // The stream might have been deleted. | 
 |   ActiveStreamMap::iterator it2 = active_streams_.find(id); | 
 |   if (it2 == active_streams_.end()) | 
 |     return; | 
 |  | 
 |   // Possibly remove from the write queue. | 
 |   WriteQueue old = write_queue_; | 
 |   write_queue_ = WriteQueue(); | 
 |   while (!old.empty()) { | 
 |     scoped_ptr<SpdyIOBufferProducer> producer(old.top()); | 
 |     old.pop(); | 
 |     StreamProducerMap::iterator it = stream_producers_.find(producer.get()); | 
 |     if (it == stream_producers_.end() || it->second->stream_id() != id) { | 
 |       write_queue_.push(producer.release()); | 
 |     } else { | 
 |       stream_producers_.erase(producer.get()); | 
 |       producer.reset(NULL); | 
 |     } | 
 |   } | 
 |  | 
 |   // If this is an active stream, call the callback. | 
 |   const scoped_refptr<SpdyStream> stream(it2->second); | 
 |   active_streams_.erase(it2); | 
 |   if (stream) | 
 |     stream->OnClose(status); | 
 |   ProcessPendingCreateStreams(); | 
 | } | 
 |  | 
 | void SpdySession::RemoveFromPool() { | 
 |   if (spdy_session_pool_) { | 
 |     SpdySessionPool* pool = spdy_session_pool_; | 
 |     spdy_session_pool_ = NULL; | 
 |     pool->Remove(make_scoped_refptr(this)); | 
 |   } | 
 | } | 
 |  | 
 | scoped_refptr<SpdyStream> SpdySession::GetActivePushStream( | 
 |     const std::string& path) { | 
 |   base::StatsCounter used_push_streams("spdy.claimed_push_streams"); | 
 |  | 
 |   PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(path); | 
 |   if (it != unclaimed_pushed_streams_.end()) { | 
 |     net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ADOPTED_PUSH_STREAM); | 
 |     scoped_refptr<SpdyStream> stream = it->second.first; | 
 |     unclaimed_pushed_streams_.erase(it); | 
 |     used_push_streams.Increment(); | 
 |     return stream; | 
 |   } | 
 |   return NULL; | 
 | } | 
 |  | 
 | bool SpdySession::GetSSLInfo(SSLInfo* ssl_info, | 
 |                              bool* was_npn_negotiated, | 
 |                              NextProto* protocol_negotiated) { | 
 |  | 
 |   *was_npn_negotiated = connection_->socket()->WasNpnNegotiated(); | 
 |   *protocol_negotiated = connection_->socket()->GetNegotiatedProtocol(); | 
 |   return connection_->socket()->GetSSLInfo(ssl_info); | 
 | } | 
 |  | 
 | bool SpdySession::GetSSLCertRequestInfo( | 
 |     SSLCertRequestInfo* cert_request_info) { | 
 |   if (!is_secure_) | 
 |     return false; | 
 |   GetSSLClientSocket()->GetSSLCertRequestInfo(cert_request_info); | 
 |   return true; | 
 | } | 
 |  | 
 | ServerBoundCertService* SpdySession::GetServerBoundCertService() const { | 
 |   if (!is_secure_) | 
 |     return NULL; | 
 |   return GetSSLClientSocket()->GetServerBoundCertService(); | 
 | } | 
 |  | 
 | void SpdySession::OnError(SpdyFramer::SpdyError error_code) { | 
 |   RecordProtocolErrorHistogram( | 
 |       static_cast<SpdyProtocolErrorDetails>(error_code)); | 
 |   std::string description = base::StringPrintf( | 
 |       "SPDY_ERROR error_code: %d.", error_code); | 
 |   CloseSessionOnError(net::ERR_SPDY_PROTOCOL_ERROR, true, description); | 
 | } | 
 |  | 
 | void SpdySession::OnStreamError(SpdyStreamId stream_id, | 
 |                                 const std::string& description) { | 
 |   if (IsStreamActive(stream_id)) | 
 |     ResetStream(stream_id, PROTOCOL_ERROR, description); | 
 | } | 
 |  | 
 | void SpdySession::OnStreamFrameData(SpdyStreamId stream_id, | 
 |                                     const char* data, | 
 |                                     size_t len, | 
 |                                     SpdyDataFlags flags) { | 
 |   if (net_log().IsLoggingAllEvents()) { | 
 |     net_log().AddEvent( | 
 |         NetLog::TYPE_SPDY_SESSION_RECV_DATA, | 
 |         base::Bind(&NetLogSpdyDataCallback, stream_id, len, flags)); | 
 |   } | 
 |  | 
 |   if (!IsStreamActive(stream_id)) { | 
 |     // NOTE:  it may just be that the stream was cancelled. | 
 |     return; | 
 |   } | 
 |  | 
 |   scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; | 
 |   stream->OnDataReceived(data, len); | 
 | } | 
 |  | 
 | void SpdySession::OnSetting(SpdySettingsIds id, | 
 |                             uint8 flags, | 
 |                             uint32 value) { | 
 |   HandleSetting(id, value); | 
 |   http_server_properties_->SetSpdySetting( | 
 |       host_port_pair(), | 
 |       id, | 
 |       static_cast<SpdySettingsFlags>(flags), | 
 |       value); | 
 |   received_settings_ = true; | 
 |  | 
 |   // Log the setting. | 
 |   net_log_.AddEvent( | 
 |       NetLog::TYPE_SPDY_SESSION_RECV_SETTING, | 
 |       base::Bind(&NetLogSpdySettingCallback, | 
 |                  id, static_cast<SpdySettingsFlags>(flags), value)); | 
 | } | 
 |  | 
 | void SpdySession::OnControlFrameCompressed( | 
 |     const SpdyControlFrame& uncompressed_frame, | 
 |     const SpdyControlFrame& compressed_frame) { | 
 |   if (uncompressed_frame.type() == SYN_STREAM) { | 
 |     int uncompressed_size = uncompressed_frame.length(); | 
 |     int compressed_size = compressed_frame.length(); | 
 |     // Make sure we avoid early decimal truncation. | 
 |     int compression_pct = 100 - (100 * compressed_size) / uncompressed_size; | 
 |     UMA_HISTOGRAM_PERCENTAGE("Net.SpdySynStreamCompressionPercentage", | 
 |                              compression_pct); | 
 |   } | 
 | } | 
 |  | 
 |  | 
 | bool SpdySession::Respond(const SpdyHeaderBlock& headers, | 
 |                           const scoped_refptr<SpdyStream> stream) { | 
 |   int rv = OK; | 
 |   rv = stream->OnResponseReceived(headers); | 
 |   if (rv < 0) { | 
 |     DCHECK_NE(rv, ERR_IO_PENDING); | 
 |     const SpdyStreamId stream_id = stream->stream_id(); | 
 |     DeleteStream(stream_id, rv); | 
 |     return false; | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | void SpdySession::OnSynStream(SpdyStreamId stream_id, | 
 |                               SpdyStreamId associated_stream_id, | 
 |                               SpdyPriority priority, | 
 |                               uint8 credential_slot, | 
 |                               bool fin, | 
 |                               bool unidirectional, | 
 |                               const SpdyHeaderBlock& headers) { | 
 |   if (net_log_.IsLoggingAllEvents()) { | 
 |     net_log_.AddEvent( | 
 |         NetLog::TYPE_SPDY_SESSION_PUSHED_SYN_STREAM, | 
 |         base::Bind(&NetLogSpdySynCallback, | 
 |                    &headers, fin, unidirectional, | 
 |                    stream_id, associated_stream_id)); | 
 |   } | 
 |  | 
 |   // Server-initiated streams should have even sequence numbers. | 
 |   if ((stream_id & 0x1) != 0) { | 
 |     LOG(WARNING) << "Received invalid OnSyn stream id " << stream_id; | 
 |     return; | 
 |   } | 
 |  | 
 |   if (IsStreamActive(stream_id)) { | 
 |     LOG(WARNING) << "Received OnSyn for active stream " << stream_id; | 
 |     return; | 
 |   } | 
 |  | 
 |   if (associated_stream_id == 0) { | 
 |     std::string description = base::StringPrintf( | 
 |         "Received invalid OnSyn associated stream id %d for stream %d", | 
 |         associated_stream_id, stream_id); | 
 |     ResetStream(stream_id, REFUSED_STREAM, description); | 
 |     return; | 
 |   } | 
 |  | 
 |   streams_pushed_count_++; | 
 |  | 
 |   // TODO(mbelshe): DCHECK that this is a GET method? | 
 |  | 
 |   // Verify that the response had a URL for us. | 
 |   GURL gurl = GetUrlFromHeaderBlock(headers, GetProtocolVersion(), true); | 
 |   if (!gurl.is_valid()) { | 
 |     ResetStream(stream_id, PROTOCOL_ERROR, | 
 |                 "Pushed stream url was invalid: " + gurl.spec()); | 
 |     return; | 
 |   } | 
 |   const std::string& url = gurl.spec(); | 
 |  | 
 |   // Verify we have a valid stream association. | 
 |   if (!IsStreamActive(associated_stream_id)) { | 
 |     ResetStream(stream_id, INVALID_STREAM, | 
 |                 base::StringPrintf( | 
 |                     "Received OnSyn with inactive associated stream %d", | 
 |                     associated_stream_id)); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Check that the SYN advertises the same origin as its associated stream. | 
 |   // Bypass this check if and only if this session is with a SPDY proxy that | 
 |   // is trusted explicitly via the --trusted-spdy-proxy switch. | 
 |   if (trusted_spdy_proxy_.Equals(host_port_pair())) { | 
 |     // Disallow pushing of HTTPS content. | 
 |     if (gurl.SchemeIs("https")) { | 
 |       ResetStream(stream_id, REFUSED_STREAM, | 
 |                   base::StringPrintf( | 
 |                       "Rejected push of Cross Origin HTTPS content %d", | 
 |                       associated_stream_id)); | 
 |     } | 
 |   } else { | 
 |     scoped_refptr<SpdyStream> associated_stream = | 
 |         active_streams_[associated_stream_id]; | 
 |     GURL associated_url(associated_stream->GetUrl()); | 
 |     if (associated_url.GetOrigin() != gurl.GetOrigin()) { | 
 |       ResetStream(stream_id, REFUSED_STREAM, | 
 |                   base::StringPrintf( | 
 |                       "Rejected Cross Origin Push Stream %d", | 
 |                       associated_stream_id)); | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   // There should not be an existing pushed stream with the same path. | 
 |   PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(url); | 
 |   if (it != unclaimed_pushed_streams_.end()) { | 
 |     ResetStream(stream_id, PROTOCOL_ERROR, | 
 |                 "Received duplicate pushed stream with url: " + url); | 
 |     return; | 
 |   } | 
 |  | 
 |   scoped_refptr<SpdyStream> stream(new SpdyStream(this, true, net_log_)); | 
 |   stream->set_stream_id(stream_id); | 
 |  | 
 |   stream->set_path(gurl.PathForRequest()); | 
 |   stream->set_send_window_size(initial_send_window_size_); | 
 |   stream->set_recv_window_size(initial_recv_window_size_); | 
 |  | 
 |   DeleteExpiredPushedStreams(); | 
 |   unclaimed_pushed_streams_[url] = | 
 |       std::pair<scoped_refptr<SpdyStream>, base::TimeTicks> ( | 
 |           stream, time_func_()); | 
 |  | 
 |  | 
 |   ActivateStream(stream); | 
 |   stream->set_response_received(); | 
 |  | 
 |   // Parse the headers. | 
 |   if (!Respond(headers, stream)) | 
 |     return; | 
 |  | 
 |   base::StatsCounter push_requests("spdy.pushed_streams"); | 
 |   push_requests.Increment(); | 
 | } | 
 |  | 
 | void SpdySession::DeleteExpiredPushedStreams() { | 
 |   if (unclaimed_pushed_streams_.empty()) | 
 |     return; | 
 |  | 
 |   // Check that adequate time has elapsed since the last sweep. | 
 |   if (time_func_() < next_unclaimed_push_stream_sweep_time_) | 
 |     return; | 
 |  | 
 |   // Delete old streams. | 
 |   base::TimeTicks minimum_freshness = time_func_() - | 
 |       base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds); | 
 |   PushedStreamMap::iterator it; | 
 |   for (it = unclaimed_pushed_streams_.begin(); | 
 |       it != unclaimed_pushed_streams_.end(); ) { | 
 |     const scoped_refptr<SpdyStream>& stream = it->second.first; | 
 |     base::TimeTicks creation_time = it->second.second; | 
 |     // DeleteStream() will invalidate the current iterator, so move to next. | 
 |     ++it; | 
 |     if (minimum_freshness > creation_time) { | 
 |       DeleteStream(stream->stream_id(), ERR_INVALID_SPDY_STREAM); | 
 |       base::StatsCounter abandoned_push_streams( | 
 |           "spdy.abandoned_push_streams"); | 
 |       base::StatsCounter abandoned_streams("spdy.abandoned_streams"); | 
 |       abandoned_push_streams.Increment(); | 
 |       abandoned_streams.Increment(); | 
 |       streams_abandoned_count_++; | 
 |     } | 
 |   } | 
 |   next_unclaimed_push_stream_sweep_time_ = time_func_() + | 
 |       base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds); | 
 | } | 
 |  | 
 | void SpdySession::OnSynReply(SpdyStreamId stream_id, | 
 |                              bool fin, | 
 |                              const SpdyHeaderBlock& headers) { | 
 |   if (net_log().IsLoggingAllEvents()) { | 
 |     net_log().AddEvent( | 
 |         NetLog::TYPE_SPDY_SESSION_SYN_REPLY, | 
 |         base::Bind(&NetLogSpdySynCallback, | 
 |                    &headers, fin, false,// not unidirectional | 
 |                    stream_id, 0)); | 
 |   } | 
 |  | 
 |   if (!IsStreamActive(stream_id)) { | 
 |     // NOTE:  it may just be that the stream was cancelled. | 
 |     LOG(WARNING) << "Received SYN_REPLY for invalid stream " << stream_id; | 
 |     return; | 
 |   } | 
 |  | 
 |   scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; | 
 |   CHECK_EQ(stream->stream_id(), stream_id); | 
 |   CHECK(!stream->cancelled()); | 
 |  | 
 |   if (stream->response_received()) { | 
 |     stream->LogStreamError(ERR_SYN_REPLY_NOT_RECEIVED, | 
 |                            "Received duplicate SYN_REPLY for stream."); | 
 |     RecordProtocolErrorHistogram(PROTOCOL_ERROR_SYN_REPLY_NOT_RECEIVED); | 
 |     CloseStream(stream->stream_id(), ERR_SPDY_PROTOCOL_ERROR); | 
 |     return; | 
 |   } | 
 |   stream->set_response_received(); | 
 |  | 
 |   Respond(headers, stream); | 
 | } | 
 |  | 
 | void SpdySession::OnHeaders(SpdyStreamId stream_id, | 
 |                             bool fin, | 
 |                             const SpdyHeaderBlock& headers) { | 
 |   if (net_log().IsLoggingAllEvents()) { | 
 |     net_log().AddEvent( | 
 |         NetLog::TYPE_SPDY_SESSION_RECV_HEADERS, | 
 |         base::Bind(&NetLogSpdySynCallback, | 
 |                    &headers, fin, /*unidirectional=*/false, | 
 |                    stream_id, 0)); | 
 |   } | 
 |  | 
 |   if (!IsStreamActive(stream_id)) { | 
 |     // NOTE:  it may just be that the stream was cancelled. | 
 |     LOG(WARNING) << "Received HEADERS for invalid stream " << stream_id; | 
 |     return; | 
 |   } | 
 |  | 
 |   scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; | 
 |   CHECK_EQ(stream->stream_id(), stream_id); | 
 |   CHECK(!stream->cancelled()); | 
 |  | 
 |   int rv = stream->OnHeaders(headers); | 
 |   if (rv < 0) { | 
 |     DCHECK_NE(rv, ERR_IO_PENDING); | 
 |     const SpdyStreamId stream_id = stream->stream_id(); | 
 |     DeleteStream(stream_id, rv); | 
 |   } | 
 | } | 
 |  | 
 | void SpdySession::OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) { | 
 |   std::string description; | 
 |   net_log().AddEvent( | 
 |       NetLog::TYPE_SPDY_SESSION_RST_STREAM, | 
 |       base::Bind(&NetLogSpdyRstCallback, | 
 |                  stream_id, status, &description)); | 
 |  | 
 |   if (!IsStreamActive(stream_id)) { | 
 |     // NOTE:  it may just be that the stream was cancelled. | 
 |     LOG(WARNING) << "Received RST for invalid stream" << stream_id; | 
 |     return; | 
 |   } | 
 |   scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; | 
 |   CHECK_EQ(stream->stream_id(), stream_id); | 
 |   CHECK(!stream->cancelled()); | 
 |  | 
 |   if (status == 0) { | 
 |     stream->OnDataReceived(NULL, 0); | 
 |   } else if (status == REFUSED_STREAM) { | 
 |     DeleteStream(stream_id, ERR_SPDY_SERVER_REFUSED_STREAM); | 
 |   } else { | 
 |     RecordProtocolErrorHistogram( | 
 |         PROTOCOL_ERROR_RST_STREAM_FOR_NON_ACTIVE_STREAM); | 
 |     stream->LogStreamError(ERR_SPDY_PROTOCOL_ERROR, | 
 |                            base::StringPrintf("SPDY stream closed: %d", | 
 |                                               status)); | 
 |     // TODO(mbelshe): Map from Spdy-protocol errors to something sensical. | 
 |     //                For now, it doesn't matter much - it is a protocol error. | 
 |     DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR); | 
 |   } | 
 | } | 
 |  | 
 | void SpdySession::OnGoAway(SpdyStreamId last_accepted_stream_id, | 
 |                            SpdyGoAwayStatus status) { | 
 |   net_log_.AddEvent(NetLog::TYPE_SPDY_SESSION_GOAWAY, | 
 |       base::Bind(&NetLogSpdyGoAwayCallback, | 
 |                  last_accepted_stream_id, | 
 |                  active_streams_.size(), | 
 |                  unclaimed_pushed_streams_.size(), | 
 |                  status)); | 
 |   RemoveFromPool(); | 
 |   CloseAllStreams(net::ERR_ABORTED); | 
 |  | 
 |   // TODO(willchan): Cancel any streams that are past the GoAway frame's | 
 |   // |last_accepted_stream_id|. | 
 |  | 
 |   // Don't bother killing any streams that are still reading.  They'll either | 
 |   // complete successfully or get an ERR_CONNECTION_CLOSED when the socket is | 
 |   // closed. | 
 | } | 
 |  | 
 | void SpdySession::OnPing(uint32 unique_id) { | 
 |   net_log_.AddEvent( | 
 |       NetLog::TYPE_SPDY_SESSION_PING, | 
 |       base::Bind(&NetLogSpdyPingCallback, unique_id, "received")); | 
 |  | 
 |   // Send response to a PING from server. | 
 |   if (unique_id % 2 == 0) { | 
 |     WritePingFrame(unique_id); | 
 |     return; | 
 |   } | 
 |  | 
 |   --pings_in_flight_; | 
 |   if (pings_in_flight_ < 0) { | 
 |     RecordProtocolErrorHistogram(PROTOCOL_ERROR_UNEXPECTED_PING); | 
 |     CloseSessionOnError( | 
 |         net::ERR_SPDY_PROTOCOL_ERROR, true, "pings_in_flight_ is < 0."); | 
 |     pings_in_flight_ = 0; | 
 |     return; | 
 |   } | 
 |  | 
 |   if (pings_in_flight_ > 0) | 
 |     return; | 
 |  | 
 |   // We will record RTT in histogram when there are no more client sent | 
 |   // pings_in_flight_. | 
 |   RecordPingRTTHistogram(base::TimeTicks::Now() - last_ping_sent_time_); | 
 | } | 
 |  | 
 | void SpdySession::OnWindowUpdate(SpdyStreamId stream_id, | 
 |                                  int delta_window_size) { | 
 |   net_log_.AddEvent( | 
 |       NetLog::TYPE_SPDY_SESSION_RECEIVED_WINDOW_UPDATE, | 
 |       base::Bind(&NetLogSpdyWindowUpdateCallback, | 
 |                  stream_id, delta_window_size)); | 
 |  | 
 |   if (!IsStreamActive(stream_id)) { | 
 |     LOG(WARNING) << "Received WINDOW_UPDATE for invalid stream " << stream_id; | 
 |     return; | 
 |   } | 
 |  | 
 |   if (delta_window_size < 1) { | 
 |     ResetStream(stream_id, FLOW_CONTROL_ERROR, | 
 |                 base::StringPrintf( | 
 |                     "Received WINDOW_UPDATE with an invalid " | 
 |                     "delta_window_size %d", delta_window_size)); | 
 |     return; | 
 |   } | 
 |  | 
 |   scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; | 
 |   CHECK_EQ(stream->stream_id(), stream_id); | 
 |   CHECK(!stream->cancelled()); | 
 |  | 
 |   if (flow_control_) | 
 |     stream->IncreaseSendWindowSize(delta_window_size); | 
 | } | 
 |  | 
 | void SpdySession::SendWindowUpdate(SpdyStreamId stream_id, | 
 |                                    int32 delta_window_size) { | 
 |   CHECK(IsStreamActive(stream_id)); | 
 |   scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; | 
 |   CHECK_EQ(stream->stream_id(), stream_id); | 
 |  | 
 |   net_log_.AddEvent( | 
 |       NetLog::TYPE_SPDY_SESSION_SENT_WINDOW_UPDATE, | 
 |       base::Bind(&NetLogSpdyWindowUpdateCallback, | 
 |                  stream_id, delta_window_size)); | 
 |  | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame( | 
 |       buffered_spdy_framer_->CreateWindowUpdate(stream_id, delta_window_size)); | 
 |   QueueFrame(window_update_frame.release(), stream->priority()); | 
 | } | 
 |  | 
 | // Given a cwnd that we would have sent to the server, modify it based on the | 
 | // field trial policy. | 
 | uint32 ApplyCwndFieldTrialPolicy(int cwnd) { | 
 | #if defined(COBALT) | 
 |   // Cobalt doesn't support field trials. | 
 |   return cwnd; | 
 | #else | 
 |   base::FieldTrial* trial = base::FieldTrialList::Find("SpdyCwnd"); | 
 |   if (!trial) { | 
 |       LOG(WARNING) << "Could not find \"SpdyCwnd\" in FieldTrialList"; | 
 |       return cwnd; | 
 |   } | 
 |   if (trial->group_name() == "cwnd10") | 
 |     return 10; | 
 |   else if (trial->group_name() == "cwnd16") | 
 |     return 16; | 
 |   else if (trial->group_name() == "cwndMin16") | 
 |     return std::max(cwnd, 16); | 
 |   else if (trial->group_name() == "cwndMin10") | 
 |     return std::max(cwnd, 10); | 
 |   else if (trial->group_name() == "cwndDynamic") | 
 |     return cwnd; | 
 |   NOTREACHED(); | 
 |   return cwnd; | 
 | #endif | 
 | } | 
 |  | 
 | void SpdySession::SendInitialSettings() { | 
 |   // First notify the server about the settings they should use when | 
 |   // communicating with us. | 
 |   if (GetProtocolVersion() >= 2 && enable_sending_initial_settings_) { | 
 |     SettingsMap settings_map; | 
 |     // Create a new settings frame notifying the sever of our | 
 |     // max_concurrent_streams_ and initial window size. | 
 |     settings_map[SETTINGS_MAX_CONCURRENT_STREAMS] = | 
 |         SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams); | 
 |     if (GetProtocolVersion() > 2 && | 
 |         initial_recv_window_size_ != kSpdyStreamInitialWindowSize) { | 
 |       settings_map[SETTINGS_INITIAL_WINDOW_SIZE] = | 
 |           SettingsFlagsAndValue(SETTINGS_FLAG_NONE, initial_recv_window_size_); | 
 |     } | 
 |     SendSettings(settings_map); | 
 |   } | 
 |  | 
 |   // Next notify the server about the settings they have previously | 
 |   // told us to use when communicating with them. | 
 |   const SettingsMap& settings_map = | 
 |       http_server_properties_->GetSpdySettings(host_port_pair()); | 
 |   if (settings_map.empty()) | 
 |     return; | 
 |  | 
 |   // Record Histogram Data and Apply the SpdyCwnd FieldTrial if applicable. | 
 |   const SpdySettingsIds id = SETTINGS_CURRENT_CWND; | 
 |   SettingsMap::const_iterator it = settings_map.find(id); | 
 |   uint32 value = 0; | 
 |   if (it != settings_map.end()) | 
 |     value = it->second.second; | 
 |   uint32 cwnd = ApplyCwndFieldTrialPolicy(value); | 
 |   UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwndSent", cwnd, 1, 200, 100); | 
 |   if (cwnd != value) { | 
 |     http_server_properties_->SetSpdySetting( | 
 |         host_port_pair(), id, SETTINGS_FLAG_PLEASE_PERSIST, cwnd); | 
 |   } | 
 |  | 
 |   const SettingsMap& settings_map_new = | 
 |       http_server_properties_->GetSpdySettings(host_port_pair()); | 
 |   for (SettingsMap::const_iterator i = settings_map_new.begin(), | 
 |            end = settings_map_new.end(); i != end; ++i) { | 
 |     const SpdySettingsIds new_id = i->first; | 
 |     const uint32 new_val = i->second.second; | 
 |     HandleSetting(new_id, new_val); | 
 |   } | 
 |  | 
 |   SendSettings(settings_map_new); | 
 | } | 
 |  | 
 |  | 
 | void SpdySession::SendSettings(const SettingsMap& settings) { | 
 |   net_log_.AddEvent( | 
 |       NetLog::TYPE_SPDY_SESSION_SEND_SETTINGS, | 
 |       base::Bind(&NetLogSpdySettingsCallback, &settings)); | 
 |  | 
 |   // Create the SETTINGS frame and send it. | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   scoped_ptr<SpdySettingsControlFrame> settings_frame( | 
 |       buffered_spdy_framer_->CreateSettings(settings)); | 
 |   sent_settings_ = true; | 
 |   QueueFrame(settings_frame.release(), HIGHEST); | 
 | } | 
 |  | 
 | void SpdySession::HandleSetting(uint32 id, uint32 value) { | 
 |   switch (id) { | 
 |     case SETTINGS_MAX_CONCURRENT_STREAMS: | 
 |       max_concurrent_streams_ = std::min(static_cast<size_t>(value), | 
 |                                          kMaxConcurrentStreamLimit); | 
 |       ProcessPendingCreateStreams(); | 
 |       break; | 
 |     case SETTINGS_INITIAL_WINDOW_SIZE: | 
 |       if (static_cast<int32>(value) < 0) { | 
 |         net_log().AddEvent( | 
 |             NetLog::TYPE_SPDY_SESSION_NEGATIVE_INITIAL_WINDOW_SIZE, | 
 |             NetLog::IntegerCallback("initial_window_size", value)); | 
 |       } else { | 
 |         // SETTINGS_INITIAL_WINDOW_SIZE updates initial_send_window_size_ only. | 
 |         int32 delta_window_size = value - initial_send_window_size_; | 
 |         initial_send_window_size_ = value; | 
 |         UpdateStreamsSendWindowSize(delta_window_size); | 
 |         net_log().AddEvent( | 
 |             NetLog::TYPE_SPDY_SESSION_UPDATE_STREAMS_SEND_WINDOW_SIZE, | 
 |             NetLog::IntegerCallback("delta_window_size", delta_window_size)); | 
 |       } | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | void SpdySession::UpdateStreamsSendWindowSize(int32 delta_window_size) { | 
 |   ActiveStreamMap::iterator it; | 
 |   for (it = active_streams_.begin(); it != active_streams_.end(); ++it) { | 
 |     const scoped_refptr<SpdyStream>& stream = it->second; | 
 |     DCHECK(stream); | 
 |     stream->AdjustSendWindowSize(delta_window_size); | 
 |   } | 
 |  | 
 |   CreatedStreamSet::iterator i; | 
 |   for (i = created_streams_.begin(); i != created_streams_.end(); i++) { | 
 |     const scoped_refptr<SpdyStream>& stream = *i; | 
 |     stream->AdjustSendWindowSize(delta_window_size); | 
 |   } | 
 | } | 
 |  | 
 | void SpdySession::SendPrefacePingIfNoneInFlight() { | 
 |   if (pings_in_flight_ || !enable_ping_based_connection_checking_) | 
 |     return; | 
 |  | 
 |   base::TimeTicks now = base::TimeTicks::Now(); | 
 |   // If there is no activity in the session, then send a preface-PING. | 
 |   if ((now - last_activity_time_) > connection_at_risk_of_loss_time_) | 
 |     SendPrefacePing(); | 
 | } | 
 |  | 
 | void SpdySession::SendPrefacePing() { | 
 |   WritePingFrame(next_ping_id_); | 
 | } | 
 |  | 
 | void SpdySession::WritePingFrame(uint32 unique_id) { | 
 |   DCHECK(buffered_spdy_framer_.get()); | 
 |   scoped_ptr<SpdyPingControlFrame> ping_frame( | 
 |       buffered_spdy_framer_->CreatePingFrame(unique_id)); | 
 |   QueueFrame(ping_frame.release(), HIGHEST); | 
 |  | 
 |   if (net_log().IsLoggingAllEvents()) { | 
 |     net_log().AddEvent( | 
 |         NetLog::TYPE_SPDY_SESSION_PING, | 
 |         base::Bind(&NetLogSpdyPingCallback, unique_id, "sent")); | 
 |   } | 
 |   if (unique_id % 2 != 0) { | 
 |     next_ping_id_ += 2; | 
 |     ++pings_in_flight_; | 
 |     PlanToCheckPingStatus(); | 
 |     last_ping_sent_time_ = base::TimeTicks::Now(); | 
 |   } | 
 | } | 
 |  | 
 | void SpdySession::PlanToCheckPingStatus() { | 
 |   if (check_ping_status_pending_) | 
 |     return; | 
 |  | 
 |   check_ping_status_pending_ = true; | 
 |   MessageLoop::current()->PostDelayedTask( | 
 |       FROM_HERE, | 
 |       base::Bind(&SpdySession::CheckPingStatus, weak_factory_.GetWeakPtr(), | 
 |                  base::TimeTicks::Now()), hung_interval_); | 
 | } | 
 |  | 
 | void SpdySession::CheckPingStatus(base::TimeTicks last_check_time) { | 
 |   // Check if we got a response back for all PINGs we had sent. | 
 |   if (pings_in_flight_ == 0) { | 
 |     check_ping_status_pending_ = false; | 
 |     return; | 
 |   } | 
 |  | 
 |   DCHECK(check_ping_status_pending_); | 
 |  | 
 |   base::TimeTicks now = base::TimeTicks::Now(); | 
 |   base::TimeDelta delay = hung_interval_ - (now - last_activity_time_); | 
 |  | 
 |   if (delay.InMilliseconds() < 0 || last_activity_time_ < last_check_time) { | 
 |     CloseSessionOnError(net::ERR_SPDY_PING_FAILED, true, "Failed ping."); | 
 |     // Track all failed PING messages in a separate bucket. | 
 |     const base::TimeDelta kFailedPing = | 
 |         base::TimeDelta::FromInternalValue(INT_MAX); | 
 |     RecordPingRTTHistogram(kFailedPing); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Check the status of connection after a delay. | 
 |   MessageLoop::current()->PostDelayedTask( | 
 |       FROM_HERE, | 
 |       base::Bind(&SpdySession::CheckPingStatus, weak_factory_.GetWeakPtr(), | 
 |                  now), | 
 |       delay); | 
 | } | 
 |  | 
 | void SpdySession::RecordPingRTTHistogram(base::TimeDelta duration) { | 
 |   UMA_HISTOGRAM_TIMES("Net.SpdyPing.RTT", duration); | 
 | } | 
 |  | 
 | void SpdySession::RecordProtocolErrorHistogram( | 
 |     SpdyProtocolErrorDetails details) { | 
 |   UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionErrorDetails", details, | 
 |                             NUM_SPDY_PROTOCOL_ERROR_DETAILS); | 
 |   if (EndsWith(host_port_pair().host(), "google.com", false)) { | 
 |     UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionErrorDetails_Google", details, | 
 |                               NUM_SPDY_PROTOCOL_ERROR_DETAILS); | 
 |   } | 
 | } | 
 |  | 
 | void SpdySession::RecordHistograms() { | 
 |   UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPerSession", | 
 |                               streams_initiated_count_, | 
 |                               0, 300, 50); | 
 |   UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPushedPerSession", | 
 |                               streams_pushed_count_, | 
 |                               0, 300, 50); | 
 |   UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPushedAndClaimedPerSession", | 
 |                               streams_pushed_and_claimed_count_, | 
 |                               0, 300, 50); | 
 |   UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsAbandonedPerSession", | 
 |                               streams_abandoned_count_, | 
 |                               0, 300, 50); | 
 |   UMA_HISTOGRAM_ENUMERATION("Net.SpdySettingsSent", | 
 |                             sent_settings_ ? 1 : 0, 2); | 
 |   UMA_HISTOGRAM_ENUMERATION("Net.SpdySettingsReceived", | 
 |                             received_settings_ ? 1 : 0, 2); | 
 |   UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamStallsPerSession", | 
 |                               stalled_streams_, | 
 |                               0, 300, 50); | 
 |   UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionsWithStalls", | 
 |                             stalled_streams_ > 0 ? 1 : 0, 2); | 
 |  | 
 |   if (received_settings_) { | 
 |     // Enumerate the saved settings, and set histograms for it. | 
 |     const SettingsMap& settings_map = | 
 |         http_server_properties_->GetSpdySettings(host_port_pair()); | 
 |  | 
 |     SettingsMap::const_iterator it; | 
 |     for (it = settings_map.begin(); it != settings_map.end(); ++it) { | 
 |       const SpdySettingsIds id = it->first; | 
 |       const uint32 val = it->second.second; | 
 |       switch (id) { | 
 |         case SETTINGS_CURRENT_CWND: | 
 |           // Record several different histograms to see if cwnd converges | 
 |           // for larger volumes of data being sent. | 
 |           UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd", | 
 |                                       val, 1, 200, 100); | 
 |           if (bytes_received_ > 10 * 1024) { | 
 |             UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd10K", | 
 |                                         val, 1, 200, 100); | 
 |             if (bytes_received_ > 25 * 1024) { | 
 |               UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd25K", | 
 |                                           val, 1, 200, 100); | 
 |               if (bytes_received_ > 50 * 1024) { | 
 |                 UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd50K", | 
 |                                             val, 1, 200, 100); | 
 |                 if (bytes_received_ > 100 * 1024) { | 
 |                   UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd100K", | 
 |                                               val, 1, 200, 100); | 
 |                 } | 
 |               } | 
 |             } | 
 |           } | 
 |           break; | 
 |         case SETTINGS_ROUND_TRIP_TIME: | 
 |           UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsRTT", | 
 |                                       val, 1, 1200, 100); | 
 |           break; | 
 |         case SETTINGS_DOWNLOAD_RETRANS_RATE: | 
 |           UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsRetransRate", | 
 |                                       val, 1, 100, 50); | 
 |           break; | 
 |         default: | 
 |           break; | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void SpdySession::InvokeUserStreamCreationCallback( | 
 |     scoped_refptr<SpdyStream>* stream) { | 
 |   PendingCallbackMap::iterator it = pending_callback_map_.find(stream); | 
 |  | 
 |   // Exit if the request has already been cancelled. | 
 |   if (it == pending_callback_map_.end()) | 
 |     return; | 
 |  | 
 |   CompletionCallback callback = it->second.callback; | 
 |   int result = it->second.result; | 
 |   pending_callback_map_.erase(it); | 
 |   callback.Run(result); | 
 | } | 
 |  | 
 | SSLClientSocket* SpdySession::GetSSLClientSocket() const { | 
 |   if (!is_secure_) | 
 |     return NULL; | 
 |   SSLClientSocket* ssl_socket = | 
 |       reinterpret_cast<SSLClientSocket*>(connection_->socket()); | 
 |   DCHECK(ssl_socket); | 
 |   return ssl_socket; | 
 | } | 
 |  | 
 | }  // namespace net |