#include "base/callback_helpers.h"
#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
#include "net/base/address_list.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
const CompletionCallback& callback) OVERRIDE {
return transport_->Write(buf, buf_len, callback);
}
- virtual bool SetReceiveBufferSize(int32 size) OVERRIDE {
+ virtual int SetReceiveBufferSize(int32 size) OVERRIDE {
return transport_->SetReceiveBufferSize(size);
}
- virtual bool SetSendBufferSize(int32 size) OVERRIDE {
+ virtual int SetSendBufferSize(int32 size) OVERRIDE {
return transport_->SetSendBufferSize(size);
}
// Socket implementation:
virtual int Read(IOBuffer* buf,
int buf_len,
- const CompletionCallback& callback) OVERRIDE {
- return read_state_.RunWrappedFunction(buf, buf_len, callback);
- }
+ const CompletionCallback& callback) OVERRIDE;
virtual int Write(IOBuffer* buf,
int buf_len,
- const CompletionCallback& callback) OVERRIDE {
- return write_state_.RunWrappedFunction(buf, buf_len, callback);
- }
+ const CompletionCallback& callback) OVERRIDE;
+
+ // Blocks read results on the socket. Reads will not complete until
+ // UnblockReadResult() has been called and a result is ready from the
+ // underlying transport. Note: if BlockReadResult() is called while there is a
+ // hanging asynchronous Read(), that Read is blocked.
+ void BlockReadResult();
+ void UnblockReadResult();
+
+ // Waits for the blocked Read() call to be complete at the underlying
+ // transport.
+ void WaitForReadResult();
- // Causes the next call to Read() to return ERR_IO_PENDING, not completing
- // (invoking the callback) until UnblockRead() has been called and the
- // underlying transport has completed.
- void SetNextReadShouldBlock() { read_state_.SetShouldBlock(); }
- void UnblockRead() { read_state_.Unblock(); }
+ // Causes the next call to Write() to return ERR_IO_PENDING, not beginning the
+ // underlying transport until UnblockWrite() has been called. Note: if there
+ // is a pending asynchronous write, it is NOT blocked. For purposes of
+ // blocking writes, data is considered to have reached the underlying
+ // transport as soon as Write() is called.
+ void BlockWrite();
+ void UnblockWrite();
- // Causes the next call to Write() to return ERR_IO_PENDING, not completing
- // (invoking the callback) until UnblockWrite() has been called and the
- // underlying transport has completed.
- void SetNextWriteShouldBlock() { write_state_.SetShouldBlock(); }
- void UnblockWrite() { write_state_.Unblock(); }
+ // Waits for the blocked Write() call to be scheduled.
+ void WaitForWrite();
private:
- // Tracks the state for simulating a blocking Read/Write operation.
- class BlockingState {
- public:
- // Wrapper for the underlying Socket function to call (ie: Read/Write).
- typedef base::Callback<int(IOBuffer*, int, const CompletionCallback&)>
- WrappedSocketFunction;
-
- explicit BlockingState(const WrappedSocketFunction& function);
- ~BlockingState() {}
-
- // Sets the next call to RunWrappedFunction() to block, returning
- // ERR_IO_PENDING and not invoking the user callback until Unblock() is
- // called.
- void SetShouldBlock();
-
- // Unblocks the currently blocked pending function, invoking the user
- // callback if the results are immediately available.
- // Note: It's not valid to call this unless SetShouldBlock() has been
- // called beforehand.
- void Unblock();
-
- // Performs the wrapped socket function on the underlying transport. If
- // configured to block via SetShouldBlock(), then |user_callback| will not
- // be invoked until Unblock() has been called.
- int RunWrappedFunction(IOBuffer* buf,
- int len,
- const CompletionCallback& user_callback);
-
- private:
- // Handles completion from the underlying wrapped socket function.
- void OnCompleted(int result);
-
- WrappedSocketFunction wrapped_function_;
- bool should_block_;
- bool have_result_;
- int pending_result_;
- CompletionCallback user_callback_;
- };
+ // Handles completion from the underlying transport read.
+ void OnReadCompleted(int result);
+
+ // True if read callbacks are blocked.
+ bool should_block_read_;
+
+ // The user callback for the pending read call.
+ CompletionCallback pending_read_callback_;
+
+ // The result for the blocked read callback, or ERR_IO_PENDING if not
+ // completed.
+ int pending_read_result_;
+
+ // WaitForReadResult() wait loop.
+ scoped_ptr<base::RunLoop> read_loop_;
+
+ // True if write calls are blocked.
+ bool should_block_write_;
- BlockingState read_state_;
- BlockingState write_state_;
+ // The buffer for the pending write, or NULL if not scheduled.
+ scoped_refptr<IOBuffer> pending_write_buf_;
- DISALLOW_COPY_AND_ASSIGN(FakeBlockingStreamSocket);
+ // The callback for the pending write call.
+ CompletionCallback pending_write_callback_;
+
+ // The length for the pending write, or -1 if not scheduled.
+ int pending_write_len_;
+
+ // WaitForWrite() wait loop.
+ scoped_ptr<base::RunLoop> write_loop_;
};
FakeBlockingStreamSocket::FakeBlockingStreamSocket(
scoped_ptr<StreamSocket> transport)
: WrappedStreamSocket(transport.Pass()),
- read_state_(base::Bind(&Socket::Read,
- base::Unretained(transport_.get()))),
- write_state_(base::Bind(&Socket::Write,
- base::Unretained(transport_.get()))) {}
-
-FakeBlockingStreamSocket::BlockingState::BlockingState(
- const WrappedSocketFunction& function)
- : wrapped_function_(function),
- should_block_(false),
- have_result_(false),
- pending_result_(OK) {}
-
-void FakeBlockingStreamSocket::BlockingState::SetShouldBlock() {
- DCHECK(!should_block_);
- should_block_ = true;
+ should_block_read_(false),
+ pending_read_result_(ERR_IO_PENDING),
+ should_block_write_(false),
+ pending_write_len_(-1) {}
+
+int FakeBlockingStreamSocket::Read(IOBuffer* buf,
+ int len,
+ const CompletionCallback& callback) {
+ DCHECK(pending_read_callback_.is_null());
+ DCHECK_EQ(ERR_IO_PENDING, pending_read_result_);
+ DCHECK(!callback.is_null());
+
+ int rv = transport_->Read(buf, len, base::Bind(
+ &FakeBlockingStreamSocket::OnReadCompleted, base::Unretained(this)));
+ if (rv == ERR_IO_PENDING) {
+ // Save the callback to be called later.
+ pending_read_callback_ = callback;
+ } else if (should_block_read_) {
+ // Save the callback and read result to be called later.
+ pending_read_callback_ = callback;
+ OnReadCompleted(rv);
+ rv = ERR_IO_PENDING;
+ }
+ return rv;
}
-void FakeBlockingStreamSocket::BlockingState::Unblock() {
- DCHECK(should_block_);
- should_block_ = false;
+int FakeBlockingStreamSocket::Write(IOBuffer* buf,
+ int len,
+ const CompletionCallback& callback) {
+ DCHECK(buf);
+ DCHECK_LE(0, len);
+
+ if (!should_block_write_)
+ return transport_->Write(buf, len, callback);
+
+ // Schedule the write, but do nothing.
+ DCHECK(!pending_write_buf_);
+ DCHECK_EQ(-1, pending_write_len_);
+ DCHECK(pending_write_callback_.is_null());
+ DCHECK(!callback.is_null());
+ pending_write_buf_ = buf;
+ pending_write_len_ = len;
+ pending_write_callback_ = callback;
+
+ // Stop the write loop, if any.
+ if (write_loop_)
+ write_loop_->Quit();
+ return ERR_IO_PENDING;
+}
+
+void FakeBlockingStreamSocket::BlockReadResult() {
+ DCHECK(!should_block_read_);
+ should_block_read_ = true;
+}
+
+void FakeBlockingStreamSocket::UnblockReadResult() {
+ DCHECK(should_block_read_);
+ should_block_read_ = false;
// If the operation is still pending in the underlying transport, immediately
- // return - OnCompleted() will handle invoking the callback once the transport
- // has completed.
- if (!have_result_)
+ // return - OnReadCompleted() will handle invoking the callback once the
+ // transport has completed.
+ if (pending_read_result_ == ERR_IO_PENDING)
return;
+ int result = pending_read_result_;
+ pending_read_result_ = ERR_IO_PENDING;
+ base::ResetAndReturn(&pending_read_callback_).Run(result);
+}
- have_result_ = false;
+void FakeBlockingStreamSocket::WaitForReadResult() {
+ DCHECK(should_block_read_);
+ DCHECK(!read_loop_);
- base::ResetAndReturn(&user_callback_).Run(pending_result_);
+ if (pending_read_result_ != ERR_IO_PENDING)
+ return;
+ read_loop_.reset(new base::RunLoop);
+ read_loop_->Run();
+ read_loop_.reset();
+ DCHECK_NE(ERR_IO_PENDING, pending_read_result_);
}
-int FakeBlockingStreamSocket::BlockingState::RunWrappedFunction(
- IOBuffer* buf,
- int len,
- const CompletionCallback& callback) {
-
- // The callback to be called by the underlying transport. Either forward
- // directly to the user's callback if not set to block, or intercept it with
- // OnCompleted so that the user's callback is not invoked until Unblock() is
- // called.
- CompletionCallback transport_callback =
- !should_block_ ? callback : base::Bind(&BlockingState::OnCompleted,
- base::Unretained(this));
- int rv = wrapped_function_.Run(buf, len, transport_callback);
- if (should_block_) {
- user_callback_ = callback;
- // May have completed synchronously.
- have_result_ = (rv != ERR_IO_PENDING);
- pending_result_ = rv;
- return ERR_IO_PENDING;
+void FakeBlockingStreamSocket::BlockWrite() {
+ DCHECK(!should_block_write_);
+ should_block_write_ = true;
+}
+
+void FakeBlockingStreamSocket::UnblockWrite() {
+ DCHECK(should_block_write_);
+ should_block_write_ = false;
+
+ // Do nothing if UnblockWrite() was called after BlockWrite(),
+ // without a Write() in between.
+ if (!pending_write_buf_)
+ return;
+
+ int rv = transport_->Write(pending_write_buf_, pending_write_len_,
+ pending_write_callback_);
+ pending_write_buf_ = NULL;
+ pending_write_len_ = -1;
+ if (rv == ERR_IO_PENDING) {
+ pending_write_callback_.Reset();
+ } else {
+ base::ResetAndReturn(&pending_write_callback_).Run(rv);
}
+}
- return rv;
+void FakeBlockingStreamSocket::WaitForWrite() {
+ DCHECK(should_block_write_);
+ DCHECK(!write_loop_);
+
+ if (pending_write_buf_)
+ return;
+ write_loop_.reset(new base::RunLoop);
+ write_loop_->Run();
+ write_loop_.reset();
+ DCHECK(pending_write_buf_);
}
-void FakeBlockingStreamSocket::BlockingState::OnCompleted(int result) {
- if (should_block_) {
+void FakeBlockingStreamSocket::OnReadCompleted(int result) {
+ DCHECK_EQ(ERR_IO_PENDING, pending_read_result_);
+ DCHECK(!pending_read_callback_.is_null());
+
+ if (should_block_read_) {
// Store the result so that the callback can be invoked once Unblock() is
// called.
- have_result_ = true;
- pending_result_ = result;
- return;
+ pending_read_result_ = result;
+
+ // Stop the WaitForReadResult() call if any.
+ if (read_loop_)
+ read_loop_->Quit();
+ } else {
+ // Either the Read() was never blocked or UnblockReadResult() was called
+ // before the Read() completed. Either way, run the callback.
+ base::ResetAndReturn(&pending_read_callback_).Run(result);
}
-
- // Otherwise, the Unblock() function was called before the underlying
- // transport completed, so run the user's callback immediately.
- base::ResetAndReturn(&user_callback_).Run(result);
}
// CompletionCallback that will delete the associated StreamSocket when
}
};
+class SSLClientSocketFalseStartTest : public SSLClientSocketTest {
+ protected:
+ void TestFalseStart(const SpawnedTestServer::SSLOptions& server_options,
+ const SSLConfig& client_config,
+ bool expect_false_start) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ server_options,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ scoped_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ scoped_ptr<FakeBlockingStreamSocket> transport(
+ new FakeBlockingStreamSocket(real_transport.Pass()));
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ EXPECT_EQ(OK, rv);
+
+ FakeBlockingStreamSocket* raw_transport = transport.get();
+ scoped_ptr<SSLClientSocket> sock(
+ CreateSSLClientSocket(transport.PassAs<StreamSocket>(),
+ test_server.host_port_pair(),
+ client_config));
+
+ // Connect. Stop before the client processes the first server leg
+ // (ServerHello, etc.)
+ raw_transport->BlockReadResult();
+ rv = sock->Connect(callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ raw_transport->WaitForReadResult();
+
+ // Release the ServerHello and wait for the client to write
+ // ClientKeyExchange, etc. (A proxy for waiting for the entirety of the
+ // server's leg to complete, since it may span multiple reads.)
+ EXPECT_FALSE(callback.have_result());
+ raw_transport->BlockWrite();
+ raw_transport->UnblockReadResult();
+ raw_transport->WaitForWrite();
+
+ // And, finally, release that and block the next server leg
+ // (ChangeCipherSpec, Finished). Note: callback.have_result() may or may not
+ // be true at this point depending on whether the SSL implementation waits
+ // for the client second leg to clear the internal write buffer and hit the
+ // network.
+ raw_transport->BlockReadResult();
+ raw_transport->UnblockWrite();
+
+ if (expect_false_start) {
+ // When False Starting, the handshake should complete before receiving the
+ // Change Cipher Spec and Finished messages.
+ rv = callback.GetResult(rv);
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ static const int kRequestTextSize =
+ static_cast<int>(arraysize(request_text) - 1);
+ scoped_refptr<IOBuffer> request_buffer(new IOBuffer(kRequestTextSize));
+ memcpy(request_buffer->data(), request_text, kRequestTextSize);
+
+ // Write the request.
+ rv = callback.GetResult(sock->Write(request_buffer.get(),
+ kRequestTextSize,
+ callback.callback()));
+ EXPECT_EQ(kRequestTextSize, rv);
+
+ // The read will hang; it's waiting for the peer to complete the
+ // handshake, and the handshake is still blocked.
+ scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
+ rv = sock->Read(buf.get(), 4096, callback.callback());
+
+ // After releasing reads, the connection proceeds.
+ raw_transport->UnblockReadResult();
+ rv = callback.GetResult(rv);
+ EXPECT_LT(0, rv);
+ } else {
+ // False Start is not enabled, so the handshake will not complete because
+ // the server second leg is blocked.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(callback.have_result());
+ }
+ }
+};
+
//-----------------------------------------------------------------------------
// LogContainsSSLConnectEndEvent returns true if the given index in the given
// Simulate an unclean/forcible shutdown on the underlying socket.
// However, simulate this error asynchronously.
raw_error_socket->SetNextWriteError(ERR_CONNECTION_RESET);
- raw_transport->SetNextWriteShouldBlock();
+ raw_transport->BlockWrite();
// This write should complete synchronously, because the TLS ciphertext
// can be created and placed into the outgoing buffers independent of the
raw_error_socket->SetNextWriteError(ERR_CONNECTION_RESET);
// ... but have those errors returned asynchronously. Because the Write() will
// return first, this will trigger the error.
- raw_transport->SetNextReadShouldBlock();
- raw_transport->SetNextWriteShouldBlock();
+ raw_transport->BlockReadResult();
+ raw_transport->BlockWrite();
// Enqueue a Read() before calling Write(), which should "hang" due to
// the ERR_IO_PENDING caused by SetReadShouldBlock() and thus return.
// Start a hanging read.
TestCompletionCallback read_callback;
- raw_transport->SetNextReadShouldBlock();
+ raw_transport->BlockReadResult();
scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
rv = sock->Read(buf.get(), 4096, read_callback.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
#endif
// Release the read. Some bytes should go through.
- raw_transport->UnblockRead();
+ raw_transport->UnblockReadResult();
rv = read_callback.WaitForResult();
// Per the fix for http://crbug.com/249848, write failures currently break
// attempt to read one byte extra.
}
+TEST_F(SSLClientSocketFalseStartTest, FalseStartEnabled) {
+ // False Start requires NPN and a forward-secret cipher suite.
+ SpawnedTestServer::SSLOptions server_options;
+ server_options.key_exchanges =
+ SpawnedTestServer::SSLOptions::KEY_EXCHANGE_DHE_RSA;
+ server_options.enable_npn = true;
+ SSLConfig client_config;
+ client_config.next_protos.push_back("http/1.1");
+ TestFalseStart(server_options, client_config, true);
+}
+
+// Test that False Start is disabled without NPN.
+TEST_F(SSLClientSocketFalseStartTest, NoNPN) {
+ SpawnedTestServer::SSLOptions server_options;
+ server_options.key_exchanges =
+ SpawnedTestServer::SSLOptions::KEY_EXCHANGE_DHE_RSA;
+ SSLConfig client_config;
+ client_config.next_protos.clear();
+ TestFalseStart(server_options, client_config, false);
+}
+
+// Test that False Start is disabled without a forward-secret cipher suite.
+TEST_F(SSLClientSocketFalseStartTest, NoForwardSecrecy) {
+ SpawnedTestServer::SSLOptions server_options;
+ server_options.key_exchanges =
+ SpawnedTestServer::SSLOptions::KEY_EXCHANGE_RSA;
+ server_options.enable_npn = true;
+ SSLConfig client_config;
+ client_config.next_protos.push_back("http/1.1");
+ TestFalseStart(server_options, client_config, false);
+}
+
} // namespace net