// Copyright (c) 2011 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 "base/memory/ref_counted.h" #include "net/spdy/spdy_stream.h" #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_session.h" #include "net/spdy/spdy_test_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { // TODO(ukai): factor out common part with spdy_http_stream_unittest.cc class SpdySessionPoolPeer { public: explicit SpdySessionPoolPeer(SpdySessionPool* pool) : pool_(pool) {} void RemoveSpdySession(const scoped_refptr<SpdySession>& session) { pool_->Remove(session); } private: SpdySessionPool* const pool_; DISALLOW_COPY_AND_ASSIGN(SpdySessionPoolPeer); }; namespace { class TestSpdyStreamDelegate : public SpdyStream::Delegate { public: TestSpdyStreamDelegate(SpdyStream* stream, IOBufferWithSize* buf, CompletionCallback* callback) : stream_(stream), buf_(buf), callback_(callback), send_headers_completed_(false), response_(new spdy::SpdyHeaderBlock), data_sent_(0), closed_(false) {} virtual ~TestSpdyStreamDelegate() {} virtual bool OnSendHeadersComplete(int status) { send_headers_completed_ = true; return true; } virtual int OnSendBody() { ADD_FAILURE() << "OnSendBody should not be called"; return ERR_UNEXPECTED; } virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) { ADD_FAILURE() << "OnSendBodyComplete should not be called"; return ERR_UNEXPECTED; } virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response, base::Time response_time, int status) { EXPECT_TRUE(send_headers_completed_); *response_ = response; if (buf_) { EXPECT_EQ(ERR_IO_PENDING, stream_->WriteStreamData(buf_.get(), buf_->size(), spdy::DATA_FLAG_NONE)); } return status; } virtual void OnDataReceived(const char* buffer, int bytes) { received_data_ += std::string(buffer, bytes); } virtual void OnDataSent(int length) { data_sent_ += length; } virtual void OnClose(int status) { closed_ = true; CompletionCallback* callback = callback_; callback_ = NULL; callback->Run(OK); } virtual void set_chunk_callback(net::ChunkCallback *) {} bool send_headers_completed() const { return send_headers_completed_; } const linked_ptr<spdy::SpdyHeaderBlock>& response() const { return response_; } const std::string& received_data() const { return received_data_; } int data_sent() const { return data_sent_; } bool closed() const { return closed_; } private: SpdyStream* stream_; scoped_refptr<IOBufferWithSize> buf_; CompletionCallback* callback_; bool send_headers_completed_; linked_ptr<spdy::SpdyHeaderBlock> response_; std::string received_data_; int data_sent_; bool closed_; }; spdy::SpdyFrame* ConstructSpdyBodyFrame(const char* data, int length) { spdy::SpdyFramer framer; return framer.CreateDataFrame(1, data, length, spdy::DATA_FLAG_NONE); } } // anonymous namespace class SpdyStreamTest : public testing::Test { protected: SpdyStreamTest() { } scoped_refptr<SpdySession> CreateSpdySession() { spdy::SpdyFramer::set_enable_compression_default(false); HostPortPair host_port_pair("www.google.com", 80); HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); scoped_refptr<SpdySession> session( session_->spdy_session_pool()->Get(pair, BoundNetLog())); return session; } virtual void TearDown() { MessageLoop::current()->RunAllPending(); } scoped_refptr<HttpNetworkSession> session_; }; TEST_F(SpdyStreamTest, SendDataAfterOpen) { SpdySessionDependencies session_deps; session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps); SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool()); const SpdyHeaderInfo kSynStartHeader = { spdy::SYN_STREAM, 1, 0, net::ConvertRequestPriorityToSpdyPriority(LOWEST), spdy::CONTROL_FLAG_NONE, false, spdy::INVALID, NULL, 0, spdy::DATA_FLAG_NONE }; static const char* const kGetHeaders[] = { "method", "GET", "scheme", "http", "host", "www.google.com", "path", "/", "version", "HTTP/1.1", }; scoped_ptr<spdy::SpdyFrame> req( ConstructSpdyPacket( kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2)); scoped_ptr<spdy::SpdyFrame> msg( ConstructSpdyBodyFrame("\0hello!\xff", 8)); MockWrite writes[] = { CreateMockWrite(*req), CreateMockWrite(*msg), }; writes[0].sequence_number = 0; writes[1].sequence_number = 2; scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> echo( ConstructSpdyBodyFrame("\0hello!\xff", 8)); MockRead reads[] = { CreateMockRead(*resp), CreateMockRead(*echo), MockRead(true, 0, 0), // EOF }; reads[0].sequence_number = 1; reads[1].sequence_number = 3; reads[2].sequence_number = 4; scoped_refptr<OrderedSocketData> data( new OrderedSocketData(reads, arraysize(reads), writes, arraysize(writes))); MockConnect connect_data(false, OK); data->set_connect_data(connect_data); session_deps.socket_factory->AddSocketDataProvider(data.get()); SpdySession::SetSSLMode(false); scoped_refptr<SpdySession> session(CreateSpdySession()); const char* kStreamUrl = "http://www.google.com/"; GURL url(kStreamUrl); HostPortPair host_port_pair("www.google.com", 80); scoped_refptr<TransportSocketParams> transport_params( new TransportSocketParams(host_port_pair, LOWEST, GURL(), false, false)); scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params, LOWEST, NULL, session_->transport_socket_pool(), BoundNetLog())); session->InitializeWithSocket(connection.release(), false, OK); scoped_refptr<SpdyStream> stream; ASSERT_EQ( OK, session->CreateStream(url, LOWEST, &stream, BoundNetLog(), NULL)); scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8)); memcpy(buf->data(), "\0hello!\xff", 8); TestCompletionCallback callback; scoped_ptr<TestSpdyStreamDelegate> delegate( new TestSpdyStreamDelegate(stream.get(), buf.get(), &callback)); stream->SetDelegate(delegate.get()); EXPECT_FALSE(stream->HasUrl()); linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); (*headers)["method"] = "GET"; (*headers)["scheme"] = url.scheme(); (*headers)["host"] = url.host(); (*headers)["path"] = url.path(); (*headers)["version"] = "HTTP/1.1"; stream->set_spdy_headers(headers); EXPECT_TRUE(stream->HasUrl()); EXPECT_EQ(kStreamUrl, stream->GetUrl().spec()); EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true)); EXPECT_EQ(OK, callback.WaitForResult()); EXPECT_TRUE(delegate->send_headers_completed()); EXPECT_EQ("200", (*delegate->response())["status"]); EXPECT_EQ("HTTP/1.1", (*delegate->response())["version"]); EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data()); EXPECT_EQ(8, delegate->data_sent()); EXPECT_TRUE(delegate->closed()); } TEST_F(SpdyStreamTest, PushedStream) { const char kStreamUrl[] = "http://www.google.com/"; SpdySessionDependencies session_deps; session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps); SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool()); scoped_refptr<SpdySession> spdy_session(CreateSpdySession()); BoundNetLog net_log; // Conjure up a stream. scoped_refptr<SpdyStream> stream = new SpdyStream(spdy_session, 2, true, net_log); EXPECT_FALSE(stream->response_received()); EXPECT_FALSE(stream->HasUrl()); // Set a couple of headers. spdy::SpdyHeaderBlock response; response["url"] = kStreamUrl; stream->OnResponseReceived(response); // Send some basic headers. spdy::SpdyHeaderBlock headers; response["status"] = "200"; response["version"] = "OK"; stream->OnHeaders(headers); stream->set_response_received(); EXPECT_TRUE(stream->response_received()); EXPECT_TRUE(stream->HasUrl()); EXPECT_EQ(kStreamUrl, stream->GetUrl().spec()); } } // namespace net