diff options
-rw-r--r-- | base/base.gyp | 2 | ||||
-rw-r--r-- | base/sync_socket.h | 66 | ||||
-rw-r--r-- | base/sync_socket_win.cc | 144 | ||||
-rw-r--r-- | ipc/ipc.gyp | 5 | ||||
-rw-r--r-- | ipc/ipc_tests.cc | 9 | ||||
-rw-r--r-- | ipc/ipc_tests.h | 4 | ||||
-rw-r--r-- | ipc/sync_socket_unittest.cc | 211 |
7 files changed, 440 insertions, 1 deletions
diff --git a/base/base.gyp b/base/base.gyp index 75ce295..86e4996 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -300,6 +300,8 @@ 'string_util.cc', 'string_util.h', 'string_util_win.h', + 'sync_socket.h', + 'sync_socket_win.cc', 'sys_info.h', 'sys_info_chromeos.cc', 'sys_info_freebsd.cc', diff --git a/base/sync_socket.h b/base/sync_socket.h new file mode 100644 index 0000000..ad181ff --- /dev/null +++ b/base/sync_socket.h @@ -0,0 +1,66 @@ +// Copyright (c) 2009 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. + +#ifndef BASE_SYNC_SOCKET_H_ +#define BASE_SYNC_SOCKET_H_ + +// A socket abstraction used for sending and receiving plain +// data. Because they are blocking, they can be used to perform +// rudimentary cross-process synchronization with low latency. + +#include "base/basictypes.h" +#if defined(OS_WIN) +#include <windows.h> +#endif +#include <sys/types.h> + +namespace base { + +class SyncSocket { + public: +#if defined(OS_WIN) + typedef HANDLE Handle; +#else + typedef int Handle; +#endif + + // Creates a SyncSocket from a Handle. Used in transport. + explicit SyncSocket(Handle handle) : handle_(handle) { } + ~SyncSocket() { Close(); } + + // Creates an unnamed pair of connected sockets. + // pair is a pointer to an array of two SyncSockets in which connected socket + // descriptors are returned. Returns true on success, false on failure. + static bool CreatePair(SyncSocket* pair[2]); + + // Closes the SyncSocket. Returns true on success, false on failure. + bool Close(); + + // Sends the message to the remote peer of the SyncSocket. + // Note it is not safe to send messages from the same socket handle by + // multiple threads simultaneously. + // buffer is a pointer to the data to send. + // length is the length of the data to send (must be non-zero). + // Returns the number of bytes sent, or 0 upon failure. + size_t Send(const void* buffer, size_t length); + + // Receives a message from an SyncSocket. + // buffer is a pointer to the buffer to receive data. + // length is the number of bytes of data to receive (must be non-zero). + // Returns the number of bytes received, or 0 upon failure. + size_t Receive(void* buffer, size_t length); + + // Extracts the contained handle. Used for transferring between + // processes. + Handle handle() const { return handle_; } + + private: + Handle handle_; + + DISALLOW_COPY_AND_ASSIGN(SyncSocket); +}; + +} // namespace base + +#endif // BASE_SYNC_SOCKET_H_ diff --git a/base/sync_socket_win.cc b/base/sync_socket_win.cc new file mode 100644 index 0000000..b591bb0 --- /dev/null +++ b/base/sync_socket_win.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2009 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/sync_socket.h" +#include <limits.h> +#include <stdio.h> +#include <windows.h> +#include <sys/types.h> +#include "base/atomicops.h" +#include "base/logging.h" + + +namespace base { + +namespace { +// This prefix used to be appended to pipe names for pipes +// created in CreatePair. +const wchar_t kPipePrefix[] = L"\\\\.\\pipe\\chrome.sync."; +const size_t kPipePrefixSize = arraysize(kPipePrefix); +const size_t kPathMax = 28; // print length of process id + pair count. +const size_t kPipePathMax = kPipePrefixSize + kPathMax + 1; + +// To avoid users sending negative message lengths to Send/Receive +// we clamp message lengths, which are size_t, to no more than INT_MAX. +const size_t kMaxMessageLength = static_cast<size_t>(INT_MAX); + +const int kOutBufferSize = 4096; +const int kInBufferSize = 4096; +const int kDefaultTimeoutMilliSeconds = 1000; + +static const SyncSocket::Handle kInvalidHandle = INVALID_HANDLE_VALUE; + +} // namespace + +bool SyncSocket::CreatePair(SyncSocket* pair[2]) { + Handle handles[2]; + SyncSocket* tmp_sockets[2]; + + // Create the two SyncSocket objects first to avoid ugly cleanup issues. + tmp_sockets[0] = new SyncSocket(kInvalidHandle); + if (tmp_sockets[0] == NULL) { + return false; + } + tmp_sockets[1] = new SyncSocket(kInvalidHandle); + if (tmp_sockets[1] == NULL) { + delete tmp_sockets[0]; + return false; + } + + wchar_t name[kPipePathMax]; + do { + unsigned int rnd_name; + if (rand_s(&rnd_name) != 0) + return false; + swprintf(name, kPipePathMax, L"%s%u.%lu", + kPipePrefix, GetCurrentProcessId(), + rnd_name); + handles[0] = CreateNamedPipeW( + name, + PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, + 1, + kOutBufferSize, + kInBufferSize, + kDefaultTimeoutMilliSeconds, + NULL); + if (handles[0] == INVALID_HANDLE_VALUE && + GetLastError() != ERROR_ACCESS_DENIED && + GetLastError() != ERROR_PIPE_BUSY) { + return false; + } + } while (handles[0] == INVALID_HANDLE_VALUE); + handles[1] = CreateFileW(name, + GENERIC_READ | GENERIC_WRITE, + 0, // no sharing. + NULL, // default security attributes. + OPEN_EXISTING, // opens existing pipe. + SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, + // no impersonation. + NULL); // no template file. + if (handles[1] == INVALID_HANDLE_VALUE) { + CloseHandle(handles[0]); + return false; + } + if (ConnectNamedPipe(handles[0], NULL) == FALSE) { + DWORD error = GetLastError(); + if (error != ERROR_PIPE_CONNECTED) { + CloseHandle(handles[0]); + CloseHandle(handles[1]); + return false; + } + } + // Copy the handles out for successful return. + tmp_sockets[0]->handle_ = handles[0]; + pair[0] = tmp_sockets[0]; + tmp_sockets[1]->handle_ = handles[1]; + pair[1] = tmp_sockets[1]; + return true; +} + +bool SyncSocket::Close() { + if (handle_ == kInvalidHandle) { + return false; + } + BOOL retval = CloseHandle(handle_); + handle_ = kInvalidHandle; + return retval ? true : false; +} + +size_t SyncSocket::Send(const void* buffer, size_t length) { + DCHECK(length <= kMaxMessageLength); + size_t count = 0; + while (count < length) { + DWORD len; + // The following statement is for 64 bit portability. + DWORD chunk = static_cast<DWORD>( + ((length - count) <= UINT_MAX) ? (length - count) : UINT_MAX); + if (WriteFile(handle_, static_cast<const char*>(buffer) + count, + chunk, &len, NULL) == FALSE) { + return (0 < count) ? count : 0; + } + count += len; + } + return count; +} + +size_t SyncSocket::Receive(void* buffer, size_t length) { + DCHECK(length <= kMaxMessageLength); + size_t count = 0; + while (count < length) { + DWORD len; + DWORD chunk = static_cast<DWORD>( + ((length - count) <= UINT_MAX) ? (length - count) : UINT_MAX); + if (ReadFile(handle_, static_cast<char*>(buffer) + count, + chunk, &len, NULL) == FALSE) { + return (0 < count) ? count : 0; + } + count += len; + } + return count; +} + +} // namespace base diff --git a/ipc/ipc.gyp b/ipc/ipc.gyp index 8e393be..eecf1df 100644 --- a/ipc/ipc.gyp +++ b/ipc/ipc.gyp @@ -101,6 +101,11 @@ '../views/views.gyp:views', ], }], + ['OS=="win"', { + 'sources': [ + 'sync_socket_unittest.cc', + ], + }], ], }, ] diff --git a/ipc/ipc_tests.cc b/ipc/ipc_tests.cc index 901427d..a7546c3 100644 --- a/ipc/ipc_tests.cc +++ b/ipc/ipc_tests.cc @@ -42,6 +42,7 @@ const char kTestClientChannel[] = "T1"; const char kReflectorChannel[] = "T2"; const char kFuzzerChannel[] = "F3"; +const char kSyncSocketChannel[] = "S4"; const size_t kLongMessageStringNumBytes = 50000; @@ -78,6 +79,9 @@ base::ProcessHandle IPCChannelTest::SpawnChild(ChildType child_type, case FUZZER_SERVER: return MultiProcessTest::SpawnChild(L"RunFuzzServer", debug_on_start); break; + case SYNC_SOCKET_SERVER: + return MultiProcessTest::SpawnChild(L"RunSyncSocketServer", debug_on_start); + break; default: return NULL; break; @@ -123,6 +127,11 @@ base::ProcessHandle IPCChannelTest::SpawnChild(ChildType child_type, fds_to_map, debug_on_start); break; + case SYNC_SOCKET_SERVER: + ret = MultiProcessTest::SpawnChild(L"RunSyncSocketServer", + fds_to_map, + debug_on_start); + break; default: return NULL; break; diff --git a/ipc/ipc_tests.h b/ipc/ipc_tests.h index e800883..755dafd 100644 --- a/ipc/ipc_tests.h +++ b/ipc/ipc_tests.h @@ -15,13 +15,15 @@ enum ChildType { TEST_DESCRIPTOR_CLIENT, TEST_DESCRIPTOR_CLIENT_SANDBOXED, TEST_REFLECTOR, - FUZZER_SERVER + FUZZER_SERVER, + SYNC_SOCKET_SERVER }; // The different channel names for the child processes. extern const char kTestClientChannel[]; extern const char kReflectorChannel[]; extern const char kFuzzerChannel[]; +extern const char kSyncSocketChannel[]; class MessageLoopForIO; namespace IPC { diff --git a/ipc/sync_socket_unittest.cc b/ipc/sync_socket_unittest.cc new file mode 100644 index 0000000..47a907c --- /dev/null +++ b/ipc/sync_socket_unittest.cc @@ -0,0 +1,211 @@ +// Copyright (c) 2009 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 <stdio.h> +#include <iostream> +#include <string> +#include <sstream> + +#include "base/message_loop.h" +#include "base/platform_thread.h" +#include "base/process_util.h" +#include "base/sync_socket.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_tests.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + + +// We don't actually use the messages defined in this file, but we do this +// to get to the IPC macros. +#define MESSAGES_INTERNAL_FILE "ipc/ipc_sync_message_unittest.h" +#include "ipc/ipc_message_macros.h" + +enum IPCMessageIds { + UNUSED_IPC_TYPE, + SERVER_FIRST_IPC_TYPE, // SetHandle message sent to server. + SERVER_SECOND_IPC_TYPE, // Shutdown message sent to server. + CLIENT_FIRST_IPC_TYPE // Response message sent to client. +}; + +namespace { +const char kHelloString[] = "Hello, SyncSocket Client"; +const size_t kHelloStringLength = arraysize(kHelloString); +} // namespace + +// Message class to pass a HANDLE to another process. +class MsgClassSetHandle + : public IPC::MessageWithTuple< Tuple1<base::SyncSocket::Handle> > { + public: + enum { ID = SERVER_FIRST_IPC_TYPE }; + explicit MsgClassSetHandle(const base::SyncSocket::Handle arg1) + : IPC::MessageWithTuple< Tuple1<base::SyncSocket::Handle> >( + MSG_ROUTING_CONTROL, ID, MakeRefTuple(arg1)) {} + + private: + DISALLOW_COPY_AND_ASSIGN(MsgClassSetHandle); +}; + +// Message class to pass a response to the server. +class MsgClassResponse + : public IPC::MessageWithTuple< Tuple1<std::string> > { + public: + enum { ID = CLIENT_FIRST_IPC_TYPE }; + explicit MsgClassResponse(const std::string& arg1) + : IPC::MessageWithTuple< Tuple1<std::string> >( + MSG_ROUTING_CONTROL, ID, MakeRefTuple(arg1)) {} + + private: + DISALLOW_COPY_AND_ASSIGN(MsgClassResponse); +}; + +// Message class to tell the server to shut down. +class MsgClassShutdown + : public IPC::MessageWithTuple< Tuple0 > { + public: + enum { ID = SERVER_SECOND_IPC_TYPE }; + MsgClassShutdown() + : IPC::MessageWithTuple< Tuple0 >( + MSG_ROUTING_CONTROL, ID, MakeTuple()) {} + + private: + DISALLOW_COPY_AND_ASSIGN(MsgClassShutdown); +}; + +// The SyncSocket server listener class processes two sorts of +// messages from the client. +class SyncSocketServerListener : public IPC::Channel::Listener { + public: + SyncSocketServerListener() : chan_(NULL) { + } + + void Init(IPC::Channel* chan) { + chan_ = chan; + } + + virtual void OnMessageReceived(const IPC::Message& msg) { + if (msg.routing_id() == MSG_ROUTING_CONTROL) { + IPC_BEGIN_MESSAGE_MAP(SyncSocketServerListener, msg) + IPC_MESSAGE_HANDLER(MsgClassSetHandle, OnMsgClassSetHandle) + IPC_MESSAGE_HANDLER(MsgClassShutdown, OnMsgClassShutdown) + IPC_END_MESSAGE_MAP() + } + } + + private: + // This sort of message is sent first, causing the transfer of + // the handle for the SyncSocket. This message sends a buffer + // on the SyncSocket and then sends a response to the client. + void OnMsgClassSetHandle(const base::SyncSocket::Handle handle) { + base::SyncSocket sync_socket(handle); + EXPECT_EQ(sync_socket.Send(static_cast<const void*>(kHelloString), + kHelloStringLength), kHelloStringLength); + IPC::Message* msg = new MsgClassResponse(kHelloString); + EXPECT_NE(msg, reinterpret_cast<IPC::Message*>(NULL)); + EXPECT_TRUE(chan_->Send(msg)); + } + + // When the client responds, it sends back a shutdown message, + // which causes the message loop to exit. + void OnMsgClassShutdown() { + MessageLoop::current()->Quit(); + } + + IPC::Channel* chan_; + + DISALLOW_COPY_AND_ASSIGN(SyncSocketServerListener); +}; + +// Runs the fuzzing server child mode. Returns when the preset number +// of messages have been received. +MULTIPROCESS_TEST_MAIN(RunSyncSocketServer) { + MessageLoopForIO main_message_loop; + SyncSocketServerListener listener; + IPC::Channel chan(kSyncSocketChannel, IPC::Channel::MODE_CLIENT, &listener); + EXPECT_TRUE(chan.Connect()); + listener.Init(&chan); + MessageLoop::current()->Run(); + return 0; +} + +// The SyncSocket client listener only processes one sort of message, +// a response from the server. +class SyncSocketClientListener : public IPC::Channel::Listener { + public: + SyncSocketClientListener() { + } + + void Init(base::SyncSocket* socket, IPC::Channel* chan) { + socket_ = socket; + chan_ = chan; + } + + virtual void OnMessageReceived(const IPC::Message& msg) { + if (msg.routing_id() == MSG_ROUTING_CONTROL) { + IPC_BEGIN_MESSAGE_MAP(SyncSocketClientListener, msg) + IPC_MESSAGE_HANDLER(MsgClassResponse, OnMsgClassResponse) + IPC_END_MESSAGE_MAP() + } + } + + private: + // When a response is received from the server, it sends the same + // string as was written on the SyncSocket. These are compared + // and a shutdown message is sent back to the server. + void OnMsgClassResponse(const std::string& str) { + char buf[kHelloStringLength]; + socket_->Receive(static_cast<void*>(buf), kHelloStringLength); + EXPECT_EQ(strcmp(str.c_str(), buf), 0); + IPC::Message* msg = new MsgClassShutdown(); + EXPECT_NE(msg, reinterpret_cast<IPC::Message*>(NULL)); + EXPECT_TRUE(chan_->Send(msg)); + MessageLoop::current()->Quit(); + } + + base::SyncSocket* socket_; + IPC::Channel* chan_; + + DISALLOW_COPY_AND_ASSIGN(SyncSocketClientListener); +}; + +class SyncSocketTest : public IPCChannelTest { +}; + +TEST_F(SyncSocketTest, SanityTest) { + SyncSocketClientListener listener; + IPC::Channel chan(kSyncSocketChannel, IPC::Channel::MODE_SERVER, + &listener); + base::ProcessHandle server_process = SpawnChild(SYNC_SOCKET_SERVER, &chan); + ASSERT_TRUE(server_process); + // Create a pair of SyncSockets. + base::SyncSocket* pair[2]; + base::SyncSocket::CreatePair(pair); + base::SyncSocket::Handle target_handle; +#if defined(OS_WIN) + // On windows we need to duplicate the handle into the server process. + BOOL retval = DuplicateHandle(GetCurrentProcess(), pair[1]->handle(), + server_process, &target_handle, + 0, FALSE, DUPLICATE_SAME_ACCESS); + EXPECT_TRUE(retval); +#else + target_handle = pair[1]->handle(); +#endif // defined(OS_WIN) + // Connect the channel and listener. + ASSERT_TRUE(chan.Connect()); + listener.Init(pair[0], &chan); + // Set up a message to pass the handle to the server. + IPC::Message* msg = new MsgClassSetHandle(target_handle); + EXPECT_NE(msg, reinterpret_cast<IPC::Message*>(NULL)); + EXPECT_TRUE(chan.Send(msg)); + // Use the current thread as the I/O thread. + MessageLoop::current()->Run(); + // Shut down. + delete pair[0]; + delete pair[1]; + EXPECT_TRUE(base::WaitForSingleProcess(server_process, 5000)); + base::CloseProcessHandle(server_process); +} + |