diff options
author | ricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-06 14:36:43 +0000 |
---|---|---|
committer | ricea@chromium.org <ricea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-06 14:36:43 +0000 |
commit | e0f09778ac26c858942a423b5a2cf46f8f0295d5 (patch) | |
tree | 981fc840596176d0491ff992a0848a2c2ca7370c /net/websockets/websocket_job_test.cc | |
parent | 077ede162076f69daf951ca78eecbe675de85f87 (diff) | |
download | chromium_src-e0f09778ac26c858942a423b5a2cf46f8f0295d5.zip chromium_src-e0f09778ac26c858942a423b5a2cf46f8f0295d5.tar.gz chromium_src-e0f09778ac26c858942a423b5a2cf46f8f0295d5.tar.bz2 |
Rename websockets/*_unittest.cc to *_test.cc
The style guide says that the _test suffix should be used for tests, and the
_unittest suffix is deprecated. Rename all the src/net/websockets test files to
match.
BUG=280943
TEST=net_unittests --gtest_filter="WebSocket*"
Review URL: https://chromiumcodereview.appspot.com/23614009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@221708 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/websockets/websocket_job_test.cc')
-rw-r--r-- | net/websockets/websocket_job_test.cc | 1112 |
1 files changed, 1112 insertions, 0 deletions
diff --git a/net/websockets/websocket_job_test.cc b/net/websockets/websocket_job_test.cc new file mode 100644 index 0000000..434796d --- /dev/null +++ b/net/websockets/websocket_job_test.cc @@ -0,0 +1,1112 @@ +// Copyright 2013 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/websockets/websocket_job.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "net/base/completion_callback.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/cookies/cookie_store.h" +#include "net/cookies/cookie_store_test_helpers.h" +#include "net/dns/mock_host_resolver.h" +#include "net/http/http_transaction_factory.h" +#include "net/http/transport_security_state.h" +#include "net/proxy/proxy_service.h" +#include "net/socket/next_proto.h" +#include "net/socket/socket_test_util.h" +#include "net/socket_stream/socket_stream.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_websocket_test_util.h" +#include "net/ssl/ssl_config_service.h" +#include "net/url_request/url_request_context.h" +#include "net/websockets/websocket_throttle.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" +#include "url/gurl.h" + +namespace net { + +namespace { + +class MockSocketStream : public SocketStream { + public: + MockSocketStream(const GURL& url, SocketStream::Delegate* delegate) + : SocketStream(url, delegate) {} + + virtual void Connect() OVERRIDE {} + virtual bool SendData(const char* data, int len) OVERRIDE { + sent_data_ += std::string(data, len); + return true; + } + + virtual void Close() OVERRIDE {} + virtual void RestartWithAuth( + const AuthCredentials& credentials) OVERRIDE { + } + + virtual void DetachDelegate() OVERRIDE { + delegate_ = NULL; + } + + const std::string& sent_data() const { + return sent_data_; + } + + protected: + virtual ~MockSocketStream() {} + + private: + std::string sent_data_; +}; + +class MockSocketStreamDelegate : public SocketStream::Delegate { + public: + MockSocketStreamDelegate() + : amount_sent_(0), allow_all_cookies_(true) {} + void set_allow_all_cookies(bool allow_all_cookies) { + allow_all_cookies_ = allow_all_cookies; + } + virtual ~MockSocketStreamDelegate() {} + + void SetOnStartOpenConnection(const base::Closure& callback) { + on_start_open_connection_ = callback; + } + void SetOnConnected(const base::Closure& callback) { + on_connected_ = callback; + } + void SetOnSentData(const base::Closure& callback) { + on_sent_data_ = callback; + } + void SetOnReceivedData(const base::Closure& callback) { + on_received_data_ = callback; + } + void SetOnClose(const base::Closure& callback) { + on_close_ = callback; + } + + virtual int OnStartOpenConnection( + SocketStream* socket, + const CompletionCallback& callback) OVERRIDE { + if (!on_start_open_connection_.is_null()) + on_start_open_connection_.Run(); + return OK; + } + virtual void OnConnected(SocketStream* socket, + int max_pending_send_allowed) OVERRIDE { + if (!on_connected_.is_null()) + on_connected_.Run(); + } + virtual void OnSentData(SocketStream* socket, + int amount_sent) OVERRIDE { + amount_sent_ += amount_sent; + if (!on_sent_data_.is_null()) + on_sent_data_.Run(); + } + virtual void OnReceivedData(SocketStream* socket, + const char* data, int len) OVERRIDE { + received_data_ += std::string(data, len); + if (!on_received_data_.is_null()) + on_received_data_.Run(); + } + virtual void OnClose(SocketStream* socket) OVERRIDE { + if (!on_close_.is_null()) + on_close_.Run(); + } + virtual bool CanGetCookies(SocketStream* socket, + const GURL& url) OVERRIDE { + return allow_all_cookies_; + } + virtual bool CanSetCookie(SocketStream* request, + const GURL& url, + const std::string& cookie_line, + CookieOptions* options) OVERRIDE { + return allow_all_cookies_; + } + + size_t amount_sent() const { return amount_sent_; } + const std::string& received_data() const { return received_data_; } + + private: + int amount_sent_; + bool allow_all_cookies_; + std::string received_data_; + base::Closure on_start_open_connection_; + base::Closure on_connected_; + base::Closure on_sent_data_; + base::Closure on_received_data_; + base::Closure on_close_; +}; + +class MockCookieStore : public CookieStore { + public: + struct Entry { + GURL url; + std::string cookie_line; + CookieOptions options; + }; + + MockCookieStore() {} + + bool SetCookieWithOptions(const GURL& url, + const std::string& cookie_line, + const CookieOptions& options) { + Entry entry; + entry.url = url; + entry.cookie_line = cookie_line; + entry.options = options; + entries_.push_back(entry); + return true; + } + + std::string GetCookiesWithOptions(const GURL& url, + const CookieOptions& options) { + std::string result; + for (size_t i = 0; i < entries_.size(); i++) { + Entry& entry = entries_[i]; + if (url == entry.url) { + if (!result.empty()) { + result += "; "; + } + result += entry.cookie_line; + } + } + return result; + } + + // CookieStore: + virtual void SetCookieWithOptionsAsync( + const GURL& url, + const std::string& cookie_line, + const CookieOptions& options, + const SetCookiesCallback& callback) OVERRIDE { + bool result = SetCookieWithOptions(url, cookie_line, options); + if (!callback.is_null()) + callback.Run(result); + } + + virtual void GetCookiesWithOptionsAsync( + const GURL& url, + const CookieOptions& options, + const GetCookiesCallback& callback) OVERRIDE { + if (!callback.is_null()) + callback.Run(GetCookiesWithOptions(url, options)); + } + + virtual void DeleteCookieAsync(const GURL& url, + const std::string& cookie_name, + const base::Closure& callback) OVERRIDE { + ADD_FAILURE(); + } + + virtual void DeleteAllCreatedBetweenAsync( + const base::Time& delete_begin, + const base::Time& delete_end, + const DeleteCallback& callback) OVERRIDE { + ADD_FAILURE(); + } + + virtual void DeleteSessionCookiesAsync(const DeleteCallback&) OVERRIDE { + ADD_FAILURE(); + } + + virtual CookieMonster* GetCookieMonster() OVERRIDE { return NULL; } + + const std::vector<Entry>& entries() const { return entries_; } + + private: + friend class base::RefCountedThreadSafe<MockCookieStore>; + virtual ~MockCookieStore() {} + + std::vector<Entry> entries_; +}; + +class MockSSLConfigService : public SSLConfigService { + public: + virtual void GetSSLConfig(SSLConfig* config) OVERRIDE {} + + protected: + virtual ~MockSSLConfigService() {} +}; + +class MockURLRequestContext : public URLRequestContext { + public: + explicit MockURLRequestContext(CookieStore* cookie_store) + : transport_security_state_() { + set_cookie_store(cookie_store); + set_transport_security_state(&transport_security_state_); + base::Time expiry = base::Time::Now() + base::TimeDelta::FromDays(1000); + bool include_subdomains = false; + transport_security_state_.AddHSTS("upgrademe.com", expiry, + include_subdomains); + } + + virtual ~MockURLRequestContext() {} + + private: + TransportSecurityState transport_security_state_; +}; + +class MockHttpTransactionFactory : public HttpTransactionFactory { + public: + MockHttpTransactionFactory(NextProto next_proto, OrderedSocketData* data) { + data_ = data; + MockConnect connect_data(SYNCHRONOUS, OK); + data_->set_connect_data(connect_data); + session_deps_.reset(new SpdySessionDependencies(next_proto)); + session_deps_->socket_factory->AddSocketDataProvider(data_); + http_session_ = + SpdySessionDependencies::SpdyCreateSession(session_deps_.get()); + host_port_pair_.set_host("example.com"); + host_port_pair_.set_port(80); + spdy_session_key_ = SpdySessionKey(host_port_pair_, + ProxyServer::Direct(), + kPrivacyModeDisabled); + session_ = CreateInsecureSpdySession( + http_session_, spdy_session_key_, BoundNetLog()); + } + + virtual int CreateTransaction( + RequestPriority priority, + scoped_ptr<HttpTransaction>* trans, + HttpTransactionDelegate* delegate) OVERRIDE { + NOTREACHED(); + return ERR_UNEXPECTED; + } + + virtual HttpCache* GetCache() OVERRIDE { + NOTREACHED(); + return NULL; + } + + virtual HttpNetworkSession* GetSession() OVERRIDE { + return http_session_.get(); + } + + private: + OrderedSocketData* data_; + scoped_ptr<SpdySessionDependencies> session_deps_; + scoped_refptr<HttpNetworkSession> http_session_; + base::WeakPtr<SpdySession> session_; + HostPortPair host_port_pair_; + SpdySessionKey spdy_session_key_; +}; + +} // namespace + +class WebSocketJobTest : public PlatformTest, + public ::testing::WithParamInterface<NextProto> { + public: + WebSocketJobTest() : spdy_util_(GetParam()) {} + + virtual void SetUp() OVERRIDE { + stream_type_ = STREAM_INVALID; + cookie_store_ = new MockCookieStore; + context_.reset(new MockURLRequestContext(cookie_store_.get())); + } + virtual void TearDown() OVERRIDE { + cookie_store_ = NULL; + context_.reset(); + websocket_ = NULL; + socket_ = NULL; + } + void DoSendRequest() { + EXPECT_TRUE(websocket_->SendData(kHandshakeRequestWithoutCookie, + kHandshakeRequestWithoutCookieLength)); + } + void DoSendData() { + if (received_data().size() == kHandshakeResponseWithoutCookieLength) + websocket_->SendData(kDataHello, kDataHelloLength); + } + void DoSync() { + sync_test_callback_.callback().Run(OK); + } + int WaitForResult() { + return sync_test_callback_.WaitForResult(); + } + protected: + enum StreamType { + STREAM_INVALID, + STREAM_MOCK_SOCKET, + STREAM_SOCKET, + STREAM_SPDY_WEBSOCKET, + }; + enum ThrottlingOption { + THROTTLING_OFF, + THROTTLING_ON, + }; + enum SpdyOption { + SPDY_OFF, + SPDY_ON, + }; + void InitWebSocketJob(const GURL& url, + MockSocketStreamDelegate* delegate, + StreamType stream_type) { + DCHECK_NE(STREAM_INVALID, stream_type); + stream_type_ = stream_type; + websocket_ = new WebSocketJob(delegate); + + if (stream_type == STREAM_MOCK_SOCKET) + socket_ = new MockSocketStream(url, websocket_.get()); + + if (stream_type == STREAM_SOCKET || stream_type == STREAM_SPDY_WEBSOCKET) { + if (stream_type == STREAM_SPDY_WEBSOCKET) { + http_factory_.reset( + new MockHttpTransactionFactory(GetParam(), data_.get())); + context_->set_http_transaction_factory(http_factory_.get()); + } + + ssl_config_service_ = new MockSSLConfigService(); + context_->set_ssl_config_service(ssl_config_service_.get()); + proxy_service_.reset(ProxyService::CreateDirect()); + context_->set_proxy_service(proxy_service_.get()); + host_resolver_.reset(new MockHostResolver); + context_->set_host_resolver(host_resolver_.get()); + + socket_ = new SocketStream(url, websocket_.get()); + socket_factory_.reset(new MockClientSocketFactory); + DCHECK(data_.get()); + socket_factory_->AddSocketDataProvider(data_.get()); + socket_->SetClientSocketFactory(socket_factory_.get()); + } + + websocket_->InitSocketStream(socket_.get()); + websocket_->set_context(context_.get()); + // MockHostResolver resolves all hosts to 127.0.0.1; however, when we create + // a WebSocketJob purely to block another one in a throttling test, we don't + // perform a real connect. In that case, the following address is used + // instead. + IPAddressNumber ip; + ParseIPLiteralToNumber("127.0.0.1", &ip); + websocket_->addresses_ = AddressList::CreateFromIPAddress(ip, 80); + } + void SkipToConnecting() { + websocket_->state_ = WebSocketJob::CONNECTING; + ASSERT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(websocket_.get())); + } + WebSocketJob::State GetWebSocketJobState() { + return websocket_->state_; + } + void CloseWebSocketJob() { + if (websocket_->socket_.get()) { + websocket_->socket_->DetachDelegate(); + WebSocketThrottle::GetInstance()->RemoveFromQueue(websocket_.get()); + } + websocket_->state_ = WebSocketJob::CLOSED; + websocket_->delegate_ = NULL; + websocket_->socket_ = NULL; + } + SocketStream* GetSocket(SocketStreamJob* job) { + return job->socket_.get(); + } + const std::string& sent_data() const { + DCHECK_EQ(STREAM_MOCK_SOCKET, stream_type_); + MockSocketStream* socket = + static_cast<MockSocketStream*>(socket_.get()); + DCHECK(socket); + return socket->sent_data(); + } + const std::string& received_data() const { + DCHECK_NE(STREAM_INVALID, stream_type_); + MockSocketStreamDelegate* delegate = + static_cast<MockSocketStreamDelegate*>(websocket_->delegate_); + DCHECK(delegate); + return delegate->received_data(); + } + + void TestSimpleHandshake(); + void TestSlowHandshake(); + void TestHandshakeWithCookie(); + void TestHandshakeWithCookieButNotAllowed(); + void TestHSTSUpgrade(); + void TestInvalidSendData(); + void TestConnectByWebSocket(ThrottlingOption throttling); + void TestConnectBySpdy(SpdyOption spdy, ThrottlingOption throttling); + void TestThrottlingLimit(); + + SpdyWebSocketTestUtil spdy_util_; + StreamType stream_type_; + scoped_refptr<MockCookieStore> cookie_store_; + scoped_ptr<MockURLRequestContext> context_; + scoped_refptr<WebSocketJob> websocket_; + scoped_refptr<SocketStream> socket_; + scoped_ptr<MockClientSocketFactory> socket_factory_; + scoped_ptr<OrderedSocketData> data_; + TestCompletionCallback sync_test_callback_; + scoped_refptr<MockSSLConfigService> ssl_config_service_; + scoped_ptr<ProxyService> proxy_service_; + scoped_ptr<MockHostResolver> host_resolver_; + scoped_ptr<MockHttpTransactionFactory> http_factory_; + + static const char kHandshakeRequestWithoutCookie[]; + static const char kHandshakeRequestWithCookie[]; + static const char kHandshakeRequestWithFilteredCookie[]; + static const char kHandshakeResponseWithoutCookie[]; + static const char kHandshakeResponseWithCookie[]; + static const char kDataHello[]; + static const char kDataWorld[]; + static const char* const kHandshakeRequestForSpdy[]; + static const char* const kHandshakeResponseForSpdy[]; + static const size_t kHandshakeRequestWithoutCookieLength; + static const size_t kHandshakeRequestWithCookieLength; + static const size_t kHandshakeRequestWithFilteredCookieLength; + static const size_t kHandshakeResponseWithoutCookieLength; + static const size_t kHandshakeResponseWithCookieLength; + static const size_t kDataHelloLength; + static const size_t kDataWorldLength; +}; + +const char WebSocketJobTest::kHandshakeRequestWithoutCookie[] = + "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Origin: http://example.com\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"; + +const char WebSocketJobTest::kHandshakeRequestWithCookie[] = + "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Origin: http://example.com\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Cookie: WK-test=1\r\n" + "\r\n"; + +const char WebSocketJobTest::kHandshakeRequestWithFilteredCookie[] = + "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Origin: http://example.com\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Cookie: CR-test=1; CR-test-httponly=1\r\n" + "\r\n"; + +const char WebSocketJobTest::kHandshakeResponseWithoutCookie[] = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "\r\n"; + +const char WebSocketJobTest::kHandshakeResponseWithCookie[] = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Set-Cookie: CR-set-test=1\r\n" + "\r\n"; + +const char WebSocketJobTest::kDataHello[] = "Hello, "; + +const char WebSocketJobTest::kDataWorld[] = "World!\n"; + +const size_t WebSocketJobTest::kHandshakeRequestWithoutCookieLength = + arraysize(kHandshakeRequestWithoutCookie) - 1; +const size_t WebSocketJobTest::kHandshakeRequestWithCookieLength = + arraysize(kHandshakeRequestWithCookie) - 1; +const size_t WebSocketJobTest::kHandshakeRequestWithFilteredCookieLength = + arraysize(kHandshakeRequestWithFilteredCookie) - 1; +const size_t WebSocketJobTest::kHandshakeResponseWithoutCookieLength = + arraysize(kHandshakeResponseWithoutCookie) - 1; +const size_t WebSocketJobTest::kHandshakeResponseWithCookieLength = + arraysize(kHandshakeResponseWithCookie) - 1; +const size_t WebSocketJobTest::kDataHelloLength = + arraysize(kDataHello) - 1; +const size_t WebSocketJobTest::kDataWorldLength = + arraysize(kDataWorld) - 1; + +void WebSocketJobTest::TestSimpleHandshake() { + GURL url("ws://example.com/demo"); + MockSocketStreamDelegate delegate; + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + DoSendRequest(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_.get(), + kHandshakeRequestWithoutCookieLength); + EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); + + websocket_->OnReceivedData(socket_.get(), + kHandshakeResponseWithoutCookie, + kHandshakeResponseWithoutCookieLength); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); + EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); + CloseWebSocketJob(); +} + +void WebSocketJobTest::TestSlowHandshake() { + GURL url("ws://example.com/demo"); + MockSocketStreamDelegate delegate; + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + DoSendRequest(); + // We assume request is sent in one data chunk (from WebKit) + // We don't support streaming request. + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_.get(), + kHandshakeRequestWithoutCookieLength); + EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); + + std::vector<std::string> lines; + base::SplitString(kHandshakeResponseWithoutCookie, '\n', &lines); + for (size_t i = 0; i < lines.size() - 2; i++) { + std::string line = lines[i] + "\r\n"; + SCOPED_TRACE("Line: " + line); + websocket_->OnReceivedData(socket_.get(), line.c_str(), line.size()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(delegate.received_data().empty()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + } + websocket_->OnReceivedData(socket_.get(), "\r\n", 2); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_FALSE(delegate.received_data().empty()); + EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); + EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); + CloseWebSocketJob(); +} + +INSTANTIATE_TEST_CASE_P( + NextProto, + WebSocketJobTest, + testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2, + kProtoHTTP2Draft04)); + +TEST_P(WebSocketJobTest, DelayedCookies) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + GURL url("ws://example.com/demo"); + GURL cookieUrl("http://example.com/demo"); + CookieOptions cookie_options; + scoped_refptr<DelayedCookieMonster> cookie_store = new DelayedCookieMonster(); + context_->set_cookie_store(cookie_store.get()); + cookie_store->SetCookieWithOptionsAsync(cookieUrl, + "CR-test=1", + cookie_options, + CookieMonster::SetCookiesCallback()); + cookie_options.set_include_httponly(); + cookie_store->SetCookieWithOptionsAsync( + cookieUrl, "CR-test-httponly=1", cookie_options, + CookieMonster::SetCookiesCallback()); + + MockSocketStreamDelegate delegate; + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + bool sent = websocket_->SendData(kHandshakeRequestWithCookie, + kHandshakeRequestWithCookieLength); + EXPECT_TRUE(sent); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_.get(), + kHandshakeRequestWithFilteredCookieLength); + EXPECT_EQ(kHandshakeRequestWithCookieLength, + delegate.amount_sent()); + + websocket_->OnReceivedData(socket_.get(), + kHandshakeResponseWithCookie, + kHandshakeResponseWithCookieLength); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); + EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); + + CloseWebSocketJob(); +} + +void WebSocketJobTest::TestHandshakeWithCookie() { + GURL url("ws://example.com/demo"); + GURL cookieUrl("http://example.com/demo"); + CookieOptions cookie_options; + cookie_store_->SetCookieWithOptions( + cookieUrl, "CR-test=1", cookie_options); + cookie_options.set_include_httponly(); + cookie_store_->SetCookieWithOptions( + cookieUrl, "CR-test-httponly=1", cookie_options); + + MockSocketStreamDelegate delegate; + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + bool sent = websocket_->SendData(kHandshakeRequestWithCookie, + kHandshakeRequestWithCookieLength); + EXPECT_TRUE(sent); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_.get(), + kHandshakeRequestWithFilteredCookieLength); + EXPECT_EQ(kHandshakeRequestWithCookieLength, + delegate.amount_sent()); + + websocket_->OnReceivedData(socket_.get(), + kHandshakeResponseWithCookie, + kHandshakeResponseWithCookieLength); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); + EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); + + EXPECT_EQ(3U, cookie_store_->entries().size()); + EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); + EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); + EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); + EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); + EXPECT_EQ(cookieUrl, cookie_store_->entries()[2].url); + EXPECT_EQ("CR-set-test=1", cookie_store_->entries()[2].cookie_line); + + CloseWebSocketJob(); +} + +void WebSocketJobTest::TestHandshakeWithCookieButNotAllowed() { + GURL url("ws://example.com/demo"); + GURL cookieUrl("http://example.com/demo"); + CookieOptions cookie_options; + cookie_store_->SetCookieWithOptions( + cookieUrl, "CR-test=1", cookie_options); + cookie_options.set_include_httponly(); + cookie_store_->SetCookieWithOptions( + cookieUrl, "CR-test-httponly=1", cookie_options); + + MockSocketStreamDelegate delegate; + delegate.set_allow_all_cookies(false); + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + bool sent = websocket_->SendData(kHandshakeRequestWithCookie, + kHandshakeRequestWithCookieLength); + EXPECT_TRUE(sent); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_.get(), kHandshakeRequestWithoutCookieLength); + EXPECT_EQ(kHandshakeRequestWithCookieLength, delegate.amount_sent()); + + websocket_->OnReceivedData(socket_.get(), + kHandshakeResponseWithCookie, + kHandshakeResponseWithCookieLength); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); + EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); + + EXPECT_EQ(2U, cookie_store_->entries().size()); + EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); + EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); + EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); + EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); + + CloseWebSocketJob(); +} + +void WebSocketJobTest::TestHSTSUpgrade() { + GURL url("ws://upgrademe.com/"); + MockSocketStreamDelegate delegate; + scoped_refptr<SocketStreamJob> job = + SocketStreamJob::CreateSocketStreamJob( + url, &delegate, context_->transport_security_state(), + context_->ssl_config_service()); + EXPECT_TRUE(GetSocket(job.get())->is_secure()); + job->DetachDelegate(); + + url = GURL("ws://donotupgrademe.com/"); + job = SocketStreamJob::CreateSocketStreamJob( + url, &delegate, context_->transport_security_state(), + context_->ssl_config_service()); + EXPECT_FALSE(GetSocket(job.get())->is_secure()); + job->DetachDelegate(); +} + +void WebSocketJobTest::TestInvalidSendData() { + GURL url("ws://example.com/demo"); + MockSocketStreamDelegate delegate; + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + DoSendRequest(); + // We assume request is sent in one data chunk (from WebKit) + // We don't support streaming request. + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_.get(), + kHandshakeRequestWithoutCookieLength); + EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); + + // We could not send any data until connection is established. + bool sent = websocket_->SendData(kHandshakeRequestWithoutCookie, + kHandshakeRequestWithoutCookieLength); + EXPECT_FALSE(sent); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + CloseWebSocketJob(); +} + +// Following tests verify cooperation between WebSocketJob and SocketStream. +// Other former tests use MockSocketStream as SocketStream, so we could not +// check SocketStream behavior. +// OrderedSocketData provide socket level verifiation by checking out-going +// packets in comparison with the MockWrite array and emulating in-coming +// packets with MockRead array. + +void WebSocketJobTest::TestConnectByWebSocket( + ThrottlingOption throttling) { + // This is a test for verifying cooperation between WebSocketJob and + // SocketStream. If |throttling| was |THROTTLING_OFF|, it test basic + // situation. If |throttling| was |THROTTLING_ON|, throttling limits the + // latter connection. + MockWrite writes[] = { + MockWrite(ASYNC, + kHandshakeRequestWithoutCookie, + kHandshakeRequestWithoutCookieLength, + 1), + MockWrite(ASYNC, + kDataHello, + kDataHelloLength, + 3) + }; + MockRead reads[] = { + MockRead(ASYNC, + kHandshakeResponseWithoutCookie, + kHandshakeResponseWithoutCookieLength, + 2), + MockRead(ASYNC, + kDataWorld, + kDataWorldLength, + 4), + MockRead(SYNCHRONOUS, 0, 5) // EOF + }; + data_.reset(new OrderedSocketData( + reads, arraysize(reads), writes, arraysize(writes))); + + GURL url("ws://example.com/demo"); + MockSocketStreamDelegate delegate; + WebSocketJobTest* test = this; + if (throttling == THROTTLING_ON) + delegate.SetOnStartOpenConnection( + base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); + delegate.SetOnConnected( + base::Bind(&WebSocketJobTest::DoSendRequest, + base::Unretained(test))); + delegate.SetOnReceivedData( + base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test))); + delegate.SetOnClose( + base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); + InitWebSocketJob(url, &delegate, STREAM_SOCKET); + + scoped_refptr<WebSocketJob> block_websocket; + if (throttling == THROTTLING_ON) { + // Create former WebSocket object which obstructs the latter one. + block_websocket = new WebSocketJob(NULL); + block_websocket->addresses_ = AddressList(websocket_->address_list()); + ASSERT_TRUE( + WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get())); + } + + websocket_->Connect(); + + if (throttling == THROTTLING_ON) { + EXPECT_EQ(OK, WaitForResult()); + EXPECT_TRUE(websocket_->IsWaiting()); + + // Remove the former WebSocket object from throttling queue to unblock the + // latter. + block_websocket->state_ = WebSocketJob::CLOSED; + WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get()); + block_websocket = NULL; + } + + EXPECT_EQ(OK, WaitForResult()); + EXPECT_TRUE(data_->at_read_eof()); + EXPECT_TRUE(data_->at_write_eof()); + EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState()); +} + +void WebSocketJobTest::TestConnectBySpdy( + SpdyOption spdy, ThrottlingOption throttling) { + // This is a test for verifying cooperation between WebSocketJob and + // SocketStream in the situation we have SPDY session to the server. If + // |throttling| was |THROTTLING_ON|, throttling limits the latter connection. + // If you enabled spdy, you should specify |spdy| as |SPDY_ON|. Expected + // results depend on its configuration. + MockWrite writes_websocket[] = { + MockWrite(ASYNC, + kHandshakeRequestWithoutCookie, + kHandshakeRequestWithoutCookieLength, + 1), + MockWrite(ASYNC, + kDataHello, + kDataHelloLength, + 3) + }; + MockRead reads_websocket[] = { + MockRead(ASYNC, + kHandshakeResponseWithoutCookie, + kHandshakeResponseWithoutCookieLength, + 2), + MockRead(ASYNC, + kDataWorld, + kDataWorldLength, + 4), + MockRead(SYNCHRONOUS, 0, 5) // EOF + }; + + scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock()); + spdy_util_.SetHeader("path", "/demo", request_headers.get()); + spdy_util_.SetHeader("version", "WebSocket/13", request_headers.get()); + spdy_util_.SetHeader("scheme", "ws", request_headers.get()); + spdy_util_.SetHeader("host", "example.com", request_headers.get()); + spdy_util_.SetHeader("origin", "http://example.com", request_headers.get()); + spdy_util_.SetHeader("sec-websocket-protocol", "sample", + request_headers.get()); + + scoped_ptr<SpdyHeaderBlock> response_headers(new SpdyHeaderBlock()); + spdy_util_.SetHeader("status", "101 Switching Protocols", + response_headers.get()); + spdy_util_.SetHeader("sec-websocket-protocol", "sample", + response_headers.get()); + + const SpdyStreamId kStreamId = 1; + scoped_ptr<SpdyFrame> request_frame( + spdy_util_.ConstructSpdyWebSocketHandshakeRequestFrame( + request_headers.Pass(), + kStreamId, + MEDIUM)); + scoped_ptr<SpdyFrame> response_frame( + spdy_util_.ConstructSpdyWebSocketHandshakeResponseFrame( + response_headers.Pass(), + kStreamId, + MEDIUM)); + scoped_ptr<SpdyFrame> data_hello_frame( + spdy_util_.ConstructSpdyWebSocketDataFrame( + kDataHello, + kDataHelloLength, + kStreamId, + false)); + scoped_ptr<SpdyFrame> data_world_frame( + spdy_util_.ConstructSpdyWebSocketDataFrame( + kDataWorld, + kDataWorldLength, + kStreamId, + false)); + MockWrite writes_spdy[] = { + CreateMockWrite(*request_frame.get(), 1), + CreateMockWrite(*data_hello_frame.get(), 3), + }; + MockRead reads_spdy[] = { + CreateMockRead(*response_frame.get(), 2), + CreateMockRead(*data_world_frame.get(), 4), + MockRead(SYNCHRONOUS, 0, 5) // EOF + }; + + if (spdy == SPDY_ON) + data_.reset(new OrderedSocketData( + reads_spdy, arraysize(reads_spdy), + writes_spdy, arraysize(writes_spdy))); + else + data_.reset(new OrderedSocketData( + reads_websocket, arraysize(reads_websocket), + writes_websocket, arraysize(writes_websocket))); + + GURL url("ws://example.com/demo"); + MockSocketStreamDelegate delegate; + WebSocketJobTest* test = this; + if (throttling == THROTTLING_ON) + delegate.SetOnStartOpenConnection( + base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); + delegate.SetOnConnected( + base::Bind(&WebSocketJobTest::DoSendRequest, + base::Unretained(test))); + delegate.SetOnReceivedData( + base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test))); + delegate.SetOnClose( + base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); + InitWebSocketJob(url, &delegate, STREAM_SPDY_WEBSOCKET); + + scoped_refptr<WebSocketJob> block_websocket; + if (throttling == THROTTLING_ON) { + // Create former WebSocket object which obstructs the latter one. + block_websocket = new WebSocketJob(NULL); + block_websocket->addresses_ = AddressList(websocket_->address_list()); + ASSERT_TRUE( + WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get())); + } + + websocket_->Connect(); + + if (throttling == THROTTLING_ON) { + EXPECT_EQ(OK, WaitForResult()); + EXPECT_TRUE(websocket_->IsWaiting()); + + // Remove the former WebSocket object from throttling queue to unblock the + // latter. + block_websocket->state_ = WebSocketJob::CLOSED; + WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get()); + block_websocket = NULL; + } + + EXPECT_EQ(OK, WaitForResult()); + EXPECT_TRUE(data_->at_read_eof()); + EXPECT_TRUE(data_->at_write_eof()); + EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState()); +} + +void WebSocketJobTest::TestThrottlingLimit() { + std::vector<scoped_refptr<WebSocketJob> > jobs; + const int kMaxWebSocketJobsThrottled = 1024; + IPAddressNumber ip; + ParseIPLiteralToNumber("127.0.0.1", &ip); + for (int i = 0; i < kMaxWebSocketJobsThrottled + 1; ++i) { + scoped_refptr<WebSocketJob> job = new WebSocketJob(NULL); + job->addresses_ = AddressList(AddressList::CreateFromIPAddress(ip, 80)); + if (i >= kMaxWebSocketJobsThrottled) + EXPECT_FALSE(WebSocketThrottle::GetInstance()->PutInQueue(job)); + else + EXPECT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(job)); + jobs.push_back(job); + } + + // Close the jobs in reverse order. Otherwise, We need to make them prepared + // for Wakeup call. + for (std::vector<scoped_refptr<WebSocketJob> >::reverse_iterator iter = + jobs.rbegin(); + iter != jobs.rend(); + ++iter) { + WebSocketJob* job = (*iter).get(); + job->state_ = WebSocketJob::CLOSED; + WebSocketThrottle::GetInstance()->RemoveFromQueue(job); + } +} + +// Execute tests in both spdy-disabled mode and spdy-enabled mode. +TEST_P(WebSocketJobTest, SimpleHandshake) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestSimpleHandshake(); +} + +TEST_P(WebSocketJobTest, SlowHandshake) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestSlowHandshake(); +} + +TEST_P(WebSocketJobTest, HandshakeWithCookie) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestHandshakeWithCookie(); +} + +TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowed) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestHandshakeWithCookieButNotAllowed(); +} + +TEST_P(WebSocketJobTest, HSTSUpgrade) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestHSTSUpgrade(); +} + +TEST_P(WebSocketJobTest, InvalidSendData) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestInvalidSendData(); +} + +TEST_P(WebSocketJobTest, SimpleHandshakeSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestSimpleHandshake(); +} + +TEST_P(WebSocketJobTest, SlowHandshakeSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestSlowHandshake(); +} + +TEST_P(WebSocketJobTest, HandshakeWithCookieSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestHandshakeWithCookie(); +} + +TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowedSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestHandshakeWithCookieButNotAllowed(); +} + +TEST_P(WebSocketJobTest, HSTSUpgradeSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestHSTSUpgrade(); +} + +TEST_P(WebSocketJobTest, InvalidSendDataSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestInvalidSendData(); +} + +TEST_P(WebSocketJobTest, ConnectByWebSocket) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestConnectByWebSocket(THROTTLING_OFF); +} + +TEST_P(WebSocketJobTest, ConnectByWebSocketSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestConnectByWebSocket(THROTTLING_OFF); +} + +TEST_P(WebSocketJobTest, ConnectBySpdy) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestConnectBySpdy(SPDY_OFF, THROTTLING_OFF); +} + +TEST_P(WebSocketJobTest, ConnectBySpdySpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestConnectBySpdy(SPDY_ON, THROTTLING_OFF); +} + +TEST_P(WebSocketJobTest, ThrottlingWebSocket) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestConnectByWebSocket(THROTTLING_ON); +} + +TEST_P(WebSocketJobTest, ThrottlingMaxNumberOfThrottledJobLimit) { + TestThrottlingLimit(); +} + +TEST_P(WebSocketJobTest, ThrottlingWebSocketSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestConnectByWebSocket(THROTTLING_ON); +} + +TEST_P(WebSocketJobTest, ThrottlingSpdy) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestConnectBySpdy(SPDY_OFF, THROTTLING_ON); +} + +TEST_P(WebSocketJobTest, ThrottlingSpdySpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestConnectBySpdy(SPDY_ON, THROTTLING_ON); +} + +// TODO(toyoshim): Add tests to verify throttling, SPDY stream limitation. +// TODO(toyoshim,yutak): Add tests to verify closing handshake. +} // namespace net |