|  | // 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_websocket_stream.h" | 
|  |  | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "net/base/completion_callback.h" | 
|  | #include "net/proxy/proxy_server.h" | 
|  | #include "net/socket/ssl_client_socket.h" | 
|  | #include "net/spdy/spdy_http_utils.h" | 
|  | #include "net/spdy/spdy_protocol.h" | 
|  | #include "net/spdy/spdy_session.h" | 
|  | #include "net/spdy/spdy_test_util_spdy2.h" | 
|  | #include "net/spdy/spdy_websocket_test_util_spdy2.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | using namespace net::test_spdy2; | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | struct SpdyWebSocketStreamEvent { | 
|  | enum EventType { | 
|  | EVENT_CREATED, | 
|  | EVENT_SENT_HEADERS, | 
|  | EVENT_RECEIVED_HEADER, | 
|  | EVENT_SENT_DATA, | 
|  | EVENT_RECEIVED_DATA, | 
|  | EVENT_CLOSE, | 
|  | }; | 
|  | SpdyWebSocketStreamEvent(EventType type, | 
|  | const SpdyHeaderBlock& headers, | 
|  | int result, | 
|  | const std::string& data) | 
|  | : event_type(type), | 
|  | headers(headers), | 
|  | result(result), | 
|  | data(data) {} | 
|  |  | 
|  | EventType event_type; | 
|  | SpdyHeaderBlock headers; | 
|  | int result; | 
|  | std::string data; | 
|  | }; | 
|  |  | 
|  | class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStream::Delegate { | 
|  | public: | 
|  | explicit SpdyWebSocketStreamEventRecorder(const CompletionCallback& callback) | 
|  | : callback_(callback) {} | 
|  | virtual ~SpdyWebSocketStreamEventRecorder() {} | 
|  |  | 
|  | typedef base::Callback<void(SpdyWebSocketStreamEvent*)> StreamEventCallback; | 
|  |  | 
|  | void SetOnCreated(const StreamEventCallback& callback) { | 
|  | on_created_ = callback; | 
|  | } | 
|  | void SetOnSentHeaders(const StreamEventCallback& callback) { | 
|  | on_sent_headers_ = callback; | 
|  | } | 
|  | void SetOnReceivedHeader(const StreamEventCallback& callback) { | 
|  | on_received_header_ = callback; | 
|  | } | 
|  | void SetOnSentData(const StreamEventCallback& callback) { | 
|  | on_sent_data_ = callback; | 
|  | } | 
|  | void SetOnReceivedData(const StreamEventCallback& callback) { | 
|  | on_received_data_ = callback; | 
|  | } | 
|  | void SetOnClose(const StreamEventCallback& callback) { | 
|  | on_close_ = callback; | 
|  | } | 
|  |  | 
|  | virtual void OnCreatedSpdyStream(int result) { | 
|  | events_.push_back( | 
|  | SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED, | 
|  | SpdyHeaderBlock(), | 
|  | result, | 
|  | std::string())); | 
|  | if (!on_created_.is_null()) | 
|  | on_created_.Run(&events_.back()); | 
|  | } | 
|  | virtual void OnSentSpdyHeaders(int result) { | 
|  | events_.push_back( | 
|  | SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, | 
|  | SpdyHeaderBlock(), | 
|  | result, | 
|  | std::string())); | 
|  | if (!on_sent_data_.is_null()) | 
|  | on_sent_data_.Run(&events_.back()); | 
|  | } | 
|  | virtual int OnReceivedSpdyResponseHeader( | 
|  | const SpdyHeaderBlock& headers, int status) { | 
|  | events_.push_back( | 
|  | SpdyWebSocketStreamEvent( | 
|  | SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, | 
|  | headers, | 
|  | status, | 
|  | std::string())); | 
|  | if (!on_received_header_.is_null()) | 
|  | on_received_header_.Run(&events_.back()); | 
|  | return status; | 
|  | } | 
|  | virtual void OnSentSpdyData(int amount_sent) { | 
|  | events_.push_back( | 
|  | SpdyWebSocketStreamEvent( | 
|  | SpdyWebSocketStreamEvent::EVENT_SENT_DATA, | 
|  | SpdyHeaderBlock(), | 
|  | amount_sent, | 
|  | std::string())); | 
|  | if (!on_sent_data_.is_null()) | 
|  | on_sent_data_.Run(&events_.back()); | 
|  | } | 
|  | virtual void OnReceivedSpdyData(const char* data, int length) { | 
|  | events_.push_back( | 
|  | SpdyWebSocketStreamEvent( | 
|  | SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, | 
|  | SpdyHeaderBlock(), | 
|  | length, | 
|  | std::string(data, length))); | 
|  | if (!on_received_data_.is_null()) | 
|  | on_received_data_.Run(&events_.back()); | 
|  | } | 
|  | virtual void OnCloseSpdyStream() { | 
|  | events_.push_back( | 
|  | SpdyWebSocketStreamEvent( | 
|  | SpdyWebSocketStreamEvent::EVENT_CLOSE, | 
|  | SpdyHeaderBlock(), | 
|  | OK, | 
|  | std::string())); | 
|  | if (!on_close_.is_null()) | 
|  | on_close_.Run(&events_.back()); | 
|  | if (!callback_.is_null()) | 
|  | callback_.Run(OK); | 
|  | } | 
|  |  | 
|  | const std::vector<SpdyWebSocketStreamEvent>& GetSeenEvents() const { | 
|  | return events_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::vector<SpdyWebSocketStreamEvent> events_; | 
|  | StreamEventCallback on_created_; | 
|  | StreamEventCallback on_sent_headers_; | 
|  | StreamEventCallback on_received_header_; | 
|  | StreamEventCallback on_sent_data_; | 
|  | StreamEventCallback on_received_data_; | 
|  | StreamEventCallback on_close_; | 
|  | CompletionCallback callback_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStreamEventRecorder); | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class SpdyWebSocketStreamSpdy2Test : public testing::Test { | 
|  | public: | 
|  | OrderedSocketData* data() { return data_.get(); } | 
|  |  | 
|  | void DoSendHelloFrame(SpdyWebSocketStreamEvent* event) { | 
|  | // Record the actual stream_id. | 
|  | created_stream_id_ = websocket_stream_->stream_->stream_id(); | 
|  | websocket_stream_->SendData(kMessageFrame, kMessageFrameLength); | 
|  | } | 
|  |  | 
|  | void DoSendClosingFrame(SpdyWebSocketStreamEvent* event) { | 
|  | websocket_stream_->SendData(kClosingFrame, kClosingFrameLength); | 
|  | } | 
|  |  | 
|  | void DoClose(SpdyWebSocketStreamEvent* event) { | 
|  | websocket_stream_->Close(); | 
|  | } | 
|  |  | 
|  | void DoSync(SpdyWebSocketStreamEvent* event) { | 
|  | sync_callback_.callback().Run(OK); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | SpdyWebSocketStreamSpdy2Test() {} | 
|  | virtual ~SpdyWebSocketStreamSpdy2Test() {} | 
|  |  | 
|  | virtual void SetUp() { | 
|  | host_port_pair_.set_host("example.com"); | 
|  | host_port_pair_.set_port(80); | 
|  | host_port_proxy_pair_.first = host_port_pair_; | 
|  | host_port_proxy_pair_.second = ProxyServer::Direct(); | 
|  |  | 
|  | spdy_settings_id_to_set_ = SETTINGS_MAX_CONCURRENT_STREAMS; | 
|  | spdy_settings_flags_to_set_ = SETTINGS_FLAG_PLEASE_PERSIST; | 
|  | spdy_settings_value_to_set_ = 1; | 
|  |  | 
|  | spdy_settings_to_send_[spdy_settings_id_to_set_] = | 
|  | SettingsFlagsAndValue( | 
|  | SETTINGS_FLAG_PERSISTED, spdy_settings_value_to_set_); | 
|  | } | 
|  |  | 
|  | virtual void TearDown() { | 
|  | MessageLoop::current()->RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | void Prepare(SpdyStreamId stream_id) { | 
|  | stream_id_ = stream_id; | 
|  |  | 
|  | request_frame_.reset(ConstructSpdyWebSocketSynStream( | 
|  | stream_id_, | 
|  | "/echo", | 
|  | "example.com", | 
|  | "http://example.com/wsdemo")); | 
|  |  | 
|  | response_frame_.reset(ConstructSpdyWebSocketSynReply(stream_id_)); | 
|  |  | 
|  | message_frame_.reset(ConstructSpdyWebSocketDataFrame( | 
|  | kMessageFrame, | 
|  | kMessageFrameLength, | 
|  | stream_id_, | 
|  | false)); | 
|  |  | 
|  | closing_frame_.reset(ConstructSpdyWebSocketDataFrame( | 
|  | kClosingFrame, | 
|  | kClosingFrameLength, | 
|  | stream_id_, | 
|  | false)); | 
|  | } | 
|  |  | 
|  | int InitSession(MockRead* reads, size_t reads_count, | 
|  | MockWrite* writes, size_t writes_count, | 
|  | bool throttling) { | 
|  | data_.reset(new OrderedSocketData(reads, reads_count, | 
|  | writes, writes_count)); | 
|  | session_deps_.socket_factory->AddSocketDataProvider(data_.get()); | 
|  | http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); | 
|  | SpdySessionPool* spdy_session_pool(http_session_->spdy_session_pool()); | 
|  |  | 
|  | if (throttling) { | 
|  | // Set max concurrent streams to 1. | 
|  | spdy_session_pool->http_server_properties()->SetSpdySetting( | 
|  | host_port_pair_, | 
|  | spdy_settings_id_to_set_, | 
|  | spdy_settings_flags_to_set_, | 
|  | spdy_settings_value_to_set_); | 
|  | } | 
|  |  | 
|  | EXPECT_FALSE(spdy_session_pool->HasSession(host_port_proxy_pair_)); | 
|  | session_ = spdy_session_pool->Get(host_port_proxy_pair_, BoundNetLog()); | 
|  | EXPECT_TRUE(spdy_session_pool->HasSession(host_port_proxy_pair_)); | 
|  | transport_params_ = new TransportSocketParams(host_port_pair_, MEDIUM, | 
|  | false, false, | 
|  | OnHostResolutionCallback()); | 
|  | TestCompletionCallback callback; | 
|  | scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); | 
|  | EXPECT_EQ(ERR_IO_PENDING, | 
|  | connection->Init(host_port_pair_.ToString(), transport_params_, | 
|  | MEDIUM, callback.callback(), | 
|  | http_session_->GetTransportSocketPool( | 
|  | HttpNetworkSession::NORMAL_SOCKET_POOL), | 
|  | BoundNetLog())); | 
|  | EXPECT_EQ(OK, callback.WaitForResult()); | 
|  | return session_->InitializeWithSocket(connection.release(), false, OK); | 
|  | } | 
|  |  | 
|  | void SendRequest() { | 
|  | scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock); | 
|  | (*headers)["path"] = "/echo"; | 
|  | (*headers)["host"] = "example.com"; | 
|  | (*headers)["version"] = "WebSocket/13"; | 
|  | (*headers)["scheme"] = "ws"; | 
|  | (*headers)["origin"] = "http://example.com/wsdemo"; | 
|  |  | 
|  | websocket_stream_->SendRequest(headers.Pass()); | 
|  | } | 
|  |  | 
|  | SpdySettingsIds spdy_settings_id_to_set_; | 
|  | SpdySettingsFlags spdy_settings_flags_to_set_; | 
|  | uint32 spdy_settings_value_to_set_; | 
|  | SettingsMap spdy_settings_to_send_; | 
|  | SpdySessionDependencies session_deps_; | 
|  | scoped_ptr<OrderedSocketData> data_; | 
|  | scoped_refptr<HttpNetworkSession> http_session_; | 
|  | scoped_refptr<SpdySession> session_; | 
|  | scoped_refptr<TransportSocketParams> transport_params_; | 
|  | scoped_ptr<SpdyWebSocketStream> websocket_stream_; | 
|  | SpdyStreamId stream_id_; | 
|  | SpdyStreamId created_stream_id_; | 
|  | scoped_ptr<SpdyFrame> request_frame_; | 
|  | scoped_ptr<SpdyFrame> response_frame_; | 
|  | scoped_ptr<SpdyFrame> message_frame_; | 
|  | scoped_ptr<SpdyFrame> closing_frame_; | 
|  | HostPortPair host_port_pair_; | 
|  | HostPortProxyPair host_port_proxy_pair_; | 
|  | TestCompletionCallback completion_callback_; | 
|  | TestCompletionCallback sync_callback_; | 
|  |  | 
|  | static const char kMessageFrame[]; | 
|  | static const char kClosingFrame[]; | 
|  | static const size_t kMessageFrameLength; | 
|  | static const size_t kClosingFrameLength; | 
|  | }; | 
|  |  | 
|  | // TODO(toyoshim): Replace old framing data to new one, then use HEADERS and | 
|  | // data frames. | 
|  | const char SpdyWebSocketStreamSpdy2Test::kMessageFrame[] = "\x81\x05hello"; | 
|  | const char SpdyWebSocketStreamSpdy2Test::kClosingFrame[] = "\x88\0"; | 
|  | const size_t SpdyWebSocketStreamSpdy2Test::kMessageFrameLength = | 
|  | arraysize(SpdyWebSocketStreamSpdy2Test::kMessageFrame) - 1; | 
|  | const size_t SpdyWebSocketStreamSpdy2Test::kClosingFrameLength = | 
|  | arraysize(SpdyWebSocketStreamSpdy2Test::kClosingFrame) - 1; | 
|  |  | 
|  | TEST_F(SpdyWebSocketStreamSpdy2Test, Basic) { | 
|  | Prepare(1); | 
|  | MockWrite writes[] = { | 
|  | CreateMockWrite(*request_frame_.get(), 1), | 
|  | CreateMockWrite(*message_frame_.get(), 3), | 
|  | CreateMockWrite(*closing_frame_.get(), 5) | 
|  | }; | 
|  |  | 
|  | MockRead reads[] = { | 
|  | CreateMockRead(*response_frame_.get(), 2), | 
|  | CreateMockRead(*message_frame_.get(), 4), | 
|  | // Skip sequence 6 to notify closing has been sent. | 
|  | CreateMockRead(*closing_frame_.get(), 7), | 
|  | MockRead(SYNCHRONOUS, 0, 8)  // EOF cause OnCloseSpdyStream event. | 
|  | }; | 
|  |  | 
|  | EXPECT_EQ(OK, InitSession(reads, arraysize(reads), | 
|  | writes, arraysize(writes), false)); | 
|  |  | 
|  | SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); | 
|  | delegate.SetOnReceivedHeader( | 
|  | base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame, | 
|  | base::Unretained(this))); | 
|  | delegate.SetOnReceivedData( | 
|  | base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendClosingFrame, | 
|  | base::Unretained(this))); | 
|  |  | 
|  | websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); | 
|  |  | 
|  | BoundNetLog net_log; | 
|  | GURL url("ws://example.com/echo"); | 
|  | ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log)); | 
|  |  | 
|  | ASSERT_TRUE(websocket_stream_->stream_); | 
|  |  | 
|  | SendRequest(); | 
|  |  | 
|  | completion_callback_.WaitForResult(); | 
|  |  | 
|  | EXPECT_EQ(stream_id_, created_stream_id_); | 
|  |  | 
|  | websocket_stream_.reset(); | 
|  |  | 
|  | const std::vector<SpdyWebSocketStreamEvent>& events = | 
|  | delegate.GetSeenEvents(); | 
|  | ASSERT_EQ(7U, events.size()); | 
|  |  | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, | 
|  | events[0].event_type); | 
|  | EXPECT_LT(0, events[0].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, | 
|  | events[1].event_type); | 
|  | EXPECT_EQ(OK, events[1].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, | 
|  | events[2].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, | 
|  | events[3].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, | 
|  | events[4].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[4].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, | 
|  | events[5].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, | 
|  | events[6].event_type); | 
|  | EXPECT_EQ(OK, events[6].result); | 
|  |  | 
|  | // EOF close SPDY session. | 
|  | EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession( | 
|  | host_port_proxy_pair_)); | 
|  | EXPECT_TRUE(data()->at_read_eof()); | 
|  | EXPECT_TRUE(data()->at_write_eof()); | 
|  | } | 
|  |  | 
|  | TEST_F(SpdyWebSocketStreamSpdy2Test, DestructionBeforeClose) { | 
|  | Prepare(1); | 
|  | MockWrite writes[] = { | 
|  | CreateMockWrite(*request_frame_.get(), 1), | 
|  | CreateMockWrite(*message_frame_.get(), 3) | 
|  | }; | 
|  |  | 
|  | MockRead reads[] = { | 
|  | CreateMockRead(*response_frame_.get(), 2), | 
|  | CreateMockRead(*message_frame_.get(), 4), | 
|  | MockRead(ASYNC, ERR_IO_PENDING, 5) | 
|  | }; | 
|  |  | 
|  | EXPECT_EQ(OK, InitSession(reads, arraysize(reads), | 
|  | writes, arraysize(writes), false)); | 
|  |  | 
|  | SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); | 
|  | delegate.SetOnReceivedHeader( | 
|  | base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame, | 
|  | base::Unretained(this))); | 
|  | delegate.SetOnReceivedData( | 
|  | base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSync, | 
|  | base::Unretained(this))); | 
|  |  | 
|  | websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); | 
|  |  | 
|  | BoundNetLog net_log; | 
|  | GURL url("ws://example.com/echo"); | 
|  | ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log)); | 
|  |  | 
|  | SendRequest(); | 
|  |  | 
|  | sync_callback_.WaitForResult(); | 
|  |  | 
|  | // WebSocketStream destruction remove its SPDY stream from the session. | 
|  | EXPECT_TRUE(session_->IsStreamActive(stream_id_)); | 
|  | websocket_stream_.reset(); | 
|  | EXPECT_FALSE(session_->IsStreamActive(stream_id_)); | 
|  |  | 
|  | const std::vector<SpdyWebSocketStreamEvent>& events = | 
|  | delegate.GetSeenEvents(); | 
|  | ASSERT_GE(4U, events.size()); | 
|  |  | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, | 
|  | events[0].event_type); | 
|  | EXPECT_LT(0, events[0].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, | 
|  | events[1].event_type); | 
|  | EXPECT_EQ(OK, events[1].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, | 
|  | events[2].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, | 
|  | events[3].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); | 
|  |  | 
|  | EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession( | 
|  | host_port_proxy_pair_)); | 
|  | EXPECT_TRUE(data()->at_read_eof()); | 
|  | EXPECT_TRUE(data()->at_write_eof()); | 
|  | } | 
|  |  | 
|  | TEST_F(SpdyWebSocketStreamSpdy2Test, DestructionAfterExplicitClose) { | 
|  | Prepare(1); | 
|  | MockWrite writes[] = { | 
|  | CreateMockWrite(*request_frame_.get(), 1), | 
|  | CreateMockWrite(*message_frame_.get(), 3), | 
|  | CreateMockWrite(*closing_frame_.get(), 5) | 
|  | }; | 
|  |  | 
|  | MockRead reads[] = { | 
|  | CreateMockRead(*response_frame_.get(), 2), | 
|  | CreateMockRead(*message_frame_.get(), 4), | 
|  | MockRead(ASYNC, ERR_IO_PENDING, 6) | 
|  | }; | 
|  |  | 
|  | EXPECT_EQ(OK, InitSession(reads, arraysize(reads), | 
|  | writes, arraysize(writes), false)); | 
|  |  | 
|  | SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); | 
|  | delegate.SetOnReceivedHeader( | 
|  | base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame, | 
|  | base::Unretained(this))); | 
|  | delegate.SetOnReceivedData( | 
|  | base::Bind(&SpdyWebSocketStreamSpdy2Test::DoClose, | 
|  | base::Unretained(this))); | 
|  |  | 
|  | websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); | 
|  |  | 
|  | BoundNetLog net_log; | 
|  | GURL url("ws://example.com/echo"); | 
|  | ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log)); | 
|  |  | 
|  | SendRequest(); | 
|  |  | 
|  | completion_callback_.WaitForResult(); | 
|  |  | 
|  | // SPDY stream has already been removed from the session by Close(). | 
|  | EXPECT_FALSE(session_->IsStreamActive(stream_id_)); | 
|  | websocket_stream_.reset(); | 
|  |  | 
|  | const std::vector<SpdyWebSocketStreamEvent>& events = | 
|  | delegate.GetSeenEvents(); | 
|  | ASSERT_EQ(5U, events.size()); | 
|  |  | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, | 
|  | events[0].event_type); | 
|  | EXPECT_LT(0, events[0].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, | 
|  | events[1].event_type); | 
|  | EXPECT_EQ(OK, events[1].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, | 
|  | events[2].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, | 
|  | events[3].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, events[4].event_type); | 
|  |  | 
|  | EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession( | 
|  | host_port_proxy_pair_)); | 
|  | } | 
|  |  | 
|  | TEST_F(SpdyWebSocketStreamSpdy2Test, IOPending) { | 
|  | Prepare(1); | 
|  | scoped_ptr<SpdyFrame> settings_frame( | 
|  | ConstructSpdySettings(spdy_settings_to_send_)); | 
|  | MockWrite writes[] = { | 
|  | // Setting throttling make SpdySession send settings frame automatically. | 
|  | CreateMockWrite(*settings_frame.get(), 1), | 
|  | CreateMockWrite(*request_frame_.get(), 3), | 
|  | CreateMockWrite(*message_frame_.get(), 6), | 
|  | CreateMockWrite(*closing_frame_.get(), 9) | 
|  | }; | 
|  |  | 
|  | MockRead reads[] = { | 
|  | CreateMockRead(*settings_frame.get(), 2), | 
|  | CreateMockRead(*response_frame_.get(), 4), | 
|  | // Skip sequence 5 (I/O Pending) | 
|  | CreateMockRead(*message_frame_.get(), 7), | 
|  | // Skip sequence 8 (I/O Pending) | 
|  | CreateMockRead(*closing_frame_.get(), 10), | 
|  | MockRead(SYNCHRONOUS, 0, 11)  // EOF cause OnCloseSpdyStream event. | 
|  | }; | 
|  |  | 
|  | EXPECT_EQ(OK, InitSession(reads, arraysize(reads), | 
|  | writes, arraysize(writes), true)); | 
|  |  | 
|  | // Create a dummy WebSocketStream which cause ERR_IO_PENDING to another | 
|  | // WebSocketStream under test. | 
|  | SpdyWebSocketStreamEventRecorder block_delegate((CompletionCallback())); | 
|  |  | 
|  | scoped_ptr<SpdyWebSocketStream> block_stream( | 
|  | new SpdyWebSocketStream(session_, &block_delegate)); | 
|  | BoundNetLog block_net_log; | 
|  | GURL block_url("ws://example.com/block"); | 
|  | ASSERT_EQ(OK, | 
|  | block_stream->InitializeStream(block_url, HIGHEST, block_net_log)); | 
|  |  | 
|  | // Create a WebSocketStream under test. | 
|  | SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); | 
|  | delegate.SetOnCreated( | 
|  | base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSync, | 
|  | base::Unretained(this))); | 
|  | delegate.SetOnReceivedHeader( | 
|  | base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame, | 
|  | base::Unretained(this))); | 
|  | delegate.SetOnReceivedData( | 
|  | base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendClosingFrame, | 
|  | base::Unretained(this))); | 
|  |  | 
|  | websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); | 
|  | BoundNetLog net_log; | 
|  | GURL url("ws://example.com/echo"); | 
|  | ASSERT_EQ(ERR_IO_PENDING, websocket_stream_->InitializeStream( | 
|  | url, HIGHEST, net_log)); | 
|  |  | 
|  | // Delete the fist stream to allow create the second stream. | 
|  | block_stream.reset(); | 
|  | ASSERT_EQ(OK, sync_callback_.WaitForResult()); | 
|  |  | 
|  | SendRequest(); | 
|  |  | 
|  | completion_callback_.WaitForResult(); | 
|  |  | 
|  | websocket_stream_.reset(); | 
|  |  | 
|  | const std::vector<SpdyWebSocketStreamEvent>& block_events = | 
|  | block_delegate.GetSeenEvents(); | 
|  | ASSERT_EQ(0U, block_events.size()); | 
|  |  | 
|  | const std::vector<SpdyWebSocketStreamEvent>& events = | 
|  | delegate.GetSeenEvents(); | 
|  | ASSERT_EQ(8U, events.size()); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CREATED, | 
|  | events[0].event_type); | 
|  | EXPECT_EQ(0, events[0].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, | 
|  | events[1].event_type); | 
|  | EXPECT_LT(0, events[1].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, | 
|  | events[2].event_type); | 
|  | EXPECT_EQ(OK, events[2].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, | 
|  | events[3].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, | 
|  | events[4].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[4].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, | 
|  | events[5].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, | 
|  | events[6].event_type); | 
|  | EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[6].result); | 
|  | EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, | 
|  | events[7].event_type); | 
|  | EXPECT_EQ(OK, events[7].result); | 
|  |  | 
|  | // EOF close SPDY session. | 
|  | EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession( | 
|  | host_port_proxy_pair_)); | 
|  | EXPECT_TRUE(data()->at_read_eof()); | 
|  | EXPECT_TRUE(data()->at_write_eof()); | 
|  | } | 
|  |  | 
|  | }  // namespace net |