diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/ipc | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/ipc')
76 files changed, 15540 insertions, 0 deletions
diff --git a/chromium/ipc/DEPS b/chromium/ipc/DEPS new file mode 100644 index 00000000000..eea2192499e --- /dev/null +++ b/chromium/ipc/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + # For ipc_channel_nacl.cc: + "+native_client/src/public", +] diff --git a/chromium/ipc/OWNERS b/chromium/ipc/OWNERS new file mode 100644 index 00000000000..eaf88320e66 --- /dev/null +++ b/chromium/ipc/OWNERS @@ -0,0 +1,20 @@ +agl@chromium.org +cpu@chromium.org +darin@chromium.org +jam@chromium.org +jeremy@chromium.org +tsepez@chromium.org + +# For NaCl-related code (ipc_channel_nacl.*) +dmichael@chromium.org + +# New IPC message files require a security review to avoid introducing +# new sandbox escapes. +per-file ipc_message_start.h=set noparent +per-file ipc_message_start.h=cdn@chromium.org +per-file ipc_message_start.h=cevans@chromium.org +per-file ipc_message_start.h=jln@chromium.org +per-file ipc_message_start.h=jschuh@chromium.org +per-file ipc_message_start.h=palmer@chromium.org +per-file ipc_message_start.h=tsepez@chromium.org +per-file ipc_message_start.h=kenrb@chromium.org diff --git a/chromium/ipc/file_descriptor_set_posix.cc b/chromium/ipc/file_descriptor_set_posix.cc new file mode 100644 index 00000000000..3cb58803704 --- /dev/null +++ b/chromium/ipc/file_descriptor_set_posix.cc @@ -0,0 +1,137 @@ +// 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 "ipc/file_descriptor_set_posix.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +FileDescriptorSet::FileDescriptorSet() + : consumed_descriptor_highwater_(0) { +} + +FileDescriptorSet::~FileDescriptorSet() { + if (consumed_descriptor_highwater_ == descriptors_.size()) + return; + + LOG(WARNING) << "FileDescriptorSet destroyed with unconsumed descriptors"; + // We close all the descriptors where the close flag is set. If this + // message should have been transmitted, then closing those with close + // flags set mirrors the expected behaviour. + // + // If this message was received with more descriptors than expected + // (which could a DOS against the browser by a rogue renderer) then all + // the descriptors have their close flag set and we free all the extra + // kernel resources. + for (unsigned i = consumed_descriptor_highwater_; + i < descriptors_.size(); ++i) { + if (descriptors_[i].auto_close) + if (HANDLE_EINTR(close(descriptors_[i].fd)) < 0) + PLOG(ERROR) << "close"; + } +} + +bool FileDescriptorSet::Add(int fd) { + if (descriptors_.size() == kMaxDescriptorsPerMessage) + return false; + + struct base::FileDescriptor sd; + sd.fd = fd; + sd.auto_close = false; + descriptors_.push_back(sd); + return true; +} + +bool FileDescriptorSet::AddAndAutoClose(int fd) { + if (descriptors_.size() == kMaxDescriptorsPerMessage) + return false; + + struct base::FileDescriptor sd; + sd.fd = fd; + sd.auto_close = true; + descriptors_.push_back(sd); + DCHECK(descriptors_.size() <= kMaxDescriptorsPerMessage); + return true; +} + +int FileDescriptorSet::GetDescriptorAt(unsigned index) const { + if (index >= descriptors_.size()) + return -1; + + // We should always walk the descriptors in order, so it's reasonable to + // enforce this. Consider the case where a compromised renderer sends us + // the following message: + // + // ExampleMsg: + // num_fds:2 msg:FD(index = 1) control:SCM_RIGHTS {n, m} + // + // Here the renderer sent us a message which should have a descriptor, but + // actually sent two in an attempt to fill our fd table and kill us. By + // setting the index of the descriptor in the message to 1 (it should be + // 0), we would record a highwater of 1 and then consider all the + // descriptors to have been used. + // + // So we can either track of the use of each descriptor in a bitset, or we + // can enforce that we walk the indexes strictly in order. + // + // There's one more wrinkle: When logging messages, we may reparse them. So + // we have an exception: When the consumed_descriptor_highwater_ is at the + // end of the array and index 0 is requested, we reset the highwater value. + if (index == 0 && consumed_descriptor_highwater_ == descriptors_.size()) + consumed_descriptor_highwater_ = 0; + + if (index != consumed_descriptor_highwater_) + return -1; + + consumed_descriptor_highwater_ = index + 1; + return descriptors_[index].fd; +} + +void FileDescriptorSet::GetDescriptors(int* buffer) const { + for (std::vector<base::FileDescriptor>::const_iterator + i = descriptors_.begin(); i != descriptors_.end(); ++i) { + *(buffer++) = i->fd; + } +} + +bool FileDescriptorSet::ContainsDirectoryDescriptor() const { + struct stat st; + + for (std::vector<base::FileDescriptor>::const_iterator + i = descriptors_.begin(); i != descriptors_.end(); ++i) { + if (fstat(i->fd, &st) == 0 && S_ISDIR(st.st_mode)) + return true; + } + + return false; +} + +void FileDescriptorSet::CommitAll() { + for (std::vector<base::FileDescriptor>::iterator + i = descriptors_.begin(); i != descriptors_.end(); ++i) { + if (i->auto_close) + if (HANDLE_EINTR(close(i->fd)) < 0) + PLOG(ERROR) << "close"; + } + descriptors_.clear(); + consumed_descriptor_highwater_ = 0; +} + +void FileDescriptorSet::SetDescriptors(const int* buffer, unsigned count) { + DCHECK(count <= kMaxDescriptorsPerMessage); + DCHECK_EQ(descriptors_.size(), 0u); + DCHECK_EQ(consumed_descriptor_highwater_, 0u); + + descriptors_.reserve(count); + for (unsigned i = 0; i < count; ++i) { + struct base::FileDescriptor sd; + sd.fd = buffer[i]; + sd.auto_close = true; + descriptors_.push_back(sd); + } +} diff --git a/chromium/ipc/file_descriptor_set_posix.h b/chromium/ipc/file_descriptor_set_posix.h new file mode 100644 index 00000000000..de4c5c62329 --- /dev/null +++ b/chromium/ipc/file_descriptor_set_posix.h @@ -0,0 +1,114 @@ +// 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. + +#ifndef IPC_FILE_DESCRIPTOR_SET_POSIX_H_ +#define IPC_FILE_DESCRIPTOR_SET_POSIX_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/file_descriptor_posix.h" +#include "base/memory/ref_counted.h" +#include "ipc/ipc_export.h" + +// ----------------------------------------------------------------------------- +// A FileDescriptorSet is an ordered set of POSIX file descriptors. These are +// associated with IPC messages so that descriptors can be transmitted over a +// UNIX domain socket. +// ----------------------------------------------------------------------------- +class IPC_EXPORT FileDescriptorSet + : public base::RefCountedThreadSafe<FileDescriptorSet> { + public: + FileDescriptorSet(); + + // This is the maximum number of descriptors per message. We need to know this + // because the control message kernel interface has to be given a buffer which + // is large enough to store all the descriptor numbers. Otherwise the kernel + // tells us that it truncated the control data and the extra descriptors are + // lost. + // + // In debugging mode, it's a fatal error to try and add more than this number + // of descriptors to a FileDescriptorSet. + static const size_t kMaxDescriptorsPerMessage = 5; + + // --------------------------------------------------------------------------- + // Interfaces for building during message serialisation... + + // Add a descriptor to the end of the set. Returns false iff the set is full. + bool Add(int fd); + // Add a descriptor to the end of the set and automatically close it after + // transmission. Returns false iff the set is full. + bool AddAndAutoClose(int fd); + + // --------------------------------------------------------------------------- + + + // --------------------------------------------------------------------------- + // Interfaces for accessing during message deserialisation... + + // Return the number of descriptors + unsigned size() const { return descriptors_.size(); } + // Return true if no unconsumed descriptors remain + bool empty() const { return descriptors_.empty(); } + // Fetch the nth descriptor from the beginning of the set. Code using this + // /must/ access the descriptors in order, except that it may wrap from the + // end to index 0 again. + // + // This interface is designed for the deserialising code as it doesn't + // support close flags. + // returns: file descriptor, or -1 on error + int GetDescriptorAt(unsigned n) const; + + // --------------------------------------------------------------------------- + + + // --------------------------------------------------------------------------- + // Interfaces for transmission... + + // Fill an array with file descriptors without 'consuming' them. CommitAll + // must be called after these descriptors have been transmitted. + // buffer: (output) a buffer of, at least, size() integers. + void GetDescriptors(int* buffer) const; + // This must be called after transmitting the descriptors returned by + // GetDescriptors. It marks all the descriptors as consumed and closes those + // which are auto-close. + void CommitAll(); + // Returns true if any contained file descriptors appear to be handles to a + // directory. + bool ContainsDirectoryDescriptor() const; + + // --------------------------------------------------------------------------- + + + // --------------------------------------------------------------------------- + // Interfaces for receiving... + + // Set the contents of the set from the given buffer. This set must be empty + // before calling. The auto-close flag is set on all the descriptors so that + // unconsumed descriptors are closed on destruction. + void SetDescriptors(const int* buffer, unsigned count); + + // --------------------------------------------------------------------------- + + private: + friend class base::RefCountedThreadSafe<FileDescriptorSet>; + + ~FileDescriptorSet(); + + // A vector of descriptors and close flags. If this message is sent, then + // these descriptors are sent as control data. After sending, any descriptors + // with a true flag are closed. If this message has been received, then these + // are the descriptors which were received and all close flags are true. + std::vector<base::FileDescriptor> descriptors_; + + // This contains the index of the next descriptor which should be consumed. + // It's used in a couple of ways. Firstly, at destruction we can check that + // all the descriptors have been read (with GetNthDescriptor). Secondly, we + // can check that they are read in order. + mutable unsigned consumed_descriptor_highwater_; + + DISALLOW_COPY_AND_ASSIGN(FileDescriptorSet); +}; + +#endif // IPC_FILE_DESCRIPTOR_SET_POSIX_H_ diff --git a/chromium/ipc/file_descriptor_set_posix_unittest.cc b/chromium/ipc/file_descriptor_set_posix_unittest.cc new file mode 100644 index 00000000000..b4a01414a55 --- /dev/null +++ b/chromium/ipc/file_descriptor_set_posix_unittest.cc @@ -0,0 +1,177 @@ +// 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. + +// This test is POSIX only. + +#include "ipc/file_descriptor_set_posix.h" + +#include <unistd.h> +#include <fcntl.h> + +#include "base/basictypes.h" +#include "base/posix/eintr_wrapper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Get a safe file descriptor for test purposes. +int GetSafeFd() { + return open("/dev/null", O_RDONLY); +} + +// Returns true if fd was already closed. Closes fd if not closed. +bool VerifyClosed(int fd) { + const int duped = dup(fd); + if (duped != -1) { + EXPECT_NE(HANDLE_EINTR(close(duped)), -1); + EXPECT_NE(HANDLE_EINTR(close(fd)), -1); + return false; + } + return true; +} + +// The FileDescriptorSet will try and close some of the descriptor numbers +// which we given it. This is the base descriptor value. It's great enough such +// that no real descriptor will accidently be closed. +static const int kFDBase = 50000; + +TEST(FileDescriptorSet, BasicAdd) { + scoped_refptr<FileDescriptorSet> set(new FileDescriptorSet); + + ASSERT_EQ(set->size(), 0u); + ASSERT_TRUE(set->empty()); + ASSERT_TRUE(set->Add(kFDBase)); + ASSERT_EQ(set->size(), 1u); + ASSERT_TRUE(!set->empty()); + + // Empties the set and stops a warning about deleting a set with unconsumed + // descriptors + set->CommitAll(); +} + +TEST(FileDescriptorSet, BasicAddAndClose) { + scoped_refptr<FileDescriptorSet> set(new FileDescriptorSet); + + ASSERT_EQ(set->size(), 0u); + ASSERT_TRUE(set->empty()); + const int fd = GetSafeFd(); + ASSERT_TRUE(set->AddAndAutoClose(fd)); + ASSERT_EQ(set->size(), 1u); + ASSERT_TRUE(!set->empty()); + + set->CommitAll(); + + ASSERT_TRUE(VerifyClosed(fd)); +} +TEST(FileDescriptorSet, MaxSize) { + scoped_refptr<FileDescriptorSet> set(new FileDescriptorSet); + + for (size_t i = 0; i < FileDescriptorSet::kMaxDescriptorsPerMessage; ++i) + ASSERT_TRUE(set->Add(kFDBase + 1 + i)); + + ASSERT_TRUE(!set->Add(kFDBase)); + + set->CommitAll(); +} + +TEST(FileDescriptorSet, SetDescriptors) { + scoped_refptr<FileDescriptorSet> set(new FileDescriptorSet); + + ASSERT_TRUE(set->empty()); + set->SetDescriptors(NULL, 0); + ASSERT_TRUE(set->empty()); + + const int fd = GetSafeFd(); + static const int fds[] = {fd}; + set->SetDescriptors(fds, 1); + ASSERT_TRUE(!set->empty()); + ASSERT_EQ(set->size(), 1u); + + set->CommitAll(); + + ASSERT_TRUE(VerifyClosed(fd)); +} + +TEST(FileDescriptorSet, GetDescriptors) { + scoped_refptr<FileDescriptorSet> set(new FileDescriptorSet); + + set->GetDescriptors(NULL); + ASSERT_TRUE(set->Add(kFDBase)); + + int fds[1]; + fds[0] = 0; + set->GetDescriptors(fds); + ASSERT_EQ(fds[0], kFDBase); + set->CommitAll(); + ASSERT_TRUE(set->empty()); +} + +TEST(FileDescriptorSet, WalkInOrder) { + scoped_refptr<FileDescriptorSet> set(new FileDescriptorSet); + + ASSERT_TRUE(set->Add(kFDBase)); + ASSERT_TRUE(set->Add(kFDBase + 1)); + ASSERT_TRUE(set->Add(kFDBase + 2)); + + ASSERT_EQ(set->GetDescriptorAt(0), kFDBase); + ASSERT_EQ(set->GetDescriptorAt(1), kFDBase + 1); + ASSERT_EQ(set->GetDescriptorAt(2), kFDBase + 2); + + set->CommitAll(); +} + +TEST(FileDescriptorSet, WalkWrongOrder) { + scoped_refptr<FileDescriptorSet> set(new FileDescriptorSet); + + ASSERT_TRUE(set->Add(kFDBase)); + ASSERT_TRUE(set->Add(kFDBase + 1)); + ASSERT_TRUE(set->Add(kFDBase + 2)); + + ASSERT_EQ(set->GetDescriptorAt(0), kFDBase); + ASSERT_EQ(set->GetDescriptorAt(2), -1); + + set->CommitAll(); +} + +TEST(FileDescriptorSet, WalkCycle) { + scoped_refptr<FileDescriptorSet> set(new FileDescriptorSet); + + ASSERT_TRUE(set->Add(kFDBase)); + ASSERT_TRUE(set->Add(kFDBase + 1)); + ASSERT_TRUE(set->Add(kFDBase + 2)); + + ASSERT_EQ(set->GetDescriptorAt(0), kFDBase); + ASSERT_EQ(set->GetDescriptorAt(1), kFDBase + 1); + ASSERT_EQ(set->GetDescriptorAt(2), kFDBase + 2); + ASSERT_EQ(set->GetDescriptorAt(0), kFDBase); + ASSERT_EQ(set->GetDescriptorAt(1), kFDBase + 1); + ASSERT_EQ(set->GetDescriptorAt(2), kFDBase + 2); + ASSERT_EQ(set->GetDescriptorAt(0), kFDBase); + ASSERT_EQ(set->GetDescriptorAt(1), kFDBase + 1); + ASSERT_EQ(set->GetDescriptorAt(2), kFDBase + 2); + + set->CommitAll(); +} + +TEST(FileDescriptorSet, DontClose) { + scoped_refptr<FileDescriptorSet> set(new FileDescriptorSet); + + const int fd = GetSafeFd(); + ASSERT_TRUE(set->Add(fd)); + set->CommitAll(); + + ASSERT_FALSE(VerifyClosed(fd)); +} + +TEST(FileDescriptorSet, DoClose) { + scoped_refptr<FileDescriptorSet> set(new FileDescriptorSet); + + const int fd = GetSafeFd(); + ASSERT_TRUE(set->AddAndAutoClose(fd)); + set->CommitAll(); + + ASSERT_TRUE(VerifyClosed(fd)); +} + +} // namespace diff --git a/chromium/ipc/ipc.gyp b/chromium/ipc/ipc.gyp new file mode 100644 index 00000000000..a2476511a84 --- /dev/null +++ b/chromium/ipc/ipc.gyp @@ -0,0 +1,198 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'includes': [ + 'ipc.gypi', + ], + 'targets': [ + { + 'target_name': 'ipc', + 'type': '<(component)', + 'variables': { + 'ipc_target': 1, + }, + 'dependencies': [ + '../base/base.gyp:base', + # TODO(viettrungluu): Needed for base/lazy_instance.h, which is suspect. + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + ], + # TODO(gregoryd): direct_dependent_settings should be shared with the + # 64-bit target, but it doesn't work due to a bug in gyp + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, + }, + { + 'target_name': 'ipc_tests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'ipc', + 'test_support_ipc', + '../base/base.gyp:base', + '../base/base.gyp:base_i18n', + '../base/base.gyp:run_all_unittests', + '../base/base.gyp:test_support_base', + '../testing/gtest.gyp:gtest', + ], + 'include_dirs': [ + '..' + ], + 'sources': [ + 'file_descriptor_set_posix_unittest.cc', + 'ipc_channel_posix_unittest.cc', + 'ipc_channel_unittest.cc', + 'ipc_fuzzing_tests.cc', + 'ipc_message_unittest.cc', + 'ipc_message_utils_unittest.cc', + 'ipc_send_fds_test.cc', + 'ipc_sync_channel_unittest.cc', + 'ipc_sync_message_unittest.cc', + 'ipc_sync_message_unittest.h', + 'ipc_test_base.cc', + 'ipc_test_base.h', + 'sync_socket_unittest.cc', + 'unix_domain_socket_util_unittest.cc', + ], + 'conditions': [ + ['toolkit_uses_gtk == 1', { + 'dependencies': [ + '../build/linux/system.gyp:gtk', + ], + }], + ['OS == "win" or OS == "ios"', { + 'sources!': [ + 'unix_domain_socket_util_unittest.cc', + ], + }], + ['OS == "android" and gtest_target_type == "shared_library"', { + 'dependencies': [ + '../testing/android/native_test.gyp:native_test_native_code', + ], + }], + ['os_posix == 1 and OS != "mac" and OS != "android"', { + 'conditions': [ + ['linux_use_tcmalloc==1', { + 'dependencies': [ + '../base/allocator/allocator.gyp:allocator', + ], + }], + ], + }] + ], + }, + { + 'target_name': 'ipc_perftests', + 'type': '<(gtest_target_type)', + # TODO(viettrungluu): Figure out which dependencies are really needed. + 'dependencies': [ + 'ipc', + 'test_support_ipc', + '../base/base.gyp:base', + '../base/base.gyp:base_i18n', + '../base/base.gyp:test_support_base', + '../base/base.gyp:test_support_perf', + '../testing/gtest.gyp:gtest', + ], + 'include_dirs': [ + '..' + ], + 'sources': [ + 'ipc_perftests.cc', + 'ipc_test_base.cc', + 'ipc_test_base.h', + ], + 'conditions': [ + ['toolkit_uses_gtk == 1', { + 'dependencies': [ + '../build/linux/system.gyp:gtk', + ], + }], + ['OS == "android" and gtest_target_type == "shared_library"', { + 'dependencies': [ + '../testing/android/native_test.gyp:native_test_native_code', + ], + }], + ['os_posix == 1 and OS != "mac" and OS != "android"', { + 'conditions': [ + ['linux_use_tcmalloc==1', { + 'dependencies': [ + '../base/allocator/allocator.gyp:allocator', + ], + }], + ], + }] + ], + }, + { + 'target_name': 'test_support_ipc', + 'type': 'static_library', + 'dependencies': [ + 'ipc', + '../base/base.gyp:base', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'ipc_multiprocess_test.cc', + 'ipc_multiprocess_test.h', + 'ipc_test_sink.cc', + 'ipc_test_sink.h', + ], + }, + ], + 'conditions': [ + ['OS=="win" and target_arch=="ia32"', { + 'targets': [ + { + 'target_name': 'ipc_win64', + 'type': '<(component)', + 'variables': { + 'ipc_target': 1, + }, + 'dependencies': [ + '../base/base.gyp:base_nacl_win64', + # TODO(viettrungluu): Needed for base/lazy_instance.h, which is + # suspect. + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations_win64', + ], + # TODO(gregoryd): direct_dependent_settings should be shared with the + # 32-bit target, but it doesn't work due to a bug in gyp + 'direct_dependent_settings': { + 'include_dirs': [ + '..', + ], + }, + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + }, + ], + }], + # Special target to wrap a gtest_target_type==shared_library + # ipc_tests into an android apk for execution. + # See base.gyp for TODO(jrg)s about this strategy. + ['OS == "android" and gtest_target_type == "shared_library"', { + 'targets': [ + { + 'target_name': 'ipc_tests_apk', + 'type': 'none', + 'dependencies': [ + 'ipc_tests', + ], + 'variables': { + 'test_suite_name': 'ipc_tests', + 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)ipc_tests<(SHARED_LIB_SUFFIX)', + }, + 'includes': [ '../build/apk_test.gypi' ], + }], + }], + ], +} diff --git a/chromium/ipc/ipc.gypi b/chromium/ipc/ipc.gypi new file mode 100644 index 00000000000..ba80e421404 --- /dev/null +++ b/chromium/ipc/ipc.gypi @@ -0,0 +1,90 @@ +# 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. + +{ + 'target_defaults': { + 'variables': { + 'ipc_target': 0, + }, + 'target_conditions': [ + # This part is shared between the targets defined below. + ['ipc_target==1', { + 'sources': [ + 'file_descriptor_set_posix.cc', + 'file_descriptor_set_posix.h', + 'ipc_channel.cc', + 'ipc_channel.h', + 'ipc_channel_factory.cc', + 'ipc_channel_factory.h', + 'ipc_channel_handle.h', + 'ipc_channel_nacl.cc', + 'ipc_channel_nacl.h', + 'ipc_channel_posix.cc', + 'ipc_channel_posix.h', + 'ipc_channel_proxy.cc', + 'ipc_channel_proxy.h', + 'ipc_channel_reader.cc', + 'ipc_channel_reader.h', + 'ipc_channel_win.cc', + 'ipc_channel_win.h', + 'ipc_descriptors.h', + 'ipc_export.h', + 'ipc_forwarding_message_filter.cc', + 'ipc_forwarding_message_filter.h', + 'ipc_listener.h', + 'ipc_logging.cc', + 'ipc_logging.h', + 'ipc_message.cc', + 'ipc_message.h', + 'ipc_message_macros.h', + 'ipc_message_start.h', + 'ipc_message_utils.cc', + 'ipc_message_utils.h', + 'ipc_param_traits.h', + 'ipc_platform_file.cc', + 'ipc_platform_file.h', + 'ipc_sender.h', + 'ipc_switches.cc', + 'ipc_switches.h', + 'ipc_sync_channel.cc', + 'ipc_sync_channel.h', + 'ipc_sync_message.cc', + 'ipc_sync_message.h', + 'ipc_sync_message_filter.cc', + 'ipc_sync_message_filter.h', + 'param_traits_log_macros.h', + 'param_traits_macros.h', + 'param_traits_read_macros.h', + 'param_traits_write_macros.h', + 'struct_constructor_macros.h', + 'struct_destructor_macros.h', + 'unix_domain_socket_util.cc', + 'unix_domain_socket_util.h', + ], + 'defines': [ + 'IPC_IMPLEMENTATION', + ], + 'include_dirs': [ + '..', + ], + 'target_conditions': [ + ['>(nacl_untrusted_build)==1', { + 'sources!': [ + 'ipc_channel.cc', + 'ipc_channel_factory.cc', + 'ipc_channel_posix.cc', + 'unix_domain_socket_util.cc', + ], + }], + ['OS == "win" or OS == "ios"', { + 'sources!': [ + 'ipc_channel_factory.cc', + 'unix_domain_socket_util.cc', + ], + }], + ], + }], + ], + }, +} diff --git a/chromium/ipc/ipc_channel.cc b/chromium/ipc/ipc_channel.cc new file mode 100644 index 00000000000..a6ee14c01fc --- /dev/null +++ b/chromium/ipc/ipc_channel.cc @@ -0,0 +1,42 @@ +// 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 "ipc/ipc_channel.h" + +#include <limits> + +#include "base/atomic_sequence_num.h" +#include "base/rand_util.h" +#include "base/strings/stringprintf.h" + +#if !defined(OS_NACL) +namespace { + +// Global atomic used to guarantee channel IDs are unique. +base::StaticAtomicSequenceNumber g_last_id; + +} // namespace +#endif + +namespace IPC { + +// static +std::string Channel::GenerateUniqueRandomChannelID() { + // Note: the string must start with the current process id, this is how + // some child processes determine the pid of the parent. + // + // This is composed of a unique incremental identifier, the process ID of + // the creator, an identifier for the child instance, and a strong random + // component. The strong random component prevents other processes from + // hijacking or squatting on predictable channel names. + + int process_id = base::GetCurrentProcId(); + return base::StringPrintf("%d.%u.%d", + process_id, + g_last_id.GetNext(), + base::RandInt(0, std::numeric_limits<int32>::max())); +} + +} // namespace IPC + diff --git a/chromium/ipc/ipc_channel.h b/chromium/ipc/ipc_channel.h new file mode 100644 index 00000000000..f65a62b9cc9 --- /dev/null +++ b/chromium/ipc/ipc_channel.h @@ -0,0 +1,215 @@ +// 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. + +#ifndef IPC_IPC_CHANNEL_H_ +#define IPC_IPC_CHANNEL_H_ + +#include <string> + +#if defined(OS_POSIX) +#include <sys/types.h> +#endif + +#include "base/compiler_specific.h" +#include "base/process/process.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_sender.h" + +namespace IPC { + +class Listener; + +//------------------------------------------------------------------------------ +// See +// http://www.chromium.org/developers/design-documents/inter-process-communication +// for overview of IPC in Chromium. + +// Channels are implemented using named pipes on Windows, and +// socket pairs (or in some special cases unix domain sockets) on POSIX. +// On Windows we access pipes in various processes by name. +// On POSIX we pass file descriptors to child processes and assign names to them +// in a lookup table. +// In general on POSIX we do not use unix domain sockets due to security +// concerns and the fact that they can leave garbage around the file system +// (MacOS does not support abstract named unix domain sockets). +// You can use unix domain sockets if you like on POSIX by constructing the +// the channel with the mode set to one of the NAMED modes. NAMED modes are +// currently used by automation and service processes. + +class IPC_EXPORT Channel : public Sender { + // Security tests need access to the pipe handle. + friend class ChannelTest; + + public: + // Flags to test modes + enum ModeFlags { + MODE_NO_FLAG = 0x0, + MODE_SERVER_FLAG = 0x1, + MODE_CLIENT_FLAG = 0x2, + MODE_NAMED_FLAG = 0x4, +#if defined(OS_POSIX) + MODE_OPEN_ACCESS_FLAG = 0x8, // Don't restrict access based on client UID. +#endif + }; + + // Some Standard Modes + enum Mode { + MODE_NONE = MODE_NO_FLAG, + MODE_SERVER = MODE_SERVER_FLAG, + MODE_CLIENT = MODE_CLIENT_FLAG, + // Channels on Windows are named by default and accessible from other + // processes. On POSIX channels are anonymous by default and not accessible + // from other processes. Named channels work via named unix domain sockets. + // On Windows MODE_NAMED_SERVER is equivalent to MODE_SERVER and + // MODE_NAMED_CLIENT is equivalent to MODE_CLIENT. + MODE_NAMED_SERVER = MODE_SERVER_FLAG | MODE_NAMED_FLAG, + MODE_NAMED_CLIENT = MODE_CLIENT_FLAG | MODE_NAMED_FLAG, +#if defined(OS_POSIX) + // An "open" named server accepts connections from ANY client. + // The caller must then implement their own access-control based on the + // client process' user Id. + MODE_OPEN_NAMED_SERVER = MODE_OPEN_ACCESS_FLAG | MODE_SERVER_FLAG | + MODE_NAMED_FLAG +#endif + }; + + // The Hello message is internal to the Channel class. It is sent + // by the peer when the channel is connected. The message contains + // just the process id (pid). The message has a special routing_id + // (MSG_ROUTING_NONE) and type (HELLO_MESSAGE_TYPE). + enum { + HELLO_MESSAGE_TYPE = kuint16max // Maximum value of message type (uint16), + // to avoid conflicting with normal + // message types, which are enumeration + // constants starting from 0. + }; + + // The maximum message size in bytes. Attempting to receive a message of this + // size or bigger results in a channel error. + static const size_t kMaximumMessageSize = 128 * 1024 * 1024; + + // Amount of data to read at once from the pipe. + static const size_t kReadBufferSize = 4 * 1024; + + // Initialize a Channel. + // + // |channel_handle| identifies the communication Channel. For POSIX, if + // the file descriptor in the channel handle is != -1, the channel takes + // ownership of the file descriptor and will close it appropriately, otherwise + // it will create a new descriptor internally. + // |mode| specifies whether this Channel is to operate in server mode or + // client mode. In server mode, the Channel is responsible for setting up the + // IPC object, whereas in client mode, the Channel merely connects to the + // already established IPC object. + // |listener| receives a callback on the current thread for each newly + // received message. + // + Channel(const IPC::ChannelHandle &channel_handle, Mode mode, + Listener* listener); + + virtual ~Channel(); + + // Connect the pipe. On the server side, this will initiate + // waiting for connections. On the client, it attempts to + // connect to a pre-existing pipe. Note, calling Connect() + // will not block the calling thread and may complete + // asynchronously. + bool Connect() WARN_UNUSED_RESULT; + + // Close this Channel explicitly. May be called multiple times. + // On POSIX calling close on an IPC channel that listens for connections will + // cause it to close any accepted connections, and it will stop listening for + // new connections. If you just want to close the currently accepted + // connection and listen for new ones, use ResetToAcceptingConnectionState. + void Close(); + + // Get the process ID for the connected peer. + // + // Returns base::kNullProcessId if the peer is not connected yet. Watch out + // for race conditions. You can easily get a channel to another process, but + // if your process has not yet processed the "hello" message from the remote + // side, this will fail. You should either make sure calling this is either + // in response to a message from the remote side (which guarantees that it's + // been connected), or you wait for the "connected" notification on the + // listener. + base::ProcessId peer_pid() const; + + // Send a message over the Channel to the listener on the other end. + // + // |message| must be allocated using operator new. This object will be + // deleted once the contents of the Message have been sent. + virtual bool Send(Message* message) OVERRIDE; + +#if defined(OS_POSIX) + // On POSIX an IPC::Channel wraps a socketpair(), this method returns the + // FD # for the client end of the socket. + // This method may only be called on the server side of a channel. + // This method can be called on any thread. + int GetClientFileDescriptor() const; + + // Same as GetClientFileDescriptor, but transfers the ownership of the + // file descriptor to the caller. + // This method can be called on any thread. + int TakeClientFileDescriptor(); + + // On POSIX an IPC::Channel can either wrap an established socket, or it + // can wrap a socket that is listening for connections. Currently an + // IPC::Channel that listens for connections can only accept one connection + // at a time. + + // Returns true if the channel supports listening for connections. + bool AcceptsConnections() const; + + // Returns true if the channel supports listening for connections and is + // currently connected. + bool HasAcceptedConnection() const; + + // Returns true if the peer process' effective user id can be determined, in + // which case the supplied peer_euid is updated with it. + bool GetPeerEuid(uid_t* peer_euid) const; + + // Closes any currently connected socket, and returns to a listening state + // for more connections. + void ResetToAcceptingConnectionState(); +#endif // defined(OS_POSIX) && !defined(OS_NACL) + + // Returns true if a named server channel is initialized on the given channel + // ID. Even if true, the server may have already accepted a connection. + static bool IsNamedServerInitialized(const std::string& channel_id); + +#if !defined(OS_NACL) + // Generates a channel ID that's non-predictable and unique. + static std::string GenerateUniqueRandomChannelID(); + + // Generates a channel ID that, if passed to the client as a shared secret, + // will validate that the client's authenticity. On platforms that do not + // require additional this is simply calls GenerateUniqueRandomChannelID(). + // For portability the prefix should not include the \ character. + static std::string GenerateVerifiedChannelID(const std::string& prefix); +#endif + +#if defined(OS_LINUX) + // Sandboxed processes live in a PID namespace, so when sending the IPC hello + // message from client to server we need to send the PID from the global + // PID namespace. + static void SetGlobalPid(int pid); +#endif + + protected: + // Used in Chrome by the TestSink to provide a dummy channel implementation + // for testing. TestSink overrides the "interesting" functions in Channel so + // no actual implementation is needed. This will cause un-overridden calls to + // segfault. Do not use outside of test code! + Channel() : channel_impl_(0) { } + + private: + // PIMPL to which all channel calls are delegated. + class ChannelImpl; + ChannelImpl *channel_impl_; +}; + +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_H_ diff --git a/chromium/ipc/ipc_channel_factory.cc b/chromium/ipc/ipc_channel_factory.cc new file mode 100644 index 00000000000..f3ad11a6b4f --- /dev/null +++ b/chromium/ipc/ipc_channel_factory.cc @@ -0,0 +1,89 @@ +// 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 "ipc/ipc_channel_factory.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "ipc/unix_domain_socket_util.h" + +namespace IPC { + +ChannelFactory::ChannelFactory(const base::FilePath& path, Delegate* delegate) + : path_(path), delegate_(delegate), listen_fd_(-1) { + DCHECK(delegate_); + CreateSocket(); +} + +ChannelFactory::~ChannelFactory() { + Close(); +} + +bool ChannelFactory::CreateSocket() { + DCHECK(listen_fd_ < 0); + + // Create the socket. + return CreateServerUnixDomainSocket(path_, &listen_fd_); +} + +bool ChannelFactory::Listen() { + if (listen_fd_ < 0) + return false; + + // Watch the fd for connections, and turn any connections into + // active sockets. + base::MessageLoopForIO::current()->WatchFileDescriptor( + listen_fd_, + true, + base::MessageLoopForIO::WATCH_READ, + &server_listen_connection_watcher_, + this); + return true; +} + +// Called by libevent when we can read from the fd without blocking. +void ChannelFactory::OnFileCanReadWithoutBlocking(int fd) { + DCHECK(fd == listen_fd_); + int new_fd = -1; + if (!ServerAcceptConnection(listen_fd_, &new_fd)) { + Close(); + delegate_->OnListenError(); + return; + } + + if (new_fd < 0) { + // The accept() failed, but not in such a way that the factory needs to be + // shut down. + return; + } + + file_util::ScopedFD scoped_fd(&new_fd); + + // Verify that the IPC channel peer is running as the same user. + if (!IsPeerAuthorized(new_fd)) + return; + + ChannelHandle handle(std::string(), + base::FileDescriptor(*scoped_fd.release(), true)); + delegate_->OnClientConnected(handle); +} + +void ChannelFactory::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED() << "Listen fd should never be writable."; +} + +void ChannelFactory::Close() { + if (listen_fd_ < 0) + return; + if (HANDLE_EINTR(close(listen_fd_)) < 0) + PLOG(ERROR) << "close"; + listen_fd_ = -1; + if (unlink(path_.value().c_str()) < 0) + PLOG(ERROR) << "unlink"; + + // Unregister libevent for the listening socket and close it. + server_listen_connection_watcher_.StopWatchingFileDescriptor(); +} + +} // namespace IPC diff --git a/chromium/ipc/ipc_channel_factory.h b/chromium/ipc/ipc_channel_factory.h new file mode 100644 index 00000000000..3115601b491 --- /dev/null +++ b/chromium/ipc/ipc_channel_factory.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef IPC_IPC_CHANNEL_FACTORY_H_ +#define IPC_IPC_CHANNEL_FACTORY_H_ + +#include "base/files/file_path.h" +#include "base/message_loop/message_loop.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_export.h" + +namespace IPC { + +// A ChannelFactory listens on a UNIX domain socket. When a client connects to +// the socket, it accept()s the connection and passes the new FD to the +// delegate. The delegate is then responsible for creating a new IPC::Channel +// for the FD. +class IPC_EXPORT ChannelFactory : public base::MessageLoopForIO::Watcher { + public: + class Delegate { + public: + // Called when a client connects to the factory. It is the delegate's + // responsibility to create an IPC::Channel for the handle, or else close + // the file descriptor contained therein. + virtual void OnClientConnected(const ChannelHandle& handle) = 0; + + // Called when an error occurs and the channel is closed. + virtual void OnListenError() = 0; + }; + + ChannelFactory(const base::FilePath& path, Delegate* delegate); + + virtual ~ChannelFactory(); + + // Call this to start listening on the socket. + bool Listen(); + + // Close and unlink the socket, and stop accepting connections. + void Close(); + + private: + bool CreateSocket(); + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + base::MessageLoopForIO::FileDescriptorWatcher + server_listen_connection_watcher_; + base::FilePath path_; + Delegate* delegate_; + int listen_fd_; + + DISALLOW_COPY_AND_ASSIGN(ChannelFactory); +}; + +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_FACTORY_H_ diff --git a/chromium/ipc/ipc_channel_handle.h b/chromium/ipc/ipc_channel_handle.h new file mode 100644 index 00000000000..1b6fd8ca205 --- /dev/null +++ b/chromium/ipc/ipc_channel_handle.h @@ -0,0 +1,67 @@ +// 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. + +#ifndef IPC_IPC_CHANNEL_HANDLE_H_ +#define IPC_IPC_CHANNEL_HANDLE_H_ + +#include <string> + +#include "build/build_config.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#elif defined(OS_WIN) +#include <windows.h> +#endif // defined (OS_WIN) + +// On Windows, any process can create an IPC channel and others can fetch +// it by name. We pass around the channel names over IPC. +// On Windows the initialization of ChannelHandle with an existing pipe +// handle is provided for convenience. +// NOTE: A ChannelHandle with a pipe handle Will NOT be marshalled over IPC. + +// On POSIX, we instead pass around handles to channel endpoints via IPC. +// When it's time to IPC a new channel endpoint around, we send both the +// channel name as well as a base::FileDescriptor, which is itself a special +// type that knows how to copy a socket endpoint over IPC. +// +// In sum, this data structure can be used to pass channel information by name +// in both Windows and Posix. When passing a handle to a channel over IPC, +// use this data structure only for POSIX. + +namespace IPC { + +struct ChannelHandle { + // Note that serialization for this object is defined in the ParamTraits + // template specialization in ipc_message_utils.h. + ChannelHandle() {} + // The name that is passed in should be an absolute path for Posix. + // Otherwise there may be a problem in IPC communication between + // processes with different working directories. + ChannelHandle(const std::string& n) : name(n) {} + ChannelHandle(const char* n) : name(n) {} +#if defined(OS_WIN) + explicit ChannelHandle(HANDLE h) : pipe(h) {} +#elif defined(OS_POSIX) + ChannelHandle(const std::string& n, const base::FileDescriptor& s) + : name(n), socket(s) {} +#endif // defined(OS_POSIX) + + std::string name; +#if defined(OS_POSIX) + base::FileDescriptor socket; +#elif defined(OS_WIN) + // A simple container to automatically initialize pipe handle + struct PipeHandle { + PipeHandle() : handle(NULL) {} + PipeHandle(HANDLE h) : handle(h) {} + HANDLE handle; + }; + PipeHandle pipe; +#endif // defined (OS_WIN) +}; + +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_HANDLE_H_ diff --git a/chromium/ipc/ipc_channel_nacl.cc b/chromium/ipc/ipc_channel_nacl.cc new file mode 100644 index 00000000000..860815e91cb --- /dev/null +++ b/chromium/ipc/ipc_channel_nacl.cc @@ -0,0 +1,384 @@ +// 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 "ipc/ipc_channel_nacl.h" + +#include <errno.h> +#include <stddef.h> +#include <sys/types.h> + +#include <algorithm> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/synchronization/lock.h" +#include "base/task_runner_util.h" +#include "base/threading/simple_thread.h" +#include "ipc/file_descriptor_set_posix.h" +#include "ipc/ipc_logging.h" +#include "native_client/src/public/imc_syscalls.h" +#include "native_client/src/public/imc_types.h" + +namespace IPC { + +struct MessageContents { + std::vector<char> data; + std::vector<int> fds; +}; + +namespace { + +bool ReadDataOnReaderThread(int pipe, MessageContents* contents) { + DCHECK(pipe >= 0); + if (pipe < 0) + return false; + + contents->data.resize(Channel::kReadBufferSize); + contents->fds.resize(FileDescriptorSet::kMaxDescriptorsPerMessage); + + NaClAbiNaClImcMsgIoVec iov = { &contents->data[0], contents->data.size() }; + NaClAbiNaClImcMsgHdr msg = { + &iov, 1, &contents->fds[0], contents->fds.size() + }; + + int bytes_read = imc_recvmsg(pipe, &msg, 0); + + if (bytes_read <= 0) { + // NaClIPCAdapter::BlockingReceive returns -1 when the pipe closes (either + // due to error or for regular shutdown). + contents->data.clear(); + contents->fds.clear(); + return false; + } + DCHECK(bytes_read); + // Resize the buffers down to the number of bytes and fds we actually read. + contents->data.resize(bytes_read); + contents->fds.resize(msg.desc_length); + return true; +} + +} // namespace + +class Channel::ChannelImpl::ReaderThreadRunner + : public base::DelegateSimpleThread::Delegate { + public: + // |pipe|: A file descriptor from which we will read using imc_recvmsg. + // |data_read_callback|: A callback we invoke (on the main thread) when we + // have read data. + // |failure_callback|: A callback we invoke when we have a failure reading + // from |pipe|. + // |main_message_loop|: A proxy for the main thread, where we will invoke the + // above callbacks. + ReaderThreadRunner( + int pipe, + base::Callback<void (scoped_ptr<MessageContents>)> data_read_callback, + base::Callback<void ()> failure_callback, + scoped_refptr<base::MessageLoopProxy> main_message_loop); + + // DelegateSimpleThread implementation. Reads data from the pipe in a loop + // until either we are told to quit or a read fails. + virtual void Run() OVERRIDE; + + private: + int pipe_; + base::Callback<void (scoped_ptr<MessageContents>)> data_read_callback_; + base::Callback<void ()> failure_callback_; + scoped_refptr<base::MessageLoopProxy> main_message_loop_; + + DISALLOW_COPY_AND_ASSIGN(ReaderThreadRunner); +}; + +Channel::ChannelImpl::ReaderThreadRunner::ReaderThreadRunner( + int pipe, + base::Callback<void (scoped_ptr<MessageContents>)> data_read_callback, + base::Callback<void ()> failure_callback, + scoped_refptr<base::MessageLoopProxy> main_message_loop) + : pipe_(pipe), + data_read_callback_(data_read_callback), + failure_callback_(failure_callback), + main_message_loop_(main_message_loop) { +} + +void Channel::ChannelImpl::ReaderThreadRunner::Run() { + while (true) { + scoped_ptr<MessageContents> msg_contents(new MessageContents); + bool success = ReadDataOnReaderThread(pipe_, msg_contents.get()); + if (success) { + main_message_loop_->PostTask(FROM_HERE, + base::Bind(data_read_callback_, base::Passed(&msg_contents))); + } else { + main_message_loop_->PostTask(FROM_HERE, failure_callback_); + // Because the read failed, we know we're going to quit. Don't bother + // trying to read again. + return; + } + } +} + +Channel::ChannelImpl::ChannelImpl(const IPC::ChannelHandle& channel_handle, + Mode mode, + Listener* listener) + : ChannelReader(listener), + mode_(mode), + waiting_connect_(true), + pipe_(-1), + pipe_name_(channel_handle.name), + weak_ptr_factory_(this) { + if (!CreatePipe(channel_handle)) { + // The pipe may have been closed already. + const char *modestr = (mode_ & MODE_SERVER_FLAG) ? "server" : "client"; + LOG(WARNING) << "Unable to create pipe named \"" << channel_handle.name + << "\" in " << modestr << " mode"; + } +} + +Channel::ChannelImpl::~ChannelImpl() { + Close(); +} + +bool Channel::ChannelImpl::Connect() { + if (pipe_ == -1) { + DLOG(INFO) << "Channel creation failed: " << pipe_name_; + return false; + } + + // Note that Connect is called on the "Channel" thread (i.e., the same thread + // where Channel::Send will be called, and the same thread that should receive + // messages). The constructor might be invoked on another thread (see + // ChannelProxy for an example of that). Therefore, we must wait until Connect + // is called to decide which MessageLoopProxy to pass to ReaderThreadRunner. + reader_thread_runner_.reset( + new ReaderThreadRunner( + pipe_, + base::Bind(&Channel::ChannelImpl::DidRecvMsg, + weak_ptr_factory_.GetWeakPtr()), + base::Bind(&Channel::ChannelImpl::ReadDidFail, + weak_ptr_factory_.GetWeakPtr()), + base::MessageLoopProxy::current())); + reader_thread_.reset( + new base::DelegateSimpleThread(reader_thread_runner_.get(), + "ipc_channel_nacl reader thread")); + reader_thread_->Start(); + waiting_connect_ = false; + // If there were any messages queued before connection, send them. + ProcessOutgoingMessages(); + return true; +} + +void Channel::ChannelImpl::Close() { + // For now, we assume that at shutdown, the reader thread will be woken with + // a failure (see NaClIPCAdapter::BlockingRead and CloseChannel). Or... we + // might simply be killed with no chance to clean up anyway :-). + // If untrusted code tries to close the channel prior to shutdown, it's likely + // to hang. + // TODO(dmichael): Can we do anything smarter here to make sure the reader + // thread wakes up and quits? + reader_thread_->Join(); + close(pipe_); + pipe_ = -1; + reader_thread_runner_.reset(); + reader_thread_.reset(); + read_queue_.clear(); + output_queue_.clear(); +} + +bool Channel::ChannelImpl::Send(Message* message) { + DVLOG(2) << "sending message @" << message << " on channel @" << this + << " with type " << message->type(); + scoped_ptr<Message> message_ptr(message); + +#ifdef IPC_MESSAGE_LOG_ENABLED + Logging::GetInstance()->OnSendMessage(message_ptr.get(), ""); +#endif // IPC_MESSAGE_LOG_ENABLED + + message->TraceMessageBegin(); + output_queue_.push_back(linked_ptr<Message>(message_ptr.release())); + if (!waiting_connect_) + return ProcessOutgoingMessages(); + + return true; +} + +void Channel::ChannelImpl::DidRecvMsg(scoped_ptr<MessageContents> contents) { + // Close sets the pipe to -1. It's possible we'll get a buffer sent to us from + // the reader thread after Close is called. If so, we ignore it. + if (pipe_ == -1) + return; + + linked_ptr<std::vector<char> > data(new std::vector<char>); + data->swap(contents->data); + read_queue_.push_back(data); + + input_fds_.insert(input_fds_.end(), + contents->fds.begin(), contents->fds.end()); + contents->fds.clear(); + + // In POSIX, we would be told when there are bytes to read by implementing + // OnFileCanReadWithoutBlocking in MessageLoopForIO::Watcher. In NaCl, we + // instead know at this point because the reader thread posted some data to + // us. + ProcessIncomingMessages(); +} + +void Channel::ChannelImpl::ReadDidFail() { + Close(); +} + +bool Channel::ChannelImpl::CreatePipe( + const IPC::ChannelHandle& channel_handle) { + DCHECK(pipe_ == -1); + + // There's one possible case in NaCl: + // 1) It's a channel wrapping a pipe that is given to us. + // We don't support these: + // 2) It's for a named channel. + // 3) It's for a client that we implement ourself. + // 4) It's the initial IPC channel. + + if (channel_handle.socket.fd == -1) { + NOTIMPLEMENTED(); + return false; + } + pipe_ = channel_handle.socket.fd; + return true; +} + +bool Channel::ChannelImpl::ProcessOutgoingMessages() { + DCHECK(!waiting_connect_); // Why are we trying to send messages if there's + // no connection? + if (output_queue_.empty()) + return true; + + if (pipe_ == -1) + return false; + + // Write out all the messages. The trusted implementation is guaranteed to not + // block. See NaClIPCAdapter::Send for the implementation of imc_sendmsg. + while (!output_queue_.empty()) { + linked_ptr<Message> msg = output_queue_.front(); + output_queue_.pop_front(); + + int fds[FileDescriptorSet::kMaxDescriptorsPerMessage]; + const size_t num_fds = msg->file_descriptor_set()->size(); + DCHECK(num_fds <= FileDescriptorSet::kMaxDescriptorsPerMessage); + msg->file_descriptor_set()->GetDescriptors(fds); + + NaClAbiNaClImcMsgIoVec iov = { + const_cast<void*>(msg->data()), msg->size() + }; + NaClAbiNaClImcMsgHdr msgh = { &iov, 1, fds, num_fds }; + ssize_t bytes_written = imc_sendmsg(pipe_, &msgh, 0); + + DCHECK(bytes_written); // The trusted side shouldn't return 0. + if (bytes_written < 0) { + // The trusted side should only ever give us an error of EPIPE. We + // should never be interrupted, nor should we get EAGAIN. + DCHECK(errno == EPIPE); + Close(); + PLOG(ERROR) << "pipe_ error on " + << pipe_ + << " Currently writing message of size: " + << msg->size(); + return false; + } else { + msg->file_descriptor_set()->CommitAll(); + } + + // Message sent OK! + DVLOG(2) << "sent message @" << msg.get() << " with type " << msg->type() + << " on fd " << pipe_; + } + return true; +} + +Channel::ChannelImpl::ReadState Channel::ChannelImpl::ReadData( + char* buffer, + int buffer_len, + int* bytes_read) { + *bytes_read = 0; + if (pipe_ == -1) + return READ_FAILED; + if (read_queue_.empty()) + return READ_PENDING; + while (!read_queue_.empty() && *bytes_read < buffer_len) { + linked_ptr<std::vector<char> > vec(read_queue_.front()); + size_t bytes_to_read = buffer_len - *bytes_read; + if (vec->size() <= bytes_to_read) { + // We can read and discard the entire vector. + std::copy(vec->begin(), vec->end(), buffer + *bytes_read); + *bytes_read += vec->size(); + read_queue_.pop_front(); + } else { + // Read all the bytes we can and discard them from the front of the + // vector. (This can be slowish, since erase has to move the back of the + // vector to the front, but it's hopefully a temporary hack and it keeps + // the code simple). + std::copy(vec->begin(), vec->begin() + bytes_to_read, + buffer + *bytes_read); + vec->erase(vec->begin(), vec->begin() + bytes_to_read); + *bytes_read += bytes_to_read; + } + } + return READ_SUCCEEDED; +} + +bool Channel::ChannelImpl::WillDispatchInputMessage(Message* msg) { + uint16 header_fds = msg->header()->num_fds; + CHECK(header_fds == input_fds_.size()); + if (header_fds == 0) + return true; // Nothing to do. + + // The shenaniganery below with &foo.front() requires input_fds_ to have + // contiguous underlying storage (such as a simple array or a std::vector). + // This is why the header warns not to make input_fds_ a deque<>. + msg->file_descriptor_set()->SetDescriptors(&input_fds_.front(), + header_fds); + input_fds_.clear(); + return true; +} + +bool Channel::ChannelImpl::DidEmptyInputBuffers() { + // When the input data buffer is empty, the fds should be too. + return input_fds_.empty(); +} + +void Channel::ChannelImpl::HandleHelloMessage(const Message& msg) { + // The trusted side IPC::Channel should handle the "hello" handshake; we + // should not receive the "Hello" message. + NOTREACHED(); +} + +//------------------------------------------------------------------------------ +// Channel's methods simply call through to ChannelImpl. + +Channel::Channel(const IPC::ChannelHandle& channel_handle, + Mode mode, + Listener* listener) + : channel_impl_(new ChannelImpl(channel_handle, mode, listener)) { +} + +Channel::~Channel() { + delete channel_impl_; +} + +bool Channel::Connect() { + return channel_impl_->Connect(); +} + +void Channel::Close() { + channel_impl_->Close(); +} + +base::ProcessId Channel::peer_pid() const { + // This shouldn't actually get used in the untrusted side of the proxy, and we + // don't have the real pid anyway. + return -1; +} + +bool Channel::Send(Message* message) { + return channel_impl_->Send(message); +} + +} // namespace IPC diff --git a/chromium/ipc/ipc_channel_nacl.h b/chromium/ipc/ipc_channel_nacl.h new file mode 100644 index 00000000000..7c8960f6d71 --- /dev/null +++ b/chromium/ipc/ipc_channel_nacl.h @@ -0,0 +1,118 @@ +// 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. + +#ifndef IPC_IPC_CHANNEL_NACL_H_ +#define IPC_IPC_CHANNEL_NACL_H_ + +#include <deque> +#include <string> + +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/process/process.h" +#include "base/threading/simple_thread.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_channel_reader.h" + +namespace IPC { + +// Contains the results from one call to imc_recvmsg (data and file +// descriptors). +struct MessageContents; + +// Similar to the Posix version of ChannelImpl but for Native Client code. +// This is somewhat different because sendmsg/recvmsg here do not follow POSIX +// semantics. Instead, they are implemented by a custom embedding of +// NaClDescCustom. See NaClIPCAdapter for the trusted-side implementation. +// +// We don't need to worry about complicated set up and READWRITE mode for +// sharing handles. We also currently do not support passing file descriptors or +// named pipes, and we use background threads to emulate signaling when we can +// read or write without blocking. +class Channel::ChannelImpl : public internal::ChannelReader { + public: + // Mirror methods of Channel, see ipc_channel.h for description. + ChannelImpl(const IPC::ChannelHandle& channel_handle, + Mode mode, + Listener* listener); + virtual ~ChannelImpl(); + + // Channel implementation. + bool Connect(); + void Close(); + bool Send(Message* message); + + // Posted to the main thread by ReaderThreadRunner. + void DidRecvMsg(scoped_ptr<MessageContents> contents); + void ReadDidFail(); + + private: + class ReaderThreadRunner; + + bool CreatePipe(const IPC::ChannelHandle& channel_handle); + bool ProcessOutgoingMessages(); + + // ChannelReader implementation. + virtual ReadState ReadData(char* buffer, + int buffer_len, + int* bytes_read) OVERRIDE; + virtual bool WillDispatchInputMessage(Message* msg) OVERRIDE; + virtual bool DidEmptyInputBuffers() OVERRIDE; + virtual void HandleHelloMessage(const Message& msg) OVERRIDE; + + Mode mode_; + bool waiting_connect_; + + // The pipe used for communication. + int pipe_; + + // The "name" of our pipe. On Windows this is the global identifier for + // the pipe. On POSIX it's used as a key in a local map of file descriptors. + // For NaCl, we don't actually support looking up file descriptors by name, + // and it's only used for debug information. + std::string pipe_name_; + + // We use a thread for reading, so that we can simply block on reading and + // post the received data back to the main thread to be properly interleaved + // with other tasks in the MessagePump. + // + // imc_recvmsg supports non-blocking reads, but there's no easy way to be + // informed when a write or read can be done without blocking (this is handled + // by libevent in Posix). + scoped_ptr<ReaderThreadRunner> reader_thread_runner_; + scoped_ptr<base::DelegateSimpleThread> reader_thread_; + + // IPC::ChannelReader expects to be able to call ReadData on us to + // synchronously read data waiting in the pipe's buffer without blocking. + // Since we can't do that (see 1 and 2 above), the reader thread does blocking + // reads and posts the data over to the main thread in MessageContents. Each + // MessageContents object is the result of one call to "imc_recvmsg". + // DidRecvMsg breaks the MessageContents out in to the data and the file + // descriptors, and puts them on these two queues. + // TODO(dmichael): There's probably a more efficient way to emulate this with + // a circular buffer or something, so we don't have to do so + // many heap allocations. But it maybe isn't worth + // the trouble given that we probably want to implement 1 and + // 2 above in NaCl eventually. + // When ReadData is called, it pulls the bytes out of this queue in order. + std::deque<linked_ptr<std::vector<char> > > read_queue_; + // Queue of file descriptors extracted from imc_recvmsg messages. + // NOTE: The implementation assumes underlying storage here is contiguous, so + // don't change to something like std::deque<> without changing the + // implementation! + std::vector<int> input_fds_; + + // This queue is used when a message is sent prior to Connect having been + // called. Normally after we're connected, the queue is empty. + std::deque<linked_ptr<Message> > output_queue_; + + base::WeakPtrFactory<ChannelImpl> weak_ptr_factory_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(ChannelImpl); +}; + +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_NACL_H_ diff --git a/chromium/ipc/ipc_channel_posix.cc b/chromium/ipc/ipc_channel_posix.cc new file mode 100644 index 00000000000..98a7cd8a9fa --- /dev/null +++ b/chromium/ipc/ipc_channel_posix.cc @@ -0,0 +1,1028 @@ +// 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 "ipc/ipc_channel_posix.h" + +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <unistd.h> + +#if defined(OS_OPENBSD) +#include <sys/uio.h> +#endif + +#include <map> +#include <string> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/global_descriptors.h" +#include "base/process/process_handle.h" +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/synchronization/lock.h" +#include "ipc/file_descriptor_set_posix.h" +#include "ipc/ipc_descriptors.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_logging.h" +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_switches.h" +#include "ipc/unix_domain_socket_util.h" + +namespace IPC { + +// IPC channels on Windows use named pipes (CreateNamedPipe()) with +// channel ids as the pipe names. Channels on POSIX use sockets as +// pipes These don't quite line up. +// +// When creating a child subprocess we use a socket pair and the parent side of +// the fork arranges it such that the initial control channel ends up on the +// magic file descriptor kPrimaryIPCChannel in the child. Future +// connections (file descriptors) can then be passed via that +// connection via sendmsg(). +// +// A POSIX IPC channel can also be set up as a server for a bound UNIX domain +// socket, and will handle multiple connect and disconnect sequences. Currently +// it is limited to one connection at a time. + +//------------------------------------------------------------------------------ +namespace { + +// The PipeMap class works around this quirk related to unit tests: +// +// When running as a server, we install the client socket in a +// specific file descriptor number (@kPrimaryIPCChannel). However, we +// also have to support the case where we are running unittests in the +// same process. (We do not support forking without execing.) +// +// Case 1: normal running +// The IPC server object will install a mapping in PipeMap from the +// name which it was given to the client pipe. When forking the client, the +// GetClientFileDescriptorMapping will ensure that the socket is installed in +// the magic slot (@kPrimaryIPCChannel). The client will search for the +// mapping, but it won't find any since we are in a new process. Thus the +// magic fd number is returned. Once the client connects, the server will +// close its copy of the client socket and remove the mapping. +// +// Case 2: unittests - client and server in the same process +// The IPC server will install a mapping as before. The client will search +// for a mapping and find out. It duplicates the file descriptor and +// connects. Once the client connects, the server will close the original +// copy of the client socket and remove the mapping. Thus, when the client +// object closes, it will close the only remaining copy of the client socket +// in the fd table and the server will see EOF on its side. +// +// TODO(port): a client process cannot connect to multiple IPC channels with +// this scheme. + +class PipeMap { + public: + static PipeMap* GetInstance() { + return Singleton<PipeMap>::get(); + } + + ~PipeMap() { + // Shouldn't have left over pipes. + DCHECK(map_.empty()); + } + + // Lookup a given channel id. Return -1 if not found. + int Lookup(const std::string& channel_id) { + base::AutoLock locked(lock_); + + ChannelToFDMap::const_iterator i = map_.find(channel_id); + if (i == map_.end()) + return -1; + return i->second; + } + + // Remove the mapping for the given channel id. No error is signaled if the + // channel_id doesn't exist + void Remove(const std::string& channel_id) { + base::AutoLock locked(lock_); + map_.erase(channel_id); + } + + // Insert a mapping from @channel_id to @fd. It's a fatal error to insert a + // mapping if one already exists for the given channel_id + void Insert(const std::string& channel_id, int fd) { + base::AutoLock locked(lock_); + DCHECK_NE(-1, fd); + + ChannelToFDMap::const_iterator i = map_.find(channel_id); + CHECK(i == map_.end()) << "Creating second IPC server (fd " << fd << ") " + << "for '" << channel_id << "' while first " + << "(fd " << i->second << ") still exists"; + map_[channel_id] = fd; + } + + private: + base::Lock lock_; + typedef std::map<std::string, int> ChannelToFDMap; + ChannelToFDMap map_; + + friend struct DefaultSingletonTraits<PipeMap>; +}; + +//------------------------------------------------------------------------------ + +bool SocketWriteErrorIsRecoverable() { +#if defined(OS_MACOSX) + // On OS X if sendmsg() is trying to send fds between processes and there + // isn't enough room in the output buffer to send the fd structure over + // atomically then EMSGSIZE is returned. + // + // EMSGSIZE presents a problem since the system APIs can only call us when + // there's room in the socket buffer and not when there is "enough" room. + // + // The current behavior is to return to the event loop when EMSGSIZE is + // received and hopefull service another FD. This is however still + // technically a busy wait since the event loop will call us right back until + // the receiver has read enough data to allow passing the FD over atomically. + return errno == EAGAIN || errno == EMSGSIZE; +#else + return errno == EAGAIN; +#endif // OS_MACOSX +} + +} // namespace +//------------------------------------------------------------------------------ + +#if defined(OS_LINUX) +int Channel::ChannelImpl::global_pid_ = 0; +#endif // OS_LINUX + +Channel::ChannelImpl::ChannelImpl(const IPC::ChannelHandle& channel_handle, + Mode mode, Listener* listener) + : ChannelReader(listener), + mode_(mode), + peer_pid_(base::kNullProcessId), + is_blocked_on_write_(false), + waiting_connect_(true), + message_send_bytes_written_(0), + server_listen_pipe_(-1), + pipe_(-1), + client_pipe_(-1), +#if defined(IPC_USES_READWRITE) + fd_pipe_(-1), + remote_fd_pipe_(-1), +#endif // IPC_USES_READWRITE + pipe_name_(channel_handle.name), + must_unlink_(false) { + memset(input_cmsg_buf_, 0, sizeof(input_cmsg_buf_)); + if (!CreatePipe(channel_handle)) { + // The pipe may have been closed already. + const char *modestr = (mode_ & MODE_SERVER_FLAG) ? "server" : "client"; + LOG(WARNING) << "Unable to create pipe named \"" << channel_handle.name + << "\" in " << modestr << " mode"; + } +} + +Channel::ChannelImpl::~ChannelImpl() { + Close(); +} + +bool SocketPair(int* fd1, int* fd2) { + int pipe_fds[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds) != 0) { + PLOG(ERROR) << "socketpair()"; + return false; + } + + // Set both ends to be non-blocking. + if (fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK) == -1 || + fcntl(pipe_fds[1], F_SETFL, O_NONBLOCK) == -1) { + PLOG(ERROR) << "fcntl(O_NONBLOCK)"; + if (HANDLE_EINTR(close(pipe_fds[0])) < 0) + PLOG(ERROR) << "close"; + if (HANDLE_EINTR(close(pipe_fds[1])) < 0) + PLOG(ERROR) << "close"; + return false; + } + + *fd1 = pipe_fds[0]; + *fd2 = pipe_fds[1]; + + return true; +} + +bool Channel::ChannelImpl::CreatePipe( + const IPC::ChannelHandle& channel_handle) { + DCHECK(server_listen_pipe_ == -1 && pipe_ == -1); + + // Four possible cases: + // 1) It's a channel wrapping a pipe that is given to us. + // 2) It's for a named channel, so we create it. + // 3) It's for a client that we implement ourself. This is used + // in unittesting. + // 4) It's the initial IPC channel: + // 4a) Client side: Pull the pipe out of the GlobalDescriptors set. + // 4b) Server side: create the pipe. + + int local_pipe = -1; + if (channel_handle.socket.fd != -1) { + // Case 1 from comment above. + local_pipe = channel_handle.socket.fd; +#if defined(IPC_USES_READWRITE) + // Test the socket passed into us to make sure it is nonblocking. + // We don't want to call read/write on a blocking socket. + int value = fcntl(local_pipe, F_GETFL); + if (value == -1) { + PLOG(ERROR) << "fcntl(F_GETFL) " << pipe_name_; + return false; + } + if (!(value & O_NONBLOCK)) { + LOG(ERROR) << "Socket " << pipe_name_ << " must be O_NONBLOCK"; + return false; + } +#endif // IPC_USES_READWRITE + } else if (mode_ & MODE_NAMED_FLAG) { + // Case 2 from comment above. + if (mode_ & MODE_SERVER_FLAG) { + if (!CreateServerUnixDomainSocket(base::FilePath(pipe_name_), + &local_pipe)) { + return false; + } + must_unlink_ = true; + } else if (mode_ & MODE_CLIENT_FLAG) { + if (!CreateClientUnixDomainSocket(base::FilePath(pipe_name_), + &local_pipe)) { + return false; + } + } else { + LOG(ERROR) << "Bad mode: " << mode_; + return false; + } + } else { + local_pipe = PipeMap::GetInstance()->Lookup(pipe_name_); + if (mode_ & MODE_CLIENT_FLAG) { + if (local_pipe != -1) { + // Case 3 from comment above. + // We only allow one connection. + local_pipe = HANDLE_EINTR(dup(local_pipe)); + PipeMap::GetInstance()->Remove(pipe_name_); + } else { + // Case 4a from comment above. + // Guard against inappropriate reuse of the initial IPC channel. If + // an IPC channel closes and someone attempts to reuse it by name, the + // initial channel must not be recycled here. http://crbug.com/26754. + static bool used_initial_channel = false; + if (used_initial_channel) { + LOG(FATAL) << "Denying attempt to reuse initial IPC channel for " + << pipe_name_; + return false; + } + used_initial_channel = true; + + local_pipe = + base::GlobalDescriptors::GetInstance()->Get(kPrimaryIPCChannel); + } + } else if (mode_ & MODE_SERVER_FLAG) { + // Case 4b from comment above. + if (local_pipe != -1) { + LOG(ERROR) << "Server already exists for " << pipe_name_; + return false; + } + base::AutoLock lock(client_pipe_lock_); + if (!SocketPair(&local_pipe, &client_pipe_)) + return false; + PipeMap::GetInstance()->Insert(pipe_name_, client_pipe_); + } else { + LOG(ERROR) << "Bad mode: " << mode_; + return false; + } + } + +#if defined(IPC_USES_READWRITE) + // Create a dedicated socketpair() for exchanging file descriptors. + // See comments for IPC_USES_READWRITE for details. + if (mode_ & MODE_CLIENT_FLAG) { + if (!SocketPair(&fd_pipe_, &remote_fd_pipe_)) { + return false; + } + } +#endif // IPC_USES_READWRITE + + if ((mode_ & MODE_SERVER_FLAG) && (mode_ & MODE_NAMED_FLAG)) { + server_listen_pipe_ = local_pipe; + local_pipe = -1; + } + + pipe_ = local_pipe; + return true; +} + +bool Channel::ChannelImpl::Connect() { + if (server_listen_pipe_ == -1 && pipe_ == -1) { + DLOG(INFO) << "Channel creation failed: " << pipe_name_; + return false; + } + + bool did_connect = true; + if (server_listen_pipe_ != -1) { + // Watch the pipe for connections, and turn any connections into + // active sockets. + base::MessageLoopForIO::current()->WatchFileDescriptor( + server_listen_pipe_, + true, + base::MessageLoopForIO::WATCH_READ, + &server_listen_connection_watcher_, + this); + } else { + did_connect = AcceptConnection(); + } + return did_connect; +} + +bool Channel::ChannelImpl::ProcessOutgoingMessages() { + DCHECK(!waiting_connect_); // Why are we trying to send messages if there's + // no connection? + if (output_queue_.empty()) + return true; + + if (pipe_ == -1) + return false; + + // Write out all the messages we can till the write blocks or there are no + // more outgoing messages. + while (!output_queue_.empty()) { + Message* msg = output_queue_.front(); + + size_t amt_to_write = msg->size() - message_send_bytes_written_; + DCHECK_NE(0U, amt_to_write); + const char* out_bytes = reinterpret_cast<const char*>(msg->data()) + + message_send_bytes_written_; + + struct msghdr msgh = {0}; + struct iovec iov = {const_cast<char*>(out_bytes), amt_to_write}; + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + char buf[CMSG_SPACE( + sizeof(int) * FileDescriptorSet::kMaxDescriptorsPerMessage)]; + + ssize_t bytes_written = 1; + int fd_written = -1; + + if (message_send_bytes_written_ == 0 && + !msg->file_descriptor_set()->empty()) { + // This is the first chunk of a message which has descriptors to send + struct cmsghdr *cmsg; + const unsigned num_fds = msg->file_descriptor_set()->size(); + + DCHECK(num_fds <= FileDescriptorSet::kMaxDescriptorsPerMessage); + if (msg->file_descriptor_set()->ContainsDirectoryDescriptor()) { + LOG(FATAL) << "Panic: attempting to transport directory descriptor over" + " IPC. Aborting to maintain sandbox isolation."; + // If you have hit this then something tried to send a file descriptor + // to a directory over an IPC channel. Since IPC channels span + // sandboxes this is very bad: the receiving process can use openat + // with ".." elements in the path in order to reach the real + // filesystem. + } + + msgh.msg_control = buf; + msgh.msg_controllen = CMSG_SPACE(sizeof(int) * num_fds); + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds); + msg->file_descriptor_set()->GetDescriptors( + reinterpret_cast<int*>(CMSG_DATA(cmsg))); + msgh.msg_controllen = cmsg->cmsg_len; + + // DCHECK_LE above already checks that + // num_fds < kMaxDescriptorsPerMessage so no danger of overflow. + msg->header()->num_fds = static_cast<uint16>(num_fds); + +#if defined(IPC_USES_READWRITE) + if (!IsHelloMessage(*msg)) { + // Only the Hello message sends the file descriptor with the message. + // Subsequently, we can send file descriptors on the dedicated + // fd_pipe_ which makes Seccomp sandbox operation more efficient. + struct iovec fd_pipe_iov = { const_cast<char *>(""), 1 }; + msgh.msg_iov = &fd_pipe_iov; + fd_written = fd_pipe_; + bytes_written = HANDLE_EINTR(sendmsg(fd_pipe_, &msgh, MSG_DONTWAIT)); + msgh.msg_iov = &iov; + msgh.msg_controllen = 0; + if (bytes_written > 0) { + msg->file_descriptor_set()->CommitAll(); + } + } +#endif // IPC_USES_READWRITE + } + + if (bytes_written == 1) { + fd_written = pipe_; +#if defined(IPC_USES_READWRITE) + if ((mode_ & MODE_CLIENT_FLAG) && IsHelloMessage(*msg)) { + DCHECK_EQ(msg->file_descriptor_set()->size(), 1U); + } + if (!msgh.msg_controllen) { + bytes_written = HANDLE_EINTR(write(pipe_, out_bytes, amt_to_write)); + } else +#endif // IPC_USES_READWRITE + { + bytes_written = HANDLE_EINTR(sendmsg(pipe_, &msgh, MSG_DONTWAIT)); + } + } + if (bytes_written > 0) + msg->file_descriptor_set()->CommitAll(); + + if (bytes_written < 0 && !SocketWriteErrorIsRecoverable()) { +#if defined(OS_MACOSX) + // On OSX writing to a pipe with no listener returns EPERM. + if (errno == EPERM) { + Close(); + return false; + } +#endif // OS_MACOSX + if (errno == EPIPE) { + Close(); + return false; + } + PLOG(ERROR) << "pipe error on " + << fd_written + << " Currently writing message of size: " + << msg->size(); + return false; + } + + if (static_cast<size_t>(bytes_written) != amt_to_write) { + if (bytes_written > 0) { + // If write() fails with EAGAIN then bytes_written will be -1. + message_send_bytes_written_ += bytes_written; + } + + // Tell libevent to call us back once things are unblocked. + is_blocked_on_write_ = true; + base::MessageLoopForIO::current()->WatchFileDescriptor( + pipe_, + false, // One shot + base::MessageLoopForIO::WATCH_WRITE, + &write_watcher_, + this); + return true; + } else { + message_send_bytes_written_ = 0; + + // Message sent OK! + DVLOG(2) << "sent message @" << msg << " on channel @" << this + << " with type " << msg->type() << " on fd " << pipe_; + delete output_queue_.front(); + output_queue_.pop(); + } + } + return true; +} + +bool Channel::ChannelImpl::Send(Message* message) { + DVLOG(2) << "sending message @" << message << " on channel @" << this + << " with type " << message->type() + << " (" << output_queue_.size() << " in queue)"; + +#ifdef IPC_MESSAGE_LOG_ENABLED + Logging::GetInstance()->OnSendMessage(message, ""); +#endif // IPC_MESSAGE_LOG_ENABLED + + message->TraceMessageBegin(); + output_queue_.push(message); + if (!is_blocked_on_write_ && !waiting_connect_) { + return ProcessOutgoingMessages(); + } + + return true; +} + +int Channel::ChannelImpl::GetClientFileDescriptor() { + base::AutoLock lock(client_pipe_lock_); + return client_pipe_; +} + +int Channel::ChannelImpl::TakeClientFileDescriptor() { + base::AutoLock lock(client_pipe_lock_); + int fd = client_pipe_; + if (client_pipe_ != -1) { + PipeMap::GetInstance()->Remove(pipe_name_); + client_pipe_ = -1; + } + return fd; +} + +void Channel::ChannelImpl::CloseClientFileDescriptor() { + base::AutoLock lock(client_pipe_lock_); + if (client_pipe_ != -1) { + PipeMap::GetInstance()->Remove(pipe_name_); + if (HANDLE_EINTR(close(client_pipe_)) < 0) + PLOG(ERROR) << "close " << pipe_name_; + client_pipe_ = -1; + } +} + +bool Channel::ChannelImpl::AcceptsConnections() const { + return server_listen_pipe_ != -1; +} + +bool Channel::ChannelImpl::HasAcceptedConnection() const { + return AcceptsConnections() && pipe_ != -1; +} + +bool Channel::ChannelImpl::GetPeerEuid(uid_t* peer_euid) const { + DCHECK(!(mode_ & MODE_SERVER) || HasAcceptedConnection()); + return IPC::GetPeerEuid(pipe_, peer_euid); +} + +void Channel::ChannelImpl::ResetToAcceptingConnectionState() { + // Unregister libevent for the unix domain socket and close it. + read_watcher_.StopWatchingFileDescriptor(); + write_watcher_.StopWatchingFileDescriptor(); + if (pipe_ != -1) { + if (HANDLE_EINTR(close(pipe_)) < 0) + PLOG(ERROR) << "close pipe_ " << pipe_name_; + pipe_ = -1; + } +#if defined(IPC_USES_READWRITE) + if (fd_pipe_ != -1) { + if (HANDLE_EINTR(close(fd_pipe_)) < 0) + PLOG(ERROR) << "close fd_pipe_ " << pipe_name_; + fd_pipe_ = -1; + } + if (remote_fd_pipe_ != -1) { + if (HANDLE_EINTR(close(remote_fd_pipe_)) < 0) + PLOG(ERROR) << "close remote_fd_pipe_ " << pipe_name_; + remote_fd_pipe_ = -1; + } +#endif // IPC_USES_READWRITE + + while (!output_queue_.empty()) { + Message* m = output_queue_.front(); + output_queue_.pop(); + delete m; + } + + // Close any outstanding, received file descriptors. + ClearInputFDs(); +} + +// static +bool Channel::ChannelImpl::IsNamedServerInitialized( + const std::string& channel_id) { + return base::PathExists(base::FilePath(channel_id)); +} + +#if defined(OS_LINUX) +// static +void Channel::ChannelImpl::SetGlobalPid(int pid) { + global_pid_ = pid; +} +#endif // OS_LINUX + +// Called by libevent when we can read from the pipe without blocking. +void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { + bool send_server_hello_msg = false; + if (fd == server_listen_pipe_) { + int new_pipe = 0; + if (!ServerAcceptConnection(server_listen_pipe_, &new_pipe) || + new_pipe < 0) { + Close(); + listener()->OnChannelListenError(); + } + + if (pipe_ != -1) { + // We already have a connection. We only handle one at a time. + // close our new descriptor. + if (HANDLE_EINTR(shutdown(new_pipe, SHUT_RDWR)) < 0) + DPLOG(ERROR) << "shutdown " << pipe_name_; + if (HANDLE_EINTR(close(new_pipe)) < 0) + DPLOG(ERROR) << "close " << pipe_name_; + listener()->OnChannelDenied(); + return; + } + pipe_ = new_pipe; + + if ((mode_ & MODE_OPEN_ACCESS_FLAG) == 0) { + // Verify that the IPC channel peer is running as the same user. + uid_t client_euid; + if (!GetPeerEuid(&client_euid)) { + DLOG(ERROR) << "Unable to query client euid"; + ResetToAcceptingConnectionState(); + return; + } + if (client_euid != geteuid()) { + DLOG(WARNING) << "Client euid is not authorised"; + ResetToAcceptingConnectionState(); + return; + } + } + + if (!AcceptConnection()) { + NOTREACHED() << "AcceptConnection should not fail on server"; + } + send_server_hello_msg = true; + waiting_connect_ = false; + } else if (fd == pipe_) { + if (waiting_connect_ && (mode_ & MODE_SERVER_FLAG)) { + send_server_hello_msg = true; + waiting_connect_ = false; + } + if (!ProcessIncomingMessages()) { + // ClosePipeOnError may delete this object, so we mustn't call + // ProcessOutgoingMessages. + send_server_hello_msg = false; + ClosePipeOnError(); + } + } else { + NOTREACHED() << "Unknown pipe " << fd; + } + + // If we're a server and handshaking, then we want to make sure that we + // only send our handshake message after we've processed the client's. + // This gives us a chance to kill the client if the incoming handshake + // is invalid. + if (send_server_hello_msg) { + ProcessOutgoingMessages(); + } +} + +// Called by libevent when we can write to the pipe without blocking. +void Channel::ChannelImpl::OnFileCanWriteWithoutBlocking(int fd) { + DCHECK_EQ(pipe_, fd); + is_blocked_on_write_ = false; + if (!ProcessOutgoingMessages()) { + ClosePipeOnError(); + } +} + +bool Channel::ChannelImpl::AcceptConnection() { + base::MessageLoopForIO::current()->WatchFileDescriptor( + pipe_, true, base::MessageLoopForIO::WATCH_READ, &read_watcher_, this); + QueueHelloMessage(); + + if (mode_ & MODE_CLIENT_FLAG) { + // If we are a client we want to send a hello message out immediately. + // In server mode we will send a hello message when we receive one from a + // client. + waiting_connect_ = false; + return ProcessOutgoingMessages(); + } else if (mode_ & MODE_SERVER_FLAG) { + waiting_connect_ = true; + return true; + } else { + NOTREACHED(); + return false; + } +} + +void Channel::ChannelImpl::ClosePipeOnError() { + if (HasAcceptedConnection()) { + ResetToAcceptingConnectionState(); + listener()->OnChannelError(); + } else { + Close(); + if (AcceptsConnections()) { + listener()->OnChannelListenError(); + } else { + listener()->OnChannelError(); + } + } +} + +int Channel::ChannelImpl::GetHelloMessageProcId() { + int pid = base::GetCurrentProcId(); +#if defined(OS_LINUX) + // Our process may be in a sandbox with a separate PID namespace. + if (global_pid_) { + pid = global_pid_; + } +#endif + return pid; +} + +void Channel::ChannelImpl::QueueHelloMessage() { + // Create the Hello message + scoped_ptr<Message> msg(new Message(MSG_ROUTING_NONE, + HELLO_MESSAGE_TYPE, + IPC::Message::PRIORITY_NORMAL)); + if (!msg->WriteInt(GetHelloMessageProcId())) { + NOTREACHED() << "Unable to pickle hello message proc id"; + } +#if defined(IPC_USES_READWRITE) + scoped_ptr<Message> hello; + if (remote_fd_pipe_ != -1) { + if (!msg->WriteFileDescriptor(base::FileDescriptor(remote_fd_pipe_, + false))) { + NOTREACHED() << "Unable to pickle hello message file descriptors"; + } + DCHECK_EQ(msg->file_descriptor_set()->size(), 1U); + } +#endif // IPC_USES_READWRITE + output_queue_.push(msg.release()); +} + +Channel::ChannelImpl::ReadState Channel::ChannelImpl::ReadData( + char* buffer, + int buffer_len, + int* bytes_read) { + if (pipe_ == -1) + return READ_FAILED; + + struct msghdr msg = {0}; + + struct iovec iov = {buffer, static_cast<size_t>(buffer_len)}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_control = input_cmsg_buf_; + + // recvmsg() returns 0 if the connection has closed or EAGAIN if no data + // is waiting on the pipe. +#if defined(IPC_USES_READWRITE) + if (fd_pipe_ >= 0) { + *bytes_read = HANDLE_EINTR(read(pipe_, buffer, buffer_len)); + msg.msg_controllen = 0; + } else +#endif // IPC_USES_READWRITE + { + msg.msg_controllen = sizeof(input_cmsg_buf_); + *bytes_read = HANDLE_EINTR(recvmsg(pipe_, &msg, MSG_DONTWAIT)); + } + if (*bytes_read < 0) { + if (errno == EAGAIN) { + return READ_PENDING; +#if defined(OS_MACOSX) + } else if (errno == EPERM) { + // On OSX, reading from a pipe with no listener returns EPERM + // treat this as a special case to prevent spurious error messages + // to the console. + return READ_FAILED; +#endif // OS_MACOSX + } else if (errno == ECONNRESET || errno == EPIPE) { + return READ_FAILED; + } else { + PLOG(ERROR) << "pipe error (" << pipe_ << ")"; + return READ_FAILED; + } + } else if (*bytes_read == 0) { + // The pipe has closed... + return READ_FAILED; + } + DCHECK(*bytes_read); + + CloseClientFileDescriptor(); + + // Read any file descriptors from the message. + if (!ExtractFileDescriptorsFromMsghdr(&msg)) + return READ_FAILED; + return READ_SUCCEEDED; +} + +#if defined(IPC_USES_READWRITE) +bool Channel::ChannelImpl::ReadFileDescriptorsFromFDPipe() { + char dummy; + struct iovec fd_pipe_iov = { &dummy, 1 }; + + struct msghdr msg = { 0 }; + msg.msg_iov = &fd_pipe_iov; + msg.msg_iovlen = 1; + msg.msg_control = input_cmsg_buf_; + msg.msg_controllen = sizeof(input_cmsg_buf_); + ssize_t bytes_received = HANDLE_EINTR(recvmsg(fd_pipe_, &msg, MSG_DONTWAIT)); + + if (bytes_received != 1) + return true; // No message waiting. + + if (!ExtractFileDescriptorsFromMsghdr(&msg)) + return false; + return true; +} +#endif + +// On Posix, we need to fix up the file descriptors before the input message +// is dispatched. +// +// This will read from the input_fds_ (READWRITE mode only) and read more +// handles from the FD pipe if necessary. +bool Channel::ChannelImpl::WillDispatchInputMessage(Message* msg) { + uint16 header_fds = msg->header()->num_fds; + if (!header_fds) + return true; // Nothing to do. + + // The message has file descriptors. + const char* error = NULL; + if (header_fds > input_fds_.size()) { + // The message has been completely received, but we didn't get + // enough file descriptors. +#if defined(IPC_USES_READWRITE) + if (!ReadFileDescriptorsFromFDPipe()) + return false; + if (header_fds > input_fds_.size()) +#endif // IPC_USES_READWRITE + error = "Message needs unreceived descriptors"; + } + + if (header_fds > FileDescriptorSet::kMaxDescriptorsPerMessage) + error = "Message requires an excessive number of descriptors"; + + if (error) { + LOG(WARNING) << error + << " channel:" << this + << " message-type:" << msg->type() + << " header()->num_fds:" << header_fds; + // Abort the connection. + ClearInputFDs(); + return false; + } + + // The shenaniganery below with &foo.front() requires input_fds_ to have + // contiguous underlying storage (such as a simple array or a std::vector). + // This is why the header warns not to make input_fds_ a deque<>. + msg->file_descriptor_set()->SetDescriptors(&input_fds_.front(), + header_fds); + input_fds_.erase(input_fds_.begin(), input_fds_.begin() + header_fds); + return true; +} + +bool Channel::ChannelImpl::DidEmptyInputBuffers() { + // When the input data buffer is empty, the fds should be too. If this is + // not the case, we probably have a rogue renderer which is trying to fill + // our descriptor table. + return input_fds_.empty(); +} + +bool Channel::ChannelImpl::ExtractFileDescriptorsFromMsghdr(msghdr* msg) { + // Check that there are any control messages. On OSX, CMSG_FIRSTHDR will + // return an invalid non-NULL pointer in the case that controllen == 0. + if (msg->msg_controllen == 0) + return true; + + for (cmsghdr* cmsg = CMSG_FIRSTHDR(msg); + cmsg; + cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); + DCHECK_EQ(0U, payload_len % sizeof(int)); + const int* file_descriptors = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + unsigned num_file_descriptors = payload_len / 4; + input_fds_.insert(input_fds_.end(), + file_descriptors, + file_descriptors + num_file_descriptors); + + // Check this after adding the FDs so we don't leak them. + if (msg->msg_flags & MSG_CTRUNC) { + ClearInputFDs(); + return false; + } + + return true; + } + } + + // No file descriptors found, but that's OK. + return true; +} + +void Channel::ChannelImpl::ClearInputFDs() { + for (size_t i = 0; i < input_fds_.size(); ++i) { + if (HANDLE_EINTR(close(input_fds_[i])) < 0) + PLOG(ERROR) << "close "; + } + input_fds_.clear(); +} + +void Channel::ChannelImpl::HandleHelloMessage(const Message& msg) { + // The Hello message contains only the process id. + PickleIterator iter(msg); + int pid; + if (!msg.ReadInt(&iter, &pid)) + NOTREACHED(); + +#if defined(IPC_USES_READWRITE) + if (mode_ & MODE_SERVER_FLAG) { + // With IPC_USES_READWRITE, the Hello message from the client to the + // server also contains the fd_pipe_, which will be used for all + // subsequent file descriptor passing. + DCHECK_EQ(msg.file_descriptor_set()->size(), 1U); + base::FileDescriptor descriptor; + if (!msg.ReadFileDescriptor(&iter, &descriptor)) { + NOTREACHED(); + } + fd_pipe_ = descriptor.fd; + CHECK(descriptor.auto_close); + } +#endif // IPC_USES_READWRITE + peer_pid_ = pid; + listener()->OnChannelConnected(pid); +} + +void Channel::ChannelImpl::Close() { + // Close can be called multiple time, so we need to make sure we're + // idempotent. + + ResetToAcceptingConnectionState(); + + if (must_unlink_) { + unlink(pipe_name_.c_str()); + must_unlink_ = false; + } + if (server_listen_pipe_ != -1) { + if (HANDLE_EINTR(close(server_listen_pipe_)) < 0) + DPLOG(ERROR) << "close " << server_listen_pipe_; + server_listen_pipe_ = -1; + // Unregister libevent for the listening socket and close it. + server_listen_connection_watcher_.StopWatchingFileDescriptor(); + } + + CloseClientFileDescriptor(); +} + +//------------------------------------------------------------------------------ +// Channel's methods simply call through to ChannelImpl. +Channel::Channel(const IPC::ChannelHandle& channel_handle, Mode mode, + Listener* listener) + : channel_impl_(new ChannelImpl(channel_handle, mode, listener)) { +} + +Channel::~Channel() { + delete channel_impl_; +} + +bool Channel::Connect() { + return channel_impl_->Connect(); +} + +void Channel::Close() { + if (channel_impl_) + channel_impl_->Close(); +} + +base::ProcessId Channel::peer_pid() const { + return channel_impl_->peer_pid(); +} + +bool Channel::Send(Message* message) { + return channel_impl_->Send(message); +} + +int Channel::GetClientFileDescriptor() const { + return channel_impl_->GetClientFileDescriptor(); +} + +int Channel::TakeClientFileDescriptor() { + return channel_impl_->TakeClientFileDescriptor(); +} + +bool Channel::AcceptsConnections() const { + return channel_impl_->AcceptsConnections(); +} + +bool Channel::HasAcceptedConnection() const { + return channel_impl_->HasAcceptedConnection(); +} + +bool Channel::GetPeerEuid(uid_t* peer_euid) const { + return channel_impl_->GetPeerEuid(peer_euid); +} + +void Channel::ResetToAcceptingConnectionState() { + channel_impl_->ResetToAcceptingConnectionState(); +} + +// static +bool Channel::IsNamedServerInitialized(const std::string& channel_id) { + return ChannelImpl::IsNamedServerInitialized(channel_id); +} + +// static +std::string Channel::GenerateVerifiedChannelID(const std::string& prefix) { + // A random name is sufficient validation on posix systems, so we don't need + // an additional shared secret. + + std::string id = prefix; + if (!id.empty()) + id.append("."); + + return id.append(GenerateUniqueRandomChannelID()); +} + + +#if defined(OS_LINUX) +// static +void Channel::SetGlobalPid(int pid) { + ChannelImpl::SetGlobalPid(pid); +} +#endif // OS_LINUX + +} // namespace IPC diff --git a/chromium/ipc/ipc_channel_posix.h b/chromium/ipc/ipc_channel_posix.h new file mode 100644 index 00000000000..645a1303713 --- /dev/null +++ b/chromium/ipc/ipc_channel_posix.h @@ -0,0 +1,200 @@ +// 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. + +#ifndef IPC_IPC_CHANNEL_POSIX_H_ +#define IPC_IPC_CHANNEL_POSIX_H_ + +#include "ipc/ipc_channel.h" + +#include <sys/socket.h> // for CMSG macros + +#include <queue> +#include <string> +#include <vector> + +#include "base/message_loop/message_loop.h" +#include "base/process/process.h" +#include "ipc/file_descriptor_set_posix.h" +#include "ipc/ipc_channel_reader.h" + +#if !defined(OS_MACOSX) +// On Linux, the seccomp sandbox makes it very expensive to call +// recvmsg() and sendmsg(). The restriction on calling read() and write(), which +// are cheap, is that we can't pass file descriptors over them. +// +// As we cannot anticipate when the sender will provide us with file +// descriptors, we have to make the decision about whether we call read() or +// recvmsg() before we actually make the call. The easiest option is to +// create a dedicated socketpair() for exchanging file descriptors. Any file +// descriptors are split out of a message, with the non-file-descriptor payload +// going over the normal connection, and the file descriptors being sent +// separately over the other channel. When read()ing from a channel, we'll +// notice if the message was supposed to have come with file descriptors and +// use recvmsg on the other socketpair to retrieve them and combine them +// back with the rest of the message. +// +// Mac can also run in IPC_USES_READWRITE mode if necessary, but at this time +// doesn't take a performance hit from recvmsg and sendmsg, so it doesn't +// make sense to waste resources on having the separate dedicated socketpair. +// It is however useful for debugging between Linux and Mac to be able to turn +// this switch 'on' on the Mac as well. +// +// The HELLO message from the client to the server is always sent using +// sendmsg because it will contain the file descriptor that the server +// needs to send file descriptors in later messages. +#define IPC_USES_READWRITE 1 +#endif + +namespace IPC { + +class Channel::ChannelImpl : public internal::ChannelReader, + public base::MessageLoopForIO::Watcher { + public: + // Mirror methods of Channel, see ipc_channel.h for description. + ChannelImpl(const IPC::ChannelHandle& channel_handle, Mode mode, + Listener* listener); + virtual ~ChannelImpl(); + bool Connect(); + void Close(); + bool Send(Message* message); + int GetClientFileDescriptor(); + int TakeClientFileDescriptor(); + void CloseClientFileDescriptor(); + bool AcceptsConnections() const; + bool HasAcceptedConnection() const; + bool GetPeerEuid(uid_t* peer_euid) const; + void ResetToAcceptingConnectionState(); + base::ProcessId peer_pid() const { return peer_pid_; } + static bool IsNamedServerInitialized(const std::string& channel_id); +#if defined(OS_LINUX) + static void SetGlobalPid(int pid); +#endif // OS_LINUX + + private: + bool CreatePipe(const IPC::ChannelHandle& channel_handle); + + bool ProcessOutgoingMessages(); + + bool AcceptConnection(); + void ClosePipeOnError(); + int GetHelloMessageProcId(); + void QueueHelloMessage(); + + // ChannelReader implementation. + virtual ReadState ReadData(char* buffer, + int buffer_len, + int* bytes_read) OVERRIDE; + virtual bool WillDispatchInputMessage(Message* msg) OVERRIDE; + virtual bool DidEmptyInputBuffers() OVERRIDE; + virtual void HandleHelloMessage(const Message& msg) OVERRIDE; + +#if defined(IPC_USES_READWRITE) + // Reads the next message from the fd_pipe_ and appends them to the + // input_fds_ queue. Returns false if there was a message receiving error. + // True means there was a message and it was processed properly, or there was + // no messages. + bool ReadFileDescriptorsFromFDPipe(); +#endif + + // Finds the set of file descriptors in the given message. On success, + // appends the descriptors to the input_fds_ member and returns true + // + // Returns false if the message was truncated. In this case, any handles that + // were sent will be closed. + bool ExtractFileDescriptorsFromMsghdr(msghdr* msg); + + // Closes all handles in the input_fds_ list and clears the list. This is + // used to clean up handles in error conditions to avoid leaking the handles. + void ClearInputFDs(); + + // MessageLoopForIO::Watcher implementation. + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + Mode mode_; + + base::ProcessId peer_pid_; + + // After accepting one client connection on our server socket we want to + // stop listening. + base::MessageLoopForIO::FileDescriptorWatcher + server_listen_connection_watcher_; + base::MessageLoopForIO::FileDescriptorWatcher read_watcher_; + base::MessageLoopForIO::FileDescriptorWatcher write_watcher_; + + // Indicates whether we're currently blocked waiting for a write to complete. + bool is_blocked_on_write_; + bool waiting_connect_; + + // If sending a message blocks then we use this variable + // to keep track of where we are. + size_t message_send_bytes_written_; + + // File descriptor we're listening on for new connections if we listen + // for connections. + int server_listen_pipe_; + + // The pipe used for communication. + int pipe_; + + // For a server, the client end of our socketpair() -- the other end of our + // pipe_ that is passed to the client. + int client_pipe_; + base::Lock client_pipe_lock_; // Lock that protects |client_pipe_|. + +#if defined(IPC_USES_READWRITE) + // Linux/BSD use a dedicated socketpair() for passing file descriptors. + int fd_pipe_; + int remote_fd_pipe_; +#endif + + // The "name" of our pipe. On Windows this is the global identifier for + // the pipe. On POSIX it's used as a key in a local map of file descriptors. + std::string pipe_name_; + + // Messages to be sent are queued here. + std::queue<Message*> output_queue_; + + // We assume a worst case: kReadBufferSize bytes of messages, where each + // message has no payload and a full complement of descriptors. + static const size_t kMaxReadFDs = + (Channel::kReadBufferSize / sizeof(IPC::Message::Header)) * + FileDescriptorSet::kMaxDescriptorsPerMessage; + + // Buffer size for file descriptors used for recvmsg. On Mac the CMSG macros + // don't seem to be constant so we have to pick a "large enough" value. +#if defined(OS_MACOSX) + static const size_t kMaxReadFDBuffer = 1024; +#else + static const size_t kMaxReadFDBuffer = CMSG_SPACE(sizeof(int) * kMaxReadFDs); +#endif + + // Temporary buffer used to receive the file descriptors from recvmsg. + // Code that writes into this should immediately read them out and save + // them to input_fds_, since this buffer will be re-used anytime we call + // recvmsg. + char input_cmsg_buf_[kMaxReadFDBuffer]; + + // File descriptors extracted from messages coming off of the channel. The + // handles may span messages and come off different channels from the message + // data (in the case of READWRITE), and are processed in FIFO here. + // NOTE: The implementation assumes underlying storage here is contiguous, so + // don't change to something like std::deque<> without changing the + // implementation! + std::vector<int> input_fds_; + + // True if we are responsible for unlinking the unix domain socket file. + bool must_unlink_; + +#if defined(OS_LINUX) + // If non-zero, overrides the process ID sent in the hello message. + static int global_pid_; +#endif // OS_LINUX + + DISALLOW_IMPLICIT_CONSTRUCTORS(ChannelImpl); +}; + +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_POSIX_H_ diff --git a/chromium/ipc/ipc_channel_posix_unittest.cc b/chromium/ipc/ipc_channel_posix_unittest.cc new file mode 100644 index 00000000000..66ddeb2b496 --- /dev/null +++ b/chromium/ipc/ipc_channel_posix_unittest.cc @@ -0,0 +1,426 @@ +// 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. + +// These tests are POSIX only. + +#include "ipc/ipc_channel_posix.h" + +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/kill.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "ipc/ipc_listener.h" +#include "ipc/unix_domain_socket_util.h" +#include "testing/multiprocess_func_list.h" + +namespace { + +static const uint32 kQuitMessage = 47; + +class IPCChannelPosixTestListener : public IPC::Listener { + public: + enum STATUS { + DISCONNECTED, + MESSAGE_RECEIVED, + CHANNEL_ERROR, + CONNECTED, + DENIED, + LISTEN_ERROR + }; + + IPCChannelPosixTestListener(bool quit_only_on_message) + : status_(DISCONNECTED), quit_only_on_message_(quit_only_on_message) {} + + virtual ~IPCChannelPosixTestListener() {} + + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + EXPECT_EQ(message.type(), kQuitMessage); + status_ = MESSAGE_RECEIVED; + QuitRunLoop(); + return true; + } + + virtual void OnChannelConnected(int32 peer_pid) OVERRIDE { + status_ = CONNECTED; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + virtual void OnChannelError() OVERRIDE { + status_ = CHANNEL_ERROR; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + virtual void OnChannelDenied() OVERRIDE { + status_ = DENIED; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + virtual void OnChannelListenError() OVERRIDE { + status_ = LISTEN_ERROR; + if (!quit_only_on_message_) { + QuitRunLoop(); + } + } + + STATUS status() { return status_; } + + void QuitRunLoop() { + base::MessageLoopForIO::current()->QuitNow(); + } + + private: + // The current status of the listener. + STATUS status_; + // If |quit_only_on_message_| then the listener will only break out of + // the run loop when kQuitMessage is received. + bool quit_only_on_message_; +}; + +class IPCChannelPosixTest : public base::MultiProcessTest { + public: + static void SetUpSocket(IPC::ChannelHandle *handle, + IPC::Channel::Mode mode); + static void SpinRunLoop(base::TimeDelta delay); + static const std::string GetConnectionSocketName(); + static const std::string GetChannelDirName(); + + protected: + virtual void SetUp(); + virtual void TearDown(); + + private: + scoped_ptr<base::MessageLoopForIO> message_loop_; +}; + +const std::string IPCChannelPosixTest::GetChannelDirName() { +#if defined(OS_ANDROID) + base::FilePath tmp_dir; + PathService::Get(base::DIR_CACHE, &tmp_dir); + return tmp_dir.value(); +#else + return "/var/tmp"; +#endif +} + +const std::string IPCChannelPosixTest::GetConnectionSocketName() { + return GetChannelDirName() + "/chrome_IPCChannelPosixTest__ConnectionSocket"; +} + +void IPCChannelPosixTest::SetUp() { + MultiProcessTest::SetUp(); + // Construct a fresh IO Message loop for the duration of each test. + message_loop_.reset(new base::MessageLoopForIO()); +} + +void IPCChannelPosixTest::TearDown() { + message_loop_.reset(NULL); + MultiProcessTest::TearDown(); +} + +// Create up a socket and bind and listen to it, or connect it +// depending on the |mode|. +void IPCChannelPosixTest::SetUpSocket(IPC::ChannelHandle *handle, + IPC::Channel::Mode mode) { + const std::string& name = handle->name; + + int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0); + ASSERT_GE(socket_fd, 0) << name; + ASSERT_GE(fcntl(socket_fd, F_SETFL, O_NONBLOCK), 0); + struct sockaddr_un server_address = { 0 }; + memset(&server_address, 0, sizeof(server_address)); + server_address.sun_family = AF_UNIX; + int path_len = snprintf(server_address.sun_path, IPC::kMaxSocketNameLength, + "%s", name.c_str()); + DCHECK_EQ(static_cast<int>(name.length()), path_len); + size_t server_address_len = offsetof(struct sockaddr_un, + sun_path) + path_len + 1; + + if (mode == IPC::Channel::MODE_NAMED_SERVER) { + // Only one server at a time. Cleanup garbage if it exists. + unlink(name.c_str()); + // Make sure the path we need exists. + base::FilePath path(name); + base::FilePath dir_path = path.DirName(); + ASSERT_TRUE(file_util::CreateDirectory(dir_path)); + ASSERT_GE(bind(socket_fd, + reinterpret_cast<struct sockaddr *>(&server_address), + server_address_len), 0) << server_address.sun_path + << ": " << strerror(errno) + << "(" << errno << ")"; + ASSERT_GE(listen(socket_fd, SOMAXCONN), 0) << server_address.sun_path + << ": " << strerror(errno) + << "(" << errno << ")"; + } else if (mode == IPC::Channel::MODE_NAMED_CLIENT) { + ASSERT_GE(connect(socket_fd, + reinterpret_cast<struct sockaddr *>(&server_address), + server_address_len), 0) << server_address.sun_path + << ": " << strerror(errno) + << "(" << errno << ")"; + } else { + FAIL() << "Unknown mode " << mode; + } + handle->socket.fd = socket_fd; +} + +void IPCChannelPosixTest::SpinRunLoop(base::TimeDelta delay) { + base::MessageLoopForIO* loop = base::MessageLoopForIO::current(); + // Post a quit task so that this loop eventually ends and we don't hang + // in the case of a bad test. Usually, the run loop will quit sooner than + // that because all tests use a IPCChannelPosixTestListener which quits the + // current run loop on any channel activity. + loop->PostDelayedTask(FROM_HERE, base::MessageLoop::QuitClosure(), delay); + loop->Run(); +} + +TEST_F(IPCChannelPosixTest, BasicListen) { + const std::string kChannelName = + GetChannelDirName() + "/IPCChannelPosixTest_BasicListen"; + + // Test creating a socket that is listening. + IPC::ChannelHandle handle(kChannelName); + SetUpSocket(&handle, IPC::Channel::MODE_NAMED_SERVER); + unlink(handle.name.c_str()); + IPC::Channel channel(handle, IPC::Channel::MODE_NAMED_SERVER, NULL); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + channel.ResetToAcceptingConnectionState(); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +TEST_F(IPCChannelPosixTest, BasicConnected) { + // Test creating a socket that is connected. + int pipe_fds[2]; + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds)); + std::string socket_name("/var/tmp/IPCChannelPosixTest_BasicConnected"); + ASSERT_GE(fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK), 0); + + base::FileDescriptor fd(pipe_fds[0], false); + IPC::ChannelHandle handle(socket_name, fd); + IPC::Channel channel(handle, IPC::Channel::MODE_SERVER, NULL); + ASSERT_TRUE(channel.Connect()); + ASSERT_FALSE(channel.AcceptsConnections()); + channel.Close(); + ASSERT_TRUE(HANDLE_EINTR(close(pipe_fds[1])) == 0); + + // Make sure that we can use the socket that is created for us by + // a standard channel. + IPC::Channel channel2(socket_name, IPC::Channel::MODE_SERVER, NULL); + ASSERT_TRUE(channel2.Connect()); + ASSERT_FALSE(channel2.AcceptsConnections()); +} + +TEST_F(IPCChannelPosixTest, AdvancedConnected) { + // Test creating a connection to an external process. + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(GetConnectionSocketName()); + SetUpSocket(&chan_handle, IPC::Channel::MODE_NAMED_SERVER); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NAMED_SERVER, &listener); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_max_timeout()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + IPC::Message* message = new IPC::Message(0, // routing_id + kQuitMessage, // message type + IPC::Message::PRIORITY_NORMAL); + channel.Send(message); + SpinRunLoop(TestTimeouts::action_timeout()); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle, &exit_code)); + EXPECT_EQ(0, exit_code); + ASSERT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +TEST_F(IPCChannelPosixTest, ResetState) { + // Test creating a connection to an external process. Close the connection, + // but continue to listen and make sure another external process can connect + // to us. + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(GetConnectionSocketName()); + SetUpSocket(&chan_handle, IPC::Channel::MODE_NAMED_SERVER); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NAMED_SERVER, &listener); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_max_timeout()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + channel.ResetToAcceptingConnectionState(); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle2 = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle2); + SpinRunLoop(TestTimeouts::action_max_timeout()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + IPC::Message* message = new IPC::Message(0, // routing_id + kQuitMessage, // message type + IPC::Message::PRIORITY_NORMAL); + channel.Send(message); + SpinRunLoop(TestTimeouts::action_timeout()); + EXPECT_TRUE(base::KillProcess(handle, 0, false)); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle2, &exit_code)); + EXPECT_EQ(0, exit_code); + ASSERT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +TEST_F(IPCChannelPosixTest, BadChannelName) { + // Test empty name + IPC::ChannelHandle handle(""); + IPC::Channel channel(handle, IPC::Channel::MODE_NAMED_SERVER, NULL); + ASSERT_FALSE(channel.Connect()); + + // Test name that is too long. + const char *kTooLongName = "This_is_a_very_long_name_to_proactively_implement" + "client-centered_synergy_through_top-line" + "platforms_Phosfluorescently_disintermediate_" + "clicks-and-mortar_best_practices_without_" + "future-proof_growth_strategies_Continually" + "pontificate_proactive_potentialities_before" + "leading-edge_processes"; + EXPECT_GE(strlen(kTooLongName), IPC::kMaxSocketNameLength); + IPC::ChannelHandle handle2(kTooLongName); + IPC::Channel channel2(handle2, IPC::Channel::MODE_NAMED_SERVER, NULL); + EXPECT_FALSE(channel2.Connect()); +} + +TEST_F(IPCChannelPosixTest, MultiConnection) { + // Test setting up a connection to an external process, and then have + // another external process attempt to connect to us. + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(GetConnectionSocketName()); + SetUpSocket(&chan_handle, IPC::Channel::MODE_NAMED_SERVER); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NAMED_SERVER, &listener); + ASSERT_TRUE(channel.Connect()); + ASSERT_TRUE(channel.AcceptsConnections()); + ASSERT_FALSE(channel.HasAcceptedConnection()); + + base::ProcessHandle handle = SpawnChild("IPCChannelPosixTestConnectionProc", + false); + ASSERT_TRUE(handle); + SpinRunLoop(TestTimeouts::action_max_timeout()); + ASSERT_EQ(IPCChannelPosixTestListener::CONNECTED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + base::ProcessHandle handle2 = SpawnChild("IPCChannelPosixFailConnectionProc", + false); + ASSERT_TRUE(handle2); + SpinRunLoop(TestTimeouts::action_max_timeout()); + int exit_code = 0; + EXPECT_TRUE(base::WaitForExitCode(handle2, &exit_code)); + EXPECT_EQ(exit_code, 0); + ASSERT_EQ(IPCChannelPosixTestListener::DENIED, listener.status()); + ASSERT_TRUE(channel.HasAcceptedConnection()); + IPC::Message* message = new IPC::Message(0, // routing_id + kQuitMessage, // message type + IPC::Message::PRIORITY_NORMAL); + channel.Send(message); + SpinRunLoop(TestTimeouts::action_timeout()); + EXPECT_TRUE(base::WaitForExitCode(handle, &exit_code)); + EXPECT_EQ(exit_code, 0); + ASSERT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + ASSERT_FALSE(channel.HasAcceptedConnection()); +} + +TEST_F(IPCChannelPosixTest, DoubleServer) { + // Test setting up two servers with the same name. + IPCChannelPosixTestListener listener(false); + IPCChannelPosixTestListener listener2(false); + IPC::ChannelHandle chan_handle(GetConnectionSocketName()); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_SERVER, &listener); + IPC::Channel channel2(chan_handle, IPC::Channel::MODE_SERVER, &listener2); + ASSERT_TRUE(channel.Connect()); + ASSERT_FALSE(channel2.Connect()); +} + +TEST_F(IPCChannelPosixTest, BadMode) { + // Test setting up two servers with a bad mode. + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(GetConnectionSocketName()); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NONE, &listener); + ASSERT_FALSE(channel.Connect()); +} + +TEST_F(IPCChannelPosixTest, IsNamedServerInitialized) { + const std::string& connection_socket_name = GetConnectionSocketName(); + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle chan_handle(connection_socket_name); + ASSERT_TRUE(base::DeleteFile(base::FilePath(connection_socket_name), false)); + ASSERT_FALSE(IPC::Channel::IsNamedServerInitialized( + connection_socket_name)); + IPC::Channel channel(chan_handle, IPC::Channel::MODE_NAMED_SERVER, &listener); + ASSERT_TRUE(IPC::Channel::IsNamedServerInitialized( + connection_socket_name)); + channel.Close(); + ASSERT_FALSE(IPC::Channel::IsNamedServerInitialized( + connection_socket_name)); +} + +// A long running process that connects to us +MULTIPROCESS_TEST_MAIN(IPCChannelPosixTestConnectionProc) { + base::MessageLoopForIO message_loop; + IPCChannelPosixTestListener listener(true); + IPC::ChannelHandle handle(IPCChannelPosixTest::GetConnectionSocketName()); + IPCChannelPosixTest::SetUpSocket(&handle, IPC::Channel::MODE_NAMED_CLIENT); + IPC::Channel channel(handle, IPC::Channel::MODE_NAMED_CLIENT, &listener); + EXPECT_TRUE(channel.Connect()); + IPCChannelPosixTest::SpinRunLoop(TestTimeouts::action_max_timeout()); + EXPECT_EQ(IPCChannelPosixTestListener::MESSAGE_RECEIVED, listener.status()); + return 0; +} + +// Simple external process that shouldn't be able to connect to us. +MULTIPROCESS_TEST_MAIN(IPCChannelPosixFailConnectionProc) { + base::MessageLoopForIO message_loop; + IPCChannelPosixTestListener listener(false); + IPC::ChannelHandle handle(IPCChannelPosixTest::GetConnectionSocketName()); + IPCChannelPosixTest::SetUpSocket(&handle, IPC::Channel::MODE_NAMED_CLIENT); + IPC::Channel channel(handle, IPC::Channel::MODE_NAMED_CLIENT, &listener); + + // In this case connect may succeed or fail depending on if the packet + // actually gets sent at sendmsg. Since we never delay on send, we may not + // see the error. However even if connect succeeds, eventually we will get an + // error back since the channel will be closed when we attempt to read from + // it. + bool connected = channel.Connect(); + if (connected) { + IPCChannelPosixTest::SpinRunLoop(TestTimeouts::action_max_timeout()); + EXPECT_EQ(IPCChannelPosixTestListener::CHANNEL_ERROR, listener.status()); + } else { + EXPECT_EQ(IPCChannelPosixTestListener::DISCONNECTED, listener.status()); + } + return 0; +} + +} // namespace diff --git a/chromium/ipc/ipc_channel_proxy.cc b/chromium/ipc/ipc_channel_proxy.cc new file mode 100644 index 00000000000..45512f97e18 --- /dev/null +++ b/chromium/ipc/ipc_channel_proxy.cc @@ -0,0 +1,432 @@ +// 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 "base/bind.h" +#include "base/compiler_specific.h" +#include "base/debug/trace_event.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_logging.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_message_utils.h" + +namespace IPC { + +//------------------------------------------------------------------------------ + +ChannelProxy::MessageFilter::MessageFilter() {} + +void ChannelProxy::MessageFilter::OnFilterAdded(Channel* channel) {} + +void ChannelProxy::MessageFilter::OnFilterRemoved() {} + +void ChannelProxy::MessageFilter::OnChannelConnected(int32 peer_pid) {} + +void ChannelProxy::MessageFilter::OnChannelError() {} + +void ChannelProxy::MessageFilter::OnChannelClosing() {} + +bool ChannelProxy::MessageFilter::OnMessageReceived(const Message& message) { + return false; +} + +void ChannelProxy::MessageFilter::OnDestruct() const { + delete this; +} + +ChannelProxy::MessageFilter::~MessageFilter() {} + +//------------------------------------------------------------------------------ + +ChannelProxy::Context::Context(Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner) + : listener_task_runner_(base::ThreadTaskRunnerHandle::Get()), + listener_(listener), + ipc_task_runner_(ipc_task_runner), + channel_connected_called_(false), + peer_pid_(base::kNullProcessId) { + DCHECK(ipc_task_runner_.get()); +} + +ChannelProxy::Context::~Context() { +} + +void ChannelProxy::Context::ClearIPCTaskRunner() { + ipc_task_runner_ = NULL; +} + +void ChannelProxy::Context::CreateChannel(const IPC::ChannelHandle& handle, + const Channel::Mode& mode) { + DCHECK(channel_.get() == NULL); + channel_id_ = handle.name; + channel_.reset(new Channel(handle, mode, this)); +} + +bool ChannelProxy::Context::TryFilters(const Message& message) { +#ifdef IPC_MESSAGE_LOG_ENABLED + Logging* logger = Logging::GetInstance(); + if (logger->Enabled()) + logger->OnPreDispatchMessage(message); +#endif + + for (size_t i = 0; i < filters_.size(); ++i) { + if (filters_[i]->OnMessageReceived(message)) { +#ifdef IPC_MESSAGE_LOG_ENABLED + if (logger->Enabled()) + logger->OnPostDispatchMessage(message, channel_id_); +#endif + return true; + } + } + return false; +} + +// Called on the IPC::Channel thread +bool ChannelProxy::Context::OnMessageReceived(const Message& message) { + // First give a chance to the filters to process this message. + if (!TryFilters(message)) + OnMessageReceivedNoFilter(message); + return true; +} + +// Called on the IPC::Channel thread +bool ChannelProxy::Context::OnMessageReceivedNoFilter(const Message& message) { + // NOTE: This code relies on the listener's message loop not going away while + // this thread is active. That should be a reasonable assumption, but it + // feels risky. We may want to invent some more indirect way of referring to + // a MessageLoop if this becomes a problem. + listener_task_runner_->PostTask( + FROM_HERE, base::Bind(&Context::OnDispatchMessage, this, message)); + return true; +} + +// Called on the IPC::Channel thread +void ChannelProxy::Context::OnChannelConnected(int32 peer_pid) { + // Add any pending filters. This avoids a race condition where someone + // creates a ChannelProxy, calls AddFilter, and then right after starts the + // peer process. The IO thread could receive a message before the task to add + // the filter is run on the IO thread. + OnAddFilter(); + + // We cache off the peer_pid so it can be safely accessed from both threads. + peer_pid_ = channel_->peer_pid(); + for (size_t i = 0; i < filters_.size(); ++i) + filters_[i]->OnChannelConnected(peer_pid); + + // See above comment about using listener_task_runner_ here. + listener_task_runner_->PostTask( + FROM_HERE, base::Bind(&Context::OnDispatchConnected, this)); +} + +// Called on the IPC::Channel thread +void ChannelProxy::Context::OnChannelError() { + for (size_t i = 0; i < filters_.size(); ++i) + filters_[i]->OnChannelError(); + + // See above comment about using listener_task_runner_ here. + listener_task_runner_->PostTask( + FROM_HERE, base::Bind(&Context::OnDispatchError, this)); +} + +// Called on the IPC::Channel thread +void ChannelProxy::Context::OnChannelOpened() { + DCHECK(channel_ != NULL); + + // Assume a reference to ourselves on behalf of this thread. This reference + // will be released when we are closed. + AddRef(); + + if (!channel_->Connect()) { + OnChannelError(); + return; + } + + for (size_t i = 0; i < filters_.size(); ++i) + filters_[i]->OnFilterAdded(channel_.get()); +} + +// Called on the IPC::Channel thread +void ChannelProxy::Context::OnChannelClosed() { + // It's okay for IPC::ChannelProxy::Close to be called more than once, which + // would result in this branch being taken. + if (!channel_.get()) + return; + + for (size_t i = 0; i < filters_.size(); ++i) { + filters_[i]->OnChannelClosing(); + filters_[i]->OnFilterRemoved(); + } + + // We don't need the filters anymore. + filters_.clear(); + + channel_.reset(); + + // Balance with the reference taken during startup. This may result in + // self-destruction. + Release(); +} + +void ChannelProxy::Context::Clear() { + listener_ = NULL; +} + +// Called on the IPC::Channel thread +void ChannelProxy::Context::OnSendMessage(scoped_ptr<Message> message) { + if (!channel_.get()) { + OnChannelClosed(); + return; + } + if (!channel_->Send(message.release())) + OnChannelError(); +} + +// Called on the IPC::Channel thread +void ChannelProxy::Context::OnAddFilter() { + std::vector<scoped_refptr<MessageFilter> > new_filters; + { + base::AutoLock auto_lock(pending_filters_lock_); + new_filters.swap(pending_filters_); + } + + for (size_t i = 0; i < new_filters.size(); ++i) { + filters_.push_back(new_filters[i]); + + // If the channel has already been created, then we need to send this + // message so that the filter gets access to the Channel. + if (channel_.get()) + new_filters[i]->OnFilterAdded(channel_.get()); + // Ditto for if the channel has been connected. + if (peer_pid_) + new_filters[i]->OnChannelConnected(peer_pid_); + } +} + +// Called on the IPC::Channel thread +void ChannelProxy::Context::OnRemoveFilter(MessageFilter* filter) { + if (!channel_.get()) + return; // The filters have already been deleted. + + for (size_t i = 0; i < filters_.size(); ++i) { + if (filters_[i].get() == filter) { + filter->OnFilterRemoved(); + filters_.erase(filters_.begin() + i); + return; + } + } + + NOTREACHED() << "filter to be removed not found"; +} + +// Called on the listener's thread +void ChannelProxy::Context::AddFilter(MessageFilter* filter) { + base::AutoLock auto_lock(pending_filters_lock_); + pending_filters_.push_back(make_scoped_refptr(filter)); + ipc_task_runner_->PostTask( + FROM_HERE, base::Bind(&Context::OnAddFilter, this)); +} + +// Called on the listener's thread +void ChannelProxy::Context::OnDispatchMessage(const Message& message) { +#ifdef IPC_MESSAGE_LOG_ENABLED + Logging* logger = Logging::GetInstance(); + std::string name; + logger->GetMessageText(message.type(), &name, &message, NULL); + TRACE_EVENT1("task", "ChannelProxy::Context::OnDispatchMessage", + "name", name); +#else + TRACE_EVENT2("task", "ChannelProxy::Context::OnDispatchMessage", + "class", IPC_MESSAGE_ID_CLASS(message.type()), + "line", IPC_MESSAGE_ID_LINE(message.type())); +#endif + + if (!listener_) + return; + + OnDispatchConnected(); + +#ifdef IPC_MESSAGE_LOG_ENABLED + if (message.type() == IPC_LOGGING_ID) { + logger->OnReceivedLoggingMessage(message); + return; + } + + if (logger->Enabled()) + logger->OnPreDispatchMessage(message); +#endif + + listener_->OnMessageReceived(message); + +#ifdef IPC_MESSAGE_LOG_ENABLED + if (logger->Enabled()) + logger->OnPostDispatchMessage(message, channel_id_); +#endif +} + +// Called on the listener's thread +void ChannelProxy::Context::OnDispatchConnected() { + if (channel_connected_called_) + return; + + channel_connected_called_ = true; + if (listener_) + listener_->OnChannelConnected(peer_pid_); +} + +// Called on the listener's thread +void ChannelProxy::Context::OnDispatchError() { + if (listener_) + listener_->OnChannelError(); +} + +//----------------------------------------------------------------------------- + +ChannelProxy::ChannelProxy(const IPC::ChannelHandle& channel_handle, + Channel::Mode mode, + Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner) + : context_(new Context(listener, ipc_task_runner)), + outgoing_message_filter_(NULL), + did_init_(false) { + Init(channel_handle, mode, true); +} + +ChannelProxy::ChannelProxy(Context* context) + : context_(context), + outgoing_message_filter_(NULL), + did_init_(false) { +} + +ChannelProxy::~ChannelProxy() { + DCHECK(CalledOnValidThread()); + + Close(); +} + +void ChannelProxy::Init(const IPC::ChannelHandle& channel_handle, + Channel::Mode mode, + bool create_pipe_now) { + DCHECK(CalledOnValidThread()); + DCHECK(!did_init_); +#if defined(OS_POSIX) + // When we are creating a server on POSIX, we need its file descriptor + // to be created immediately so that it can be accessed and passed + // to other processes. Forcing it to be created immediately avoids + // race conditions that may otherwise arise. + if (mode & Channel::MODE_SERVER_FLAG) { + create_pipe_now = true; + } +#endif // defined(OS_POSIX) + + if (create_pipe_now) { + // Create the channel immediately. This effectively sets up the + // low-level pipe so that the client can connect. Without creating + // the pipe immediately, it is possible for a listener to attempt + // to connect and get an error since the pipe doesn't exist yet. + context_->CreateChannel(channel_handle, mode); + } else { + context_->ipc_task_runner()->PostTask( + FROM_HERE, base::Bind(&Context::CreateChannel, context_.get(), + channel_handle, mode)); + } + + // complete initialization on the background thread + context_->ipc_task_runner()->PostTask( + FROM_HERE, base::Bind(&Context::OnChannelOpened, context_.get())); + + did_init_ = true; +} + +void ChannelProxy::Close() { + DCHECK(CalledOnValidThread()); + + // Clear the backpointer to the listener so that any pending calls to + // Context::OnDispatchMessage or OnDispatchError will be ignored. It is + // possible that the channel could be closed while it is receiving messages! + context_->Clear(); + + if (context_->ipc_task_runner()) { + context_->ipc_task_runner()->PostTask( + FROM_HERE, base::Bind(&Context::OnChannelClosed, context_.get())); + } +} + +bool ChannelProxy::Send(Message* message) { + DCHECK(did_init_); + + // TODO(alexeypa): add DCHECK(CalledOnValidThread()) here. Currently there are + // tests that call Send() from a wrong thread. See http://crbug.com/163523. + if (outgoing_message_filter()) + message = outgoing_message_filter()->Rewrite(message); + +#ifdef IPC_MESSAGE_LOG_ENABLED + Logging::GetInstance()->OnSendMessage(message, context_->channel_id()); +#endif + + context_->ipc_task_runner()->PostTask( + FROM_HERE, + base::Bind(&ChannelProxy::Context::OnSendMessage, + context_, base::Passed(scoped_ptr<Message>(message)))); + return true; +} + +void ChannelProxy::AddFilter(MessageFilter* filter) { + DCHECK(CalledOnValidThread()); + + context_->AddFilter(filter); +} + +void ChannelProxy::RemoveFilter(MessageFilter* filter) { + DCHECK(CalledOnValidThread()); + + context_->ipc_task_runner()->PostTask( + FROM_HERE, base::Bind(&Context::OnRemoveFilter, context_.get(), + make_scoped_refptr(filter))); +} + +void ChannelProxy::ClearIPCTaskRunner() { + DCHECK(CalledOnValidThread()); + + context()->ClearIPCTaskRunner(); +} + +#if defined(OS_POSIX) && !defined(OS_NACL) +// See the TODO regarding lazy initialization of the channel in +// ChannelProxy::Init(). +int ChannelProxy::GetClientFileDescriptor() { + DCHECK(CalledOnValidThread()); + + Channel* channel = context_.get()->channel_.get(); + // Channel must have been created first. + DCHECK(channel) << context_.get()->channel_id_; + return channel->GetClientFileDescriptor(); +} + +int ChannelProxy::TakeClientFileDescriptor() { + DCHECK(CalledOnValidThread()); + + Channel* channel = context_.get()->channel_.get(); + // Channel must have been created first. + DCHECK(channel) << context_.get()->channel_id_; + return channel->TakeClientFileDescriptor(); +} + +bool ChannelProxy::GetPeerEuid(uid_t* peer_euid) const { + DCHECK(CalledOnValidThread()); + + Channel* channel = context_.get()->channel_.get(); + // Channel must have been created first. + DCHECK(channel) << context_.get()->channel_id_; + return channel->GetPeerEuid(peer_euid); +} +#endif + +//----------------------------------------------------------------------------- + +} // namespace IPC diff --git a/chromium/ipc/ipc_channel_proxy.h b/chromium/ipc/ipc_channel_proxy.h new file mode 100644 index 00000000000..4d5a7d45ec2 --- /dev/null +++ b/chromium/ipc/ipc_channel_proxy.h @@ -0,0 +1,293 @@ +// 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. + +#ifndef IPC_IPC_CHANNEL_PROXY_H_ +#define IPC_IPC_CHANNEL_PROXY_H_ + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "base/threading/non_thread_safe.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_sender.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace IPC { + +class SendCallbackHelper; + +//----------------------------------------------------------------------------- +// IPC::ChannelProxy +// +// This class is a helper class that is useful when you wish to run an IPC +// channel on a background thread. It provides you with the option of either +// handling IPC messages on that background thread or having them dispatched to +// your main thread (the thread on which the IPC::ChannelProxy is created). +// +// The API for an IPC::ChannelProxy is very similar to that of an IPC::Channel. +// When you send a message to an IPC::ChannelProxy, the message is routed to +// the background thread, where it is then passed to the IPC::Channel's Send +// method. This means that you can send a message from your thread and your +// message will be sent over the IPC channel when possible instead of being +// delayed until your thread returns to its message loop. (Often IPC messages +// will queue up on the IPC::Channel when there is a lot of traffic, and the +// channel will not get cycles to flush its message queue until the thread, on +// which it is running, returns to its message loop.) +// +// An IPC::ChannelProxy can have a MessageFilter associated with it, which will +// be notified of incoming messages on the IPC::Channel's thread. This gives +// the consumer of IPC::ChannelProxy the ability to respond to incoming +// messages on this background thread instead of on their own thread, which may +// be bogged down with other processing. The result can be greatly improved +// latency for messages that can be handled on a background thread. +// +// The consumer of IPC::ChannelProxy is responsible for allocating the Thread +// instance where the IPC::Channel will be created and operated. +// +class IPC_EXPORT ChannelProxy : public Sender, public base::NonThreadSafe { + public: + struct MessageFilterTraits; + + // A class that receives messages on the thread where the IPC channel is + // running. It can choose to prevent the default action for an IPC message. + class IPC_EXPORT MessageFilter + : public base::RefCountedThreadSafe<MessageFilter, MessageFilterTraits> { + public: + MessageFilter(); + + // Called on the background thread to provide the filter with access to the + // channel. Called when the IPC channel is initialized or when AddFilter + // is called if the channel is already initialized. + virtual void OnFilterAdded(Channel* channel); + + // Called on the background thread when the filter has been removed from + // the ChannelProxy and when the Channel is closing. After a filter is + // removed, it will not be called again. + virtual void OnFilterRemoved(); + + // Called to inform the filter that the IPC channel is connected and we + // have received the internal Hello message from the peer. + virtual void OnChannelConnected(int32 peer_pid); + + // Called when there is an error on the channel, typically that the channel + // has been closed. + virtual void OnChannelError(); + + // Called to inform the filter that the IPC channel will be destroyed. + // OnFilterRemoved is called immediately after this. + virtual void OnChannelClosing(); + + // Return true to indicate that the message was handled, or false to let + // the message be handled in the default way. + virtual bool OnMessageReceived(const Message& message); + + // Called when the message filter is about to be deleted. This gives + // derived classes the option of controlling which thread they're deleted + // on etc. + virtual void OnDestruct() const; + + protected: + virtual ~MessageFilter(); + + private: + friend class base::RefCountedThreadSafe<MessageFilter, + MessageFilterTraits>; + }; + + struct MessageFilterTraits { + static void Destruct(const MessageFilter* filter) { + filter->OnDestruct(); + } + }; + + + // Interface for a filter to be imposed on outgoing messages which can + // re-write the message. Used mainly for testing. + class OutgoingMessageFilter { + public: + // Returns a re-written message, freeing the original, or simply the + // original unchanged if no rewrite indicated. + virtual Message *Rewrite(Message *message) = 0; + }; + + // Initializes a channel proxy. The channel_handle and mode parameters are + // passed directly to the underlying IPC::Channel. The listener is called on + // the thread that creates the ChannelProxy. The filter's OnMessageReceived + // method is called on the thread where the IPC::Channel is running. The + // filter may be null if the consumer is not interested in handling messages + // on the background thread. Any message not handled by the filter will be + // dispatched to the listener. The given task runner correspond to a thread + // on which IPC::Channel is created and used (e.g. IO thread). + ChannelProxy(const IPC::ChannelHandle& channel_handle, + Channel::Mode mode, + Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner); + + virtual ~ChannelProxy(); + + // Initializes the channel proxy. Only call this once to initialize a channel + // proxy that was not initialized in its constructor. If create_pipe_now is + // true, the pipe is created synchronously. Otherwise it's created on the IO + // thread. + void Init(const IPC::ChannelHandle& channel_handle, Channel::Mode mode, + bool create_pipe_now); + + // Close the IPC::Channel. This operation completes asynchronously, once the + // background thread processes the command to close the channel. It is ok to + // call this method multiple times. Redundant calls are ignored. + // + // WARNING: The MessageFilter object held by the ChannelProxy is also + // released asynchronously, and it may in fact have its final reference + // released on the background thread. The caller should be careful to deal + // with / allow for this possibility. + void Close(); + + // Send a message asynchronously. The message is routed to the background + // thread where it is passed to the IPC::Channel's Send method. + virtual bool Send(Message* message) OVERRIDE; + + // Used to intercept messages as they are received on the background thread. + // + // Ordinarily, messages sent to the ChannelProxy are routed to the matching + // listener on the worker thread. This API allows code to intercept messages + // before they are sent to the worker thread. + // If you call this before the target process is launched, then you're + // guaranteed to not miss any messages. But if you call this anytime after, + // then some messages might be missed since the filter is added internally on + // the IO thread. + void AddFilter(MessageFilter* filter); + void RemoveFilter(MessageFilter* filter); + + void set_outgoing_message_filter(OutgoingMessageFilter* filter) { + outgoing_message_filter_ = filter; + } + + // Called to clear the pointer to the IPC task runner when it's going away. + void ClearIPCTaskRunner(); + + // Get the process ID for the connected peer. + // Returns base::kNullProcessId if the peer is not connected yet. + base::ProcessId peer_pid() const { return context_->peer_pid_; } + +#if defined(OS_POSIX) && !defined(OS_NACL) + // Calls through to the underlying channel's methods. + int GetClientFileDescriptor(); + int TakeClientFileDescriptor(); + bool GetPeerEuid(uid_t* peer_euid) const; +#endif // defined(OS_POSIX) + + protected: + class Context; + // A subclass uses this constructor if it needs to add more information + // to the internal state. + ChannelProxy(Context* context); + + // Used internally to hold state that is referenced on the IPC thread. + class Context : public base::RefCountedThreadSafe<Context>, + public Listener { + public: + Context(Listener* listener, base::SingleThreadTaskRunner* ipc_thread); + void ClearIPCTaskRunner(); + base::SingleThreadTaskRunner* ipc_task_runner() const { + return ipc_task_runner_.get(); + } + const std::string& channel_id() const { return channel_id_; } + + // Dispatches a message on the listener thread. + void OnDispatchMessage(const Message& message); + + protected: + friend class base::RefCountedThreadSafe<Context>; + virtual ~Context(); + + // IPC::Listener methods: + virtual bool OnMessageReceived(const Message& message) OVERRIDE; + virtual void OnChannelConnected(int32 peer_pid) OVERRIDE; + virtual void OnChannelError() OVERRIDE; + + // Like OnMessageReceived but doesn't try the filters. + bool OnMessageReceivedNoFilter(const Message& message); + + // Gives the filters a chance at processing |message|. + // Returns true if the message was processed, false otherwise. + bool TryFilters(const Message& message); + + // Like Open and Close, but called on the IPC thread. + virtual void OnChannelOpened(); + virtual void OnChannelClosed(); + + // Called on the consumers thread when the ChannelProxy is closed. At that + // point the consumer is telling us that they don't want to receive any + // more messages, so we honor that wish by forgetting them! + virtual void Clear(); + + private: + friend class ChannelProxy; + friend class SendCallbackHelper; + + // Create the Channel + void CreateChannel(const IPC::ChannelHandle& channel_handle, + const Channel::Mode& mode); + + // Methods called on the IO thread. + void OnSendMessage(scoped_ptr<Message> message_ptr); + void OnAddFilter(); + void OnRemoveFilter(MessageFilter* filter); + + // Methods called on the listener thread. + void AddFilter(MessageFilter* filter); + void OnDispatchConnected(); + void OnDispatchError(); + + scoped_refptr<base::SingleThreadTaskRunner> listener_task_runner_; + Listener* listener_; + + // List of filters. This is only accessed on the IPC thread. + std::vector<scoped_refptr<MessageFilter> > filters_; + scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_; + scoped_ptr<Channel> channel_; + std::string channel_id_; + bool channel_connected_called_; + + // Holds filters between the AddFilter call on the listerner thread and the + // IPC thread when they're added to filters_. + std::vector<scoped_refptr<MessageFilter> > pending_filters_; + // Lock for pending_filters_. + base::Lock pending_filters_lock_; + + // Cached copy of the peer process ID. Set on IPC but read on both IPC and + // listener threads. + base::ProcessId peer_pid_; + }; + + Context* context() { return context_.get(); } + + OutgoingMessageFilter* outgoing_message_filter() { + return outgoing_message_filter_; + } + + private: + friend class SendCallbackHelper; + + // By maintaining this indirection (ref-counted) to our internal state, we + // can safely be destroyed while the background thread continues to do stuff + // that involves this data. + scoped_refptr<Context> context_; + + OutgoingMessageFilter* outgoing_message_filter_; + + // Whether the channel has been initialized. + bool did_init_; +}; + +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_PROXY_H_ diff --git a/chromium/ipc/ipc_channel_reader.cc b/chromium/ipc/ipc_channel_reader.cc new file mode 100644 index 00000000000..9055deb1775 --- /dev/null +++ b/chromium/ipc/ipc_channel_reader.cc @@ -0,0 +1,108 @@ +// 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 "ipc/ipc_channel_reader.h" + +#include "ipc/ipc_listener.h" +#include "ipc/ipc_logging.h" +#include "ipc/ipc_message_macros.h" + +namespace IPC { +namespace internal { + +ChannelReader::ChannelReader(Listener* listener) : listener_(listener) { + memset(input_buf_, 0, sizeof(input_buf_)); +} + +ChannelReader::~ChannelReader() { +} + +bool ChannelReader::ProcessIncomingMessages() { + while (true) { + int bytes_read = 0; + ReadState read_state = ReadData(input_buf_, Channel::kReadBufferSize, + &bytes_read); + if (read_state == READ_FAILED) + return false; + if (read_state == READ_PENDING) + return true; + + DCHECK(bytes_read > 0); + if (!DispatchInputData(input_buf_, bytes_read)) + return false; + } +} + +bool ChannelReader::AsyncReadComplete(int bytes_read) { + return DispatchInputData(input_buf_, bytes_read); +} + +bool ChannelReader::IsHelloMessage(const Message& m) const { + return m.routing_id() == MSG_ROUTING_NONE && + m.type() == Channel::HELLO_MESSAGE_TYPE; +} + +bool ChannelReader::DispatchInputData(const char* input_data, + int input_data_len) { + const char* p; + const char* end; + + // Possibly combine with the overflow buffer to make a larger buffer. + if (input_overflow_buf_.empty()) { + p = input_data; + end = input_data + input_data_len; + } else { + if (input_overflow_buf_.size() + input_data_len > + Channel::kMaximumMessageSize) { + input_overflow_buf_.clear(); + LOG(ERROR) << "IPC message is too big"; + return false; + } + input_overflow_buf_.append(input_data, input_data_len); + p = input_overflow_buf_.data(); + end = p + input_overflow_buf_.size(); + } + + // Dispatch all complete messages in the data buffer. + while (p < end) { + const char* message_tail = Message::FindNext(p, end); + if (message_tail) { + int len = static_cast<int>(message_tail - p); + Message m(p, len); + if (!WillDispatchInputMessage(&m)) + return false; + +#ifdef IPC_MESSAGE_LOG_ENABLED + Logging* logger = Logging::GetInstance(); + std::string name; + logger->GetMessageText(m.type(), &name, &m, NULL); + TRACE_EVENT1("ipc", "ChannelReader::DispatchInputData", "name", name); +#else + TRACE_EVENT2("ipc", "ChannelReader::DispatchInputData", + "class", IPC_MESSAGE_ID_CLASS(m.type()), + "line", IPC_MESSAGE_ID_LINE(m.type())); +#endif + m.TraceMessageEnd(); + if (IsHelloMessage(m)) + HandleHelloMessage(m); + else + listener_->OnMessageReceived(m); + p = message_tail; + } else { + // Last message is partial. + break; + } + } + + // Save any partial data in the overflow buffer. + input_overflow_buf_.assign(p, end - p); + + if (input_overflow_buf_.empty() && !DidEmptyInputBuffers()) + return false; + return true; +} + + +} // namespace internal +} // namespace IPC diff --git a/chromium/ipc/ipc_channel_reader.h b/chromium/ipc/ipc_channel_reader.h new file mode 100644 index 00000000000..9c398bdce35 --- /dev/null +++ b/chromium/ipc/ipc_channel_reader.h @@ -0,0 +1,105 @@ +// 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. + +#ifndef IPC_IPC_CHANNEL_READER_H_ +#define IPC_IPC_CHANNEL_READER_H_ + +#include "base/basictypes.h" +#include "ipc/ipc_channel.h" + +namespace IPC { +namespace internal { + +// This class provides common pipe reading functionality for the +// platform-specific IPC channel implementations. +// +// It does the common input buffer management and message dispatch, while the +// platform-specific parts provide the pipe management through a virtual +// interface implemented on a per-platform basis. +// +// Note that there is no "writer" corresponding to this because the code for +// writing to the channel is much simpler and has very little common +// functionality that would benefit from being factored out. If we add +// something like that in the future, it would be more appropriate to add it +// here (and rename appropriately) rather than writing a different class. +class ChannelReader { + public: + explicit ChannelReader(Listener* listener); + virtual ~ChannelReader(); + + void set_listener(Listener* listener) { listener_ = listener; } + + // Call to process messages received from the IPC connection and dispatch + // them. Returns false on channel error. True indicates that everything + // succeeded, although there may not have been any messages processed. + bool ProcessIncomingMessages(); + + // Handles asynchronously read data. + // + // Optionally call this after returning READ_PENDING from ReadData to + // indicate that buffer was filled with the given number of bytes of + // data. See ReadData for more. + bool AsyncReadComplete(int bytes_read); + + // Returns true if the given message is the "hello" message sent on channel + // set-up. + bool IsHelloMessage(const Message& m) const; + + protected: + enum ReadState { READ_SUCCEEDED, READ_FAILED, READ_PENDING }; + + Listener* listener() const { return listener_; } + + // Populates the given buffer with data from the pipe. + // + // Returns the state of the read. On READ_SUCCESS, the number of bytes + // read will be placed into |*bytes_read| (which can be less than the + // buffer size). On READ_FAILED, the channel will be closed. + // + // If the return value is READ_PENDING, it means that there was no data + // ready for reading. The implementation is then responsible for either + // calling AsyncReadComplete with the number of bytes read into the + // buffer, or ProcessIncomingMessages to try the read again (depending + // on whether the platform's async I/O is "try again" or "write + // asynchronously into your buffer"). + virtual ReadState ReadData(char* buffer, int buffer_len, int* bytes_read) = 0; + + // Loads the required file desciptors into the given message. Returns true + // on success. False means a fatal channel error. + // + // This will read from the input_fds_ and read more handles from the FD + // pipe if necessary. + virtual bool WillDispatchInputMessage(Message* msg) = 0; + + // Performs post-dispatch checks. Called when all input buffers are empty, + // though there could be more data ready to be read from the OS. + virtual bool DidEmptyInputBuffers() = 0; + + // Handles the first message sent over the pipe which contains setup info. + virtual void HandleHelloMessage(const Message& msg) = 0; + + private: + // Takes the given data received from the IPC channel and dispatches any + // fully completed messages. + // + // Returns true on success. False means channel error. + bool DispatchInputData(const char* input_data, int input_data_len); + + Listener* listener_; + + // We read from the pipe into this buffer. Managed by DispatchInputData, do + // not access directly outside that function. + char input_buf_[Channel::kReadBufferSize]; + + // Large messages that span multiple pipe buffers, get built-up using + // this buffer. + std::string input_overflow_buf_; + + DISALLOW_COPY_AND_ASSIGN(ChannelReader); +}; + +} // namespace internal +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_READER_H_ diff --git a/chromium/ipc/ipc_channel_unittest.cc b/chromium/ipc/ipc_channel_unittest.cc new file mode 100644 index 00000000000..eea432a15e7 --- /dev/null +++ b/chromium/ipc/ipc_channel_unittest.cc @@ -0,0 +1,268 @@ +// 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 "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include <string> + +#include "base/message_loop/message_loop.h" +#include "base/pickle.h" +#include "base/threading/thread.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_test_base.h" + +namespace { + +const size_t kLongMessageStringNumBytes = 50000; + +static void Send(IPC::Sender* sender, const char* text) { + static int message_index = 0; + + IPC::Message* message = new IPC::Message(0, + 2, + IPC::Message::PRIORITY_NORMAL); + message->WriteInt(message_index++); + message->WriteString(std::string(text)); + + // Make sure we can handle large messages. + char junk[kLongMessageStringNumBytes]; + memset(junk, 'a', sizeof(junk)-1); + junk[sizeof(junk)-1] = 0; + message->WriteString(std::string(junk)); + + // DEBUG: printf("[%u] sending message [%s]\n", GetCurrentProcessId(), text); + sender->Send(message); +} + +// A generic listener that expects messages of a certain type (see +// OnMessageReceived()), and either sends a generic response or quits after the +// 50th message (or on channel error). +class GenericChannelListener : public IPC::Listener { + public: + GenericChannelListener() : sender_(NULL), messages_left_(50) {} + virtual ~GenericChannelListener() {} + + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + PickleIterator iter(message); + + int ignored; + EXPECT_TRUE(iter.ReadInt(&ignored)); + std::string data; + EXPECT_TRUE(iter.ReadString(&data)); + std::string big_string; + EXPECT_TRUE(iter.ReadString(&big_string)); + EXPECT_EQ(kLongMessageStringNumBytes - 1, big_string.length()); + + SendNextMessage(); + return true; + } + + virtual void OnChannelError() OVERRIDE { + // There is a race when closing the channel so the last message may be lost. + EXPECT_LE(messages_left_, 1); + base::MessageLoop::current()->Quit(); + } + + void Init(IPC::Sender* s) { + sender_ = s; + } + + protected: + void SendNextMessage() { + if (--messages_left_ <= 0) + base::MessageLoop::current()->Quit(); + else + Send(sender_, "Foo"); + } + + private: + IPC::Sender* sender_; + int messages_left_; +}; + +class IPCChannelTest : public IPCTestBase { +}; + +// TODO(viettrungluu): Move to a separate IPCMessageTest. +TEST_F(IPCChannelTest, BasicMessageTest) { + int v1 = 10; + std::string v2("foobar"); + std::wstring v3(L"hello world"); + + IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL); + EXPECT_TRUE(m.WriteInt(v1)); + EXPECT_TRUE(m.WriteString(v2)); + EXPECT_TRUE(m.WriteWString(v3)); + + PickleIterator iter(m); + + int vi; + std::string vs; + std::wstring vw; + + EXPECT_TRUE(m.ReadInt(&iter, &vi)); + EXPECT_EQ(v1, vi); + + EXPECT_TRUE(m.ReadString(&iter, &vs)); + EXPECT_EQ(v2, vs); + + EXPECT_TRUE(m.ReadWString(&iter, &vw)); + EXPECT_EQ(v3, vw); + + // should fail + EXPECT_FALSE(m.ReadInt(&iter, &vi)); + EXPECT_FALSE(m.ReadString(&iter, &vs)); + EXPECT_FALSE(m.ReadWString(&iter, &vw)); +} + +TEST_F(IPCChannelTest, ChannelTest) { + Init("GenericClient"); + + // Set up IPC channel and start client. + GenericChannelListener listener; + CreateChannel(&listener); + listener.Init(sender()); + ASSERT_TRUE(ConnectChannel()); + ASSERT_TRUE(StartClient()); + + Send(sender(), "hello from parent"); + + // Run message loop. + base::MessageLoop::current()->Run(); + + // Close the channel so the client's OnChannelError() gets fired. + channel()->Close(); + + EXPECT_TRUE(WaitForClientShutdown()); + DestroyChannel(); +} + +// TODO(viettrungluu): Move to a separate IPCChannelWinTest. +#if defined(OS_WIN) +TEST_F(IPCChannelTest, ChannelTestExistingPipe) { + Init("GenericClient"); + + // Create pipe manually using the standard Chromium name and set up IPC + // channel. + GenericChannelListener listener; + std::string name("\\\\.\\pipe\\chrome."); + name.append(GetChannelName("GenericClient")); + HANDLE pipe = CreateNamedPipeA(name.c_str(), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | + FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, + 1, + 4096, + 4096, + 5000, + NULL); + CreateChannelFromChannelHandle(IPC::ChannelHandle(pipe), &listener); + CloseHandle(pipe); // The channel duplicates the handle. + listener.Init(sender()); + + // Connect to channel and start client. + ASSERT_TRUE(ConnectChannel()); + ASSERT_TRUE(StartClient()); + + Send(sender(), "hello from parent"); + + // Run message loop. + base::MessageLoop::current()->Run(); + + // Close the channel so the client's OnChannelError() gets fired. + channel()->Close(); + + EXPECT_TRUE(WaitForClientShutdown()); + DestroyChannel(); +} +#endif // defined (OS_WIN) + +TEST_F(IPCChannelTest, ChannelProxyTest) { + Init("GenericClient"); + + base::Thread thread("ChannelProxyTestServer"); + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + thread.StartWithOptions(options); + + // Set up IPC channel proxy. + GenericChannelListener listener; + CreateChannelProxy(&listener, thread.message_loop_proxy().get()); + listener.Init(sender()); + + ASSERT_TRUE(StartClient()); + + Send(sender(), "hello from parent"); + + // Run message loop. + base::MessageLoop::current()->Run(); + + EXPECT_TRUE(WaitForClientShutdown()); + + // Destroy the channel proxy before shutting down the thread. + DestroyChannelProxy(); + thread.Stop(); +} + +class ChannelListenerWithOnConnectedSend : public GenericChannelListener { + public: + ChannelListenerWithOnConnectedSend() {} + virtual ~ChannelListenerWithOnConnectedSend() {} + + virtual void OnChannelConnected(int32 peer_pid) OVERRIDE { + SendNextMessage(); + } +}; + +#if defined(OS_WIN) +// Acting flakey in Windows. http://crbug.com/129595 +#define MAYBE_SendMessageInChannelConnected DISABLED_SendMessageInChannelConnected +#else +#define MAYBE_SendMessageInChannelConnected SendMessageInChannelConnected +#endif +// This tests the case of a listener sending back an event in its +// OnChannelConnected handler. +TEST_F(IPCChannelTest, MAYBE_SendMessageInChannelConnected) { + Init("GenericClient"); + + // Set up IPC channel and start client. + ChannelListenerWithOnConnectedSend listener; + CreateChannel(&listener); + listener.Init(sender()); + ASSERT_TRUE(ConnectChannel()); + ASSERT_TRUE(StartClient()); + + Send(sender(), "hello from parent"); + + // Run message loop. + base::MessageLoop::current()->Run(); + + // Close the channel so the client's OnChannelError() gets fired. + channel()->Close(); + + EXPECT_TRUE(WaitForClientShutdown()); + DestroyChannel(); +} + +MULTIPROCESS_IPC_TEST_CLIENT_MAIN(GenericClient) { + base::MessageLoopForIO main_message_loop; + GenericChannelListener listener; + + // Set up IPC channel. + IPC::Channel channel(IPCTestBase::GetChannelName("GenericClient"), + IPC::Channel::MODE_CLIENT, + &listener); + CHECK(channel.Connect()); + listener.Init(&channel); + Send(&channel, "hello from child"); + + base::MessageLoop::current()->Run(); + return 0; +} + +} // namespace diff --git a/chromium/ipc/ipc_channel_win.cc b/chromium/ipc/ipc_channel_win.cc new file mode 100644 index 00000000000..6d7cfe8d3f4 --- /dev/null +++ b/chromium/ipc/ipc_channel_win.cc @@ -0,0 +1,517 @@ +// 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 "ipc/ipc_channel_win.h" + +#include <windows.h> + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/process/process_handle.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_checker.h" +#include "base/win/scoped_handle.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_logging.h" +#include "ipc/ipc_message_utils.h" + +namespace IPC { + +Channel::ChannelImpl::State::State(ChannelImpl* channel) : is_pending(false) { + memset(&context.overlapped, 0, sizeof(context.overlapped)); + context.handler = channel; +} + +Channel::ChannelImpl::State::~State() { + COMPILE_ASSERT(!offsetof(Channel::ChannelImpl::State, context), + starts_with_io_context); +} + +Channel::ChannelImpl::ChannelImpl(const IPC::ChannelHandle &channel_handle, + Mode mode, Listener* listener) + : ChannelReader(listener), + input_state_(this), + output_state_(this), + pipe_(INVALID_HANDLE_VALUE), + peer_pid_(base::kNullProcessId), + waiting_connect_(mode & MODE_SERVER_FLAG), + processing_incoming_(false), + weak_factory_(this), + client_secret_(0), + validate_client_(false) { + CreatePipe(channel_handle, mode); +} + +Channel::ChannelImpl::~ChannelImpl() { + Close(); +} + +void Channel::ChannelImpl::Close() { + if (thread_check_.get()) { + DCHECK(thread_check_->CalledOnValidThread()); + } + + if (input_state_.is_pending || output_state_.is_pending) + CancelIo(pipe_); + + // Closing the handle at this point prevents us from issuing more requests + // form OnIOCompleted(). + if (pipe_ != INVALID_HANDLE_VALUE) { + CloseHandle(pipe_); + pipe_ = INVALID_HANDLE_VALUE; + } + + // Make sure all IO has completed. + base::Time start = base::Time::Now(); + while (input_state_.is_pending || output_state_.is_pending) { + base::MessageLoopForIO::current()->WaitForIOCompletion(INFINITE, this); + } + + while (!output_queue_.empty()) { + Message* m = output_queue_.front(); + output_queue_.pop(); + delete m; + } +} + +bool Channel::ChannelImpl::Send(Message* message) { + DCHECK(thread_check_->CalledOnValidThread()); + DVLOG(2) << "sending message @" << message << " on channel @" << this + << " with type " << message->type() + << " (" << output_queue_.size() << " in queue)"; + +#ifdef IPC_MESSAGE_LOG_ENABLED + Logging::GetInstance()->OnSendMessage(message, ""); +#endif + + message->TraceMessageBegin(); + output_queue_.push(message); + // ensure waiting to write + if (!waiting_connect_) { + if (!output_state_.is_pending) { + if (!ProcessOutgoingMessages(NULL, 0)) + return false; + } + } + + return true; +} + +// static +bool Channel::ChannelImpl::IsNamedServerInitialized( + const std::string& channel_id) { + if (WaitNamedPipe(PipeName(channel_id, NULL).c_str(), 1)) + return true; + // If ERROR_SEM_TIMEOUT occurred, the pipe exists but is handling another + // connection. + return GetLastError() == ERROR_SEM_TIMEOUT; +} + +Channel::ChannelImpl::ReadState Channel::ChannelImpl::ReadData( + char* buffer, + int buffer_len, + int* /* bytes_read */) { + if (INVALID_HANDLE_VALUE == pipe_) + return READ_FAILED; + + DWORD bytes_read = 0; + BOOL ok = ReadFile(pipe_, buffer, buffer_len, + &bytes_read, &input_state_.context.overlapped); + if (!ok) { + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) { + input_state_.is_pending = true; + return READ_PENDING; + } + LOG(ERROR) << "pipe error: " << err; + return READ_FAILED; + } + + // We could return READ_SUCCEEDED here. But the way that this code is + // structured we instead go back to the message loop. Our completion port + // will be signalled even in the "synchronously completed" state. + // + // This allows us to potentially process some outgoing messages and + // interleave other work on this thread when we're getting hammered with + // input messages. Potentially, this could be tuned to be more efficient + // with some testing. + input_state_.is_pending = true; + return READ_PENDING; +} + +bool Channel::ChannelImpl::WillDispatchInputMessage(Message* msg) { + // Make sure we get a hello when client validation is required. + if (validate_client_) + return IsHelloMessage(*msg); + return true; +} + +void Channel::ChannelImpl::HandleHelloMessage(const Message& msg) { + // The hello message contains one parameter containing the PID. + PickleIterator it(msg); + int32 claimed_pid; + bool failed = !it.ReadInt(&claimed_pid); + + if (!failed && validate_client_) { + int32 secret; + failed = it.ReadInt(&secret) ? (secret != client_secret_) : true; + } + + if (failed) { + NOTREACHED(); + Close(); + listener()->OnChannelError(); + return; + } + + peer_pid_ = claimed_pid; + // Validation completed. + validate_client_ = false; + listener()->OnChannelConnected(claimed_pid); +} + +bool Channel::ChannelImpl::DidEmptyInputBuffers() { + // We don't need to do anything here. + return true; +} + +// static +const string16 Channel::ChannelImpl::PipeName( + const std::string& channel_id, int32* secret) { + std::string name("\\\\.\\pipe\\chrome."); + + // Prevent the shared secret from ending up in the pipe name. + size_t index = channel_id.find_first_of('\\'); + if (index != std::string::npos) { + if (secret) // Retrieve the secret if asked for. + base::StringToInt(channel_id.substr(index + 1), secret); + return ASCIIToWide(name.append(channel_id.substr(0, index - 1))); + } + + // This case is here to support predictable named pipes in tests. + if (secret) + *secret = 0; + return ASCIIToWide(name.append(channel_id)); +} + +bool Channel::ChannelImpl::CreatePipe(const IPC::ChannelHandle &channel_handle, + Mode mode) { + DCHECK_EQ(INVALID_HANDLE_VALUE, pipe_); + string16 pipe_name; + // If we already have a valid pipe for channel just copy it. + if (channel_handle.pipe.handle) { + DCHECK(channel_handle.name.empty()); + pipe_name = L"Not Available"; // Just used for LOG + // Check that the given pipe confirms to the specified mode. We can + // only check for PIPE_TYPE_MESSAGE & PIPE_SERVER_END flags since the + // other flags (PIPE_TYPE_BYTE, and PIPE_CLIENT_END) are defined as 0. + DWORD flags = 0; + GetNamedPipeInfo(channel_handle.pipe.handle, &flags, NULL, NULL, NULL); + DCHECK(!(flags & PIPE_TYPE_MESSAGE)); + if (((mode & MODE_SERVER_FLAG) && !(flags & PIPE_SERVER_END)) || + ((mode & MODE_CLIENT_FLAG) && (flags & PIPE_SERVER_END))) { + LOG(WARNING) << "Inconsistent open mode. Mode :" << mode; + return false; + } + if (!DuplicateHandle(GetCurrentProcess(), + channel_handle.pipe.handle, + GetCurrentProcess(), + &pipe_, + 0, + FALSE, + DUPLICATE_SAME_ACCESS)) { + LOG(WARNING) << "DuplicateHandle failed. Error :" << GetLastError(); + return false; + } + } else if (mode & MODE_SERVER_FLAG) { + DCHECK(!channel_handle.pipe.handle); + const DWORD open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | + FILE_FLAG_FIRST_PIPE_INSTANCE; + pipe_name = PipeName(channel_handle.name, &client_secret_); + validate_client_ = !!client_secret_; + pipe_ = CreateNamedPipeW(pipe_name.c_str(), + open_mode, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, + 1, + Channel::kReadBufferSize, + Channel::kReadBufferSize, + 5000, + NULL); + } else if (mode & MODE_CLIENT_FLAG) { + DCHECK(!channel_handle.pipe.handle); + pipe_name = PipeName(channel_handle.name, &client_secret_); + pipe_ = CreateFileW(pipe_name.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION | + FILE_FLAG_OVERLAPPED, + NULL); + } else { + NOTREACHED(); + } + + if (pipe_ == INVALID_HANDLE_VALUE) { + // If this process is being closed, the pipe may be gone already. + LOG(WARNING) << "Unable to create pipe \"" << pipe_name << + "\" in " << (mode & MODE_SERVER_FLAG ? "server" : "client") + << " mode. Error :" << GetLastError(); + return false; + } + + // Create the Hello message to be sent when Connect is called + scoped_ptr<Message> m(new Message(MSG_ROUTING_NONE, + HELLO_MESSAGE_TYPE, + IPC::Message::PRIORITY_NORMAL)); + + // Don't send the secret to the untrusted process, and don't send a secret + // if the value is zero (for IPC backwards compatability). + int32 secret = validate_client_ ? 0 : client_secret_; + if (!m->WriteInt(GetCurrentProcessId()) || + (secret && !m->WriteUInt32(secret))) { + CloseHandle(pipe_); + pipe_ = INVALID_HANDLE_VALUE; + return false; + } + + output_queue_.push(m.release()); + return true; +} + +bool Channel::ChannelImpl::Connect() { + DLOG_IF(WARNING, thread_check_.get()) << "Connect called more than once"; + + if (!thread_check_.get()) + thread_check_.reset(new base::ThreadChecker()); + + if (pipe_ == INVALID_HANDLE_VALUE) + return false; + + base::MessageLoopForIO::current()->RegisterIOHandler(pipe_, this); + + // Check to see if there is a client connected to our pipe... + if (waiting_connect_) + ProcessConnection(); + + if (!input_state_.is_pending) { + // Complete setup asynchronously. By not setting input_state_.is_pending + // to true, we indicate to OnIOCompleted that this is the special + // initialization signal. + base::MessageLoopForIO::current()->PostTask( + FROM_HERE, + base::Bind(&Channel::ChannelImpl::OnIOCompleted, + weak_factory_.GetWeakPtr(), + &input_state_.context, + 0, + 0)); + } + + if (!waiting_connect_) + ProcessOutgoingMessages(NULL, 0); + return true; +} + +bool Channel::ChannelImpl::ProcessConnection() { + DCHECK(thread_check_->CalledOnValidThread()); + if (input_state_.is_pending) + input_state_.is_pending = false; + + // Do we have a client connected to our pipe? + if (INVALID_HANDLE_VALUE == pipe_) + return false; + + BOOL ok = ConnectNamedPipe(pipe_, &input_state_.context.overlapped); + + DWORD err = GetLastError(); + if (ok) { + // Uhm, the API documentation says that this function should never + // return success when used in overlapped mode. + NOTREACHED(); + return false; + } + + switch (err) { + case ERROR_IO_PENDING: + input_state_.is_pending = true; + break; + case ERROR_PIPE_CONNECTED: + waiting_connect_ = false; + break; + case ERROR_NO_DATA: + // The pipe is being closed. + return false; + default: + NOTREACHED(); + return false; + } + + return true; +} + +bool Channel::ChannelImpl::ProcessOutgoingMessages( + base::MessageLoopForIO::IOContext* context, + DWORD bytes_written) { + DCHECK(!waiting_connect_); // Why are we trying to send messages if there's + // no connection? + DCHECK(thread_check_->CalledOnValidThread()); + + if (output_state_.is_pending) { + DCHECK(context); + output_state_.is_pending = false; + if (!context || bytes_written == 0) { + DWORD err = GetLastError(); + LOG(ERROR) << "pipe error: " << err; + return false; + } + // Message was sent. + DCHECK(!output_queue_.empty()); + Message* m = output_queue_.front(); + output_queue_.pop(); + delete m; + } + + if (output_queue_.empty()) + return true; + + if (INVALID_HANDLE_VALUE == pipe_) + return false; + + // Write to pipe... + Message* m = output_queue_.front(); + DCHECK(m->size() <= INT_MAX); + BOOL ok = WriteFile(pipe_, + m->data(), + static_cast<int>(m->size()), + &bytes_written, + &output_state_.context.overlapped); + if (!ok) { + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) { + output_state_.is_pending = true; + + DVLOG(2) << "sent pending message @" << m << " on channel @" << this + << " with type " << m->type(); + + return true; + } + LOG(ERROR) << "pipe error: " << err; + return false; + } + + DVLOG(2) << "sent message @" << m << " on channel @" << this + << " with type " << m->type(); + + output_state_.is_pending = true; + return true; +} + +void Channel::ChannelImpl::OnIOCompleted( + base::MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, + DWORD error) { + bool ok = true; + DCHECK(thread_check_->CalledOnValidThread()); + if (context == &input_state_.context) { + if (waiting_connect_) { + if (!ProcessConnection()) + return; + // We may have some messages queued up to send... + if (!output_queue_.empty() && !output_state_.is_pending) + ProcessOutgoingMessages(NULL, 0); + if (input_state_.is_pending) + return; + // else, fall-through and look for incoming messages... + } + + // We don't support recursion through OnMessageReceived yet! + DCHECK(!processing_incoming_); + base::AutoReset<bool> auto_reset_processing_incoming( + &processing_incoming_, true); + + // Process the new data. + if (input_state_.is_pending) { + // This is the normal case for everything except the initialization step. + input_state_.is_pending = false; + if (!bytes_transfered) + ok = false; + else if (pipe_ != INVALID_HANDLE_VALUE) + ok = AsyncReadComplete(bytes_transfered); + } else { + DCHECK(!bytes_transfered); + } + + // Request more data. + if (ok) + ok = ProcessIncomingMessages(); + } else { + DCHECK(context == &output_state_.context); + ok = ProcessOutgoingMessages(context, bytes_transfered); + } + if (!ok && INVALID_HANDLE_VALUE != pipe_) { + // We don't want to re-enter Close(). + Close(); + listener()->OnChannelError(); + } +} + +//------------------------------------------------------------------------------ +// Channel's methods simply call through to ChannelImpl. +Channel::Channel(const IPC::ChannelHandle &channel_handle, Mode mode, + Listener* listener) + : channel_impl_(new ChannelImpl(channel_handle, mode, listener)) { +} + +Channel::~Channel() { + delete channel_impl_; +} + +bool Channel::Connect() { + return channel_impl_->Connect(); +} + +void Channel::Close() { + if (channel_impl_) + channel_impl_->Close(); +} + +base::ProcessId Channel::peer_pid() const { + return channel_impl_->peer_pid(); +} + +bool Channel::Send(Message* message) { + return channel_impl_->Send(message); +} + +// static +bool Channel::IsNamedServerInitialized(const std::string& channel_id) { + return ChannelImpl::IsNamedServerInitialized(channel_id); +} + +// static +std::string Channel::GenerateVerifiedChannelID(const std::string& prefix) { + // Windows pipes can be enumerated by low-privileged processes. So, we + // append a strong random value after the \ character. This value is not + // included in the pipe name, but sent as part of the client hello, to + // hijacking the pipe name to spoof the client. + + std::string id = prefix; + if (!id.empty()) + id.append("."); + + int secret; + do { // Guarantee we get a non-zero value. + secret = base::RandInt(0, std::numeric_limits<int>::max()); + } while (secret == 0); + + id.append(GenerateUniqueRandomChannelID()); + return id.append(base::StringPrintf("\\%d", secret)); +} + +} // namespace IPC diff --git a/chromium/ipc/ipc_channel_win.h b/chromium/ipc/ipc_channel_win.h new file mode 100644 index 00000000000..711f57fde72 --- /dev/null +++ b/chromium/ipc/ipc_channel_win.h @@ -0,0 +1,106 @@ +// 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. + +#ifndef IPC_IPC_CHANNEL_WIN_H_ +#define IPC_IPC_CHANNEL_WIN_H_ + +#include "ipc/ipc_channel.h" + +#include <queue> +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "ipc/ipc_channel_reader.h" + +namespace base { +class ThreadChecker; +} + +namespace IPC { + +class Channel::ChannelImpl : public internal::ChannelReader, + public base::MessageLoopForIO::IOHandler { + public: + // Mirror methods of Channel, see ipc_channel.h for description. + ChannelImpl(const IPC::ChannelHandle &channel_handle, Mode mode, + Listener* listener); + ~ChannelImpl(); + bool Connect(); + void Close(); + bool Send(Message* message); + static bool IsNamedServerInitialized(const std::string& channel_id); + base::ProcessId peer_pid() const { return peer_pid_; } + + private: + // ChannelReader implementation. + virtual ReadState ReadData(char* buffer, + int buffer_len, + int* bytes_read) OVERRIDE; + virtual bool WillDispatchInputMessage(Message* msg) OVERRIDE; + bool DidEmptyInputBuffers() OVERRIDE; + virtual void HandleHelloMessage(const Message& msg) OVERRIDE; + + static const string16 PipeName(const std::string& channel_id, + int32* secret); + bool CreatePipe(const IPC::ChannelHandle &channel_handle, Mode mode); + + bool ProcessConnection(); + bool ProcessOutgoingMessages(base::MessageLoopForIO::IOContext* context, + DWORD bytes_written); + + // MessageLoop::IOHandler implementation. + virtual void OnIOCompleted(base::MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, + DWORD error); + + private: + struct State { + explicit State(ChannelImpl* channel); + ~State(); + base::MessageLoopForIO::IOContext context; + bool is_pending; + }; + + State input_state_; + State output_state_; + + HANDLE pipe_; + + base::ProcessId peer_pid_; + + // Messages to be sent are queued here. + std::queue<Message*> output_queue_; + + // In server-mode, we have to wait for the client to connect before we + // can begin reading. We make use of the input_state_ when performing + // the connect operation in overlapped mode. + bool waiting_connect_; + + // This flag is set when processing incoming messages. It is used to + // avoid recursing through ProcessIncomingMessages, which could cause + // problems. TODO(darin): make this unnecessary + bool processing_incoming_; + + // Determines if we should validate a client's secret on connection. + bool validate_client_; + + // This is a unique per-channel value used to authenticate the client end of + // a connection. If the value is non-zero, the client passes it in the hello + // and the host validates. (We don't send the zero value fto preserve IPC + // compatability with existing clients that don't validate the channel.) + int32 client_secret_; + + + base::WeakPtrFactory<ChannelImpl> weak_factory_; + + scoped_ptr<base::ThreadChecker> thread_check_; + + DISALLOW_COPY_AND_ASSIGN(ChannelImpl); +}; + +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_WIN_H_ diff --git a/chromium/ipc/ipc_descriptors.h b/chromium/ipc/ipc_descriptors.h new file mode 100644 index 00000000000..b766bbbe769 --- /dev/null +++ b/chromium/ipc/ipc_descriptors.h @@ -0,0 +1,14 @@ +// 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 IPC_IPC_DESCRIPTORS_H_ +#define IPC_IPC_DESCRIPTORS_H_ + +// This is a list of global descriptor keys to be used with the +// base::GlobalDescriptors object (see base/posix/global_descriptors.h) +enum { + kPrimaryIPCChannel = 0, +}; + +#endif // IPC_IPC_DESCRIPTORS_H_ diff --git a/chromium/ipc/ipc_export.h b/chromium/ipc/ipc_export.h new file mode 100644 index 00000000000..776b3ee5e75 --- /dev/null +++ b/chromium/ipc/ipc_export.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef IPC_IPC_EXPORT_H_ +#define IPC_IPC_EXPORT_H_ + +// Defines IPC_EXPORT so that functionality implemented by the IPC module can be +// exported to consumers. + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(IPC_IMPLEMENTATION) +#define IPC_EXPORT __declspec(dllexport) +#else +#define IPC_EXPORT __declspec(dllimport) +#endif // defined(IPC_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(IPC_IMPLEMENTATION) +#define IPC_EXPORT __attribute__((visibility("default"))) +#else +#define IPC_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define IPC_EXPORT +#endif + +#endif // IPC_IPC_EXPORT_H_ diff --git a/chromium/ipc/ipc_forwarding_message_filter.cc b/chromium/ipc/ipc_forwarding_message_filter.cc new file mode 100644 index 00000000000..342aeb718dc --- /dev/null +++ b/chromium/ipc/ipc_forwarding_message_filter.cc @@ -0,0 +1,56 @@ +// 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 "ipc/ipc_forwarding_message_filter.h" + +#include "base/bind.h" +#include "base/location.h" + +namespace IPC { + +ForwardingMessageFilter::ForwardingMessageFilter( + const uint32* message_ids_to_filter, + size_t num_message_ids_to_filter, + base::TaskRunner* target_task_runner) + : target_task_runner_(target_task_runner) { + DCHECK(target_task_runner_.get()); + for (size_t i = 0; i < num_message_ids_to_filter; i++) + message_ids_to_filter_.insert(message_ids_to_filter[i]); +} + +void ForwardingMessageFilter::AddRoute(int routing_id, const Handler& handler) { + DCHECK(!handler.is_null()); + base::AutoLock locked(handlers_lock_); + handlers_.insert(std::make_pair(routing_id, handler)); +} + +void ForwardingMessageFilter::RemoveRoute(int routing_id) { + base::AutoLock locked(handlers_lock_); + handlers_.erase(routing_id); +} + +bool ForwardingMessageFilter::OnMessageReceived(const Message& message) { + if (message_ids_to_filter_.find(message.type()) == + message_ids_to_filter_.end()) + return false; + + + Handler handler; + + { + base::AutoLock locked(handlers_lock_); + std::map<int, Handler>::iterator it = handlers_.find(message.routing_id()); + if (it == handlers_.end()) + return false; + handler = it->second; + } + + target_task_runner_->PostTask(FROM_HERE, base::Bind(handler, message)); + return true; +} + +ForwardingMessageFilter::~ForwardingMessageFilter() { +} + +} // namespace IPC diff --git a/chromium/ipc/ipc_forwarding_message_filter.h b/chromium/ipc/ipc_forwarding_message_filter.h new file mode 100644 index 00000000000..919a44d7599 --- /dev/null +++ b/chromium/ipc/ipc_forwarding_message_filter.h @@ -0,0 +1,71 @@ +// 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. + +#ifndef IPC_IPC_FORWARDING_MESSAGE_FILTER_H_ +#define IPC_IPC_FORWARDING_MESSAGE_FILTER_H_ + +#include <map> +#include <set> + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "ipc/ipc_channel_proxy.h" + +namespace IPC { + +// This class can be used to intercept routed messages and +// deliver them to a different task runner than they would otherwise +// be sent. Messages are filtered based on type. To route these messages, +// add a MessageRouter to the handler. +// +// The user of this class implements ForwardingMessageFilter::Client, +// which will receive the intercepted messages, on the specified target thread. +class IPC_EXPORT ForwardingMessageFilter : public ChannelProxy::MessageFilter { + public: + + // The handler is invoked on the thread associated with + // |target_task_runner| with messages that were intercepted by this filter. + typedef base::Callback<void(const Message&)> Handler; + + // This filter will intercept |message_ids_to_filter| and post + // them to the provided |target_task_runner|, where they will be given + // to |handler|. + // + // The caller must ensure that |handler| outlives the lifetime of the filter. + ForwardingMessageFilter( + const uint32* message_ids_to_filter, + size_t num_message_ids_to_filter, + base::TaskRunner* target_task_runner); + + // Define the message routes to be filtered. + void AddRoute(int routing_id, const Handler& handler); + void RemoveRoute(int routing_id); + + // ChannelProxy::MessageFilter methods: + virtual bool OnMessageReceived(const Message& message) OVERRIDE; + + private: + friend class ChannelProxy::MessageFilter; + virtual ~ForwardingMessageFilter(); + + std::set<int> message_ids_to_filter_; + + // The handler_ only gets Run on the thread corresponding to + // target_task_runner_. + scoped_refptr<base::TaskRunner> target_task_runner_; + + // Protects access to routes_. + base::Lock handlers_lock_; + + // Indicates the routing_ids for which messages should be filtered. + std::map<int, Handler> handlers_; + + DISALLOW_COPY_AND_ASSIGN(ForwardingMessageFilter); +}; + +} // namespace IPC + +#endif // IPC_IPC_FORWARDING_MESSAGE_FILTER_H_ diff --git a/chromium/ipc/ipc_fuzzing_tests.cc b/chromium/ipc/ipc_fuzzing_tests.cc new file mode 100644 index 00000000000..3d2d497c5f8 --- /dev/null +++ b/chromium/ipc/ipc_fuzzing_tests.cc @@ -0,0 +1,413 @@ +// 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 <stdio.h> +#include <string> +#include <sstream> + +#include "base/message_loop/message_loop.h" +#include "base/threading/platform_thread.h" +#include "ipc/ipc_test_base.h" +#include "testing/gtest/include/gtest/gtest.h" + +// IPC messages for testing ---------------------------------------------------- + +#define IPC_MESSAGE_IMPL +#include "ipc/ipc_message_macros.h" + +#define IPC_MESSAGE_START TestMsgStart + +// Generic message class that is an int followed by a wstring. +IPC_MESSAGE_CONTROL2(MsgClassIS, int, std::wstring) + +// Generic message class that is a wstring followed by an int. +IPC_MESSAGE_CONTROL2(MsgClassSI, std::wstring, int) + +// Message to create a mutex in the IPC server, using the received name. +IPC_MESSAGE_CONTROL2(MsgDoMutex, std::wstring, int) + +// Used to generate an ID for a message that should not exist. +IPC_MESSAGE_CONTROL0(MsgUnhandled) + +// ----------------------------------------------------------------------------- + +namespace { + +TEST(IPCMessageIntegrity, ReadBeyondBufferStr) { + //This was BUG 984408. + uint32 v1 = kuint32max - 1; + int v2 = 666; + IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL); + EXPECT_TRUE(m.WriteInt(v1)); + EXPECT_TRUE(m.WriteInt(v2)); + + PickleIterator iter(m); + std::string vs; + EXPECT_FALSE(m.ReadString(&iter, &vs)); +} + +TEST(IPCMessageIntegrity, ReadBeyondBufferWStr) { + //This was BUG 984408. + uint32 v1 = kuint32max - 1; + int v2 = 777; + IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL); + EXPECT_TRUE(m.WriteInt(v1)); + EXPECT_TRUE(m.WriteInt(v2)); + + PickleIterator iter(m); + std::wstring vs; + EXPECT_FALSE(m.ReadWString(&iter, &vs)); +} + +TEST(IPCMessageIntegrity, ReadBytesBadIterator) { + // This was BUG 1035467. + IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL); + EXPECT_TRUE(m.WriteInt(1)); + EXPECT_TRUE(m.WriteInt(2)); + + PickleIterator iter(m); + const char* data = NULL; + EXPECT_TRUE(m.ReadBytes(&iter, &data, sizeof(int))); +} + +TEST(IPCMessageIntegrity, ReadVectorNegativeSize) { + // A slight variation of BUG 984408. Note that the pickling of vector<char> + // has a specialized template which is not vulnerable to this bug. So here + // try to hit the non-specialized case vector<P>. + IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL); + EXPECT_TRUE(m.WriteInt(-1)); // This is the count of elements. + EXPECT_TRUE(m.WriteInt(1)); + EXPECT_TRUE(m.WriteInt(2)); + EXPECT_TRUE(m.WriteInt(3)); + + std::vector<double> vec; + PickleIterator iter(m); + EXPECT_FALSE(ReadParam(&m, &iter, &vec)); +} + +TEST(IPCMessageIntegrity, ReadVectorTooLarge1) { + // This was BUG 1006367. This is the large but positive length case. Again + // we try to hit the non-specialized case vector<P>. + IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL); + EXPECT_TRUE(m.WriteInt(0x21000003)); // This is the count of elements. + EXPECT_TRUE(m.WriteInt64(1)); + EXPECT_TRUE(m.WriteInt64(2)); + + std::vector<int64> vec; + PickleIterator iter(m); + EXPECT_FALSE(ReadParam(&m, &iter, &vec)); +} + +TEST(IPCMessageIntegrity, ReadVectorTooLarge2) { + // This was BUG 1006367. This is the large but positive with an additional + // integer overflow when computing the actual byte size. Again we try to hit + // the non-specialized case vector<P>. + IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL); + EXPECT_TRUE(m.WriteInt(0x71000000)); // This is the count of elements. + EXPECT_TRUE(m.WriteInt64(1)); + EXPECT_TRUE(m.WriteInt64(2)); + + std::vector<int64> vec; + PickleIterator iter(m); + EXPECT_FALSE(ReadParam(&m, &iter, &vec)); +} + +class SimpleListener : public IPC::Listener { + public: + SimpleListener() : other_(NULL) { + } + void Init(IPC::Sender* s) { + other_ = s; + } + protected: + IPC::Sender* other_; +}; + +enum { + FUZZER_ROUTING_ID = 5 +}; + +// The fuzzer server class. It runs in a child process and expects +// only two IPC calls; after that it exits the message loop which +// terminates the child process. +class FuzzerServerListener : public SimpleListener { + public: + FuzzerServerListener() : message_count_(2), pending_messages_(0) { + } + virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE { + if (msg.routing_id() == MSG_ROUTING_CONTROL) { + ++pending_messages_; + IPC_BEGIN_MESSAGE_MAP(FuzzerServerListener, msg) + IPC_MESSAGE_HANDLER(MsgClassIS, OnMsgClassISMessage) + IPC_MESSAGE_HANDLER(MsgClassSI, OnMsgClassSIMessage) + IPC_END_MESSAGE_MAP() + if (pending_messages_) { + // Probably a problem de-serializing the message. + ReplyMsgNotHandled(msg.type()); + } + } + return true; + } + + private: + void OnMsgClassISMessage(int value, const std::wstring& text) { + UseData(MsgClassIS::ID, value, text); + RoundtripAckReply(FUZZER_ROUTING_ID, MsgClassIS::ID, value); + Cleanup(); + } + + void OnMsgClassSIMessage(const std::wstring& text, int value) { + UseData(MsgClassSI::ID, value, text); + RoundtripAckReply(FUZZER_ROUTING_ID, MsgClassSI::ID, value); + Cleanup(); + } + + bool RoundtripAckReply(int routing, uint32 type_id, int reply) { + IPC::Message* message = new IPC::Message(routing, type_id, + IPC::Message::PRIORITY_NORMAL); + message->WriteInt(reply + 1); + message->WriteInt(reply); + return other_->Send(message); + } + + void Cleanup() { + --message_count_; + --pending_messages_; + if (0 == message_count_) + base::MessageLoop::current()->Quit(); + } + + void ReplyMsgNotHandled(uint32 type_id) { + RoundtripAckReply(FUZZER_ROUTING_ID, MsgUnhandled::ID, type_id); + Cleanup(); + } + + void UseData(int caller, int value, const std::wstring& text) { + std::wostringstream wos; + wos << L"IPC fuzzer:" << caller << " [" << value << L" " << text << L"]\n"; + std::wstring output = wos.str(); + LOG(WARNING) << output.c_str(); + }; + + int message_count_; + int pending_messages_; +}; + +class FuzzerClientListener : public SimpleListener { + public: + FuzzerClientListener() : last_msg_(NULL) { + } + + virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE { + last_msg_ = new IPC::Message(msg); + base::MessageLoop::current()->Quit(); + return true; + } + + bool ExpectMessage(int value, uint32 type_id) { + if (!MsgHandlerInternal(type_id)) + return false; + int msg_value1 = 0; + int msg_value2 = 0; + PickleIterator iter(*last_msg_); + if (!last_msg_->ReadInt(&iter, &msg_value1)) + return false; + if (!last_msg_->ReadInt(&iter, &msg_value2)) + return false; + if ((msg_value2 + 1) != msg_value1) + return false; + if (msg_value2 != value) + return false; + + delete last_msg_; + last_msg_ = NULL; + return true; + } + + bool ExpectMsgNotHandled(uint32 type_id) { + return ExpectMessage(type_id, MsgUnhandled::ID); + } + + private: + bool MsgHandlerInternal(uint32 type_id) { + base::MessageLoop::current()->Run(); + if (NULL == last_msg_) + return false; + if (FUZZER_ROUTING_ID != last_msg_->routing_id()) + return false; + return (type_id == last_msg_->type()); + }; + + IPC::Message* last_msg_; +}; + +// Runs the fuzzing server child mode. Returns when the preset number of +// messages have been received. +MULTIPROCESS_IPC_TEST_CLIENT_MAIN(FuzzServerClient) { + base::MessageLoopForIO main_message_loop; + FuzzerServerListener listener; + IPC::Channel channel(IPCTestBase::GetChannelName("FuzzServerClient"), + IPC::Channel::MODE_CLIENT, + &listener); + CHECK(channel.Connect()); + listener.Init(&channel); + base::MessageLoop::current()->Run(); + return 0; +} + +class IPCFuzzingTest : public IPCTestBase { +}; + +// This test makes sure that the FuzzerClientListener and FuzzerServerListener +// are working properly by generating two well formed IPC calls. +TEST_F(IPCFuzzingTest, SanityTest) { + Init("FuzzServerClient"); + + FuzzerClientListener listener; + CreateChannel(&listener); + listener.Init(channel()); + ASSERT_TRUE(ConnectChannel()); + ASSERT_TRUE(StartClient()); + + IPC::Message* msg = NULL; + int value = 43; + msg = new MsgClassIS(value, L"expect 43"); + sender()->Send(msg); + EXPECT_TRUE(listener.ExpectMessage(value, MsgClassIS::ID)); + + msg = new MsgClassSI(L"expect 44", ++value); + sender()->Send(msg); + EXPECT_TRUE(listener.ExpectMessage(value, MsgClassSI::ID)); + + EXPECT_TRUE(WaitForClientShutdown()); + DestroyChannel(); +} + +// This test uses a payload that is smaller than expected. This generates an +// error while unpacking the IPC buffer which in debug trigger an assertion and +// in release is ignored (!). Right after we generate another valid IPC to make +// sure framing is working properly. +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) +TEST_F(IPCFuzzingTest, MsgBadPayloadShort) { + Init("FuzzServerClient"); + + FuzzerClientListener listener; + CreateChannel(&listener); + listener.Init(channel()); + ASSERT_TRUE(ConnectChannel()); + ASSERT_TRUE(StartClient()); + + IPC::Message* msg = new IPC::Message(MSG_ROUTING_CONTROL, MsgClassIS::ID, + IPC::Message::PRIORITY_NORMAL); + msg->WriteInt(666); + sender()->Send(msg); + EXPECT_TRUE(listener.ExpectMsgNotHandled(MsgClassIS::ID)); + + msg = new MsgClassSI(L"expect one", 1); + sender()->Send(msg); + EXPECT_TRUE(listener.ExpectMessage(1, MsgClassSI::ID)); + + EXPECT_TRUE(WaitForClientShutdown()); + DestroyChannel(); +} +#endif + +// This test uses a payload that has too many arguments, but so the payload size +// is big enough so the unpacking routine does not generate an error as in the +// case of MsgBadPayloadShort test. This test does not pinpoint a flaw (per se) +// as by design we don't carry type information on the IPC message. +TEST_F(IPCFuzzingTest, MsgBadPayloadArgs) { + Init("FuzzServerClient"); + + FuzzerClientListener listener; + CreateChannel(&listener); + listener.Init(channel()); + ASSERT_TRUE(ConnectChannel()); + ASSERT_TRUE(StartClient()); + + IPC::Message* msg = new IPC::Message(MSG_ROUTING_CONTROL, MsgClassSI::ID, + IPC::Message::PRIORITY_NORMAL); + msg->WriteWString(L"d"); + msg->WriteInt(0); + msg->WriteInt(0x65); // Extra argument. + + sender()->Send(msg); + EXPECT_TRUE(listener.ExpectMessage(0, MsgClassSI::ID)); + + // Now send a well formed message to make sure the receiver wasn't + // thrown out of sync by the extra argument. + msg = new MsgClassIS(3, L"expect three"); + sender()->Send(msg); + EXPECT_TRUE(listener.ExpectMessage(3, MsgClassIS::ID)); + + EXPECT_TRUE(WaitForClientShutdown()); + DestroyChannel(); +} + +// This class is for testing the IPC_BEGIN_MESSAGE_MAP_EX macros. +class ServerMacroExTest { + public: + ServerMacroExTest() : unhandled_msgs_(0) { + } + + virtual ~ServerMacroExTest() { + } + + virtual bool OnMessageReceived(const IPC::Message& msg) { + bool msg_is_ok = false; + IPC_BEGIN_MESSAGE_MAP_EX(ServerMacroExTest, msg, msg_is_ok) + IPC_MESSAGE_HANDLER(MsgClassIS, OnMsgClassISMessage) + IPC_MESSAGE_HANDLER(MsgClassSI, OnMsgClassSIMessage) + IPC_MESSAGE_UNHANDLED(++unhandled_msgs_) + IPC_END_MESSAGE_MAP_EX() + return msg_is_ok; + } + + int unhandled_msgs() const { + return unhandled_msgs_; + } + + private: + void OnMsgClassISMessage(int value, const std::wstring& text) { + } + void OnMsgClassSIMessage(const std::wstring& text, int value) { + } + + int unhandled_msgs_; + + DISALLOW_COPY_AND_ASSIGN(ServerMacroExTest); +}; + +TEST_F(IPCFuzzingTest, MsgMapExMacro) { + IPC::Message* msg = NULL; + ServerMacroExTest server; + + // Test the regular messages. + msg = new MsgClassIS(3, L"text3"); + EXPECT_TRUE(server.OnMessageReceived(*msg)); + delete msg; + msg = new MsgClassSI(L"text2", 2); + EXPECT_TRUE(server.OnMessageReceived(*msg)); + delete msg; + +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) + // Test a bad message. + msg = new IPC::Message(MSG_ROUTING_CONTROL, MsgClassSI::ID, + IPC::Message::PRIORITY_NORMAL); + msg->WriteInt(2); + EXPECT_FALSE(server.OnMessageReceived(*msg)); + delete msg; + + msg = new IPC::Message(MSG_ROUTING_CONTROL, MsgClassIS::ID, + IPC::Message::PRIORITY_NORMAL); + msg->WriteInt(0x64); + msg->WriteInt(0x32); + EXPECT_FALSE(server.OnMessageReceived(*msg)); + delete msg; + + EXPECT_EQ(0, server.unhandled_msgs()); +#endif +} + +} // namespace diff --git a/chromium/ipc/ipc_listener.h b/chromium/ipc/ipc_listener.h new file mode 100644 index 00000000000..9189eec7d11 --- /dev/null +++ b/chromium/ipc/ipc_listener.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef IPC_IPC_LISTENER_H_ +#define IPC_IPC_LISTENER_H_ + +#include "base/basictypes.h" +#include "build/build_config.h" +#include "ipc/ipc_export.h" + +namespace IPC { + +class Message; + +// Implemented by consumers of a Channel to receive messages. +class IPC_EXPORT Listener { + public: + // Called when a message is received. Returns true iff the message was + // handled. + virtual bool OnMessageReceived(const Message& message) = 0; + + // Called when the channel is connected and we have received the internal + // Hello message from the peer. + virtual void OnChannelConnected(int32 peer_pid) {} + + // Called when an error is detected that causes the channel to close. + // This method is not called when a channel is closed normally. + virtual void OnChannelError() {} + +#if defined(OS_POSIX) + // Called on the server side when a channel that listens for connections + // denies an attempt to connect. + virtual void OnChannelDenied() {} + + // Called on the server side when a channel that listens for connections + // has an error that causes the listening channel to close. + virtual void OnChannelListenError() {} +#endif // OS_POSIX + + protected: + virtual ~Listener() {} +}; + +} // namespace IPC + +#endif // IPC_IPC_LISTENER_H_ diff --git a/chromium/ipc/ipc_logging.cc b/chromium/ipc/ipc_logging.cc new file mode 100644 index 00000000000..65d88901d93 --- /dev/null +++ b/chromium/ipc/ipc_logging.cc @@ -0,0 +1,315 @@ +// 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 "ipc/ipc_logging.h" + +#ifdef IPC_MESSAGE_LOG_ENABLED +#define IPC_MESSAGE_MACROS_LOG_ENABLED +#endif + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_sender.h" +#include "ipc/ipc_switches.h" +#include "ipc/ipc_sync_message.h" + +#if defined(OS_POSIX) +#include <unistd.h> +#endif + +#ifdef IPC_MESSAGE_LOG_ENABLED + +using base::Time; + +namespace IPC { + +const int kLogSendDelayMs = 100; + +// We use a pointer to the function table to avoid any linker dependencies on +// all the traits used as IPC message parameters. +LogFunctionMap* Logging::log_function_map_; + +Logging::Logging() + : enabled_(false), + enabled_on_stderr_(false), + enabled_color_(false), + queue_invoke_later_pending_(false), + sender_(NULL), + main_thread_(base::MessageLoop::current()), + consumer_(NULL) { +#if defined(OS_WIN) + // getenv triggers an unsafe warning. Simply check how big of a buffer + // would be needed to fetch the value to see if the enviornment variable is + // set. + size_t requiredSize = 0; + getenv_s(&requiredSize, NULL, 0, "CHROME_IPC_LOGGING"); + bool logging_env_var_set = (requiredSize != 0); + if (requiredSize <= 6) { + char buffer[6]; + getenv_s(&requiredSize, buffer, sizeof(buffer), "CHROME_IPC_LOGGING"); + if (requiredSize && !strncmp("color", buffer, 6)) + enabled_color_ = true; + } +#else // !defined(OS_WIN) + const char* ipc_logging = getenv("CHROME_IPC_LOGGING"); + bool logging_env_var_set = (ipc_logging != NULL); + if (ipc_logging && !strcmp(ipc_logging, "color")) + enabled_color_ = true; +#endif //defined(OS_WIN) + if (logging_env_var_set) { + enabled_ = true; + enabled_on_stderr_ = true; + } +} + +Logging::~Logging() { +} + +Logging* Logging::GetInstance() { + return Singleton<Logging>::get(); +} + +void Logging::SetConsumer(Consumer* consumer) { + consumer_ = consumer; +} + +void Logging::Enable() { + enabled_ = true; +} + +void Logging::Disable() { + enabled_ = false; +} + +void Logging::OnSendLogs() { + queue_invoke_later_pending_ = false; + if (!sender_) + return; + + Message* msg = new Message( + MSG_ROUTING_CONTROL, IPC_LOGGING_ID, Message::PRIORITY_NORMAL); + WriteParam(msg, queued_logs_); + queued_logs_.clear(); + sender_->Send(msg); +} + +void Logging::SetIPCSender(IPC::Sender* sender) { + sender_ = sender; +} + +void Logging::OnReceivedLoggingMessage(const Message& message) { + std::vector<LogData> data; + PickleIterator iter(message); + if (!ReadParam(&message, &iter, &data)) + return; + + for (size_t i = 0; i < data.size(); ++i) { + Log(data[i]); + } +} + +void Logging::OnSendMessage(Message* message, const std::string& channel_id) { + if (!Enabled()) + return; + + if (message->is_reply()) { + LogData* data = message->sync_log_data(); + if (!data) + return; + + // This is actually the delayed reply to a sync message. Create a string + // of the output parameters, add it to the LogData that was earlier stashed + // with the reply, and log the result. + GenerateLogData("", *message, data, true); + data->channel = channel_id; + Log(*data); + delete data; + message->set_sync_log_data(NULL); + } else { + // If the time has already been set (i.e. by ChannelProxy), keep that time + // instead as it's more accurate. + if (!message->sent_time()) + message->set_sent_time(Time::Now().ToInternalValue()); + } +} + +void Logging::OnPreDispatchMessage(const Message& message) { + message.set_received_time(Time::Now().ToInternalValue()); +} + +void Logging::OnPostDispatchMessage(const Message& message, + const std::string& channel_id) { + if (!Enabled() || + !message.sent_time() || + !message.received_time() || + message.dont_log()) + return; + + LogData data; + GenerateLogData(channel_id, message, &data, true); + + if (base::MessageLoop::current() == main_thread_) { + Log(data); + } else { + main_thread_->PostTask( + FROM_HERE, base::Bind(&Logging::Log, base::Unretained(this), data)); + } +} + +void Logging::GetMessageText(uint32 type, std::string* name, + const Message* message, + std::string* params) { + if (!log_function_map_) + return; + + LogFunctionMap::iterator it = log_function_map_->find(type); + if (it == log_function_map_->end()) { + if (name) { + *name = "[UNKNOWN MSG "; + *name += base::IntToString(type); + *name += " ]"; + } + return; + } + + (*it->second)(name, message, params); +} + +const char* Logging::ANSIEscape(ANSIColor color) { + if (!enabled_color_) + return ""; + switch (color) { + case ANSI_COLOR_RESET: + return "\033[m"; + case ANSI_COLOR_BLACK: + return "\033[0;30m"; + case ANSI_COLOR_RED: + return "\033[0;31m"; + case ANSI_COLOR_GREEN: + return "\033[0;32m"; + case ANSI_COLOR_YELLOW: + return "\033[0;33m"; + case ANSI_COLOR_BLUE: + return "\033[0;34m"; + case ANSI_COLOR_MAGENTA: + return "\033[0;35m"; + case ANSI_COLOR_CYAN: + return "\033[0;36m"; + case ANSI_COLOR_WHITE: + return "\033[0;37m"; + } + return ""; +} + +Logging::ANSIColor Logging::DelayColor(double delay) { + if (delay < 0.1) + return ANSI_COLOR_GREEN; + if (delay < 0.25) + return ANSI_COLOR_BLACK; + if (delay < 0.5) + return ANSI_COLOR_YELLOW; + return ANSI_COLOR_RED; +} + +void Logging::Log(const LogData& data) { + if (consumer_) { + // We're in the browser process. + consumer_->Log(data); + } else { + // We're in the renderer or plugin processes. + if (sender_) { + queued_logs_.push_back(data); + if (!queue_invoke_later_pending_) { + queue_invoke_later_pending_ = true; + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&Logging::OnSendLogs, base::Unretained(this)), + base::TimeDelta::FromMilliseconds(kLogSendDelayMs)); + } + } + } + if (enabled_on_stderr_) { + std::string message_name; + if (data.message_name.empty()) { + message_name = base::StringPrintf("[unknown type %d]", data.type); + } else { + message_name = data.message_name; + } + double receive_delay = + (Time::FromInternalValue(data.receive) - + Time::FromInternalValue(data.sent)).InSecondsF(); + double dispatch_delay = + (Time::FromInternalValue(data.dispatch) - + Time::FromInternalValue(data.sent)).InSecondsF(); + fprintf(stderr, + "ipc %s %d %s %s%s %s%s\n %18.5f %s%18.5f %s%18.5f%s\n", + data.channel.c_str(), + data.routing_id, + data.flags.c_str(), + ANSIEscape(sender_ ? ANSI_COLOR_BLUE : ANSI_COLOR_CYAN), + message_name.c_str(), + ANSIEscape(ANSI_COLOR_RESET), + data.params.c_str(), + Time::FromInternalValue(data.sent).ToDoubleT(), + ANSIEscape(DelayColor(receive_delay)), + Time::FromInternalValue(data.receive).ToDoubleT(), + ANSIEscape(DelayColor(dispatch_delay)), + Time::FromInternalValue(data.dispatch).ToDoubleT(), + ANSIEscape(ANSI_COLOR_RESET) + ); + } +} + +void GenerateLogData(const std::string& channel, const Message& message, + LogData* data, bool get_params) { + if (message.is_reply()) { + // "data" should already be filled in. + std::string params; + Logging::GetMessageText(data->type, NULL, &message, ¶ms); + + if (!data->params.empty() && !params.empty()) + data->params += ", "; + + data->flags += " DR"; + + data->params += params; + } else { + std::string flags; + if (message.is_sync()) + flags = "S"; + + if (message.is_reply()) + flags += "R"; + + if (message.is_reply_error()) + flags += "E"; + + std::string params, message_name; + Logging::GetMessageText(message.type(), &message_name, &message, + get_params ? ¶ms : NULL); + + data->channel = channel; + data->routing_id = message.routing_id(); + data->type = message.type(); + data->flags = flags; + data->sent = message.sent_time(); + data->receive = message.received_time(); + data->dispatch = Time::Now().ToInternalValue(); + data->params = params; + data->message_name = message_name; + } +} + +} + +#endif // IPC_MESSAGE_LOG_ENABLED diff --git a/chromium/ipc/ipc_logging.h b/chromium/ipc/ipc_logging.h new file mode 100644 index 00000000000..f730b6ae3e2 --- /dev/null +++ b/chromium/ipc/ipc_logging.h @@ -0,0 +1,127 @@ +// 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. + +#ifndef IPC_IPC_LOGGING_H_ +#define IPC_IPC_LOGGING_H_ + +#include "ipc/ipc_message.h" // For IPC_MESSAGE_LOG_ENABLED. + +#ifdef IPC_MESSAGE_LOG_ENABLED + +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/message_loop/message_loop.h" +#include "ipc/ipc_export.h" + +// Logging function. |name| is a string in ASCII and |params| is a string in +// UTF-8. +typedef void (*LogFunction)(std::string* name, + const IPC::Message* msg, + std::string* params); + +typedef base::hash_map<uint32, LogFunction > LogFunctionMap; + +namespace IPC { + +class Message; +class Sender; + +// One instance per process. Needs to be created on the main thread (the UI +// thread in the browser) but OnPreDispatchMessage/OnPostDispatchMessage +// can be called on other threads. +class IPC_EXPORT Logging { + public: + // Implemented by consumers of log messages. + class Consumer { + public: + virtual void Log(const LogData& data) = 0; + + protected: + virtual ~Consumer() {} + }; + + void SetConsumer(Consumer* consumer); + + ~Logging(); + static Logging* GetInstance(); + + // Enable and Disable are NOT cross-process; they only affect the + // current thread/process. If you want to modify the value for all + // processes, perhaps your intent is to call + // g_browser_process->SetIPCLoggingEnabled(). + void Enable(); + void Disable(); + bool Enabled() const { return enabled_; } + + // Called by child processes to give the logger object the channel to send + // logging data to the browser process. + void SetIPCSender(Sender* sender); + + // Called in the browser process when logging data from a child process is + // received. + void OnReceivedLoggingMessage(const Message& message); + + void OnSendMessage(Message* message, const std::string& channel_id); + void OnPreDispatchMessage(const Message& message); + void OnPostDispatchMessage(const Message& message, + const std::string& channel_id); + + // Like the *MsgLog functions declared for each message class, except this + // calls the correct one based on the message type automatically. Defined in + // ipc_logging.cc. + static void GetMessageText(uint32 type, std::string* name, + const Message* message, std::string* params); + + static void set_log_function_map(LogFunctionMap* functions) { + log_function_map_ = functions; + } + + static LogFunctionMap* log_function_map() { + return log_function_map_; + } + + private: + typedef enum { + ANSI_COLOR_RESET = -1, + ANSI_COLOR_BLACK, + ANSI_COLOR_RED, + ANSI_COLOR_GREEN, + ANSI_COLOR_YELLOW, + ANSI_COLOR_BLUE, + ANSI_COLOR_MAGENTA, + ANSI_COLOR_CYAN, + ANSI_COLOR_WHITE + } ANSIColor; + const char* ANSIEscape(ANSIColor color); + ANSIColor DelayColor(double delay); + + friend struct DefaultSingletonTraits<Logging>; + Logging(); + + void OnSendLogs(); + void Log(const LogData& data); + + bool enabled_; + bool enabled_on_stderr_; // only used on POSIX for now + bool enabled_color_; // only used on POSIX for now + + std::vector<LogData> queued_logs_; + bool queue_invoke_later_pending_; + + Sender* sender_; + base::MessageLoop* main_thread_; + + Consumer* consumer_; + + static LogFunctionMap* log_function_map_; +}; + +} // namespace IPC + +#endif // IPC_MESSAGE_LOG_ENABLED + +#endif // IPC_IPC_LOGGING_H_ diff --git a/chromium/ipc/ipc_message.cc b/chromium/ipc/ipc_message.cc new file mode 100644 index 00000000000..cf3a65e077f --- /dev/null +++ b/chromium/ipc/ipc_message.cc @@ -0,0 +1,162 @@ +// 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 "ipc/ipc_message.h" + +#include "base/atomicops.h" +#include "base/logging.h" +#include "build/build_config.h" + +#if defined(OS_POSIX) +#include "ipc/file_descriptor_set_posix.h" +#endif + +namespace { + +base::subtle::Atomic32 g_ref_num = 0; + +// Create a reference number for identifying IPC messages in traces. The return +// values has the reference number stored in the upper 24 bits, leaving the low +// 8 bits set to 0 for use as flags. +inline uint32 GetRefNumUpper24() { + base::debug::TraceLog* trace_log = base::debug::TraceLog::GetInstance(); + int32 pid = trace_log ? trace_log->process_id() : 0; + int32 count = base::subtle::NoBarrier_AtomicIncrement(&g_ref_num, 1); + // The 24 bit hash is composed of 14 bits of the count and 10 bits of the + // Process ID. With the current trace event buffer cap, the 14-bit count did + // not appear to wrap during a trace. Note that it is not a big deal if + // collisions occur, as this is only used for debugging and trace analysis. + return ((pid << 14) | (count & 0x3fff)) << 8; +} + +} // namespace + +namespace IPC { + +//------------------------------------------------------------------------------ + +Message::~Message() { +} + +Message::Message() + : Pickle(sizeof(Header)) { + header()->routing = header()->type = 0; + header()->flags = GetRefNumUpper24(); +#if defined(OS_POSIX) + header()->num_fds = 0; + header()->pad = 0; +#endif + InitLoggingVariables(); +} + +Message::Message(int32 routing_id, uint32 type, PriorityValue priority) + : Pickle(sizeof(Header)) { + header()->routing = routing_id; + header()->type = type; + DCHECK((priority & 0xffffff00) == 0); + header()->flags = priority | GetRefNumUpper24(); +#if defined(OS_POSIX) + header()->num_fds = 0; + header()->pad = 0; +#endif + InitLoggingVariables(); +} + +Message::Message(const char* data, int data_len) : Pickle(data, data_len) { + InitLoggingVariables(); +} + +Message::Message(const Message& other) : Pickle(other) { + InitLoggingVariables(); +#if defined(OS_POSIX) + file_descriptor_set_ = other.file_descriptor_set_; +#endif +} + +void Message::InitLoggingVariables() { +#ifdef IPC_MESSAGE_LOG_ENABLED + received_time_ = 0; + dont_log_ = false; + log_data_ = NULL; +#endif +} + +Message& Message::operator=(const Message& other) { + *static_cast<Pickle*>(this) = other; +#if defined(OS_POSIX) + file_descriptor_set_ = other.file_descriptor_set_; +#endif + return *this; +} + +void Message::SetHeaderValues(int32 routing, uint32 type, uint32 flags) { + // This should only be called when the message is already empty. + DCHECK(payload_size() == 0); + + header()->routing = routing; + header()->type = type; + header()->flags = flags; +} + +#ifdef IPC_MESSAGE_LOG_ENABLED +void Message::set_sent_time(int64 time) { + DCHECK((header()->flags & HAS_SENT_TIME_BIT) == 0); + header()->flags |= HAS_SENT_TIME_BIT; + WriteInt64(time); +} + +int64 Message::sent_time() const { + if ((header()->flags & HAS_SENT_TIME_BIT) == 0) + return 0; + + const char* data = end_of_payload(); + data -= sizeof(int64); + return *(reinterpret_cast<const int64*>(data)); +} + +void Message::set_received_time(int64 time) const { + received_time_ = time; +} +#endif + +#if defined(OS_POSIX) +bool Message::WriteFileDescriptor(const base::FileDescriptor& descriptor) { + // We write the index of the descriptor so that we don't have to + // keep the current descriptor as extra decoding state when deserialising. + WriteInt(file_descriptor_set()->size()); + if (descriptor.auto_close) { + return file_descriptor_set()->AddAndAutoClose(descriptor.fd); + } else { + return file_descriptor_set()->Add(descriptor.fd); + } +} + +bool Message::ReadFileDescriptor(PickleIterator* iter, + base::FileDescriptor* descriptor) const { + int descriptor_index; + if (!ReadInt(iter, &descriptor_index)) + return false; + + FileDescriptorSet* file_descriptor_set = file_descriptor_set_.get(); + if (!file_descriptor_set) + return false; + + descriptor->fd = file_descriptor_set->GetDescriptorAt(descriptor_index); + descriptor->auto_close = true; + + return descriptor->fd >= 0; +} + +bool Message::HasFileDescriptors() const { + return file_descriptor_set_.get() && !file_descriptor_set_->empty(); +} + +void Message::EnsureFileDescriptorSet() { + if (file_descriptor_set_.get() == NULL) + file_descriptor_set_ = new FileDescriptorSet; +} + +#endif + +} // namespace IPC diff --git a/chromium/ipc/ipc_message.h b/chromium/ipc/ipc_message.h new file mode 100644 index 00000000000..42090166b38 --- /dev/null +++ b/chromium/ipc/ipc_message.h @@ -0,0 +1,293 @@ +// 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. + +#ifndef IPC_IPC_MESSAGE_H_ +#define IPC_IPC_MESSAGE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/debug/trace_event.h" +#include "base/pickle.h" +#include "ipc/ipc_export.h" + +#if !defined(NDEBUG) +#define IPC_MESSAGE_LOG_ENABLED +#endif + +#if defined(OS_POSIX) +#include "base/memory/ref_counted.h" +#endif + +namespace base { +struct FileDescriptor; +} + +class FileDescriptorSet; + +namespace IPC { + +//------------------------------------------------------------------------------ + +struct LogData; + +class IPC_EXPORT Message : public Pickle { + public: + enum PriorityValue { + PRIORITY_LOW = 1, + PRIORITY_NORMAL, + PRIORITY_HIGH + }; + + // Bit values used in the flags field. + // Upper 24 bits of flags store a reference number, so this enum is limited to + // 8 bits. + enum { + PRIORITY_MASK = 0x03, // Low 2 bits of store the priority value. + SYNC_BIT = 0x04, + REPLY_BIT = 0x08, + REPLY_ERROR_BIT = 0x10, + UNBLOCK_BIT = 0x20, + PUMPING_MSGS_BIT = 0x40, + HAS_SENT_TIME_BIT = 0x80, + }; + + virtual ~Message(); + + Message(); + + // Initialize a message with a user-defined type, priority value, and + // destination WebView ID. + Message(int32 routing_id, uint32 type, PriorityValue priority); + + // Initializes a message from a const block of data. The data is not copied; + // instead the data is merely referenced by this message. Only const methods + // should be used on the message when initialized this way. + Message(const char* data, int data_len); + + Message(const Message& other); + Message& operator=(const Message& other); + + PriorityValue priority() const { + return static_cast<PriorityValue>(header()->flags & PRIORITY_MASK); + } + + // True if this is a synchronous message. + void set_sync() { + header()->flags |= SYNC_BIT; + } + bool is_sync() const { + return (header()->flags & SYNC_BIT) != 0; + } + + // Set this on a reply to a synchronous message. + void set_reply() { + header()->flags |= REPLY_BIT; + } + + bool is_reply() const { + return (header()->flags & REPLY_BIT) != 0; + } + + // Set this on a reply to a synchronous message to indicate that no receiver + // was found. + void set_reply_error() { + header()->flags |= REPLY_ERROR_BIT; + } + + bool is_reply_error() const { + return (header()->flags & REPLY_ERROR_BIT) != 0; + } + + // Normally when a receiver gets a message and they're blocked on a + // synchronous message Send, they buffer a message. Setting this flag causes + // the receiver to be unblocked and the message to be dispatched immediately. + void set_unblock(bool unblock) { + if (unblock) { + header()->flags |= UNBLOCK_BIT; + } else { + header()->flags &= ~UNBLOCK_BIT; + } + } + + bool should_unblock() const { + return (header()->flags & UNBLOCK_BIT) != 0; + } + + // Tells the receiver that the caller is pumping messages while waiting + // for the result. + bool is_caller_pumping_messages() const { + return (header()->flags & PUMPING_MSGS_BIT) != 0; + } + + uint32 type() const { + return header()->type; + } + + int32 routing_id() const { + return header()->routing; + } + + void set_routing_id(int32 new_id) { + header()->routing = new_id; + } + + uint32 flags() const { + return header()->flags; + } + + // Sets all the given header values. The message should be empty at this + // call. + void SetHeaderValues(int32 routing, uint32 type, uint32 flags); + + template<class T, class S> + static bool Dispatch(const Message* msg, T* obj, S* sender, + void (T::*func)()) { + (obj->*func)(); + return true; + } + + template<class T, class S> + static bool Dispatch(const Message* msg, T* obj, S* sender, + void (T::*func)() const) { + (obj->*func)(); + return true; + } + + template<class T, class S> + static bool Dispatch(const Message* msg, T* obj, S* sender, + void (T::*func)(const Message&)) { + (obj->*func)(*msg); + return true; + } + + template<class T, class S> + static bool Dispatch(const Message* msg, T* obj, S* sender, + void (T::*func)(const Message&) const) { + (obj->*func)(*msg); + return true; + } + + // Used for async messages with no parameters. + static void Log(std::string* name, const Message* msg, std::string* l) { + } + + // Find the end of the message data that starts at range_start. Returns NULL + // if the entire message is not found in the given data range. + static const char* FindNext(const char* range_start, const char* range_end) { + return Pickle::FindNext(sizeof(Header), range_start, range_end); + } + +#if defined(OS_POSIX) + // On POSIX, a message supports reading / writing FileDescriptor objects. + // This is used to pass a file descriptor to the peer of an IPC channel. + + // Add a descriptor to the end of the set. Returns false if the set is full. + bool WriteFileDescriptor(const base::FileDescriptor& descriptor); + + // Get a file descriptor from the message. Returns false on error. + // iter: a Pickle iterator to the current location in the message. + bool ReadFileDescriptor(PickleIterator* iter, + base::FileDescriptor* descriptor) const; + + // Returns true if there are any file descriptors in this message. + bool HasFileDescriptors() const; +#endif + +#ifdef IPC_MESSAGE_LOG_ENABLED + // Adds the outgoing time from Time::Now() at the end of the message and sets + // a bit to indicate that it's been added. + void set_sent_time(int64 time); + int64 sent_time() const; + + void set_received_time(int64 time) const; + int64 received_time() const { return received_time_; } + void set_output_params(const std::string& op) const { output_params_ = op; } + const std::string& output_params() const { return output_params_; } + // The following four functions are needed so we can log sync messages with + // delayed replies. We stick the log data from the sent message into the + // reply message, so that when it's sent and we have the output parameters + // we can log it. As such, we set a flag on the sent message to not log it. + void set_sync_log_data(LogData* data) const { log_data_ = data; } + LogData* sync_log_data() const { return log_data_; } + void set_dont_log() const { dont_log_ = true; } + bool dont_log() const { return dont_log_; } +#endif + + // Called to trace when message is sent. + void TraceMessageBegin() { + TRACE_EVENT_FLOW_BEGIN0("ipc", "IPC", header()->flags); + } + // Called to trace when message is received. + void TraceMessageEnd() { + TRACE_EVENT_FLOW_END0("ipc", "IPC", header()->flags); + } + + protected: + friend class Channel; + friend class MessageReplyDeserializer; + friend class SyncMessage; + +#pragma pack(push, 4) + struct Header : Pickle::Header { + int32 routing; // ID of the view that this message is destined for + uint32 type; // specifies the user-defined message type + uint32 flags; // specifies control flags for the message +#if defined(OS_POSIX) + uint16 num_fds; // the number of descriptors included with this message + uint16 pad; // explicitly initialize this to appease valgrind +#endif + }; +#pragma pack(pop) + + Header* header() { + return headerT<Header>(); + } + const Header* header() const { + return headerT<Header>(); + } + + void InitLoggingVariables(); + +#if defined(OS_POSIX) + // The set of file descriptors associated with this message. + scoped_refptr<FileDescriptorSet> file_descriptor_set_; + + // Ensure that a FileDescriptorSet is allocated + void EnsureFileDescriptorSet(); + + FileDescriptorSet* file_descriptor_set() { + EnsureFileDescriptorSet(); + return file_descriptor_set_.get(); + } + const FileDescriptorSet* file_descriptor_set() const { + return file_descriptor_set_.get(); + } +#endif + +#ifdef IPC_MESSAGE_LOG_ENABLED + // Used for logging. + mutable int64 received_time_; + mutable std::string output_params_; + mutable LogData* log_data_; + mutable bool dont_log_; +#endif +}; + +//------------------------------------------------------------------------------ + +} // namespace IPC + +enum SpecialRoutingIDs { + // indicates that we don't have a routing ID yet. + MSG_ROUTING_NONE = -2, + + // indicates a general message not sent to a particular tab. + MSG_ROUTING_CONTROL = kint32max, +}; + +#define IPC_REPLY_ID 0xFFFFFFF0 // Special message id for replies +#define IPC_LOGGING_ID 0xFFFFFFF1 // Special message id for logging + +#endif // IPC_IPC_MESSAGE_H_ diff --git a/chromium/ipc/ipc_message_macros.h b/chromium/ipc/ipc_message_macros.h new file mode 100644 index 00000000000..cd036802528 --- /dev/null +++ b/chromium/ipc/ipc_message_macros.h @@ -0,0 +1,1016 @@ +// 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. + +// Defining IPC Messages +// +// Your IPC messages will be defined by macros inside of an XXX_messages.h +// header file. Most of the time, the system can automatically generate all +// of messaging mechanism from these definitions, but sometimes some manual +// coding is required. In these cases, you will also have an XXX_messages.cc +// implemation file as well. +// +// The senders of your messages will include your XXX_messages.h file to +// get the full set of definitions they need to send your messages. +// +// Each XXX_messages.h file must be registered with the IPC system. This +// requires adding two things: +// - An XXXMsgStart value to the IPCMessageStart enum in ipc_message_start.h +// - An inclusion of XXX_messages.h file in a message generator .h file +// +// The XXXMsgStart value is an enumeration that ensures uniqueness for +// each different message file. Later, you will use this inside your +// XXX_messages.h file before invoking message declaration macros: +// #define IPC_MESSAGE_START XXXMsgStart +// ( ... your macro invocations go here ... ) +// +// Message Generator Files +// +// A message generator .h header file pulls in all other message-declaring +// headers for a given component. It is included by a message generator +// .cc file, which is where all the generated code will wind up. Typically, +// you will use an existing generator (e.g. common_message_generator.cc +// in /chrome/common), but there are circumstances where you may add a +// new one. +// +// In the rare cicrucmstances where you can't re-use an existing file, +// your YYY_message_generator.cc file for a component YYY would contain +// the following code: +// // Get basic type definitions. +// #define IPC_MESSAGE_IMPL +// #include "path/to/YYY_message_generator.h" +// // Generate constructors. +// #include "ipc/struct_constructor_macros.h" +// #include "path/to/YYY_message_generator.h" +// // Generate destructors. +// #include "ipc/struct_destructor_macros.h" +// #include "path/to/YYY_message_generator.h" +// // Generate param traits write methods. +// #include "ipc/param_traits_write_macros.h" +// namespace IPC { +// #include "path/to/YYY_message_generator.h" +// } // namespace IPC +// // Generate param traits read methods. +// #include "ipc/param_traits_read_macros.h" +// namespace IPC { +// #include "path/to/YYY_message_generator.h" +// } // namespace IPC +// // Generate param traits log methods. +// #include "ipc/param_traits_log_macros.h" +// namespace IPC { +// #include "path/to/YYY_message_generator.h" +// } // namespace IPC +// +// In cases where manual generation is required, in your XXX_messages.cc +// file, put the following after all the includes for param types: +// #define IPC_MESSAGE_IMPL +// #include "XXX_messages.h" +// (... implementation of traits not auto-generated ...) +// +// Multiple Inclusion +// +// The XXX_messages.h file will be multiply-included by the +// YYY_message_generator.cc file, so your XXX_messages file can't be +// guarded in the usual manner. Ideally, there will be no need for any +// inclusion guard, since the XXX_messages.h file should consist soley +// of inclusions of other headers (which are self-guarding) and IPC +// macros (which are multiply evaluating). +// +// Note that #pragma once cannot be used here; doing so would mark the whole +// file as being singly-included. Since your XXX_messages.h file is only +// partially-guarded, care must be taken to ensure that it is only included +// by other .cc files (and the YYY_message_generator.h file). Including an +// XXX_messages.h file in some other .h file may result in duplicate +// declarations and a compilation failure. +// +// Type Declarations +// +// It is generally a bad idea to have type definitions in a XXX_messages.h +// file; most likely the typedef will then be used in the message, as opposed +// to the struct iself. Later, an IPC message dispatcher wil need to call +// a function taking that type, and that function is declared in some other +// header. Thus, in order to get the type definition, the other header +// would have to include the XXX_messages.h file, violating the rule above +// about not including XXX_messages.h file in other .h files. +// +// One approach here is to move these type definitions to another (guarded) +// .h file and include this second .h in your XXX_messages.h file. This +// is still less than ideal, because the dispatched function would have to +// redeclare the typedef or include this second header. This may be +// reasonable in a few cases. +// +// Failing all of the above, then you will want to bracket the smallest +// possible section of your XXX_messages.h file containing these types +// with an include guard macro. Be aware that providing an incomplete +// class type declaration to avoid pulling in a long chain of headers is +// acceptable when your XXX_messages.h header is being included by the +// message sending caller's code, but not when the YYY_message_generator.c +// is building the messages. In addtion, due to the multiple inclusion +// restriction, these type ought to be guarded. Follow a convention like: +// #ifndef SOME_GUARD_MACRO +// #define SOME_GUARD_MACRO +// class some_class; // One incomplete class declaration +// class_some_other_class; // Another incomplete class declaration +// #endif // SOME_GUARD_MACRO +// #ifdef IPC_MESSAGE_IMPL +// #inlcude "path/to/some_class.h" // Full class declaration +// #inlcude "path/to/some_other_class.h" // Full class declaration +// #endif // IPC_MESSAGE_IMPL +// (.. IPC macros using some_class and some_other_class ...) +// +// Macro Invocations +// +// You will use IPC message macro invocations for three things: +// - New struct definitions for IPC +// - Registering existing struct and enum definitions with IPC +// - Defining the messages themselves +// +// New structs are defined with IPC_STRUCT_BEGIN(), IPC_STRUCT_MEMBER(), +// IPC_STRUCT_END() family of macros. These cause the XXX_messages.h +// to proclaim equivalent struct declarations for use by callers, as well +// as later registering the type with the message generation. Note that +// IPC_STRUCT_MEMBER() is only permitted inside matching calls to +// IPC_STRUCT_BEGIN() / IPC_STRUCT_END(). There is also an +// IPC_STRUCT_BEGIN_WITH_PARENT(), which behaves like IPC_STRUCT_BEGIN(), +// but also accomodates structs that inherit from other structs. +// +// Externally-defined structs are registered with IPC_STRUCT_TRAITS_BEGIN(), +// IPC_STRUCT_TRAITS_MEMBER(), and IPC_STRUCT_TRAITS_END() macros. These +// cause registration of the types with message generation only. +// There's also IPC_STRUCT_TRAITS_PARENT, which is used to register a parent +// class (whose own traits are already defined). Note that +// IPC_STRUCT_TRAITS_MEMBER() and IPC_STRUCT_TRAITS_PARENT are only permitted +// inside matching calls to IPC_STRUCT_TRAITS_BEGIN() / +// IPC_STRUCT_TRAITS_END(). +// +// Enum types are registered with a single IPC_ENUM_TRAITS_VALIDATE() macro. +// There is no need to enumerate each value to the IPC mechanism. Instead, +// pass an expression in terms of the parameter |value| to provide +// range-checking. For convenience, the IPC_ENUM_TRAITS() is provided which +// performs no checking, passing everything including out-of-range values. +// Its use is discouraged. The IPC_ENUM_TRAITS_MAX_VALUE() macro can be used +// for the typical case where the enum must be in the range 0..maxvalue +// inclusive. The IPC_ENUM_TRAITS_MIN_MAX_VALUE() macro can be used for the +// less typical case where the enum must be in the range minvalue..maxvalue +// inclusive. +// +// Do not place semicolons following these IPC_ macro invocations. There +// is no reason to expect that their expansion corresponds one-to-one with +// C++ statements. +// +// Once the types have been declared / registered, message definitions follow. +// "Sync" messages are just synchronous calls, the Send() call doesn't return +// until a reply comes back. To declare a sync message, use the IPC_SYNC_ +// macros. The numbers at the end show how many input/output parameters there +// are (i.e. 1_2 is 1 in, 2 out). Input parameters are first, followed by +// output parameters. The caller uses Send([route id, ], in1, &out1, &out2). +// The receiver's handler function will be +// void OnSyncMessageName(const type1& in1, type2* out1, type3* out2) +// +// A caller can also send a synchronous message, while the receiver can respond +// at a later time. This is transparent from the sender's side. The receiver +// needs to use a different handler that takes in a IPC::Message* as the output +// type, stash the message, and when it has the data it can Send the message. +// +// Use the IPC_MESSAGE_HANDLER_DELAY_REPLY macro instead of IPC_MESSAGE_HANDLER +// IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_SyncMessageName, +// OnSyncMessageName) +// +// The handler function will look like: +// void OnSyncMessageName(const type1& in1, IPC::Message* reply_msg); +// +// Receiver stashes the IPC::Message* pointer, and when it's ready, it does: +// ViewHostMsg_SyncMessageName::WriteReplyParams(reply_msg, out1, out2); +// Send(reply_msg); + +// Files that want to export their ipc messages should do +// #undef IPC_MESSAGE_EXPORT +// #define IPC_MESSAGE_EXPORT VISIBILITY_MACRO +// after including this header, but before using any of the macros below. +// (This needs to be before the include guard.) +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT + +#ifndef IPC_IPC_MESSAGE_MACROS_H_ +#define IPC_IPC_MESSAGE_MACROS_H_ + +#include "base/profiler/scoped_profile.h" +#include "ipc/ipc_message_utils.h" +#include "ipc/param_traits_macros.h" + +#if defined(IPC_MESSAGE_IMPL) +#include "ipc/ipc_message_utils_impl.h" +#endif + +// Convenience macro for defining structs without inheritence. Should not need +// to be subsequently redefined. +#define IPC_STRUCT_BEGIN(struct_name) \ + IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, IPC::NoParams) + +// Macros for defining structs. Will be subsequently redefined. +#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \ + struct struct_name; \ + IPC_STRUCT_TRAITS_BEGIN(struct_name) \ + IPC_STRUCT_TRAITS_END() \ + struct IPC_MESSAGE_EXPORT struct_name : parent { \ + struct_name(); \ + ~struct_name(); +// Optional variadic parameters specify the default value for this struct +// member. They are passed through to the constructor for |type|. +#define IPC_STRUCT_MEMBER(type, name, ...) type name; +#define IPC_STRUCT_END() }; + +// Message macros collect specific numbers of arguments and funnel them into +// the common message generation macro. These should never be redefined. +#define IPC_MESSAGE_CONTROL0(msg_class) \ + IPC_MESSAGE_DECL(EMPTY, CONTROL, msg_class, 0, 0, (), ()) + +#define IPC_MESSAGE_CONTROL1(msg_class, type1) \ + IPC_MESSAGE_DECL(ASYNC, CONTROL, msg_class, 1, 0, (type1), ()) + +#define IPC_MESSAGE_CONTROL2(msg_class, type1, type2) \ + IPC_MESSAGE_DECL(ASYNC, CONTROL, msg_class, 2, 0, (type1, type2), ()) + +#define IPC_MESSAGE_CONTROL3(msg_class, type1, type2, type3) \ + IPC_MESSAGE_DECL(ASYNC, CONTROL, msg_class, 3, 0, (type1, type2, type3), ()) + +#define IPC_MESSAGE_CONTROL4(msg_class, type1, type2, type3, type4) \ + IPC_MESSAGE_DECL(ASYNC, CONTROL, msg_class, 4, 0, (type1, type2, type3, type4), ()) + +#define IPC_MESSAGE_CONTROL5(msg_class, type1, type2, type3, type4, type5) \ + IPC_MESSAGE_DECL(ASYNC, CONTROL, msg_class, 5, 0, (type1, type2, type3, type4, type5), ()) + +#define IPC_MESSAGE_ROUTED0(msg_class) \ + IPC_MESSAGE_DECL(EMPTY, ROUTED, msg_class, 0, 0, (), ()) + +#define IPC_MESSAGE_ROUTED1(msg_class, type1) \ + IPC_MESSAGE_DECL(ASYNC, ROUTED, msg_class, 1, 0, (type1), ()) + +#define IPC_MESSAGE_ROUTED2(msg_class, type1, type2) \ + IPC_MESSAGE_DECL(ASYNC, ROUTED, msg_class, 2, 0, (type1, type2), ()) + +#define IPC_MESSAGE_ROUTED3(msg_class, type1, type2, type3) \ + IPC_MESSAGE_DECL(ASYNC, ROUTED, msg_class, 3, 0, (type1, type2, type3), ()) + +#define IPC_MESSAGE_ROUTED4(msg_class, type1, type2, type3, type4) \ + IPC_MESSAGE_DECL(ASYNC, ROUTED, msg_class, 4, 0, (type1, type2, type3, type4), ()) + +#define IPC_MESSAGE_ROUTED5(msg_class, type1, type2, type3, type4, type5) \ + IPC_MESSAGE_DECL(ASYNC, ROUTED, msg_class, 5, 0, (type1, type2, type3, type4, type5), ()) + +#define IPC_SYNC_MESSAGE_CONTROL0_0(msg_class) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 0, 0, (), ()) + +#define IPC_SYNC_MESSAGE_CONTROL0_1(msg_class, type1_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 0, 1, (), (type1_out)) + +#define IPC_SYNC_MESSAGE_CONTROL0_2(msg_class, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 0, 2, (), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_CONTROL0_3(msg_class, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 0, 3, (), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_CONTROL0_4(msg_class, type1_out, type2_out, type3_out, type4_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 0, 4, (), (type1_out, type2_out, type3_out, type4_out)) + +#define IPC_SYNC_MESSAGE_CONTROL1_0(msg_class, type1_in) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 1, 0, (type1_in), ()) + +#define IPC_SYNC_MESSAGE_CONTROL1_1(msg_class, type1_in, type1_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 1, 1, (type1_in), (type1_out)) + +#define IPC_SYNC_MESSAGE_CONTROL1_2(msg_class, type1_in, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 1, 2, (type1_in), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_CONTROL1_3(msg_class, type1_in, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 1, 3, (type1_in), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_CONTROL1_4(msg_class, type1_in, type1_out, type2_out, type3_out, type4_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 1, 4, (type1_in), (type1_out, type2_out, type3_out, type4_out)) + +#define IPC_SYNC_MESSAGE_CONTROL2_0(msg_class, type1_in, type2_in) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 2, 0, (type1_in, type2_in), ()) + +#define IPC_SYNC_MESSAGE_CONTROL2_1(msg_class, type1_in, type2_in, type1_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 2, 1, (type1_in, type2_in), (type1_out)) + +#define IPC_SYNC_MESSAGE_CONTROL2_2(msg_class, type1_in, type2_in, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 2, 2, (type1_in, type2_in), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_CONTROL2_3(msg_class, type1_in, type2_in, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 2, 3, (type1_in, type2_in), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_CONTROL2_4(msg_class, type1_in, type2_in, type1_out, type2_out, type3_out, type4_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 2, 4, (type1_in, type2_in), (type1_out, type2_out, type3_out, type4_out)) + +#define IPC_SYNC_MESSAGE_CONTROL3_0(msg_class, type1_in, type2_in, type3_in) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 3, 0, (type1_in, type2_in, type3_in), ()) + +#define IPC_SYNC_MESSAGE_CONTROL3_1(msg_class, type1_in, type2_in, type3_in, type1_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 3, 1, (type1_in, type2_in, type3_in), (type1_out)) + +#define IPC_SYNC_MESSAGE_CONTROL3_2(msg_class, type1_in, type2_in, type3_in, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 3, 2, (type1_in, type2_in, type3_in), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_CONTROL3_3(msg_class, type1_in, type2_in, type3_in, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 3, 3, (type1_in, type2_in, type3_in), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_CONTROL3_4(msg_class, type1_in, type2_in, type3_in, type1_out, type2_out, type3_out, type4_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 3, 4, (type1_in, type2_in, type3_in), (type1_out, type2_out, type3_out, type4_out)) + +#define IPC_SYNC_MESSAGE_CONTROL4_0(msg_class, type1_in, type2_in, type3_in, type4_in) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 4, 0, (type1_in, type2_in, type3_in, type4_in), ()) + +#define IPC_SYNC_MESSAGE_CONTROL4_1(msg_class, type1_in, type2_in, type3_in, type4_in, type1_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 4, 1, (type1_in, type2_in, type3_in, type4_in), (type1_out)) + +#define IPC_SYNC_MESSAGE_CONTROL4_2(msg_class, type1_in, type2_in, type3_in, type4_in, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 4, 2, (type1_in, type2_in, type3_in, type4_in), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_CONTROL4_3(msg_class, type1_in, type2_in, type3_in, type4_in, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 4, 3, (type1_in, type2_in, type3_in, type4_in), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_CONTROL4_4(msg_class, type1_in, type2_in, type3_in, type4_in, type1_out, type2_out, type3_out, type4_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 4, 4, (type1_in, type2_in, type3_in, type4_in), (type1_out, type2_out, type3_out, type4_out)) + +#define IPC_SYNC_MESSAGE_CONTROL5_0(msg_class, type1_in, type2_in, type3_in, type4_in, type5_in) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 5, 0, (type1_in, type2_in, type3_in, type4_in, type5_in), ()) + +#define IPC_SYNC_MESSAGE_CONTROL5_1(msg_class, type1_in, type2_in, type3_in, type4_in, type5_in, type1_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 5, 1, (type1_in, type2_in, type3_in, type4_in, type5_in), (type1_out)) + +#define IPC_SYNC_MESSAGE_CONTROL5_2(msg_class, type1_in, type2_in, type3_in, type4_in, type5_in, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 5, 2, (type1_in, type2_in, type3_in, type4_in, type5_in), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_CONTROL5_3(msg_class, type1_in, type2_in, type3_in, type4_in, type5_in, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, CONTROL, msg_class, 5, 3, (type1_in, type2_in, type3_in, type4_in, type5_in), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_ROUTED0_0(msg_class) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 0, 0, (), ()) + +#define IPC_SYNC_MESSAGE_ROUTED0_1(msg_class, type1_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 0, 1, (), (type1_out)) + +#define IPC_SYNC_MESSAGE_ROUTED0_2(msg_class, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 0, 2, (), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_ROUTED0_3(msg_class, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 0, 3, (), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_ROUTED0_4(msg_class, type1_out, type2_out, type3_out, type4_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 0, 4, (), (type1_out, type2_out, type3_out, type4_out)) + +#define IPC_SYNC_MESSAGE_ROUTED1_0(msg_class, type1_in) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 1, 0, (type1_in), ()) + +#define IPC_SYNC_MESSAGE_ROUTED1_1(msg_class, type1_in, type1_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 1, 1, (type1_in), (type1_out)) + +#define IPC_SYNC_MESSAGE_ROUTED1_2(msg_class, type1_in, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 1, 2, (type1_in), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_ROUTED1_3(msg_class, type1_in, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 1, 3, (type1_in), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_ROUTED1_4(msg_class, type1_in, type1_out, type2_out, type3_out, type4_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 1, 4, (type1_in), (type1_out, type2_out, type3_out, type4_out)) + +#define IPC_SYNC_MESSAGE_ROUTED2_0(msg_class, type1_in, type2_in) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 2, 0, (type1_in, type2_in), ()) + +#define IPC_SYNC_MESSAGE_ROUTED2_1(msg_class, type1_in, type2_in, type1_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 2, 1, (type1_in, type2_in), (type1_out)) + +#define IPC_SYNC_MESSAGE_ROUTED2_2(msg_class, type1_in, type2_in, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 2, 2, (type1_in, type2_in), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_ROUTED2_3(msg_class, type1_in, type2_in, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 2, 3, (type1_in, type2_in), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_ROUTED2_4(msg_class, type1_in, type2_in, type1_out, type2_out, type3_out, type4_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 2, 4, (type1_in, type2_in), (type1_out, type2_out, type3_out, type4_out)) + +#define IPC_SYNC_MESSAGE_ROUTED3_0(msg_class, type1_in, type2_in, type3_in) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 3, 0, (type1_in, type2_in, type3_in), ()) + +#define IPC_SYNC_MESSAGE_ROUTED3_1(msg_class, type1_in, type2_in, type3_in, type1_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 3, 1, (type1_in, type2_in, type3_in), (type1_out)) + +#define IPC_SYNC_MESSAGE_ROUTED3_2(msg_class, type1_in, type2_in, type3_in, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 3, 2, (type1_in, type2_in, type3_in), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_ROUTED3_3(msg_class, type1_in, type2_in, type3_in, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 3, 3, (type1_in, type2_in, type3_in), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_ROUTED3_4(msg_class, type1_in, type2_in, type3_in, type1_out, type2_out, type3_out, type4_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 3, 4, (type1_in, type2_in, type3_in), (type1_out, type2_out, type3_out, type4_out)) + +#define IPC_SYNC_MESSAGE_ROUTED4_0(msg_class, type1_in, type2_in, type3_in, type4_in) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 4, 0, (type1_in, type2_in, type3_in, type4_in), ()) + +#define IPC_SYNC_MESSAGE_ROUTED4_1(msg_class, type1_in, type2_in, type3_in, type4_in, type1_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 4, 1, (type1_in, type2_in, type3_in, type4_in), (type1_out)) + +#define IPC_SYNC_MESSAGE_ROUTED4_2(msg_class, type1_in, type2_in, type3_in, type4_in, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 4, 2, (type1_in, type2_in, type3_in, type4_in), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_ROUTED4_3(msg_class, type1_in, type2_in, type3_in, type4_in, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 4, 3, (type1_in, type2_in, type3_in, type4_in), (type1_out, type2_out, type3_out)) + +#define IPC_SYNC_MESSAGE_ROUTED4_4(msg_class, type1_in, type2_in, type3_in, type4_in, type1_out, type2_out, type3_out, type4_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 4, 4, (type1_in, type2_in, type3_in, type4_in), (type1_out, type2_out, type3_out, type4_out)) + +#define IPC_SYNC_MESSAGE_ROUTED5_0(msg_class, type1_in, type2_in, type3_in, type4_in, type5_in) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 5, 0, (type1_in, type2_in, type3_in, type4_in, type5_in), ()) + +#define IPC_SYNC_MESSAGE_ROUTED5_1(msg_class, type1_in, type2_in, type3_in, type4_in, type5_in, type1_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 5, 1, (type1_in, type2_in, type3_in, type4_in, type5_in), (type1_out)) + +#define IPC_SYNC_MESSAGE_ROUTED5_2(msg_class, type1_in, type2_in, type3_in, type4_in, type5_in, type1_out, type2_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 5, 2, (type1_in, type2_in, type3_in, type4_in, type5_in), (type1_out, type2_out)) + +#define IPC_SYNC_MESSAGE_ROUTED5_3(msg_class, type1_in, type2_in, type3_in, type4_in, type5_in, type1_out, type2_out, type3_out) \ + IPC_MESSAGE_DECL(SYNC, ROUTED, msg_class, 5, 3, (type1_in, type2_in, type3_in, type4_in, type5_in), (type1_out, type2_out, type3_out)) + +// The following macros define the common set of methods provided by ASYNC +// message classes. +#define IPC_ASYNC_MESSAGE_METHODS_GENERIC \ + template<class T, class S, class Method> \ + static bool Dispatch(const Message* msg, T* obj, S* sender, Method func) { \ + Schema::Param p; \ + if (Read(msg, &p)) { \ + DispatchToMethod(obj, func, p); \ + return true; \ + } \ + return false; \ + } +#define IPC_ASYNC_MESSAGE_METHODS_1 \ + IPC_ASYNC_MESSAGE_METHODS_GENERIC \ + template<class T, class S, typename TA> \ + static bool Dispatch(const Message* msg, T* obj, S* sender, \ + void (T::*func)(const Message&, TA)) { \ + Schema::Param p; \ + if (Read(msg, &p)) { \ + (obj->*func)(*msg, p.a); \ + return true; \ + } \ + return false; \ + } +#define IPC_ASYNC_MESSAGE_METHODS_2 \ + IPC_ASYNC_MESSAGE_METHODS_GENERIC \ + template<class T, class S, typename TA, typename TB> \ + static bool Dispatch(const Message* msg, T* obj, S* sender, \ + void (T::*func)(const Message&, TA, TB)) { \ + Schema::Param p; \ + if (Read(msg, &p)) { \ + (obj->*func)(*msg, p.a, p.b); \ + return true; \ + } \ + return false; \ + } \ + template<typename TA, typename TB> \ + static bool Read(const IPC::Message* msg, TA* a, TB* b) { \ + Schema::Param p; \ + if (!Read(msg, &p)) \ + return false; \ + *a = p.a; \ + *b = p.b; \ + return true; \ + } +#define IPC_ASYNC_MESSAGE_METHODS_3 \ + IPC_ASYNC_MESSAGE_METHODS_GENERIC \ + template<class T, class S, typename TA, typename TB, typename TC> \ + static bool Dispatch(const Message* msg, T* obj, S* sender, \ + void (T::*func)(const Message&, TA, TB, TC)) { \ + Schema::Param p; \ + if (Read(msg, &p)) { \ + (obj->*func)(*msg, p.a, p.b, p.c); \ + return true; \ + } \ + return false; \ + } \ + template<typename TA, typename TB, typename TC> \ + static bool Read(const IPC::Message* msg, TA* a, TB* b, TC* c) { \ + Schema::Param p; \ + if (!Read(msg, &p)) \ + return false; \ + *a = p.a; \ + *b = p.b; \ + *c = p.c; \ + return true; \ + } +#define IPC_ASYNC_MESSAGE_METHODS_4 \ + IPC_ASYNC_MESSAGE_METHODS_GENERIC \ + template<class T, class S, typename TA, typename TB, typename TC, \ + typename TD> \ + static bool Dispatch(const Message* msg, T* obj, S* sender, \ + void (T::*func)(const Message&, TA, TB, TC, TD)) { \ + Schema::Param p; \ + if (Read(msg, &p)) { \ + (obj->*func)(*msg, p.a, p.b, p.c, p.d); \ + return true; \ + } \ + return false; \ + } \ + template<typename TA, typename TB, typename TC, typename TD> \ + static bool Read(const IPC::Message* msg, TA* a, TB* b, TC* c, TD* d) { \ + Schema::Param p; \ + if (!Read(msg, &p)) \ + return false; \ + *a = p.a; \ + *b = p.b; \ + *c = p.c; \ + *d = p.d; \ + return true; \ + } +#define IPC_ASYNC_MESSAGE_METHODS_5 \ + IPC_ASYNC_MESSAGE_METHODS_GENERIC \ + template<class T, class S, typename TA, typename TB, typename TC, \ + typename TD, typename TE> \ + static bool Dispatch(const Message* msg, T* obj, S* sender, \ + void (T::*func)(const Message&, TA, TB, TC, TD, TE)) { \ + Schema::Param p; \ + if (Read(msg, &p)) { \ + (obj->*func)(*msg, p.a, p.b, p.c, p.d, p.e); \ + return true; \ + } \ + return false; \ + } \ + template<typename TA, typename TB, typename TC, typename TD, typename TE> \ + static bool Read(const IPC::Message* msg, TA* a, TB* b, TC* c, TD* d, \ + TE* e) { \ + Schema::Param p; \ + if (!Read(msg, &p)) \ + return false; \ + *a = p.a; \ + *b = p.b; \ + *c = p.c; \ + *d = p.d; \ + *e = p.e; \ + return true; \ + } + +// The following macros define the common set of methods provided by SYNC +// message classes. +#define IPC_SYNC_MESSAGE_METHODS_GENERIC \ + template<class T, class S, class Method> \ + static bool Dispatch(const Message* msg, T* obj, S* sender, Method func) { \ + Schema::SendParam send_params; \ + bool ok = ReadSendParam(msg, &send_params); \ + return Schema::DispatchWithSendParams(ok, send_params, msg, obj, sender, \ + func); \ + } \ + template<class T, class Method> \ + static bool DispatchDelayReply(const Message* msg, T* obj, Method func) { \ + Schema::SendParam send_params; \ + bool ok = ReadSendParam(msg, &send_params); \ + return Schema::DispatchDelayReplyWithSendParams(ok, send_params, msg, \ + obj, func); \ + } +#define IPC_SYNC_MESSAGE_METHODS_0 \ + IPC_SYNC_MESSAGE_METHODS_GENERIC +#define IPC_SYNC_MESSAGE_METHODS_1 \ + IPC_SYNC_MESSAGE_METHODS_GENERIC \ + template<typename TA> \ + static void WriteReplyParams(Message* reply, TA a) { \ + Schema::WriteReplyParams(reply, a); \ + } +#define IPC_SYNC_MESSAGE_METHODS_2 \ + IPC_SYNC_MESSAGE_METHODS_GENERIC \ + template<typename TA, typename TB> \ + static void WriteReplyParams(Message* reply, TA a, TB b) { \ + Schema::WriteReplyParams(reply, a, b); \ + } +#define IPC_SYNC_MESSAGE_METHODS_3 \ + IPC_SYNC_MESSAGE_METHODS_GENERIC \ + template<typename TA, typename TB, typename TC> \ + static void WriteReplyParams(Message* reply, TA a, TB b, TC c) { \ + Schema::WriteReplyParams(reply, a, b, c); \ + } +#define IPC_SYNC_MESSAGE_METHODS_4 \ + IPC_SYNC_MESSAGE_METHODS_GENERIC \ + template<typename TA, typename TB, typename TC, typename TD> \ + static void WriteReplyParams(Message* reply, TA a, TB b, TC c, TD d) { \ + Schema::WriteReplyParams(reply, a, b, c, d); \ + } +#define IPC_SYNC_MESSAGE_METHODS_5 \ + IPC_SYNC_MESSAGE_METHODS_GENERIC \ + template<typename TA, typename TB, typename TC, typename TD, typename TE> \ + static void WriteReplyParams(Message* reply, TA a, TB b, TC c, TD d, TE e) { \ + Schema::WriteReplyParams(reply, a, b, c, d, e); \ + } + +// Common message macro which dispatches into one of the 6 (sync x kind) +// routines. There is a way that these 6 cases can be lumped together, +// but the macros get very complicated in that case. +// Note: intended be redefined to generate other information. +#define IPC_MESSAGE_DECL(sync, kind, msg_class, \ + in_cnt, out_cnt, in_list, out_list) \ + IPC_##sync##_##kind##_DECL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + IPC_MESSAGE_EXTRA(sync, kind, msg_class, in_cnt, out_cnt, in_list, out_list) + +#define IPC_EMPTY_CONTROL_DECL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + class IPC_MESSAGE_EXPORT msg_class : public IPC::Message { \ + public: \ + typedef IPC::Message Schema; \ + enum { ID = IPC_MESSAGE_ID() }; \ + msg_class() : IPC::Message(MSG_ROUTING_CONTROL, ID, PRIORITY_NORMAL) {} \ + static void Log(std::string* name, const Message* msg, std::string* l); \ + }; + +#define IPC_EMPTY_ROUTED_DECL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + class IPC_MESSAGE_EXPORT msg_class : public IPC::Message { \ + public: \ + typedef IPC::Message Schema; \ + enum { ID = IPC_MESSAGE_ID() }; \ + msg_class(int32 routing_id) \ + : IPC::Message(routing_id, ID, PRIORITY_NORMAL) {} \ + static void Log(std::string* name, const Message* msg, std::string* l); \ + }; + +#define IPC_ASYNC_CONTROL_DECL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + class IPC_MESSAGE_EXPORT msg_class : public IPC::Message { \ + public: \ + typedef IPC::MessageSchema<IPC_TUPLE_IN_##in_cnt in_list> Schema; \ + typedef Schema::Param Param; \ + enum { ID = IPC_MESSAGE_ID() }; \ + msg_class(IPC_TYPE_IN_##in_cnt in_list); \ + virtual ~msg_class(); \ + static bool Read(const Message* msg, Schema::Param* p); \ + static void Log(std::string* name, const Message* msg, std::string* l); \ + IPC_ASYNC_MESSAGE_METHODS_##in_cnt \ + }; + +#define IPC_ASYNC_ROUTED_DECL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + class IPC_MESSAGE_EXPORT msg_class : public IPC::Message { \ + public: \ + typedef IPC::MessageSchema<IPC_TUPLE_IN_##in_cnt in_list> Schema; \ + typedef Schema::Param Param; \ + enum { ID = IPC_MESSAGE_ID() }; \ + msg_class(int32 routing_id IPC_COMMA_##in_cnt \ + IPC_TYPE_IN_##in_cnt in_list); \ + virtual ~msg_class(); \ + static bool Read(const Message* msg, Schema::Param* p); \ + static void Log(std::string* name, const Message* msg, std::string* l); \ + IPC_ASYNC_MESSAGE_METHODS_##in_cnt \ + }; + +#define IPC_SYNC_CONTROL_DECL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + class IPC_MESSAGE_EXPORT msg_class : public IPC::SyncMessage { \ + public: \ + typedef IPC::SyncMessageSchema<IPC_TUPLE_IN_##in_cnt in_list, \ + IPC_TUPLE_OUT_##out_cnt out_list> Schema; \ + typedef Schema::ReplyParam ReplyParam; \ + typedef Schema::SendParam SendParam; \ + enum { ID = IPC_MESSAGE_ID() }; \ + msg_class(IPC_TYPE_IN_##in_cnt in_list \ + IPC_COMMA_AND_##in_cnt(IPC_COMMA_##out_cnt) \ + IPC_TYPE_OUT_##out_cnt out_list); \ + virtual ~msg_class(); \ + static bool ReadSendParam(const Message* msg, Schema::SendParam* p); \ + static bool ReadReplyParam( \ + const Message* msg, \ + TupleTypes<ReplyParam>::ValueTuple* p); \ + static void Log(std::string* name, const Message* msg, std::string* l); \ + IPC_SYNC_MESSAGE_METHODS_##out_cnt \ + }; + +#define IPC_SYNC_ROUTED_DECL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + class IPC_MESSAGE_EXPORT msg_class : public IPC::SyncMessage { \ + public: \ + typedef IPC::SyncMessageSchema<IPC_TUPLE_IN_##in_cnt in_list, \ + IPC_TUPLE_OUT_##out_cnt out_list> Schema; \ + typedef Schema::ReplyParam ReplyParam; \ + typedef Schema::SendParam SendParam; \ + enum { ID = IPC_MESSAGE_ID() }; \ + msg_class(int32 routing_id \ + IPC_COMMA_OR_##in_cnt(IPC_COMMA_##out_cnt) \ + IPC_TYPE_IN_##in_cnt in_list \ + IPC_COMMA_AND_##in_cnt(IPC_COMMA_##out_cnt) \ + IPC_TYPE_OUT_##out_cnt out_list); \ + virtual ~msg_class(); \ + static bool ReadSendParam(const Message* msg, Schema::SendParam* p); \ + static bool ReadReplyParam( \ + const Message* msg, \ + TupleTypes<ReplyParam>::ValueTuple* p); \ + static void Log(std::string* name, const Message* msg, std::string* l); \ + IPC_SYNC_MESSAGE_METHODS_##out_cnt \ + }; + +#if defined(IPC_MESSAGE_IMPL) + +// "Implementation" inclusion produces constructors, destructors, and +// logging functions, except for the no-arg special cases, where the +// implementation occurs in the declaration, and there is no special +// logging function. +#define IPC_MESSAGE_EXTRA(sync, kind, msg_class, \ + in_cnt, out_cnt, in_list, out_list) \ + IPC_##sync##_##kind##_IMPL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + IPC_##sync##_MESSAGE_LOG(msg_class) + +#define IPC_EMPTY_CONTROL_IMPL(msg_class, in_cnt, out_cnt, in_list, out_list) +#define IPC_EMPTY_ROUTED_IMPL(msg_class, in_cnt, out_cnt, in_list, out_list) + +#define IPC_ASYNC_CONTROL_IMPL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + msg_class::msg_class(IPC_TYPE_IN_##in_cnt in_list) : \ + IPC::Message(MSG_ROUTING_CONTROL, ID, PRIORITY_NORMAL) { \ + Schema::Write(this, IPC_NAME_IN_##in_cnt in_list); \ + } \ + msg_class::~msg_class() {} \ + bool msg_class::Read(const Message* msg, Schema::Param* p) { \ + return Schema::Read(msg, p); \ + } + +#define IPC_ASYNC_ROUTED_IMPL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + msg_class::msg_class(int32 routing_id IPC_COMMA_##in_cnt \ + IPC_TYPE_IN_##in_cnt in_list) : \ + IPC::Message(routing_id, ID, PRIORITY_NORMAL) { \ + Schema::Write(this, IPC_NAME_IN_##in_cnt in_list); \ + } \ + msg_class::~msg_class() {} \ + bool msg_class::Read(const Message* msg, Schema::Param* p) { \ + return Schema::Read(msg, p); \ + } + +#define IPC_SYNC_CONTROL_IMPL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + msg_class::msg_class(IPC_TYPE_IN_##in_cnt in_list \ + IPC_COMMA_AND_##in_cnt(IPC_COMMA_##out_cnt) \ + IPC_TYPE_OUT_##out_cnt out_list) : \ + IPC::SyncMessage(MSG_ROUTING_CONTROL, ID, PRIORITY_NORMAL, \ + new IPC::ParamDeserializer<Schema::ReplyParam>( \ + IPC_NAME_OUT_##out_cnt out_list)) { \ + Schema::Write(this, IPC_NAME_IN_##in_cnt in_list); \ + } \ + msg_class::~msg_class() {} \ + bool msg_class::ReadSendParam(const Message* msg, Schema::SendParam* p) { \ + return Schema::ReadSendParam(msg, p); \ + } \ + bool msg_class::ReadReplyParam(const Message* msg, \ + TupleTypes<ReplyParam>::ValueTuple* p) { \ + return Schema::ReadReplyParam(msg, p); \ + } + +#define IPC_SYNC_ROUTED_IMPL(msg_class, in_cnt, out_cnt, in_list, out_list) \ + msg_class::msg_class(int32 routing_id \ + IPC_COMMA_OR_##in_cnt(IPC_COMMA_##out_cnt) \ + IPC_TYPE_IN_##in_cnt in_list \ + IPC_COMMA_AND_##in_cnt(IPC_COMMA_##out_cnt) \ + IPC_TYPE_OUT_##out_cnt out_list) : \ + IPC::SyncMessage(routing_id, ID, PRIORITY_NORMAL, \ + new IPC::ParamDeserializer<Schema::ReplyParam>( \ + IPC_NAME_OUT_##out_cnt out_list)) { \ + Schema::Write(this, IPC_NAME_IN_##in_cnt in_list); \ + } \ + msg_class::~msg_class() {} \ + bool msg_class::ReadSendParam(const Message* msg, Schema::SendParam* p) { \ + return Schema::ReadSendParam(msg, p); \ + } \ + bool msg_class::ReadReplyParam(const Message* msg, \ + TupleTypes<ReplyParam>::ValueTuple* p) { \ + return Schema::ReadReplyParam(msg, p); \ + } + +#define IPC_EMPTY_MESSAGE_LOG(msg_class) \ + void msg_class::Log(std::string* name, \ + const Message* msg, \ + std::string* l) { \ + if (name) \ + *name = #msg_class; \ + } + +#define IPC_ASYNC_MESSAGE_LOG(msg_class) \ + void msg_class::Log(std::string* name, \ + const Message* msg, \ + std::string* l) { \ + if (name) \ + *name = #msg_class; \ + if (!msg || !l) \ + return; \ + Schema::Param p; \ + if (Schema::Read(msg, &p)) \ + IPC::LogParam(p, l); \ + } + +#define IPC_SYNC_MESSAGE_LOG(msg_class) \ + void msg_class::Log(std::string* name, \ + const Message* msg, \ + std::string* l) { \ + if (name) \ + *name = #msg_class; \ + if (!msg || !l) \ + return; \ + if (msg->is_sync()) { \ + TupleTypes<Schema::SendParam>::ValueTuple p; \ + if (Schema::ReadSendParam(msg, &p)) \ + IPC::LogParam(p, l); \ + AddOutputParamsToLog(msg, l); \ + } else { \ + TupleTypes<Schema::ReplyParam>::ValueTuple p; \ + if (Schema::ReadReplyParam(msg, &p)) \ + IPC::LogParam(p, l); \ + } \ + } + +#elif defined(IPC_MESSAGE_MACROS_LOG_ENABLED) + +#ifndef IPC_LOG_TABLE_ADD_ENTRY +#error You need to define IPC_LOG_TABLE_ADD_ENTRY(msg_id, logger) +#endif + +// "Log table" inclusion produces extra logging registration code. +#define IPC_MESSAGE_EXTRA(sync, kind, msg_class, \ + in_cnt, out_cnt, in_list, out_list) \ + class LoggerRegisterHelper##msg_class { \ + public: \ + LoggerRegisterHelper##msg_class() { \ + const uint32 msg_id = static_cast<uint32>(msg_class::ID); \ + IPC_LOG_TABLE_ADD_ENTRY(msg_id, msg_class::Log); \ + } \ + }; \ + LoggerRegisterHelper##msg_class g_LoggerRegisterHelper##msg_class; + +#else + +// Normal inclusion produces nothing extra. +#define IPC_MESSAGE_EXTRA(sync, kind, msg_class, \ + in_cnt, out_cnt, in_list, out_list) + +#endif // defined(IPC_MESSAGE_IMPL) + +// Handle variable sized argument lists. These are usually invoked by token +// pasting against the argument counts. +#define IPC_TYPE_IN_0() +#define IPC_TYPE_IN_1(t1) const t1& arg1 +#define IPC_TYPE_IN_2(t1, t2) const t1& arg1, const t2& arg2 +#define IPC_TYPE_IN_3(t1, t2, t3) const t1& arg1, const t2& arg2, const t3& arg3 +#define IPC_TYPE_IN_4(t1, t2, t3, t4) const t1& arg1, const t2& arg2, const t3& arg3, const t4& arg4 +#define IPC_TYPE_IN_5(t1, t2, t3, t4, t5) const t1& arg1, const t2& arg2, const t3& arg3, const t4& arg4, const t5& arg5 + +#define IPC_TYPE_OUT_0() +#define IPC_TYPE_OUT_1(t1) t1* arg6 +#define IPC_TYPE_OUT_2(t1, t2) t1* arg6, t2* arg7 +#define IPC_TYPE_OUT_3(t1, t2, t3) t1* arg6, t2* arg7, t3* arg8 +#define IPC_TYPE_OUT_4(t1, t2, t3, t4) t1* arg6, t2* arg7, t3* arg8, t4* arg9 + +#define IPC_TUPLE_IN_0() Tuple0 +#define IPC_TUPLE_IN_1(t1) Tuple1<t1> +#define IPC_TUPLE_IN_2(t1, t2) Tuple2<t1, t2> +#define IPC_TUPLE_IN_3(t1, t2, t3) Tuple3<t1, t2, t3> +#define IPC_TUPLE_IN_4(t1, t2, t3, t4) Tuple4<t1, t2, t3, t4> +#define IPC_TUPLE_IN_5(t1, t2, t3, t4, t5) Tuple5<t1, t2, t3, t4, t5> + +#define IPC_TUPLE_OUT_0() Tuple0 +#define IPC_TUPLE_OUT_1(t1) Tuple1<t1&> +#define IPC_TUPLE_OUT_2(t1, t2) Tuple2<t1&, t2&> +#define IPC_TUPLE_OUT_3(t1, t2, t3) Tuple3<t1&, t2&, t3&> +#define IPC_TUPLE_OUT_4(t1, t2, t3, t4) Tuple4<t1&, t2&, t3&, t4&> + +#define IPC_NAME_IN_0() MakeTuple() +#define IPC_NAME_IN_1(t1) MakeRefTuple(arg1) +#define IPC_NAME_IN_2(t1, t2) MakeRefTuple(arg1, arg2) +#define IPC_NAME_IN_3(t1, t2, t3) MakeRefTuple(arg1, arg2, arg3) +#define IPC_NAME_IN_4(t1, t2, t3, t4) MakeRefTuple(arg1, arg2, arg3, arg4) +#define IPC_NAME_IN_5(t1, t2, t3, t4, t5) MakeRefTuple(arg1, arg2, arg3, arg4, arg5) + +#define IPC_NAME_OUT_0() MakeTuple() +#define IPC_NAME_OUT_1(t1) MakeRefTuple(*arg6) +#define IPC_NAME_OUT_2(t1, t2) MakeRefTuple(*arg6, *arg7) +#define IPC_NAME_OUT_3(t1, t2, t3) MakeRefTuple(*arg6, *arg7, *arg8) +#define IPC_NAME_OUT_4(t1, t2, t3, t4) MakeRefTuple(*arg6, *arg7, *arg8, *arg9) + +// There are places where the syntax requires a comma if there are input args, +// if there are input args and output args, or if there are input args or +// output args. These macros allow generation of the comma as needed; invoke +// by token pasting against the argument counts. +#define IPC_COMMA_0 +#define IPC_COMMA_1 , +#define IPC_COMMA_2 , +#define IPC_COMMA_3 , +#define IPC_COMMA_4 , +#define IPC_COMMA_5 , + +#define IPC_COMMA_AND_0(x) +#define IPC_COMMA_AND_1(x) x +#define IPC_COMMA_AND_2(x) x +#define IPC_COMMA_AND_3(x) x +#define IPC_COMMA_AND_4(x) x +#define IPC_COMMA_AND_5(x) x + +#define IPC_COMMA_OR_0(x) x +#define IPC_COMMA_OR_1(x) , +#define IPC_COMMA_OR_2(x) , +#define IPC_COMMA_OR_3(x) , +#define IPC_COMMA_OR_4(x) , +#define IPC_COMMA_OR_5(x) , + +// Message IDs +// Note: we currently use __LINE__ to give unique IDs to messages within +// a file. They're globally unique since each file defines its own +// IPC_MESSAGE_START. +#define IPC_MESSAGE_ID() ((IPC_MESSAGE_START << 16) + __LINE__) +#define IPC_MESSAGE_ID_CLASS(id) ((id) >> 16) +#define IPC_MESSAGE_ID_LINE(id) ((id) & 0xffff) + +// Message crackers and handlers. +// Prefer to use the IPC_BEGIN_MESSAGE_MAP_EX to the older macros since they +// allow you to detect when a message could not be de-serialized. Usage: +// +// bool MyClass::OnMessageReceived(const IPC::Message& msg) { +// bool handled = true; +// bool msg_is_good = false; +// IPC_BEGIN_MESSAGE_MAP_EX(MyClass, msg, msg_is_good) +// IPC_MESSAGE_HANDLER(MsgClassOne, OnMsgClassOne) +// ...more handlers here ... +// IPC_MESSAGE_HANDLER(MsgClassTen, OnMsgClassTen) +// IPC_MESSAGE_UNHANDLED(handled = false) +// IPC_END_MESSAGE_MAP_EX() +// if (!msg_is_good) { +// // Signal error here or terminate offending process. +// } +// return handled; +// } + + +#define IPC_BEGIN_MESSAGE_MAP_EX(class_name, msg, msg_is_ok) \ + { \ + typedef class_name _IpcMessageHandlerClass; \ + const IPC::Message& ipc_message__ = msg; \ + bool& msg_is_ok__ = msg_is_ok; \ + switch (ipc_message__.type()) { \ + +#define IPC_BEGIN_MESSAGE_MAP(class_name, msg) \ + { \ + typedef class_name _IpcMessageHandlerClass; \ + const IPC::Message& ipc_message__ = msg; \ + bool msg_is_ok__ = true; \ + switch (ipc_message__.type()) { \ + +#define IPC_MESSAGE_FORWARD(msg_class, obj, member_func) \ + case msg_class::ID: { \ + TRACK_RUN_IN_IPC_HANDLER(member_func); \ + msg_is_ok__ = msg_class::Dispatch(&ipc_message__, obj, this, \ + &member_func); \ + } \ + break; + +#define IPC_MESSAGE_HANDLER(msg_class, member_func) \ + IPC_MESSAGE_FORWARD(msg_class, this, _IpcMessageHandlerClass::member_func) + +#define IPC_MESSAGE_FORWARD_DELAY_REPLY(msg_class, obj, member_func) \ + case msg_class::ID: { \ + TRACK_RUN_IN_IPC_HANDLER(member_func); \ + msg_is_ok__ = msg_class::DispatchDelayReply(&ipc_message__, obj, \ + &member_func); \ + } \ + break; + +#define IPC_MESSAGE_HANDLER_DELAY_REPLY(msg_class, member_func) \ + IPC_MESSAGE_FORWARD_DELAY_REPLY(msg_class, this, \ + _IpcMessageHandlerClass::member_func) + +// TODO(jar): fix chrome frame to always supply |code| argument. +#define IPC_MESSAGE_HANDLER_GENERIC(msg_class, code) \ + case msg_class::ID: { \ + /* TRACK_RUN_IN_IPC_HANDLER(code); TODO(jar) */ \ + code; \ + } \ + break; + +#define IPC_REPLY_HANDLER(func) \ + case IPC_REPLY_ID: { \ + TRACK_RUN_IN_IPC_HANDLER(func); \ + func(ipc_message__); \ + } \ + break; + + +#define IPC_MESSAGE_UNHANDLED(code) \ + default: { \ + code; \ + } \ + break; + +#define IPC_MESSAGE_UNHANDLED_ERROR() \ + IPC_MESSAGE_UNHANDLED(NOTREACHED() << \ + "Invalid message with type = " << \ + ipc_message__.type()) + +#define IPC_END_MESSAGE_MAP() \ + DCHECK(msg_is_ok__); \ + } \ +} + +#define IPC_END_MESSAGE_MAP_EX() \ + } \ +} + +// This corresponds to an enum value from IPCMessageStart. +#define IPC_MESSAGE_CLASS(message) \ + IPC_MESSAGE_ID_CLASS(message.type()) + +#endif // IPC_IPC_MESSAGE_MACROS_H_ + +// Clean up IPC_MESSAGE_START in this unguarded section so that the +// XXX_messages.h files need not do so themselves. This makes the +// XXX_messages.h files easier to write. +#undef IPC_MESSAGE_START diff --git a/chromium/ipc/ipc_message_null_macros.h b/chromium/ipc/ipc_message_null_macros.h new file mode 100644 index 00000000000..5a1ff4f4fa7 --- /dev/null +++ b/chromium/ipc/ipc_message_null_macros.h @@ -0,0 +1,29 @@ +// 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. + +// No include guard, may be included multiple times. + +// NULL out all the macros that need NULLing, so that multiple includes of +// the XXXX_messages_internal.h files will not generate noise. +#undef IPC_STRUCT_BEGIN_WITH_PARENT +#undef IPC_STRUCT_MEMBER +#undef IPC_STRUCT_END +#undef IPC_STRUCT_TRAITS_BEGIN +#undef IPC_STRUCT_TRAITS_MEMBER +#undef IPC_STRUCT_TRAITS_PARENT +#undef IPC_STRUCT_TRAITS_END +#undef IPC_ENUM_TRAITS_VALIDATE +#undef IPC_MESSAGE_DECL + +#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) +#define IPC_STRUCT_MEMBER(type, name, ...) +#define IPC_STRUCT_END() +#define IPC_STRUCT_TRAITS_BEGIN(struct_name) +#define IPC_STRUCT_TRAITS_MEMBER(name) +#define IPC_STRUCT_TRAITS_PARENT(type) +#define IPC_STRUCT_TRAITS_END() +#define IPC_ENUM_TRAITS_VALIDATE(enum_name, validation_expression) +#define IPC_MESSAGE_DECL(sync, kind, msg_class, \ + in_cnt, out_cnt, in_list, out_list) + diff --git a/chromium/ipc/ipc_message_start.h b/chromium/ipc/ipc_message_start.h new file mode 100644 index 00000000000..f5833017f91 --- /dev/null +++ b/chromium/ipc/ipc_message_start.h @@ -0,0 +1,94 @@ +// Copyright 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. + +#ifndef IPC_IPC_MESSAGE_START_H_ +#define IPC_IPC_MESSAGE_START_H_ + +// Used by IPC_BEGIN_MESSAGES so that each message class starts from a unique +// base. Messages have unique IDs across channels in order for the IPC logging +// code to figure out the message class from its ID. +enum IPCMessageStart { + AutomationMsgStart = 0, + ViewMsgStart, + InputMsgStart, + PluginMsgStart, + PluginProcessMsgStart, + ProfileImportMsgStart, + TestMsgStart, + DevToolsMsgStart, + WorkerMsgStart, + NaClMsgStart, + UtilityMsgStart, + GpuMsgStart, + ServiceMsgStart, + PpapiMsgStart, + FirefoxImporterUnittestMsgStart, + FileUtilitiesMsgStart, + MimeRegistryMsgStart, + DatabaseMsgStart, + DOMStorageMsgStart, + IndexedDBMsgStart, + PepperFileMsgStart, + SpeechRecognitionMsgStart, + PepperMsgStart, + AutofillMsgStart, + SafeBrowsingMsgStart, + P2PMsgStart, + SocketStreamMsgStart, + ResourceMsgStart, + FileSystemMsgStart, + ChildProcessMsgStart, + ClipboardMsgStart, + BlobMsgStart, + AppCacheMsgStart, + DeviceMotionMsgStart, + DeviceOrientationMsgStart, + DesktopNotificationMsgStart, + GeolocationMsgStart, + AudioMsgStart, + MIDIMsgStart, + ChromeMsgStart, + DragMsgStart, + PrintMsgStart, + SpellCheckMsgStart, + ExtensionMsgStart, + VideoCaptureMsgStart, + QuotaMsgStart, + ImageMsgStart, + TextInputClientMsgStart, + ChromeUtilityMsgStart, + MediaStreamMsgStart, + ChromeBenchmarkingMsgStart, + IntentsMsgStart, + JavaBridgeMsgStart, + GamepadMsgStart, + ShellMsgStart, + AccessibilityMsgStart, + PrerenderMsgStart, + ChromotingMsgStart, + OldBrowserPluginMsgStart, + BrowserPluginMsgStart, + HyphenatorMsgStart, + AndroidWebViewMsgStart, + MetroViewerMsgStart, + CCMsgStart, + MediaPlayerMsgStart, + TracingMsgStart, + PeerConnectionTrackerMsgStart, + VisitedLinkMsgStart, + OneClickSigninMsgStart, + AppShimMsgStart, + ValidationMessageMsgStart, + WebRtcLoggingMsgStart, + TtsMsgStart, + MemoryBenchmarkMsgStart, + WebSocketMsgStart, + NaClHostMsgStart, + WebRTCIdentityMsgStart, + LocalDiscoveryMsgStart, + PowerMonitorMsgStart, + LastIPCMsgStart // Must come last. +}; + +#endif // IPC_IPC_MESSAGE_START_H_ diff --git a/chromium/ipc/ipc_message_unittest.cc b/chromium/ipc/ipc_message_unittest.cc new file mode 100644 index 00000000000..971314a290c --- /dev/null +++ b/chromium/ipc/ipc_message_unittest.cc @@ -0,0 +1,73 @@ +// 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 "ipc/ipc_message.h" + +#include <string.h> + +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "ipc/ipc_message_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +TEST(IPCMessageTest, ListValue) { + base::ListValue input; + input.Set(0, new base::FundamentalValue(42.42)); + input.Set(1, new base::StringValue("forty")); + input.Set(2, base::Value::CreateNullValue()); + + IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL); + IPC::WriteParam(&msg, input); + + base::ListValue output; + PickleIterator iter(msg); + EXPECT_TRUE(IPC::ReadParam(&msg, &iter, &output)); + + EXPECT_TRUE(input.Equals(&output)); + + // Also test the corrupt case. + IPC::Message bad_msg(1, 2, IPC::Message::PRIORITY_NORMAL); + bad_msg.WriteInt(99); + iter = PickleIterator(bad_msg); + EXPECT_FALSE(IPC::ReadParam(&bad_msg, &iter, &output)); +} + +TEST(IPCMessageTest, DictionaryValue) { + base::DictionaryValue input; + input.Set("null", base::Value::CreateNullValue()); + input.Set("bool", new base::FundamentalValue(true)); + input.Set("int", new base::FundamentalValue(42)); + input.SetWithoutPathExpansion("int.with.dot", new base::FundamentalValue(43)); + + scoped_ptr<base::DictionaryValue> subdict(new base::DictionaryValue()); + subdict->Set("str", new base::StringValue("forty two")); + subdict->Set("bool", new base::FundamentalValue(false)); + + scoped_ptr<base::ListValue> sublist(new base::ListValue()); + sublist->Set(0, new base::FundamentalValue(42.42)); + sublist->Set(1, new base::StringValue("forty")); + sublist->Set(2, new base::StringValue("two")); + subdict->Set("list", sublist.release()); + + input.Set("dict", subdict.release()); + + IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL); + IPC::WriteParam(&msg, input); + + base::DictionaryValue output; + PickleIterator iter(msg); + EXPECT_TRUE(IPC::ReadParam(&msg, &iter, &output)); + + EXPECT_TRUE(input.Equals(&output)); + + // Also test the corrupt case. + IPC::Message bad_msg(1, 2, IPC::Message::PRIORITY_NORMAL); + bad_msg.WriteInt(99); + iter = PickleIterator(bad_msg); + EXPECT_FALSE(IPC::ReadParam(&bad_msg, &iter, &output)); +} + +} // namespace diff --git a/chromium/ipc/ipc_message_utils.cc b/chromium/ipc/ipc_message_utils.cc new file mode 100644 index 00000000000..2acddceb51c --- /dev/null +++ b/chromium/ipc/ipc_message_utils.cc @@ -0,0 +1,851 @@ +// 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 "ipc/ipc_message_utils.h" + +#include "base/files/file_path.h" +#include "base/json/json_writer.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/nullable_string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/values.h" +#include "ipc/ipc_channel_handle.h" + +#if defined(OS_POSIX) +#include "ipc/file_descriptor_set_posix.h" +#elif defined(OS_WIN) +#include <tchar.h> +#endif + +namespace IPC { + +namespace { + +const int kMaxRecursionDepth = 100; + +template<typename CharType> +void LogBytes(const std::vector<CharType>& data, std::string* out) { +#if defined(OS_WIN) + // Windows has a GUI for logging, which can handle arbitrary binary data. + for (size_t i = 0; i < data.size(); ++i) + out->push_back(data[i]); +#else + // On POSIX, we log to stdout, which we assume can display ASCII. + static const size_t kMaxBytesToLog = 100; + for (size_t i = 0; i < std::min(data.size(), kMaxBytesToLog); ++i) { + if (isprint(data[i])) + out->push_back(data[i]); + else + out->append( + base::StringPrintf("[%02X]", static_cast<unsigned char>(data[i]))); + } + if (data.size() > kMaxBytesToLog) { + out->append(base::StringPrintf( + " and %u more bytes", + static_cast<unsigned>(data.size() - kMaxBytesToLog))); + } +#endif +} + +bool ReadValue(const Message* m, PickleIterator* iter, base::Value** value, + int recursion); + +void WriteValue(Message* m, const base::Value* value, int recursion) { + bool result; + if (recursion > kMaxRecursionDepth) { + LOG(WARNING) << "Max recursion depth hit in WriteValue."; + return; + } + + m->WriteInt(value->GetType()); + + switch (value->GetType()) { + case base::Value::TYPE_NULL: + break; + case base::Value::TYPE_BOOLEAN: { + bool val; + result = value->GetAsBoolean(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::TYPE_INTEGER: { + int val; + result = value->GetAsInteger(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::TYPE_DOUBLE: { + double val; + result = value->GetAsDouble(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::TYPE_STRING: { + std::string val; + result = value->GetAsString(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::TYPE_BINARY: { + const base::BinaryValue* binary = + static_cast<const base::BinaryValue*>(value); + m->WriteData(binary->GetBuffer(), static_cast<int>(binary->GetSize())); + break; + } + case base::Value::TYPE_DICTIONARY: { + const base::DictionaryValue* dict = + static_cast<const base::DictionaryValue*>(value); + + WriteParam(m, static_cast<int>(dict->size())); + + for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); + it.Advance()) { + WriteParam(m, it.key()); + WriteValue(m, &it.value(), recursion + 1); + } + break; + } + case base::Value::TYPE_LIST: { + const base::ListValue* list = static_cast<const base::ListValue*>(value); + WriteParam(m, static_cast<int>(list->GetSize())); + for (base::ListValue::const_iterator it = list->begin(); + it != list->end(); ++it) { + WriteValue(m, *it, recursion + 1); + } + break; + } + } +} + +// Helper for ReadValue that reads a DictionaryValue into a pre-allocated +// object. +bool ReadDictionaryValue(const Message* m, PickleIterator* iter, + base::DictionaryValue* value, int recursion) { + int size; + if (!ReadParam(m, iter, &size)) + return false; + + for (int i = 0; i < size; ++i) { + std::string key; + base::Value* subval; + if (!ReadParam(m, iter, &key) || + !ReadValue(m, iter, &subval, recursion + 1)) + return false; + value->SetWithoutPathExpansion(key, subval); + } + + return true; +} + +// Helper for ReadValue that reads a ReadListValue into a pre-allocated +// object. +bool ReadListValue(const Message* m, PickleIterator* iter, + base::ListValue* value, int recursion) { + int size; + if (!ReadParam(m, iter, &size)) + return false; + + for (int i = 0; i < size; ++i) { + base::Value* subval; + if (!ReadValue(m, iter, &subval, recursion + 1)) + return false; + value->Set(i, subval); + } + + return true; +} + +bool ReadValue(const Message* m, PickleIterator* iter, base::Value** value, + int recursion) { + if (recursion > kMaxRecursionDepth) { + LOG(WARNING) << "Max recursion depth hit in ReadValue."; + return false; + } + + int type; + if (!ReadParam(m, iter, &type)) + return false; + + switch (type) { + case base::Value::TYPE_NULL: + *value = base::Value::CreateNullValue(); + break; + case base::Value::TYPE_BOOLEAN: { + bool val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::FundamentalValue(val); + break; + } + case base::Value::TYPE_INTEGER: { + int val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::FundamentalValue(val); + break; + } + case base::Value::TYPE_DOUBLE: { + double val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::FundamentalValue(val); + break; + } + case base::Value::TYPE_STRING: { + std::string val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::StringValue(val); + break; + } + case base::Value::TYPE_BINARY: { + const char* data; + int length; + if (!m->ReadData(iter, &data, &length)) + return false; + *value = base::BinaryValue::CreateWithCopiedBuffer(data, length); + break; + } + case base::Value::TYPE_DICTIONARY: { + scoped_ptr<base::DictionaryValue> val(new base::DictionaryValue()); + if (!ReadDictionaryValue(m, iter, val.get(), recursion)) + return false; + *value = val.release(); + break; + } + case base::Value::TYPE_LIST: { + scoped_ptr<base::ListValue> val(new base::ListValue()); + if (!ReadListValue(m, iter, val.get(), recursion)) + return false; + *value = val.release(); + break; + } + default: + return false; + } + + return true; +} + +} // namespace + +// ----------------------------------------------------------------------------- + +LogData::LogData() + : routing_id(0), + type(0), + sent(0), + receive(0), + dispatch(0) { +} + +LogData::~LogData() { +} + +void ParamTraits<bool>::Log(const param_type& p, std::string* l) { + l->append(p ? "true" : "false"); +} + +void ParamTraits<unsigned char>::Write(Message* m, const param_type& p) { + m->WriteBytes(&p, sizeof(param_type)); +} + +bool ParamTraits<unsigned char>::Read(const Message* m, PickleIterator* iter, + param_type* r) { + const char* data; + if (!m->ReadBytes(iter, &data, sizeof(param_type))) + return false; + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits<unsigned char>::Log(const param_type& p, std::string* l) { + l->append(base::UintToString(p)); +} + +void ParamTraits<unsigned short>::Write(Message* m, const param_type& p) { + m->WriteBytes(&p, sizeof(param_type)); +} + +bool ParamTraits<unsigned short>::Read(const Message* m, PickleIterator* iter, + param_type* r) { + const char* data; + if (!m->ReadBytes(iter, &data, sizeof(param_type))) + return false; + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits<unsigned short>::Log(const param_type& p, std::string* l) { + l->append(base::UintToString(p)); +} + +void ParamTraits<int>::Log(const param_type& p, std::string* l) { + l->append(base::IntToString(p)); +} + +void ParamTraits<unsigned int>::Log(const param_type& p, std::string* l) { + l->append(base::UintToString(p)); +} + +void ParamTraits<long>::Log(const param_type& p, std::string* l) { + l->append(base::Int64ToString(static_cast<int64>(p))); +} + +void ParamTraits<unsigned long>::Log(const param_type& p, std::string* l) { + l->append(base::Uint64ToString(static_cast<uint64>(p))); +} + +void ParamTraits<long long>::Log(const param_type& p, std::string* l) { + l->append(base::Int64ToString(static_cast<int64>(p))); +} + +void ParamTraits<unsigned long long>::Log(const param_type& p, std::string* l) { + l->append(base::Uint64ToString(p)); +} + +void ParamTraits<float>::Write(Message* m, const param_type& p) { + m->WriteData(reinterpret_cast<const char*>(&p), sizeof(param_type)); +} + +bool ParamTraits<float>::Read(const Message* m, PickleIterator* iter, + param_type* r) { + const char *data; + int data_size; + if (!m->ReadData(iter, &data, &data_size) || + data_size != sizeof(param_type)) { + NOTREACHED(); + return false; + } + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits<float>::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("%e", p)); +} + +void ParamTraits<double>::Write(Message* m, const param_type& p) { + m->WriteData(reinterpret_cast<const char*>(&p), sizeof(param_type)); +} + +bool ParamTraits<double>::Read(const Message* m, PickleIterator* iter, + param_type* r) { + const char *data; + int data_size; + if (!m->ReadData(iter, &data, &data_size) || + data_size != sizeof(param_type)) { + NOTREACHED(); + return false; + } + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits<double>::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("%e", p)); +} + + +void ParamTraits<std::string>::Log(const param_type& p, std::string* l) { + l->append(p); +} + +void ParamTraits<std::wstring>::Log(const param_type& p, std::string* l) { + l->append(WideToUTF8(p)); +} + +#if !defined(WCHAR_T_IS_UTF16) +void ParamTraits<string16>::Log(const param_type& p, std::string* l) { + l->append(UTF16ToUTF8(p)); +} +#endif + +void ParamTraits<std::vector<char> >::Write(Message* m, const param_type& p) { + if (p.empty()) { + m->WriteData(NULL, 0); + } else { + m->WriteData(&p.front(), static_cast<int>(p.size())); + } +} + +bool ParamTraits<std::vector<char> >::Read(const Message* m, + PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + if (!m->ReadData(iter, &data, &data_size) || data_size < 0) + return false; + r->resize(data_size); + if (data_size) + memcpy(&r->front(), data, data_size); + return true; +} + +void ParamTraits<std::vector<char> >::Log(const param_type& p, std::string* l) { + LogBytes(p, l); +} + +void ParamTraits<std::vector<unsigned char> >::Write(Message* m, + const param_type& p) { + if (p.empty()) { + m->WriteData(NULL, 0); + } else { + m->WriteData(reinterpret_cast<const char*>(&p.front()), + static_cast<int>(p.size())); + } +} + +bool ParamTraits<std::vector<unsigned char> >::Read(const Message* m, + PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + if (!m->ReadData(iter, &data, &data_size) || data_size < 0) + return false; + r->resize(data_size); + if (data_size) + memcpy(&r->front(), data, data_size); + return true; +} + +void ParamTraits<std::vector<unsigned char> >::Log(const param_type& p, + std::string* l) { + LogBytes(p, l); +} + +void ParamTraits<std::vector<bool> >::Write(Message* m, const param_type& p) { + WriteParam(m, static_cast<int>(p.size())); + // Cast to bool below is required because libc++'s + // vector<bool>::const_reference is different from bool, and we want to avoid + // writing an extra specialization of ParamTraits for it. + for (size_t i = 0; i < p.size(); i++) + WriteParam(m, static_cast<bool>(p[i])); +} + +bool ParamTraits<std::vector<bool> >::Read(const Message* m, + PickleIterator* iter, + param_type* r) { + int size; + // ReadLength() checks for < 0 itself. + if (!m->ReadLength(iter, &size)) + return false; + r->resize(size); + for (int i = 0; i < size; i++) { + bool value; + if (!ReadParam(m, iter, &value)) + return false; + (*r)[i] = value; + } + return true; +} + +void ParamTraits<std::vector<bool> >::Log(const param_type& p, std::string* l) { + for (size_t i = 0; i < p.size(); ++i) { + if (i != 0) + l->push_back(' '); + LogParam(static_cast<bool>(p[i]), l); + } +} + +void ParamTraits<base::DictionaryValue>::Write(Message* m, + const param_type& p) { + WriteValue(m, &p, 0); +} + +bool ParamTraits<base::DictionaryValue>::Read( + const Message* m, PickleIterator* iter, param_type* r) { + int type; + if (!ReadParam(m, iter, &type) || type != base::Value::TYPE_DICTIONARY) + return false; + + return ReadDictionaryValue(m, iter, r, 0); +} + +void ParamTraits<base::DictionaryValue>::Log(const param_type& p, + std::string* l) { + std::string json; + base::JSONWriter::Write(&p, &json); + l->append(json); +} + +#if defined(OS_POSIX) +void ParamTraits<base::FileDescriptor>::Write(Message* m, const param_type& p) { + const bool valid = p.fd >= 0; + WriteParam(m, valid); + + if (valid) { + if (!m->WriteFileDescriptor(p)) + NOTREACHED(); + } +} + +bool ParamTraits<base::FileDescriptor>::Read(const Message* m, + PickleIterator* iter, + param_type* r) { + bool valid; + if (!ReadParam(m, iter, &valid)) + return false; + + if (!valid) { + r->fd = -1; + r->auto_close = false; + return true; + } + + return m->ReadFileDescriptor(iter, r); +} + +void ParamTraits<base::FileDescriptor>::Log(const param_type& p, + std::string* l) { + if (p.auto_close) { + l->append(base::StringPrintf("FD(%d auto-close)", p.fd)); + } else { + l->append(base::StringPrintf("FD(%d)", p.fd)); + } +} +#endif // defined(OS_POSIX) + +void ParamTraits<base::FilePath>::Write(Message* m, const param_type& p) { + p.WriteToPickle(m); +} + +bool ParamTraits<base::FilePath>::Read(const Message* m, + PickleIterator* iter, + param_type* r) { + return r->ReadFromPickle(iter); +} + +void ParamTraits<base::FilePath>::Log(const param_type& p, std::string* l) { + ParamTraits<base::FilePath::StringType>::Log(p.value(), l); +} + +void ParamTraits<base::ListValue>::Write(Message* m, const param_type& p) { + WriteValue(m, &p, 0); +} + +bool ParamTraits<base::ListValue>::Read( + const Message* m, PickleIterator* iter, param_type* r) { + int type; + if (!ReadParam(m, iter, &type) || type != base::Value::TYPE_LIST) + return false; + + return ReadListValue(m, iter, r, 0); +} + +void ParamTraits<base::ListValue>::Log(const param_type& p, std::string* l) { + std::string json; + base::JSONWriter::Write(&p, &json); + l->append(json); +} + +void ParamTraits<base::NullableString16>::Write(Message* m, + const param_type& p) { + WriteParam(m, p.string()); + WriteParam(m, p.is_null()); +} + +bool ParamTraits<base::NullableString16>::Read(const Message* m, + PickleIterator* iter, + param_type* r) { + string16 string; + if (!ReadParam(m, iter, &string)) + return false; + bool is_null; + if (!ReadParam(m, iter, &is_null)) + return false; + *r = base::NullableString16(string, is_null); + return true; +} + +void ParamTraits<base::NullableString16>::Log(const param_type& p, + std::string* l) { + l->append("("); + LogParam(p.string(), l); + l->append(", "); + LogParam(p.is_null(), l); + l->append(")"); +} + +void ParamTraits<base::PlatformFileInfo>::Write(Message* m, + const param_type& p) { + WriteParam(m, p.size); + WriteParam(m, p.is_directory); + WriteParam(m, p.last_modified.ToDoubleT()); + WriteParam(m, p.last_accessed.ToDoubleT()); + WriteParam(m, p.creation_time.ToDoubleT()); +} + +bool ParamTraits<base::PlatformFileInfo>::Read(const Message* m, + PickleIterator* iter, + param_type* p) { + double last_modified; + double last_accessed; + double creation_time; + bool result = + ReadParam(m, iter, &p->size) && + ReadParam(m, iter, &p->is_directory) && + ReadParam(m, iter, &last_modified) && + ReadParam(m, iter, &last_accessed) && + ReadParam(m, iter, &creation_time); + if (result) { + p->last_modified = base::Time::FromDoubleT(last_modified); + p->last_accessed = base::Time::FromDoubleT(last_accessed); + p->creation_time = base::Time::FromDoubleT(creation_time); + } + return result; +} + +void ParamTraits<base::PlatformFileInfo>::Log(const param_type& p, + std::string* l) { + l->append("("); + LogParam(p.size, l); + l->append(","); + LogParam(p.is_directory, l); + l->append(","); + LogParam(p.last_modified.ToDoubleT(), l); + l->append(","); + LogParam(p.last_accessed.ToDoubleT(), l); + l->append(","); + LogParam(p.creation_time.ToDoubleT(), l); + l->append(")"); +} + +void ParamTraits<base::Time>::Write(Message* m, const param_type& p) { + ParamTraits<int64>::Write(m, p.ToInternalValue()); +} + +bool ParamTraits<base::Time>::Read(const Message* m, PickleIterator* iter, + param_type* r) { + int64 value; + if (!ParamTraits<int64>::Read(m, iter, &value)) + return false; + *r = base::Time::FromInternalValue(value); + return true; +} + +void ParamTraits<base::Time>::Log(const param_type& p, std::string* l) { + ParamTraits<int64>::Log(p.ToInternalValue(), l); +} + +void ParamTraits<base::TimeDelta>::Write(Message* m, const param_type& p) { + ParamTraits<int64>::Write(m, p.ToInternalValue()); +} + +bool ParamTraits<base::TimeDelta>::Read(const Message* m, + PickleIterator* iter, + param_type* r) { + int64 value; + bool ret = ParamTraits<int64>::Read(m, iter, &value); + if (ret) + *r = base::TimeDelta::FromInternalValue(value); + + return ret; +} + +void ParamTraits<base::TimeDelta>::Log(const param_type& p, std::string* l) { + ParamTraits<int64>::Log(p.ToInternalValue(), l); +} + +void ParamTraits<base::TimeTicks>::Write(Message* m, const param_type& p) { + ParamTraits<int64>::Write(m, p.ToInternalValue()); +} + +bool ParamTraits<base::TimeTicks>::Read(const Message* m, + PickleIterator* iter, + param_type* r) { + int64 value; + bool ret = ParamTraits<int64>::Read(m, iter, &value); + if (ret) + *r = base::TimeTicks::FromInternalValue(value); + + return ret; +} + +void ParamTraits<base::TimeTicks>::Log(const param_type& p, std::string* l) { + ParamTraits<int64>::Log(p.ToInternalValue(), l); +} + +void ParamTraits<IPC::ChannelHandle>::Write(Message* m, const param_type& p) { +#if defined(OS_WIN) + // On Windows marshalling pipe handle is not supported. + DCHECK(p.pipe.handle == NULL); +#endif // defined (OS_WIN) + WriteParam(m, p.name); +#if defined(OS_POSIX) + WriteParam(m, p.socket); +#endif +} + +bool ParamTraits<IPC::ChannelHandle>::Read(const Message* m, + PickleIterator* iter, + param_type* r) { + return ReadParam(m, iter, &r->name) +#if defined(OS_POSIX) + && ReadParam(m, iter, &r->socket) +#endif + ; +} + +void ParamTraits<IPC::ChannelHandle>::Log(const param_type& p, + std::string* l) { + l->append(base::StringPrintf("ChannelHandle(%s", p.name.c_str())); +#if defined(OS_POSIX) + l->append(", "); + ParamTraits<base::FileDescriptor>::Log(p.socket, l); +#endif + l->append(")"); +} + +void ParamTraits<LogData>::Write(Message* m, const param_type& p) { + WriteParam(m, p.channel); + WriteParam(m, p.routing_id); + WriteParam(m, p.type); + WriteParam(m, p.flags); + WriteParam(m, p.sent); + WriteParam(m, p.receive); + WriteParam(m, p.dispatch); + WriteParam(m, p.message_name); + WriteParam(m, p.params); +} + +bool ParamTraits<LogData>::Read(const Message* m, + PickleIterator* iter, + param_type* r) { + return + ReadParam(m, iter, &r->channel) && + ReadParam(m, iter, &r->routing_id) && + ReadParam(m, iter, &r->type) && + ReadParam(m, iter, &r->flags) && + ReadParam(m, iter, &r->sent) && + ReadParam(m, iter, &r->receive) && + ReadParam(m, iter, &r->dispatch) && + ReadParam(m, iter, &r->message_name) && + ReadParam(m, iter, &r->params); +} + +void ParamTraits<LogData>::Log(const param_type& p, std::string* l) { + // Doesn't make sense to implement this! +} + +void ParamTraits<Message>::Write(Message* m, const Message& p) { +#if defined(OS_POSIX) + // We don't serialize the file descriptors in the nested message, so there + // better not be any. + DCHECK(!p.HasFileDescriptors()); +#endif + + // Don't just write out the message. This is used to send messages between + // NaCl (Posix environment) and the browser (could be on Windows). The message + // header formats differ between these systems (so does handle sharing, but + // we already asserted we don't have any handles). So just write out the + // parts of the header we use. + // + // Be careful also to use only explicitly-sized types. The NaCl environment + // could be 64-bit and the host browser could be 32-bits. The nested message + // may or may not be safe to send between 32-bit and 64-bit systems, but we + // leave that up to the code sending the message to ensure. + m->WriteUInt32(static_cast<uint32>(p.routing_id())); + m->WriteUInt32(p.type()); + m->WriteUInt32(p.flags()); + m->WriteData(p.payload(), static_cast<uint32>(p.payload_size())); +} + +bool ParamTraits<Message>::Read(const Message* m, PickleIterator* iter, + Message* r) { + uint32 routing_id, type, flags; + if (!m->ReadUInt32(iter, &routing_id) || + !m->ReadUInt32(iter, &type) || + !m->ReadUInt32(iter, &flags)) + return false; + + int payload_size; + const char* payload; + if (!m->ReadData(iter, &payload, &payload_size)) + return false; + + r->SetHeaderValues(static_cast<int32>(routing_id), type, flags); + return r->WriteBytes(payload, payload_size); +} + +void ParamTraits<Message>::Log(const Message& p, std::string* l) { + l->append("<IPC::Message>"); +} + +#if defined(OS_WIN) +// Note that HWNDs/HANDLE/HCURSOR/HACCEL etc are always 32 bits, even on 64 +// bit systems. That's why we use the Windows macros to convert to 32 bits. +void ParamTraits<HANDLE>::Write(Message* m, const param_type& p) { + m->WriteInt(HandleToLong(p)); +} + +bool ParamTraits<HANDLE>::Read(const Message* m, PickleIterator* iter, + param_type* r) { + int32 temp; + if (!m->ReadInt(iter, &temp)) + return false; + *r = LongToHandle(temp); + return true; +} + +void ParamTraits<HANDLE>::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("0x%X", p)); +} + +void ParamTraits<LOGFONT>::Write(Message* m, const param_type& p) { + m->WriteData(reinterpret_cast<const char*>(&p), sizeof(LOGFONT)); +} + +bool ParamTraits<LOGFONT>::Read(const Message* m, PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + if (m->ReadData(iter, &data, &data_size) && data_size == sizeof(LOGFONT)) { + const LOGFONT *font = reinterpret_cast<LOGFONT*>(const_cast<char*>(data)); + if (_tcsnlen(font->lfFaceName, LF_FACESIZE) < LF_FACESIZE) { + memcpy(r, data, sizeof(LOGFONT)); + return true; + } + } + + NOTREACHED(); + return false; +} + +void ParamTraits<LOGFONT>::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("<LOGFONT>")); +} + +void ParamTraits<MSG>::Write(Message* m, const param_type& p) { + m->WriteData(reinterpret_cast<const char*>(&p), sizeof(MSG)); +} + +bool ParamTraits<MSG>::Read(const Message* m, PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + bool result = m->ReadData(iter, &data, &data_size); + if (result && data_size == sizeof(MSG)) { + memcpy(r, data, sizeof(MSG)); + } else { + result = false; + NOTREACHED(); + } + + return result; +} + +void ParamTraits<MSG>::Log(const param_type& p, std::string* l) { + l->append("<MSG>"); +} + +#endif // OS_WIN + +} // namespace IPC diff --git a/chromium/ipc/ipc_message_utils.h b/chromium/ipc/ipc_message_utils.h new file mode 100644 index 00000000000..7121c19e304 --- /dev/null +++ b/chromium/ipc/ipc_message_utils.h @@ -0,0 +1,883 @@ +// 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. + +#ifndef IPC_IPC_MESSAGE_UTILS_H_ +#define IPC_IPC_MESSAGE_UTILS_H_ + +#include <algorithm> +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/format_macros.h" +#include "base/memory/scoped_vector.h" +#include "base/platform_file.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/tuple.h" +#include "ipc/ipc_message_start.h" +#include "ipc/ipc_param_traits.h" +#include "ipc/ipc_sync_message.h" + +#if defined(COMPILER_GCC) +// GCC "helpfully" tries to inline template methods in release mode. Except we +// want the majority of the template junk being expanded once in the +// implementation file (and only provide the definitions in +// ipc_message_utils_impl.h in those files) and exported, instead of expanded +// at every call site. Special note: GCC happily accepts the attribute before +// the method declaration, but only acts on it if it is after. +#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= 40500 +// Starting in gcc 4.5, the noinline no longer implies the concept covered by +// the introduced noclone attribute, which will create specialized versions of +// functions/methods when certain types are constant. +// www.gnu.org/software/gcc/gcc-4.5/changes.html +#define IPC_MSG_NOINLINE __attribute__((noinline, noclone)); +#else +#define IPC_MSG_NOINLINE __attribute__((noinline)); +#endif +#elif defined(COMPILER_MSVC) +// MSVC++ doesn't do this. +#define IPC_MSG_NOINLINE +#else +#error "Please add the noinline property for your new compiler here." +#endif + +namespace base { +class DictionaryValue; +class FilePath; +class ListValue; +class NullableString16; +class Time; +class TimeDelta; +class TimeTicks; +struct FileDescriptor; +} + +namespace IPC { + +struct ChannelHandle; + +// ----------------------------------------------------------------------------- +// How we send IPC message logs across channels. +struct IPC_EXPORT LogData { + LogData(); + ~LogData(); + + std::string channel; + int32 routing_id; + uint32 type; // "User-defined" message type, from ipc_message.h. + std::string flags; + int64 sent; // Time that the message was sent (i.e. at Send()). + int64 receive; // Time before it was dispatched (i.e. before calling + // OnMessageReceived). + int64 dispatch; // Time after it was dispatched (i.e. after calling + // OnMessageReceived). + std::string message_name; + std::string params; +}; + +//----------------------------------------------------------------------------- + +// A dummy struct to place first just to allow leading commas for all +// members in the macro-generated constructor initializer lists. +struct NoParams { +}; + +template <class P> +static inline void WriteParam(Message* m, const P& p) { + typedef typename SimilarTypeTraits<P>::Type Type; + ParamTraits<Type>::Write(m, static_cast<const Type& >(p)); +} + +template <class P> +static inline bool WARN_UNUSED_RESULT ReadParam(const Message* m, + PickleIterator* iter, + P* p) { + typedef typename SimilarTypeTraits<P>::Type Type; + return ParamTraits<Type>::Read(m, iter, reinterpret_cast<Type* >(p)); +} + +template <class P> +static inline void LogParam(const P& p, std::string* l) { + typedef typename SimilarTypeTraits<P>::Type Type; + ParamTraits<Type>::Log(static_cast<const Type& >(p), l); +} + +// Primitive ParamTraits ------------------------------------------------------- + +template <> +struct ParamTraits<bool> { + typedef bool param_type; + static void Write(Message* m, const param_type& p) { + m->WriteBool(p); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadBool(iter, r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<unsigned char> { + typedef unsigned char param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<unsigned short> { + typedef unsigned short param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits<int> { + typedef int param_type; + static void Write(Message* m, const param_type& p) { + m->WriteInt(p); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadInt(iter, r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits<unsigned int> { + typedef unsigned int param_type; + static void Write(Message* m, const param_type& p) { + m->WriteInt(p); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadInt(iter, reinterpret_cast<int*>(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits<long> { + typedef long param_type; + static void Write(Message* m, const param_type& p) { + m->WriteLongUsingDangerousNonPortableLessPersistableForm(p); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadLong(iter, r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits<unsigned long> { + typedef unsigned long param_type; + static void Write(Message* m, const param_type& p) { + m->WriteLongUsingDangerousNonPortableLessPersistableForm(p); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return m->ReadLong(iter, reinterpret_cast<long*>(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits<long long> { + typedef long long param_type; + static void Write(Message* m, const param_type& p) { + m->WriteInt64(static_cast<int64>(p)); + } + static bool Read(const Message* m, PickleIterator* iter, + param_type* r) { + return m->ReadInt64(iter, reinterpret_cast<int64*>(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits<unsigned long long> { + typedef unsigned long long param_type; + static void Write(Message* m, const param_type& p) { + m->WriteInt64(p); + } + static bool Read(const Message* m, PickleIterator* iter, + param_type* r) { + return m->ReadInt64(iter, reinterpret_cast<int64*>(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +// Note that the IPC layer doesn't sanitize NaNs and +/- INF values. Clients +// should be sure to check the sanity of these values after receiving them over +// IPC. +template <> +struct IPC_EXPORT ParamTraits<float> { + typedef float param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<double> { + typedef double param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +// STL ParamTraits ------------------------------------------------------------- + +template <> +struct ParamTraits<std::string> { + typedef std::string param_type; + static void Write(Message* m, const param_type& p) { + m->WriteString(p); + } + static bool Read(const Message* m, PickleIterator* iter, + param_type* r) { + return m->ReadString(iter, r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits<std::wstring> { + typedef std::wstring param_type; + static void Write(Message* m, const param_type& p) { + m->WriteWString(p); + } + static bool Read(const Message* m, PickleIterator* iter, + param_type* r) { + return m->ReadWString(iter, r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +// If WCHAR_T_IS_UTF16 is defined, then string16 is a std::wstring so we don't +// need this trait. +#if !defined(WCHAR_T_IS_UTF16) +template <> +struct ParamTraits<string16> { + typedef string16 param_type; + static void Write(Message* m, const param_type& p) { + m->WriteString16(p); + } + static bool Read(const Message* m, PickleIterator* iter, + param_type* r) { + return m->ReadString16(iter, r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; +#endif + +template <> +struct IPC_EXPORT ParamTraits<std::vector<char> > { + typedef std::vector<char> param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message*, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<std::vector<unsigned char> > { + typedef std::vector<unsigned char> param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<std::vector<bool> > { + typedef std::vector<bool> param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <class P> +struct ParamTraits<std::vector<P> > { + typedef std::vector<P> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, static_cast<int>(p.size())); + for (size_t i = 0; i < p.size(); i++) + WriteParam(m, p[i]); + } + static bool Read(const Message* m, PickleIterator* iter, + param_type* r) { + int size; + // ReadLength() checks for < 0 itself. + if (!m->ReadLength(iter, &size)) + return false; + // Resizing beforehand is not safe, see BUG 1006367 for details. + if (INT_MAX / sizeof(P) <= static_cast<size_t>(size)) + return false; + r->resize(size); + for (int i = 0; i < size; i++) { + if (!ReadParam(m, iter, &(*r)[i])) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + for (size_t i = 0; i < p.size(); ++i) { + if (i != 0) + l->append(" "); + LogParam((p[i]), l); + } + } +}; + +template <class P> +struct ParamTraits<std::set<P> > { + typedef std::set<P> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, static_cast<int>(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) + WriteParam(m, *iter); + } + static bool Read(const Message* m, PickleIterator* iter, + param_type* r) { + int size; + if (!m->ReadLength(iter, &size)) + return false; + for (int i = 0; i < size; ++i) { + P item; + if (!ReadParam(m, iter, &item)) + return false; + r->insert(item); + } + return true; + } + static void Log(const param_type& p, std::string* l) { + l->append("<std::set>"); + } +}; + +template <class K, class V> +struct ParamTraits<std::map<K, V> > { + typedef std::map<K, V> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, static_cast<int>(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + WriteParam(m, iter->first); + WriteParam(m, iter->second); + } + } + static bool Read(const Message* m, PickleIterator* iter, + param_type* r) { + int size; + if (!ReadParam(m, iter, &size) || size < 0) + return false; + for (int i = 0; i < size; ++i) { + K k; + if (!ReadParam(m, iter, &k)) + return false; + V& value = (*r)[k]; + if (!ReadParam(m, iter, &value)) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + l->append("<std::map>"); + } +}; + +template <class A, class B> +struct ParamTraits<std::pair<A, B> > { + typedef std::pair<A, B> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.first); + WriteParam(m, p.second); + } + static bool Read(const Message* m, PickleIterator* iter, + param_type* r) { + return ReadParam(m, iter, &r->first) && ReadParam(m, iter, &r->second); + } + static void Log(const param_type& p, std::string* l) { + l->append("("); + LogParam(p.first, l); + l->append(", "); + LogParam(p.second, l); + l->append(")"); + } +}; + +// Base ParamTraits ------------------------------------------------------------ + +template <> +struct IPC_EXPORT ParamTraits<base::DictionaryValue> { + typedef base::DictionaryValue param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +#if defined(OS_POSIX) +// FileDescriptors may be serialised over IPC channels on POSIX. On the +// receiving side, the FileDescriptor is a valid duplicate of the file +// descriptor which was transmitted: *it is not just a copy of the integer like +// HANDLEs on Windows*. The only exception is if the file descriptor is < 0. In +// this case, the receiving end will see a value of -1. *Zero is a valid file +// descriptor*. +// +// The received file descriptor will have the |auto_close| flag set to true. The +// code which handles the message is responsible for taking ownership of it. +// File descriptors are OS resources and must be closed when no longer needed. +// +// When sending a file descriptor, the file descriptor must be valid at the time +// of transmission. Since transmission is not synchronous, one should consider +// dup()ing any file descriptors to be transmitted and setting the |auto_close| +// flag, which causes the file descriptor to be closed after writing. +template<> +struct IPC_EXPORT ParamTraits<base::FileDescriptor> { + typedef base::FileDescriptor param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; +#endif // defined(OS_POSIX) + +template <> +struct IPC_EXPORT ParamTraits<base::FilePath> { + typedef base::FilePath param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<base::ListValue> { + typedef base::ListValue param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<base::NullableString16> { + typedef base::NullableString16 param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<base::PlatformFileInfo> { + typedef base::PlatformFileInfo param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct SimilarTypeTraits<base::PlatformFileError> { + typedef int Type; +}; + +#if defined(OS_WIN) +template <> +struct SimilarTypeTraits<HWND> { + typedef HANDLE Type; +}; +#endif // defined(OS_WIN) + +template <> +struct IPC_EXPORT ParamTraits<base::Time> { + typedef base::Time param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<base::TimeDelta> { + typedef base::TimeDelta param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<base::TimeTicks> { + typedef base::TimeTicks param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits<Tuple0> { + typedef Tuple0 param_type; + static void Write(Message* m, const param_type& p) { + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return true; + } + static void Log(const param_type& p, std::string* l) { + } +}; + +template <class A> +struct ParamTraits< Tuple1<A> > { + typedef Tuple1<A> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.a); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return ReadParam(m, iter, &r->a); + } + static void Log(const param_type& p, std::string* l) { + LogParam(p.a, l); + } +}; + +template <class A, class B> +struct ParamTraits< Tuple2<A, B> > { + typedef Tuple2<A, B> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.a); + WriteParam(m, p.b); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return (ReadParam(m, iter, &r->a) && + ReadParam(m, iter, &r->b)); + } + static void Log(const param_type& p, std::string* l) { + LogParam(p.a, l); + l->append(", "); + LogParam(p.b, l); + } +}; + +template <class A, class B, class C> +struct ParamTraits< Tuple3<A, B, C> > { + typedef Tuple3<A, B, C> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.a); + WriteParam(m, p.b); + WriteParam(m, p.c); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return (ReadParam(m, iter, &r->a) && + ReadParam(m, iter, &r->b) && + ReadParam(m, iter, &r->c)); + } + static void Log(const param_type& p, std::string* l) { + LogParam(p.a, l); + l->append(", "); + LogParam(p.b, l); + l->append(", "); + LogParam(p.c, l); + } +}; + +template <class A, class B, class C, class D> +struct ParamTraits< Tuple4<A, B, C, D> > { + typedef Tuple4<A, B, C, D> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.a); + WriteParam(m, p.b); + WriteParam(m, p.c); + WriteParam(m, p.d); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return (ReadParam(m, iter, &r->a) && + ReadParam(m, iter, &r->b) && + ReadParam(m, iter, &r->c) && + ReadParam(m, iter, &r->d)); + } + static void Log(const param_type& p, std::string* l) { + LogParam(p.a, l); + l->append(", "); + LogParam(p.b, l); + l->append(", "); + LogParam(p.c, l); + l->append(", "); + LogParam(p.d, l); + } +}; + +template <class A, class B, class C, class D, class E> +struct ParamTraits< Tuple5<A, B, C, D, E> > { + typedef Tuple5<A, B, C, D, E> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.a); + WriteParam(m, p.b); + WriteParam(m, p.c); + WriteParam(m, p.d); + WriteParam(m, p.e); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + return (ReadParam(m, iter, &r->a) && + ReadParam(m, iter, &r->b) && + ReadParam(m, iter, &r->c) && + ReadParam(m, iter, &r->d) && + ReadParam(m, iter, &r->e)); + } + static void Log(const param_type& p, std::string* l) { + LogParam(p.a, l); + l->append(", "); + LogParam(p.b, l); + l->append(", "); + LogParam(p.c, l); + l->append(", "); + LogParam(p.d, l); + l->append(", "); + LogParam(p.e, l); + } +}; + +template<class P> +struct ParamTraits<ScopedVector<P> > { + typedef ScopedVector<P> param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, static_cast<int>(p.size())); + for (size_t i = 0; i < p.size(); i++) + WriteParam(m, *p[i]); + } + static bool Read(const Message* m, PickleIterator* iter, param_type* r) { + int size = 0; + if (!m->ReadLength(iter, &size)) + return false; + if (INT_MAX/sizeof(P) <= static_cast<size_t>(size)) + return false; + r->resize(size); + for (int i = 0; i < size; i++) { + (*r)[i] = new P(); + if (!ReadParam(m, iter, (*r)[i])) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + for (size_t i = 0; i < p.size(); ++i) { + if (i != 0) + l->append(" "); + LogParam(*p[i], l); + } + } +}; + +// IPC types ParamTraits ------------------------------------------------------- + +// A ChannelHandle is basically a platform-inspecific wrapper around the +// fact that IPC endpoints are handled specially on POSIX. See above comments +// on FileDescriptor for more background. +template<> +struct IPC_EXPORT ParamTraits<IPC::ChannelHandle> { + typedef ChannelHandle param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<LogData> { + typedef LogData param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<Message> { + static void Write(Message* m, const Message& p); + static bool Read(const Message* m, PickleIterator* iter, Message* r); + static void Log(const Message& p, std::string* l); +}; + +// Windows ParamTraits --------------------------------------------------------- + +#if defined(OS_WIN) +template <> +struct IPC_EXPORT ParamTraits<HANDLE> { + typedef HANDLE param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<LOGFONT> { + typedef LOGFONT param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits<MSG> { + typedef MSG param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, PickleIterator* iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; +#endif // defined(OS_WIN) + +//----------------------------------------------------------------------------- +// Generic message subclasses + +// Used for asynchronous messages. +template <class ParamType> +class MessageSchema { + public: + typedef ParamType Param; + typedef typename TupleTypes<ParamType>::ParamTuple RefParam; + + static void Write(Message* msg, const RefParam& p) IPC_MSG_NOINLINE; + static bool Read(const Message* msg, Param* p) IPC_MSG_NOINLINE; +}; + +// defined in ipc_logging.cc +IPC_EXPORT void GenerateLogData(const std::string& channel, + const Message& message, + LogData* data, bool get_params); + + +#if defined(IPC_MESSAGE_LOG_ENABLED) +inline void AddOutputParamsToLog(const Message* msg, std::string* l) { + const std::string& output_params = msg->output_params(); + if (!l->empty() && !output_params.empty()) + l->append(", "); + + l->append(output_params); +} + +template <class ReplyParamType> +inline void LogReplyParamsToMessage(const ReplyParamType& reply_params, + const Message* msg) { + if (msg->received_time() != 0) { + std::string output_params; + LogParam(reply_params, &output_params); + msg->set_output_params(output_params); + } +} + +inline void ConnectMessageAndReply(const Message* msg, Message* reply) { + if (msg->sent_time()) { + // Don't log the sync message after dispatch, as we don't have the + // output parameters at that point. Instead, save its data and log it + // with the outgoing reply message when it's sent. + LogData* data = new LogData; + GenerateLogData("", *msg, data, true); + msg->set_dont_log(); + reply->set_sync_log_data(data); + } +} +#else +inline void AddOutputParamsToLog(const Message* msg, std::string* l) {} + +template <class ReplyParamType> +inline void LogReplyParamsToMessage(const ReplyParamType& reply_params, + const Message* msg) {} + +inline void ConnectMessageAndReply(const Message* msg, Message* reply) {} +#endif + +// This class assumes that its template argument is a RefTuple (a Tuple with +// reference elements). This would go into ipc_message_utils_impl.h, but it is +// also used by chrome_frame. +template <class RefTuple> +class ParamDeserializer : public MessageReplyDeserializer { + public: + explicit ParamDeserializer(const RefTuple& out) : out_(out) { } + + bool SerializeOutputParameters(const IPC::Message& msg, PickleIterator iter) { + return ReadParam(&msg, &iter, &out_); + } + + RefTuple out_; +}; + +// Used for synchronous messages. +template <class SendParamType, class ReplyParamType> +class SyncMessageSchema { + public: + typedef SendParamType SendParam; + typedef typename TupleTypes<SendParam>::ParamTuple RefSendParam; + typedef ReplyParamType ReplyParam; + + static void Write(Message* msg, const RefSendParam& send) IPC_MSG_NOINLINE; + static bool ReadSendParam(const Message* msg, SendParam* p) IPC_MSG_NOINLINE; + static bool ReadReplyParam( + const Message* msg, + typename TupleTypes<ReplyParam>::ValueTuple* p) IPC_MSG_NOINLINE; + + template<class T, class S, class Method> + static bool DispatchWithSendParams(bool ok, const SendParam& send_params, + const Message* msg, T* obj, S* sender, + Method func) { + Message* reply = SyncMessage::GenerateReply(msg); + if (ok) { + typename TupleTypes<ReplyParam>::ValueTuple reply_params; + DispatchToMethod(obj, func, send_params, &reply_params); + WriteParam(reply, reply_params); + LogReplyParamsToMessage(reply_params, msg); + } else { + NOTREACHED() << "Error deserializing message " << msg->type(); + reply->set_reply_error(); + } + sender->Send(reply); + return ok; + } + + template<class T, class Method> + static bool DispatchDelayReplyWithSendParams(bool ok, + const SendParam& send_params, + const Message* msg, T* obj, + Method func) { + Message* reply = SyncMessage::GenerateReply(msg); + if (ok) { + Tuple1<Message&> t = MakeRefTuple(*reply); + ConnectMessageAndReply(msg, reply); + DispatchToMethod(obj, func, send_params, &t); + } else { + NOTREACHED() << "Error deserializing message " << msg->type(); + reply->set_reply_error(); + obj->Send(reply); + } + return ok; + } + + template<typename TA> + static void WriteReplyParams(Message* reply, TA a) { + ReplyParam p(a); + WriteParam(reply, p); + } + + template<typename TA, typename TB> + static void WriteReplyParams(Message* reply, TA a, TB b) { + ReplyParam p(a, b); + WriteParam(reply, p); + } + + template<typename TA, typename TB, typename TC> + static void WriteReplyParams(Message* reply, TA a, TB b, TC c) { + ReplyParam p(a, b, c); + WriteParam(reply, p); + } + + template<typename TA, typename TB, typename TC, typename TD> + static void WriteReplyParams(Message* reply, TA a, TB b, TC c, TD d) { + ReplyParam p(a, b, c, d); + WriteParam(reply, p); + } + + template<typename TA, typename TB, typename TC, typename TD, typename TE> + static void WriteReplyParams(Message* reply, TA a, TB b, TC c, TD d, TE e) { + ReplyParam p(a, b, c, d, e); + WriteParam(reply, p); + } +}; + +} // namespace IPC + +#endif // IPC_IPC_MESSAGE_UTILS_H_ diff --git a/chromium/ipc/ipc_message_utils_impl.h b/chromium/ipc/ipc_message_utils_impl.h new file mode 100644 index 00000000000..0931e30216b --- /dev/null +++ b/chromium/ipc/ipc_message_utils_impl.h @@ -0,0 +1,51 @@ +// 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. +// +// This file contains templates forward declared (but not defined) in +// ipc_message_utils.h so that they are only instantiated in certain files, +// notably a few IPC unit tests. + +#ifndef IPC_IPC_MESSAGE_UTILS_IMPL_H_ +#define IPC_IPC_MESSAGE_UTILS_IMPL_H_ + +namespace IPC { + +template <class ParamType> +void MessageSchema<ParamType>::Write(Message* msg, const RefParam& p) { + WriteParam(msg, p); +} + +template <class ParamType> +bool MessageSchema<ParamType>::Read(const Message* msg, Param* p) { + PickleIterator iter(*msg); + if (ReadParam(msg, &iter, p)) + return true; + NOTREACHED() << "Error deserializing message " << msg->type(); + return false; +} + +template <class SendParamType, class ReplyParamType> +void SyncMessageSchema<SendParamType, ReplyParamType>::Write( + Message* msg, + const RefSendParam& send) { + WriteParam(msg, send); +} + +template <class SendParamType, class ReplyParamType> +bool SyncMessageSchema<SendParamType, ReplyParamType>::ReadSendParam( + const Message* msg, SendParam* p) { + PickleIterator iter = SyncMessage::GetDataIterator(msg); + return ReadParam(msg, &iter, p); +} + +template <class SendParamType, class ReplyParamType> +bool SyncMessageSchema<SendParamType, ReplyParamType>::ReadReplyParam( + const Message* msg, typename TupleTypes<ReplyParam>::ValueTuple* p) { + PickleIterator iter = SyncMessage::GetDataIterator(msg); + return ReadParam(msg, &iter, p); +} + +} // namespace IPC + +#endif // IPC_IPC_MESSAGE_UTILS_IMPL_H_ diff --git a/chromium/ipc/ipc_message_utils_unittest.cc b/chromium/ipc/ipc_message_utils_unittest.cc new file mode 100644 index 00000000000..2156eeb1b82 --- /dev/null +++ b/chromium/ipc/ipc_message_utils_unittest.cc @@ -0,0 +1,74 @@ +// 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 "ipc/ipc_message_utils.h" + +#include "base/files/file_path.h" +#include "ipc/ipc_message.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace IPC { +namespace { + +// Tests nesting of messages as parameters to other messages. +TEST(IPCMessageUtilsTest, NestedMessages) { + int32 nested_routing = 12; + uint32 nested_type = 78; + int nested_content = 456789; + Message::PriorityValue nested_priority = Message::PRIORITY_HIGH; + Message nested_msg(nested_routing, nested_type, nested_priority); + nested_msg.set_sync(); + ParamTraits<int>::Write(&nested_msg, nested_content); + + // Outer message contains the nested one as its parameter. + int32 outer_routing = 91; + uint32 outer_type = 88; + Message::PriorityValue outer_priority = Message::PRIORITY_NORMAL; + Message outer_msg(outer_routing, outer_type, outer_priority); + ParamTraits<Message>::Write(&outer_msg, nested_msg); + + // Read back the nested message. + PickleIterator iter(outer_msg); + IPC::Message result_msg; + ASSERT_TRUE(ParamTraits<Message>::Read(&outer_msg, &iter, &result_msg)); + + // Verify nested message headers. + EXPECT_EQ(nested_msg.routing_id(), result_msg.routing_id()); + EXPECT_EQ(nested_msg.type(), result_msg.type()); + EXPECT_EQ(nested_msg.priority(), result_msg.priority()); + EXPECT_EQ(nested_msg.flags(), result_msg.flags()); + + // Verify nested message content + PickleIterator nested_iter(nested_msg); + int result_content = 0; + ASSERT_TRUE(ParamTraits<int>::Read(&nested_msg, &nested_iter, + &result_content)); + EXPECT_EQ(nested_content, result_content); + + // Try reading past the ends for both messages and make sure it fails. + IPC::Message dummy; + ASSERT_FALSE(ParamTraits<Message>::Read(&outer_msg, &iter, &dummy)); + ASSERT_FALSE(ParamTraits<int>::Read(&nested_msg, &nested_iter, + &result_content)); +} + +// Tests that detection of various bad parameters is working correctly. +TEST(IPCMessageUtilsTest, ParameterValidation) { + base::FilePath::StringType ok_string(FILE_PATH_LITERAL("hello"), 5); + base::FilePath::StringType bad_string(FILE_PATH_LITERAL("hel\0o"), 5); + + // Change this if ParamTraits<FilePath>::Write() changes. + IPC::Message message; + ParamTraits<base::FilePath::StringType>::Write(&message, ok_string); + ParamTraits<base::FilePath::StringType>::Write(&message, bad_string); + + PickleIterator iter(message); + base::FilePath ok_path; + base::FilePath bad_path; + ASSERT_TRUE(ParamTraits<base::FilePath>::Read(&message, &iter, &ok_path)); + ASSERT_FALSE(ParamTraits<base::FilePath>::Read(&message, &iter, &bad_path)); +} + +} // namespace +} // namespace IPC diff --git a/chromium/ipc/ipc_multiprocess_test.cc b/chromium/ipc/ipc_multiprocess_test.cc new file mode 100644 index 00000000000..8e3c03a1db6 --- /dev/null +++ b/chromium/ipc/ipc_multiprocess_test.cc @@ -0,0 +1,23 @@ +// 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 "build/build_config.h" + +#include "ipc/ipc_multiprocess_test.h" + +#if defined(OS_POSIX) +#include "base/posix/global_descriptors.h" +#include "ipc/ipc_descriptors.h" +#endif + +namespace internal { + +void MultiProcessTestIPCSetUp() { +#if defined(OS_POSIX) + base::GlobalDescriptors::GetInstance()->Set(kPrimaryIPCChannel, + kPrimaryIPCChannel + base::GlobalDescriptors::kBaseDescriptor); +#endif +} + +} // namespace internal diff --git a/chromium/ipc/ipc_multiprocess_test.h b/chromium/ipc/ipc_multiprocess_test.h new file mode 100644 index 00000000000..43aaa6b6cd3 --- /dev/null +++ b/chromium/ipc/ipc_multiprocess_test.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef IPC_IPC_MULTIPROCESS_TEST_H_ +#define IPC_IPC_MULTIPROCESS_TEST_H_ + +#include "testing/multiprocess_func_list.h" + +// Use this macro when your sub-process is using an IPCChannel to communicate +// with the test process. See the comment below for why this is needed. +#define MULTIPROCESS_IPC_TEST_MAIN(test_main) \ + MULTIPROCESS_TEST_MAIN_WITH_SETUP(test_main, \ + internal::MultiProcessTestIPCSetUp) + +namespace internal { + +// Setup function used by MULTIPROCESS_IPC_TEST_MAIN. Registers the IPC channel +// as a global descriptor in the child process. This is needed on POSIX as on +// creation the IPCChannel looks for a specific global descriptor to establish +// the connection to the parent process. +void MultiProcessTestIPCSetUp(); + +} // namespace internal + +#endif // IPC_IPC_MULTIPROCESS_TEST_H_ diff --git a/chromium/ipc/ipc_param_traits.h b/chromium/ipc/ipc_param_traits.h new file mode 100644 index 00000000000..45e975c30a7 --- /dev/null +++ b/chromium/ipc/ipc_param_traits.h @@ -0,0 +1,23 @@ +// Copyright (c) 2010 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 IPC_IPC_PARAM_TRAITS_H_ +#define IPC_IPC_PARAM_TRAITS_H_ + +// Our IPC system uses the following partially specialized header to define how +// a data type is read, written and logged in the IPC system. + +namespace IPC { + +template <class P> struct ParamTraits { +}; + +template <class P> +struct SimilarTypeTraits { + typedef P Type; +}; + +} // namespace IPC + +#endif // IPC_IPC_PARAM_TRAITS_H_ diff --git a/chromium/ipc/ipc_perftests.cc b/chromium/ipc/ipc_perftests.cc new file mode 100644 index 00000000000..81adde81136 --- /dev/null +++ b/chromium/ipc/ipc_perftests.cc @@ -0,0 +1,279 @@ +// 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 "build/build_config.h" + +#include <algorithm> +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/perftimer.h" +#include "base/pickle.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_descriptors.h" +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_sender.h" +#include "ipc/ipc_test_base.h" + +namespace { + +// This test times the roundtrip IPC message cycle. +// +// TODO(brettw): Make this test run by default. + +class IPCChannelPerfTest : public IPCTestBase { +}; + +// This class simply collects stats about abstract "events" (each of which has a +// start time and an end time). +class EventTimeTracker { + public: + explicit EventTimeTracker(const char* name) + : name_(name), + count_(0) { + } + + void AddEvent(const base::TimeTicks& start, const base::TimeTicks& end) { + DCHECK(end >= start); + count_++; + base::TimeDelta duration = end - start; + total_duration_ += duration; + max_duration_ = std::max(max_duration_, duration); + } + + void ShowResults() const { + VLOG(1) << name_ << " count: " << count_; + VLOG(1) << name_ << " total duration: " + << total_duration_.InMillisecondsF() << " ms"; + VLOG(1) << name_ << " average duration: " + << (total_duration_.InMillisecondsF() / static_cast<double>(count_)) + << " ms"; + VLOG(1) << name_ << " maximum duration: " + << max_duration_.InMillisecondsF() << " ms"; + } + + void Reset() { + count_ = 0; + total_duration_ = base::TimeDelta(); + max_duration_ = base::TimeDelta(); + } + + private: + const std::string name_; + + uint64 count_; + base::TimeDelta total_duration_; + base::TimeDelta max_duration_; + + DISALLOW_COPY_AND_ASSIGN(EventTimeTracker); +}; + +// This channel listener just replies to all messages with the exact same +// message. It assumes each message has one string parameter. When the string +// "quit" is sent, it will exit. +class ChannelReflectorListener : public IPC::Listener { + public: + ChannelReflectorListener() + : channel_(NULL), + latency_tracker_("Client messages") { + VLOG(1) << "Client listener up"; + } + + virtual ~ChannelReflectorListener() { + VLOG(1) << "Client listener down"; + latency_tracker_.ShowResults(); + } + + void Init(IPC::Channel* channel) { + DCHECK(!channel_); + channel_ = channel; + } + + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + CHECK(channel_); + + PickleIterator iter(message); + int64 time_internal; + EXPECT_TRUE(iter.ReadInt64(&time_internal)); + int msgid; + EXPECT_TRUE(iter.ReadInt(&msgid)); + std::string payload; + EXPECT_TRUE(iter.ReadString(&payload)); + + // Include message deserialization in latency. + base::TimeTicks now = base::TimeTicks::Now(); + + if (payload == "hello") { + latency_tracker_.Reset(); + } else if (payload == "quit") { + latency_tracker_.ShowResults(); + base::MessageLoop::current()->QuitWhenIdle(); + return true; + } else { + // Don't track hello and quit messages. + latency_tracker_.AddEvent( + base::TimeTicks::FromInternalValue(time_internal), now); + } + + IPC::Message* msg = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL); + msg->WriteInt64(base::TimeTicks::Now().ToInternalValue()); + msg->WriteInt(msgid); + msg->WriteString(payload); + channel_->Send(msg); + return true; + } + + private: + IPC::Channel* channel_; + EventTimeTracker latency_tracker_; +}; + +class PerformanceChannelListener : public IPC::Listener { + public: + PerformanceChannelListener() + : channel_(NULL), + msg_count_(0), + msg_size_(0), + count_down_(0), + latency_tracker_("Server messages") { + VLOG(1) << "Server listener up"; + } + + virtual ~PerformanceChannelListener() { + VLOG(1) << "Server listener down"; + } + + void Init(IPC::Channel* channel) { + DCHECK(!channel_); + channel_ = channel; + } + + // Call this before running the message loop. + void SetTestParams(int msg_count, size_t msg_size) { + DCHECK_EQ(0, count_down_); + msg_count_ = msg_count; + msg_size_ = msg_size; + count_down_ = msg_count_; + payload_ = std::string(msg_size_, 'a'); + } + + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + CHECK(channel_); + + PickleIterator iter(message); + int64 time_internal; + EXPECT_TRUE(iter.ReadInt64(&time_internal)); + int msgid; + EXPECT_TRUE(iter.ReadInt(&msgid)); + std::string reflected_payload; + EXPECT_TRUE(iter.ReadString(&reflected_payload)); + + // Include message deserialization in latency. + base::TimeTicks now = base::TimeTicks::Now(); + + if (reflected_payload == "hello") { + // Start timing on hello. + latency_tracker_.Reset(); + DCHECK(!perf_logger_.get()); + std::string test_name = base::StringPrintf( + "IPC_Perf_%dx_%u", msg_count_, static_cast<unsigned>(msg_size_)); + perf_logger_.reset(new PerfTimeLogger(test_name.c_str())); + } else { + DCHECK_EQ(payload_.size(), reflected_payload.size()); + + latency_tracker_.AddEvent( + base::TimeTicks::FromInternalValue(time_internal), now); + + CHECK(count_down_ > 0); + count_down_--; + if (count_down_ == 0) { + perf_logger_.reset(); // Stop the perf timer now. + latency_tracker_.ShowResults(); + base::MessageLoop::current()->QuitWhenIdle(); + return true; + } + } + + IPC::Message* msg = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL); + msg->WriteInt64(base::TimeTicks::Now().ToInternalValue()); + msg->WriteInt(count_down_); + msg->WriteString(payload_); + channel_->Send(msg); + return true; + } + + private: + IPC::Channel* channel_; + int msg_count_; + size_t msg_size_; + + int count_down_; + std::string payload_; + EventTimeTracker latency_tracker_; + scoped_ptr<PerfTimeLogger> perf_logger_; +}; + +TEST_F(IPCChannelPerfTest, Performance) { + Init("PerformanceClient"); + + // Set up IPC channel and start client. + PerformanceChannelListener listener; + CreateChannel(&listener); + listener.Init(channel()); + ASSERT_TRUE(ConnectChannel()); + ASSERT_TRUE(StartClient()); + + const size_t kMsgSizeBase = 12; + const int kMsgSizeMaxExp = 5; + int msg_count = 100000; + size_t msg_size = kMsgSizeBase; + for (int i = 1; i <= kMsgSizeMaxExp; i++) { + listener.SetTestParams(msg_count, msg_size); + + // This initial message will kick-start the ping-pong of messages. + IPC::Message* message = + new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL); + message->WriteInt64(base::TimeTicks::Now().ToInternalValue()); + message->WriteInt(-1); + message->WriteString("hello"); + sender()->Send(message); + + // Run message loop. + base::MessageLoop::current()->Run(); + + msg_size *= kMsgSizeBase; + } + + // Send quit message. + IPC::Message* message = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL); + message->WriteInt64(base::TimeTicks::Now().ToInternalValue()); + message->WriteInt(-1); + message->WriteString("quit"); + sender()->Send(message); + + EXPECT_TRUE(WaitForClientShutdown()); + DestroyChannel(); +} + +// This message loop bounces all messages back to the sender. +MULTIPROCESS_IPC_TEST_CLIENT_MAIN(PerformanceClient) { + base::MessageLoopForIO main_message_loop; + ChannelReflectorListener listener; + IPC::Channel channel(IPCTestBase::GetChannelName("PerformanceClient"), + IPC::Channel::MODE_CLIENT, + &listener); + listener.Init(&channel); + CHECK(channel.Connect()); + + base::MessageLoop::current()->Run(); + return 0; +} + +} // namespace diff --git a/chromium/ipc/ipc_platform_file.cc b/chromium/ipc/ipc_platform_file.cc new file mode 100644 index 00000000000..4a756ea6788 --- /dev/null +++ b/chromium/ipc/ipc_platform_file.cc @@ -0,0 +1,48 @@ +// 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 "ipc/ipc_platform_file.h" + +#if defined(OS_POSIX) +#include <unistd.h> +#endif + +namespace IPC { + +PlatformFileForTransit GetFileHandleForProcess(base::PlatformFile handle, + base::ProcessHandle process, + bool close_source_handle) { + IPC::PlatformFileForTransit out_handle; +#if defined(OS_WIN) + DWORD options = DUPLICATE_SAME_ACCESS; + if (close_source_handle) + options |= DUPLICATE_CLOSE_SOURCE; + if (handle == INVALID_HANDLE_VALUE || + !::DuplicateHandle(::GetCurrentProcess(), + handle, + process, + &out_handle, + 0, + FALSE, + options)) { + out_handle = IPC::InvalidPlatformFileForTransit(); + } +#elif defined(OS_POSIX) + // If asked to close the source, we can simply re-use the source fd instead of + // dup()ing and close()ing. + // When we're not closing the source, we need to duplicate the handle and take + // ownership of that. The reason is that this function is often used to + // generate IPC messages, and the handle must remain valid until it's sent to + // the other process from the I/O thread. Without the dup, calling code might + // close the source handle before the message is sent, creating a race + // condition. + int fd = close_source_handle ? handle : ::dup(handle); + out_handle = base::FileDescriptor(fd, true); +#else + #error Not implemented. +#endif + return out_handle; +} + +} // namespace IPC diff --git a/chromium/ipc/ipc_platform_file.h b/chromium/ipc/ipc_platform_file.h new file mode 100644 index 00000000000..553c78c522f --- /dev/null +++ b/chromium/ipc/ipc_platform_file.h @@ -0,0 +1,50 @@ +// 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. + +#ifndef IPC_IPC_PLATFORM_FILE_H_ +#define IPC_IPC_PLATFORM_FILE_H_ + +#include "base/basictypes.h" +#include "base/platform_file.h" +#include "base/process/process.h" +#include "ipc/ipc_export.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#endif + +namespace IPC { + +#if defined(OS_WIN) +typedef base::PlatformFile PlatformFileForTransit; +#elif defined(OS_POSIX) +typedef base::FileDescriptor PlatformFileForTransit; +#endif + +inline PlatformFileForTransit InvalidPlatformFileForTransit() { +#if defined(OS_WIN) + return base::kInvalidPlatformFileValue; +#elif defined(OS_POSIX) + return base::FileDescriptor(); +#endif +} + +inline base::PlatformFile PlatformFileForTransitToPlatformFile( + const PlatformFileForTransit& transit) { +#if defined(OS_WIN) + return transit; +#elif defined(OS_POSIX) + return transit.fd; +#endif +} + +// Returns a file handle equivalent to |file| that can be used in |process|. +IPC_EXPORT PlatformFileForTransit GetFileHandleForProcess( + base::PlatformFile file, + base::ProcessHandle process, + bool close_source_handle); + +} // namespace IPC + +#endif // IPC_IPC_PLATFORM_FILE_H_ diff --git a/chromium/ipc/ipc_send_fds_test.cc b/chromium/ipc/ipc_send_fds_test.cc new file mode 100644 index 00000000000..4cddc1c2274 --- /dev/null +++ b/chromium/ipc/ipc_send_fds_test.cc @@ -0,0 +1,183 @@ +// 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 "build/build_config.h" + +#if defined(OS_POSIX) +#if defined(OS_MACOSX) +extern "C" { +#include <sandbox.h> +} +#endif +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "base/file_descriptor_posix.h" +#include "base/message_loop/message_loop.h" +#include "base/pickle.h" +#include "base/posix/eintr_wrapper.h" +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_test_base.h" + +namespace { + +const unsigned kNumFDsToSend = 20; +const char* kDevZeroPath = "/dev/zero"; + +static void VerifyAndCloseDescriptor(int fd, ino_t inode_num) { + // Check that we can read from the FD. + char buf; + ssize_t amt_read = read(fd, &buf, 1); + ASSERT_EQ(amt_read, 1); + ASSERT_EQ(buf, 0); // /dev/zero always reads 0 bytes. + + struct stat st; + ASSERT_EQ(fstat(fd, &st), 0); + + ASSERT_EQ(close(fd), 0); + + // Compare inode numbers to check that the file sent over the wire is actually + // the one expected. + ASSERT_EQ(inode_num, st.st_ino); +} + +class MyChannelDescriptorListener : public IPC::Listener { + public: + explicit MyChannelDescriptorListener(ino_t expected_inode_num) + : expected_inode_num_(expected_inode_num), + num_fds_received_(0) {} + + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + PickleIterator iter(message); + + ++num_fds_received_; + base::FileDescriptor descriptor; + + IPC::ParamTraits<base::FileDescriptor>::Read(&message, &iter, &descriptor); + + VerifyAndCloseDescriptor(descriptor.fd, expected_inode_num_); + if (num_fds_received_ == kNumFDsToSend) + base::MessageLoop::current()->Quit(); + + return true; + } + + virtual void OnChannelError() OVERRIDE { + base::MessageLoop::current()->Quit(); + } + + bool GotExpectedNumberOfDescriptors() const { + return num_fds_received_ == kNumFDsToSend; + } + + private: + ino_t expected_inode_num_; + unsigned num_fds_received_; +}; + +class IPCSendFdsTest : public IPCTestBase { + protected: + void RunServer() { + // Set up IPC channel and start client. + MyChannelDescriptorListener listener(-1); + CreateChannel(&listener); + ASSERT_TRUE(ConnectChannel()); + ASSERT_TRUE(StartClient()); + + for (unsigned i = 0; i < kNumFDsToSend; ++i) { + const int fd = open(kDevZeroPath, O_RDONLY); + ASSERT_GE(fd, 0); + base::FileDescriptor descriptor(fd, true); + + IPC::Message* message = + new IPC::Message(0, 3, IPC::Message::PRIORITY_NORMAL); + IPC::ParamTraits<base::FileDescriptor>::Write(message, descriptor); + ASSERT_TRUE(sender()->Send(message)); + } + + // Run message loop. + base::MessageLoop::current()->Run(); + + // Close the channel so the client's OnChannelError() gets fired. + channel()->Close(); + + EXPECT_TRUE(WaitForClientShutdown()); + DestroyChannel(); + } +}; + +TEST_F(IPCSendFdsTest, DescriptorTest) { + Init("SendFdsClient"); + RunServer(); +} + +int SendFdsClientCommon(const std::string& test_client_name, + ino_t expected_inode_num) { + base::MessageLoopForIO main_message_loop; + MyChannelDescriptorListener listener(expected_inode_num); + + // Set up IPC channel. + IPC::Channel channel(IPCTestBase::GetChannelName(test_client_name), + IPC::Channel::MODE_CLIENT, + &listener); + CHECK(channel.Connect()); + + // Run message loop. + base::MessageLoop::current()->Run(); + + // Verify that the message loop was exited due to getting the correct number + // of descriptors, and not because of the channel closing unexpectedly. + CHECK(listener.GotExpectedNumberOfDescriptors()); + + return 0; +} + +MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsClient) { + struct stat st; + int fd = open(kDevZeroPath, O_RDONLY); + fstat(fd, &st); + EXPECT_GE(HANDLE_EINTR(close(fd)), 0); + return SendFdsClientCommon("SendFdsClient", st.st_ino); +} + +#if defined(OS_MACOSX) +// Test that FDs are correctly sent to a sandboxed process. +// TODO(port): Make this test cross-platform. +TEST_F(IPCSendFdsTest, DescriptorTestSandboxed) { + Init("SendFdsSandboxedClient"); + RunServer(); +} + +MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendFdsSandboxedClient) { + struct stat st; + const int fd = open(kDevZeroPath, O_RDONLY); + fstat(fd, &st); + if (HANDLE_EINTR(close(fd)) < 0) + return -1; + + // Enable the sandbox. + char* error_buff = NULL; + int error = sandbox_init(kSBXProfilePureComputation, SANDBOX_NAMED, + &error_buff); + bool success = (error == 0 && error_buff == NULL); + if (!success) + return -1; + + sandbox_free_error(error_buff); + + // Make sure sandbox is really enabled. + if (open(kDevZeroPath, O_RDONLY) != -1) { + LOG(ERROR) << "Sandbox wasn't properly enabled"; + return -1; + } + + // See if we can receive a file descriptor. + return SendFdsClientCommon("SendFdsSandboxedClient", st.st_ino); +} +#endif // defined(OS_MACOSX) + +} // namespace + +#endif // defined(OS_POSIX) diff --git a/chromium/ipc/ipc_sender.h b/chromium/ipc/ipc_sender.h new file mode 100644 index 00000000000..9c26bf3e204 --- /dev/null +++ b/chromium/ipc/ipc_sender.h @@ -0,0 +1,28 @@ +// 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. + +#ifndef IPC_IPC_SENDER_H_ +#define IPC_IPC_SENDER_H_ + +#include "ipc/ipc_export.h" + +namespace IPC { + +class Message; + +class IPC_EXPORT Sender { + public: + // Sends the given IPC message. The implementor takes ownership of the + // given Message regardless of whether or not this method succeeds. This + // is done to make this method easier to use. Returns true on success and + // false otherwise. + virtual bool Send(Message* msg) = 0; + + protected: + virtual ~Sender() {} +}; + +} // namespace IPC + +#endif // IPC_IPC_SENDER_H_ diff --git a/chromium/ipc/ipc_switches.cc b/chromium/ipc/ipc_switches.cc new file mode 100644 index 00000000000..bcb12256e54 --- /dev/null +++ b/chromium/ipc/ipc_switches.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2006-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 "ipc/ipc_switches.h" + +#include "base/base_switches.h" + +namespace switches { + +// Can't find the switch you are looking for? try looking in +// base/base_switches.cc instead. + +// The value of this switch tells the child process which +// IPC channel the browser expects to use to communicate with it. +const char kProcessChannelID[] = "channel"; + +// Will add kDebugOnStart to every child processes. If a value is passed, it +// will be used as a filter to determine if the child process should have the +// kDebugOnStart flag passed on or not. +const char kDebugChildren[] = "debug-children"; + +} // namespace switches + diff --git a/chromium/ipc/ipc_switches.h b/chromium/ipc/ipc_switches.h new file mode 100644 index 00000000000..d88afb5ba9f --- /dev/null +++ b/chromium/ipc/ipc_switches.h @@ -0,0 +1,19 @@ +// 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. + +// Defines all the command-line switches used by the IPC infrastructure. + +#ifndef IPC_IPC_SWITCHES_H_ +#define IPC_IPC_SWITCHES_H_ + +#include "ipc/ipc_export.h" + +namespace switches { + +IPC_EXPORT extern const char kProcessChannelID[]; +IPC_EXPORT extern const char kDebugChildren[]; + +} // namespace switches + +#endif // IPC_IPC_SWITCHES_H_ diff --git a/chromium/ipc/ipc_sync_channel.cc b/chromium/ipc/ipc_sync_channel.cc new file mode 100644 index 00000000000..491b72db8a6 --- /dev/null +++ b/chromium/ipc/ipc_sync_channel.cc @@ -0,0 +1,579 @@ +// 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 "ipc/ipc_sync_channel.h" + +#include "base/bind.h" +#include "base/debug/trace_event.h" +#include "base/lazy_instance.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/synchronization/waitable_event.h" +#include "base/synchronization/waitable_event_watcher.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/thread_local.h" +#include "ipc/ipc_logging.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_sync_message.h" + +using base::TimeDelta; +using base::TimeTicks; +using base::WaitableEvent; + +namespace IPC { +// When we're blocked in a Send(), we need to process incoming synchronous +// messages right away because it could be blocking our reply (either +// directly from the same object we're calling, or indirectly through one or +// more other channels). That means that in SyncContext's OnMessageReceived, +// we need to process sync message right away if we're blocked. However a +// simple check isn't sufficient, because the listener thread can be in the +// process of calling Send. +// To work around this, when SyncChannel filters a sync message, it sets +// an event that the listener thread waits on during its Send() call. This +// allows us to dispatch incoming sync messages when blocked. The race +// condition is handled because if Send is in the process of being called, it +// will check the event. In case the listener thread isn't sending a message, +// we queue a task on the listener thread to dispatch the received messages. +// The messages are stored in this queue object that's shared among all +// SyncChannel objects on the same thread (since one object can receive a +// sync message while another one is blocked). + +class SyncChannel::ReceivedSyncMsgQueue : + public base::RefCountedThreadSafe<ReceivedSyncMsgQueue> { + public: + // Returns the ReceivedSyncMsgQueue instance for this thread, creating one + // if necessary. Call RemoveContext on the same thread when done. + static ReceivedSyncMsgQueue* AddContext() { + // We want one ReceivedSyncMsgQueue per listener thread (i.e. since multiple + // SyncChannel objects can block the same thread). + ReceivedSyncMsgQueue* rv = lazy_tls_ptr_.Pointer()->Get(); + if (!rv) { + rv = new ReceivedSyncMsgQueue(); + ReceivedSyncMsgQueue::lazy_tls_ptr_.Pointer()->Set(rv); + } + rv->listener_count_++; + return rv; + } + + // Called on IPC thread when a synchronous message or reply arrives. + void QueueMessage(const Message& msg, SyncChannel::SyncContext* context) { + bool was_task_pending; + { + base::AutoLock auto_lock(message_lock_); + + was_task_pending = task_pending_; + task_pending_ = true; + + // We set the event in case the listener thread is blocked (or is about + // to). In case it's not, the PostTask dispatches the messages. + message_queue_.push_back(QueuedMessage(new Message(msg), context)); + message_queue_version_++; + } + + dispatch_event_.Signal(); + if (!was_task_pending) { + listener_task_runner_->PostTask( + FROM_HERE, base::Bind(&ReceivedSyncMsgQueue::DispatchMessagesTask, + this, scoped_refptr<SyncContext>(context))); + } + } + + void QueueReply(const Message &msg, SyncChannel::SyncContext* context) { + received_replies_.push_back(QueuedMessage(new Message(msg), context)); + } + + // Called on the listener's thread to process any queues synchronous + // messages. + void DispatchMessagesTask(SyncContext* context) { + { + base::AutoLock auto_lock(message_lock_); + task_pending_ = false; + } + context->DispatchMessages(); + } + + void DispatchMessages(SyncContext* dispatching_context) { + bool first_time = true; + uint32 expected_version = 0; + SyncMessageQueue::iterator it; + while (true) { + Message* message = NULL; + scoped_refptr<SyncChannel::SyncContext> context; + { + base::AutoLock auto_lock(message_lock_); + if (first_time || message_queue_version_ != expected_version) { + it = message_queue_.begin(); + first_time = false; + } + for (; it != message_queue_.end(); it++) { + int message_group = it->context->restrict_dispatch_group(); + if (message_group == kRestrictDispatchGroup_None || + message_group == dispatching_context->restrict_dispatch_group()) { + message = it->message; + context = it->context; + it = message_queue_.erase(it); + message_queue_version_++; + expected_version = message_queue_version_; + break; + } + } + } + + if (message == NULL) + break; + context->OnDispatchMessage(*message); + delete message; + } + } + + // SyncChannel calls this in its destructor. + void RemoveContext(SyncContext* context) { + base::AutoLock auto_lock(message_lock_); + + SyncMessageQueue::iterator iter = message_queue_.begin(); + while (iter != message_queue_.end()) { + if (iter->context.get() == context) { + delete iter->message; + iter = message_queue_.erase(iter); + message_queue_version_++; + } else { + iter++; + } + } + + if (--listener_count_ == 0) { + DCHECK(lazy_tls_ptr_.Pointer()->Get()); + lazy_tls_ptr_.Pointer()->Set(NULL); + } + } + + WaitableEvent* dispatch_event() { return &dispatch_event_; } + base::SingleThreadTaskRunner* listener_task_runner() { + return listener_task_runner_.get(); + } + + // Holds a pointer to the per-thread ReceivedSyncMsgQueue object. + static base::LazyInstance<base::ThreadLocalPointer<ReceivedSyncMsgQueue> > + lazy_tls_ptr_; + + // Called on the ipc thread to check if we can unblock any current Send() + // calls based on a queued reply. + void DispatchReplies() { + for (size_t i = 0; i < received_replies_.size(); ++i) { + Message* message = received_replies_[i].message; + if (received_replies_[i].context->TryToUnblockListener(message)) { + delete message; + received_replies_.erase(received_replies_.begin() + i); + return; + } + } + } + + base::WaitableEventWatcher* top_send_done_watcher() { + return top_send_done_watcher_; + } + + void set_top_send_done_watcher(base::WaitableEventWatcher* watcher) { + top_send_done_watcher_ = watcher; + } + + private: + friend class base::RefCountedThreadSafe<ReceivedSyncMsgQueue>; + + // See the comment in SyncChannel::SyncChannel for why this event is created + // as manual reset. + ReceivedSyncMsgQueue() : + message_queue_version_(0), + dispatch_event_(true, false), + listener_task_runner_(base::ThreadTaskRunnerHandle::Get()), + task_pending_(false), + listener_count_(0), + top_send_done_watcher_(NULL) { + } + + ~ReceivedSyncMsgQueue() {} + + // Holds information about a queued synchronous message or reply. + struct QueuedMessage { + QueuedMessage(Message* m, SyncContext* c) : message(m), context(c) { } + Message* message; + scoped_refptr<SyncChannel::SyncContext> context; + }; + + typedef std::list<QueuedMessage> SyncMessageQueue; + SyncMessageQueue message_queue_; + uint32 message_queue_version_; // Used to signal DispatchMessages to rescan + + std::vector<QueuedMessage> received_replies_; + + // Set when we got a synchronous message that we must respond to as the + // sender needs its reply before it can reply to our original synchronous + // message. + WaitableEvent dispatch_event_; + scoped_refptr<base::SingleThreadTaskRunner> listener_task_runner_; + base::Lock message_lock_; + bool task_pending_; + int listener_count_; + + // The current send done event watcher for this thread. Used to maintain + // a local global stack of send done watchers to ensure that nested sync + // message loops complete correctly. + base::WaitableEventWatcher* top_send_done_watcher_; +}; + +base::LazyInstance<base::ThreadLocalPointer<SyncChannel::ReceivedSyncMsgQueue> > + SyncChannel::ReceivedSyncMsgQueue::lazy_tls_ptr_ = + LAZY_INSTANCE_INITIALIZER; + +SyncChannel::SyncContext::SyncContext( + Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner, + WaitableEvent* shutdown_event) + : ChannelProxy::Context(listener, ipc_task_runner), + received_sync_msgs_(ReceivedSyncMsgQueue::AddContext()), + shutdown_event_(shutdown_event), + restrict_dispatch_group_(kRestrictDispatchGroup_None) { +} + +SyncChannel::SyncContext::~SyncContext() { + while (!deserializers_.empty()) + Pop(); +} + +// Adds information about an outgoing sync message to the context so that +// we know how to deserialize the reply. Returns a handle that's set when +// the reply has arrived. +void SyncChannel::SyncContext::Push(SyncMessage* sync_msg) { + // Create the tracking information for this message. This object is stored + // by value since all members are pointers that are cheap to copy. These + // pointers are cleaned up in the Pop() function. + // + // The event is created as manual reset because in between Signal and + // OnObjectSignalled, another Send can happen which would stop the watcher + // from being called. The event would get watched later, when the nested + // Send completes, so the event will need to remain set. + PendingSyncMsg pending(SyncMessage::GetMessageId(*sync_msg), + sync_msg->GetReplyDeserializer(), + new WaitableEvent(true, false)); + base::AutoLock auto_lock(deserializers_lock_); + deserializers_.push_back(pending); +} + +bool SyncChannel::SyncContext::Pop() { + bool result; + { + base::AutoLock auto_lock(deserializers_lock_); + PendingSyncMsg msg = deserializers_.back(); + delete msg.deserializer; + delete msg.done_event; + msg.done_event = NULL; + deserializers_.pop_back(); + result = msg.send_result; + } + + // We got a reply to a synchronous Send() call that's blocking the listener + // thread. However, further down the call stack there could be another + // blocking Send() call, whose reply we received after we made this last + // Send() call. So check if we have any queued replies available that + // can now unblock the listener thread. + ipc_task_runner()->PostTask( + FROM_HERE, base::Bind(&ReceivedSyncMsgQueue::DispatchReplies, + received_sync_msgs_.get())); + + return result; +} + +WaitableEvent* SyncChannel::SyncContext::GetSendDoneEvent() { + base::AutoLock auto_lock(deserializers_lock_); + return deserializers_.back().done_event; +} + +WaitableEvent* SyncChannel::SyncContext::GetDispatchEvent() { + return received_sync_msgs_->dispatch_event(); +} + +void SyncChannel::SyncContext::DispatchMessages() { + received_sync_msgs_->DispatchMessages(this); +} + +bool SyncChannel::SyncContext::TryToUnblockListener(const Message* msg) { + base::AutoLock auto_lock(deserializers_lock_); + if (deserializers_.empty() || + !SyncMessage::IsMessageReplyTo(*msg, deserializers_.back().id)) { + return false; + } + + // TODO(bauerb): Remove logging once investigation of http://crbug.com/141055 + // has finished. + if (!msg->is_reply_error()) { + bool send_result = deserializers_.back().deserializer-> + SerializeOutputParameters(*msg); + deserializers_.back().send_result = send_result; + VLOG_IF(1, !send_result) << "Couldn't deserialize reply message"; + } else { + VLOG(1) << "Received error reply"; + } + deserializers_.back().done_event->Signal(); + + return true; +} + +void SyncChannel::SyncContext::Clear() { + CancelPendingSends(); + received_sync_msgs_->RemoveContext(this); + Context::Clear(); +} + +bool SyncChannel::SyncContext::OnMessageReceived(const Message& msg) { + // Give the filters a chance at processing this message. + if (TryFilters(msg)) + return true; + + if (TryToUnblockListener(&msg)) + return true; + + if (msg.is_reply()) { + received_sync_msgs_->QueueReply(msg, this); + return true; + } + + if (msg.should_unblock()) { + received_sync_msgs_->QueueMessage(msg, this); + return true; + } + + return Context::OnMessageReceivedNoFilter(msg); +} + +void SyncChannel::SyncContext::OnChannelError() { + CancelPendingSends(); + shutdown_watcher_.StopWatching(); + Context::OnChannelError(); +} + +void SyncChannel::SyncContext::OnChannelOpened() { + shutdown_watcher_.StartWatching( + shutdown_event_, + base::Bind(&SyncChannel::SyncContext::OnWaitableEventSignaled, + base::Unretained(this))); + Context::OnChannelOpened(); +} + +void SyncChannel::SyncContext::OnChannelClosed() { + CancelPendingSends(); + shutdown_watcher_.StopWatching(); + Context::OnChannelClosed(); +} + +void SyncChannel::SyncContext::OnSendTimeout(int message_id) { + base::AutoLock auto_lock(deserializers_lock_); + PendingSyncMessageQueue::iterator iter; + VLOG(1) << "Send timeout"; + for (iter = deserializers_.begin(); iter != deserializers_.end(); iter++) { + if (iter->id == message_id) { + iter->done_event->Signal(); + break; + } + } +} + +void SyncChannel::SyncContext::CancelPendingSends() { + base::AutoLock auto_lock(deserializers_lock_); + PendingSyncMessageQueue::iterator iter; + // TODO(bauerb): Remove once http://crbug/141055 is fixed. + VLOG(1) << "Canceling pending sends"; + for (iter = deserializers_.begin(); iter != deserializers_.end(); iter++) + iter->done_event->Signal(); +} + +void SyncChannel::SyncContext::OnWaitableEventSignaled(WaitableEvent* event) { + if (event == shutdown_event_) { + // Process shut down before we can get a reply to a synchronous message. + // Cancel pending Send calls, which will end up setting the send done event. + CancelPendingSends(); + } else { + // We got the reply, timed out or the process shutdown. + DCHECK_EQ(GetSendDoneEvent(), event); + base::MessageLoop::current()->QuitNow(); + } +} + +base::WaitableEventWatcher::EventCallback + SyncChannel::SyncContext::MakeWaitableEventCallback() { + return base::Bind(&SyncChannel::SyncContext::OnWaitableEventSignaled, this); +} + +SyncChannel::SyncChannel( + const IPC::ChannelHandle& channel_handle, + Channel::Mode mode, + Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner, + bool create_pipe_now, + WaitableEvent* shutdown_event) + : ChannelProxy(new SyncContext(listener, ipc_task_runner, shutdown_event)), + sync_messages_with_no_timeout_allowed_(true) { + ChannelProxy::Init(channel_handle, mode, create_pipe_now); + StartWatching(); +} + +SyncChannel::SyncChannel( + Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner, + WaitableEvent* shutdown_event) + : ChannelProxy(new SyncContext(listener, ipc_task_runner, shutdown_event)), + sync_messages_with_no_timeout_allowed_(true) { + StartWatching(); +} + +SyncChannel::~SyncChannel() { +} + +void SyncChannel::SetRestrictDispatchChannelGroup(int group) { + sync_context()->set_restrict_dispatch_group(group); +} + +bool SyncChannel::Send(Message* message) { + return SendWithTimeout(message, base::kNoTimeout); +} + +bool SyncChannel::SendWithTimeout(Message* message, int timeout_ms) { +#ifdef IPC_MESSAGE_LOG_ENABLED + Logging* logger = Logging::GetInstance(); + std::string name; + logger->GetMessageText(message->type(), &name, message, NULL); + TRACE_EVENT1("task", "SyncChannel::SendWithTimeout", + "name", name); +#else + TRACE_EVENT2("task", "SyncChannel::SendWithTimeout", + "class", IPC_MESSAGE_ID_CLASS(message->type()), + "line", IPC_MESSAGE_ID_LINE(message->type())); +#endif + if (!message->is_sync()) { + ChannelProxy::Send(message); + return true; + } + + // *this* might get deleted in WaitForReply. + scoped_refptr<SyncContext> context(sync_context()); + if (context->shutdown_event()->IsSignaled()) { + VLOG(1) << "shutdown event is signaled"; + delete message; + return false; + } + + DCHECK(sync_messages_with_no_timeout_allowed_ || + timeout_ms != base::kNoTimeout); + SyncMessage* sync_msg = static_cast<SyncMessage*>(message); + context->Push(sync_msg); + int message_id = SyncMessage::GetMessageId(*sync_msg); + WaitableEvent* pump_messages_event = sync_msg->pump_messages_event(); + + ChannelProxy::Send(message); + + if (timeout_ms != base::kNoTimeout) { + // We use the sync message id so that when a message times out, we don't + // confuse it with another send that is either above/below this Send in + // the call stack. + context->ipc_task_runner()->PostDelayedTask( + FROM_HERE, + base::Bind(&SyncContext::OnSendTimeout, context.get(), message_id), + base::TimeDelta::FromMilliseconds(timeout_ms)); + } + + // Wait for reply, or for any other incoming synchronous messages. + // *this* might get deleted, so only call static functions at this point. + WaitForReply(context.get(), pump_messages_event); + + return context->Pop(); +} + +void SyncChannel::WaitForReply( + SyncContext* context, WaitableEvent* pump_messages_event) { + context->DispatchMessages(); + while (true) { + WaitableEvent* objects[] = { + context->GetDispatchEvent(), + context->GetSendDoneEvent(), + pump_messages_event + }; + + unsigned count = pump_messages_event ? 3: 2; + size_t result = WaitableEvent::WaitMany(objects, count); + if (result == 0 /* dispatch event */) { + // We're waiting for a reply, but we received a blocking synchronous + // call. We must process it or otherwise a deadlock might occur. + context->GetDispatchEvent()->Reset(); + context->DispatchMessages(); + continue; + } + + if (result == 2 /* pump_messages_event */) + WaitForReplyWithNestedMessageLoop(context); // Run a nested message loop. + + break; + } +} + +void SyncChannel::WaitForReplyWithNestedMessageLoop(SyncContext* context) { + base::WaitableEventWatcher send_done_watcher; + + ReceivedSyncMsgQueue* sync_msg_queue = context->received_sync_msgs(); + DCHECK(sync_msg_queue != NULL); + + base::WaitableEventWatcher* old_send_done_event_watcher = + sync_msg_queue->top_send_done_watcher(); + + base::WaitableEventWatcher::EventCallback old_callback; + base::WaitableEvent* old_event = NULL; + + // Maintain a local global stack of send done delegates to ensure that + // nested sync calls complete in the correct sequence, i.e. the + // outermost call completes first, etc. + if (old_send_done_event_watcher) { + old_callback = old_send_done_event_watcher->callback(); + old_event = old_send_done_event_watcher->GetWatchedEvent(); + old_send_done_event_watcher->StopWatching(); + } + + sync_msg_queue->set_top_send_done_watcher(&send_done_watcher); + + send_done_watcher.StartWatching(context->GetSendDoneEvent(), + context->MakeWaitableEventCallback()); + + { + base::MessageLoop::ScopedNestableTaskAllower allow( + base::MessageLoop::current()); + base::MessageLoop::current()->Run(); + } + + sync_msg_queue->set_top_send_done_watcher(old_send_done_event_watcher); + if (old_send_done_event_watcher && old_event) { + old_send_done_event_watcher->StartWatching(old_event, old_callback); + } +} + +void SyncChannel::OnWaitableEventSignaled(WaitableEvent* event) { + DCHECK(event == sync_context()->GetDispatchEvent()); + // The call to DispatchMessages might delete this object, so reregister + // the object watcher first. + event->Reset(); + dispatch_watcher_.StartWatching(event, dispatch_watcher_callback_); + sync_context()->DispatchMessages(); +} + +void SyncChannel::StartWatching() { + // Ideally we only want to watch this object when running a nested message + // loop. However, we don't know when it exits if there's another nested + // message loop running under it or not, so we wouldn't know whether to + // stop or keep watching. So we always watch it, and create the event as + // manual reset since the object watcher might otherwise reset the event + // when we're doing a WaitMany. + dispatch_watcher_callback_ = + base::Bind(&SyncChannel::OnWaitableEventSignaled, + base::Unretained(this)); + dispatch_watcher_.StartWatching(sync_context()->GetDispatchEvent(), + dispatch_watcher_callback_); +} + +} // namespace IPC diff --git a/chromium/ipc/ipc_sync_channel.h b/chromium/ipc/ipc_sync_channel.h new file mode 100644 index 00000000000..46ac910efdc --- /dev/null +++ b/chromium/ipc/ipc_sync_channel.h @@ -0,0 +1,226 @@ +// 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. + +#ifndef IPC_IPC_SYNC_CHANNEL_H_ +#define IPC_IPC_SYNC_CHANNEL_H_ + +#include <string> +#include <deque> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event_watcher.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_sync_message.h" + +namespace base { +class WaitableEvent; +}; + +namespace IPC { + +class SyncMessage; + +// This is similar to ChannelProxy, with the added feature of supporting sending +// synchronous messages. +// +// Overview of how the sync channel works +// -------------------------------------- +// When the sending thread sends a synchronous message, we create a bunch +// of tracking info (created in SendWithTimeout, stored in the PendingSyncMsg +// structure) associated with the message that we identify by the unique +// "MessageId" on the SyncMessage. Among the things we save is the +// "Deserializer" which is provided by the sync message. This object is in +// charge of reading the parameters from the reply message and putting them in +// the output variables provided by its caller. +// +// The info gets stashed in a queue since we could have a nested stack of sync +// messages (each side could send sync messages in response to sync messages, +// so it works like calling a function). The message is sent to the I/O thread +// for dispatch and the original thread blocks waiting for the reply. +// +// SyncContext maintains the queue in a threadsafe way and listens for replies +// on the I/O thread. When a reply comes in that matches one of the messages +// it's looking for (using the unique message ID), it will execute the +// deserializer stashed from before, and unblock the original thread. +// +// +// Significant complexity results from the fact that messages are still coming +// in while the original thread is blocked. Normal async messages are queued +// and dispatched after the blocking call is complete. Sync messages must +// be dispatched in a reentrant manner to avoid deadlock. +// +// +// Note that care must be taken that the lifetime of the ipc_thread argument +// is more than this object. If the message loop goes away while this object +// is running and it's used to send a message, then it will use the invalid +// message loop pointer to proxy it to the ipc thread. +class IPC_EXPORT SyncChannel : public ChannelProxy { + public: + enum RestrictDispatchGroup { + kRestrictDispatchGroup_None = 0, + }; + + // Creates and initializes a sync channel. If create_pipe_now is specified, + // the channel will be initialized synchronously. + SyncChannel(const IPC::ChannelHandle& channel_handle, + Channel::Mode mode, + Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner, + bool create_pipe_now, + base::WaitableEvent* shutdown_event); + + // Creates an uninitialized sync channel. Call ChannelProxy::Init to + // initialize the channel. This two-step setup allows message filters to be + // added before any messages are sent or received. + SyncChannel(Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner, + base::WaitableEvent* shutdown_event); + + virtual ~SyncChannel(); + + virtual bool Send(Message* message) OVERRIDE; + virtual bool SendWithTimeout(Message* message, int timeout_ms); + + // Whether we allow sending messages with no time-out. + void set_sync_messages_with_no_timeout_allowed(bool value) { + sync_messages_with_no_timeout_allowed_ = value; + } + + // Sets the dispatch group for this channel, to only allow re-entrant dispatch + // of messages to other channels in the same group. + // + // Normally, any unblocking message coming from any channel can be dispatched + // when any (possibly other) channel is blocked on sending a message. This is + // needed in some cases to unblock certain loops (e.g. necessary when some + // processes share a window hierarchy), but may cause re-entrancy issues in + // some cases where such loops are not possible. This flags allows the tagging + // of some particular channels to only re-enter in known correct cases. + // + // Incoming messages on channels belonging to a group that is not + // kRestrictDispatchGroup_None will only be dispatched while a sync message is + // being sent on a channel of the *same* group. + // Incoming messages belonging to the kRestrictDispatchGroup_None group (the + // default) will be dispatched in any case. + void SetRestrictDispatchChannelGroup(int group); + + protected: + class ReceivedSyncMsgQueue; + friend class ReceivedSyncMsgQueue; + + // SyncContext holds the per object data for SyncChannel, so that SyncChannel + // can be deleted while it's being used in a different thread. See + // ChannelProxy::Context for more information. + class SyncContext : public Context { + public: + SyncContext(Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner, + base::WaitableEvent* shutdown_event); + + // Adds information about an outgoing sync message to the context so that + // we know how to deserialize the reply. + void Push(SyncMessage* sync_msg); + + // Cleanly remove the top deserializer (and throw it away). Returns the + // result of the Send call for that message. + bool Pop(); + + // Returns an event that's set when the send is complete, timed out or the + // process shut down. + base::WaitableEvent* GetSendDoneEvent(); + + // Returns an event that's set when an incoming message that's not the reply + // needs to get dispatched (by calling SyncContext::DispatchMessages). + base::WaitableEvent* GetDispatchEvent(); + + void DispatchMessages(); + + // Checks if the given message is blocking the listener thread because of a + // synchronous send. If it is, the thread is unblocked and true is + // returned. Otherwise the function returns false. + bool TryToUnblockListener(const Message* msg); + + // Called on the IPC thread when a sync send that runs a nested message loop + // times out. + void OnSendTimeout(int message_id); + + base::WaitableEvent* shutdown_event() { return shutdown_event_; } + + ReceivedSyncMsgQueue* received_sync_msgs() { + return received_sync_msgs_.get(); + } + + void set_restrict_dispatch_group(int group) { + restrict_dispatch_group_ = group; + } + + int restrict_dispatch_group() const { + return restrict_dispatch_group_; + } + + base::WaitableEventWatcher::EventCallback MakeWaitableEventCallback(); + + private: + virtual ~SyncContext(); + // ChannelProxy methods that we override. + + // Called on the listener thread. + virtual void Clear() OVERRIDE; + + // Called on the IPC thread. + virtual bool OnMessageReceived(const Message& msg) OVERRIDE; + virtual void OnChannelError() OVERRIDE; + virtual void OnChannelOpened() OVERRIDE; + virtual void OnChannelClosed() OVERRIDE; + + // Cancels all pending Send calls. + void CancelPendingSends(); + + void OnWaitableEventSignaled(base::WaitableEvent* event); + + typedef std::deque<PendingSyncMsg> PendingSyncMessageQueue; + PendingSyncMessageQueue deserializers_; + base::Lock deserializers_lock_; + + scoped_refptr<ReceivedSyncMsgQueue> received_sync_msgs_; + + base::WaitableEvent* shutdown_event_; + base::WaitableEventWatcher shutdown_watcher_; + base::WaitableEventWatcher::EventCallback shutdown_watcher_callback_; + int restrict_dispatch_group_; + }; + + private: + void OnWaitableEventSignaled(base::WaitableEvent* arg); + + SyncContext* sync_context() { + return reinterpret_cast<SyncContext*>(context()); + } + + // Both these functions wait for a reply, timeout or process shutdown. The + // latter one also runs a nested message loop in the meantime. + static void WaitForReply( + SyncContext* context, base::WaitableEvent* pump_messages_event); + + // Runs a nested message loop until a reply arrives, times out, or the process + // shuts down. + static void WaitForReplyWithNestedMessageLoop(SyncContext* context); + + // Starts the dispatch watcher. + void StartWatching(); + + bool sync_messages_with_no_timeout_allowed_; + + // Used to signal events between the IPC and listener threads. + base::WaitableEventWatcher dispatch_watcher_; + base::WaitableEventWatcher::EventCallback dispatch_watcher_callback_; + + DISALLOW_COPY_AND_ASSIGN(SyncChannel); +}; + +} // namespace IPC + +#endif // IPC_IPC_SYNC_CHANNEL_H_ diff --git a/chromium/ipc/ipc_sync_channel_unittest.cc b/chromium/ipc/ipc_sync_channel_unittest.cc new file mode 100644 index 00000000000..30f02f7c4c7 --- /dev/null +++ b/chromium/ipc/ipc_sync_channel_unittest.cc @@ -0,0 +1,1904 @@ +// 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 "ipc/ipc_sync_channel.h" + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/process/process_handle.h" +#include "base/run_loop.h" +#include "base/strings/string_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_sender.h" +#include "ipc/ipc_sync_message_filter.h" +#include "ipc/ipc_sync_message_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::WaitableEvent; + +namespace IPC { +namespace { + +// Base class for a "process" with listener and IPC threads. +class Worker : public Listener, public Sender { + public: + // Will create a channel without a name. + Worker(Channel::Mode mode, const std::string& thread_name) + : done_(new WaitableEvent(false, false)), + channel_created_(new WaitableEvent(false, false)), + mode_(mode), + ipc_thread_((thread_name + "_ipc").c_str()), + listener_thread_((thread_name + "_listener").c_str()), + overrided_thread_(NULL), + shutdown_event_(true, false), + is_shutdown_(false) { + } + + // Will create a named channel and use this name for the threads' name. + Worker(const std::string& channel_name, Channel::Mode mode) + : done_(new WaitableEvent(false, false)), + channel_created_(new WaitableEvent(false, false)), + channel_name_(channel_name), + mode_(mode), + ipc_thread_((channel_name + "_ipc").c_str()), + listener_thread_((channel_name + "_listener").c_str()), + overrided_thread_(NULL), + shutdown_event_(true, false), + is_shutdown_(false) { + } + + virtual ~Worker() { + // Shutdown() must be called before destruction. + CHECK(is_shutdown_); + } + void AddRef() { } + void Release() { } + virtual bool Send(Message* msg) OVERRIDE { return channel_->Send(msg); } + bool SendWithTimeout(Message* msg, int timeout_ms) { + return channel_->SendWithTimeout(msg, timeout_ms); + } + void WaitForChannelCreation() { channel_created_->Wait(); } + void CloseChannel() { + DCHECK(base::MessageLoop::current() == ListenerThread()->message_loop()); + channel_->Close(); + } + void Start() { + StartThread(&listener_thread_, base::MessageLoop::TYPE_DEFAULT); + ListenerThread()->message_loop()->PostTask( + FROM_HERE, base::Bind(&Worker::OnStart, this)); + } + void Shutdown() { + // The IPC thread needs to outlive SyncChannel. We can't do this in + // ~Worker(), since that'll reset the vtable pointer (to Worker's), which + // may result in a race conditions. See http://crbug.com/25841. + WaitableEvent listener_done(false, false), ipc_done(false, false); + ListenerThread()->message_loop()->PostTask( + FROM_HERE, base::Bind(&Worker::OnListenerThreadShutdown1, this, + &listener_done, &ipc_done)); + listener_done.Wait(); + ipc_done.Wait(); + ipc_thread_.Stop(); + listener_thread_.Stop(); + is_shutdown_ = true; + } + void OverrideThread(base::Thread* overrided_thread) { + DCHECK(overrided_thread_ == NULL); + overrided_thread_ = overrided_thread; + } + bool SendAnswerToLife(bool pump, int timeout, bool succeed) { + int answer = 0; + SyncMessage* msg = new SyncChannelTestMsg_AnswerToLife(&answer); + if (pump) + msg->EnableMessagePumping(); + bool result = SendWithTimeout(msg, timeout); + DCHECK_EQ(result, succeed); + DCHECK_EQ(answer, (succeed ? 42 : 0)); + return result; + } + bool SendDouble(bool pump, bool succeed) { + int answer = 0; + SyncMessage* msg = new SyncChannelTestMsg_Double(5, &answer); + if (pump) + msg->EnableMessagePumping(); + bool result = Send(msg); + DCHECK_EQ(result, succeed); + DCHECK_EQ(answer, (succeed ? 10 : 0)); + return result; + } + const std::string& channel_name() { return channel_name_; } + Channel::Mode mode() { return mode_; } + WaitableEvent* done_event() { return done_.get(); } + WaitableEvent* shutdown_event() { return &shutdown_event_; } + void ResetChannel() { channel_.reset(); } + // Derived classes need to call this when they've completed their part of + // the test. + void Done() { done_->Signal(); } + + protected: + SyncChannel* channel() { return channel_.get(); } + // Functions for dervied classes to implement if they wish. + virtual void Run() { } + virtual void OnAnswer(int* answer) { NOTREACHED(); } + virtual void OnAnswerDelay(Message* reply_msg) { + // The message handler map below can only take one entry for + // SyncChannelTestMsg_AnswerToLife, so since some classes want + // the normal version while other want the delayed reply, we + // call the normal version if the derived class didn't override + // this function. + int answer; + OnAnswer(&answer); + SyncChannelTestMsg_AnswerToLife::WriteReplyParams(reply_msg, answer); + Send(reply_msg); + } + virtual void OnDouble(int in, int* out) { NOTREACHED(); } + virtual void OnDoubleDelay(int in, Message* reply_msg) { + int result; + OnDouble(in, &result); + SyncChannelTestMsg_Double::WriteReplyParams(reply_msg, result); + Send(reply_msg); + } + + virtual void OnNestedTestMsg(Message* reply_msg) { + NOTREACHED(); + } + + virtual SyncChannel* CreateChannel() { + return new SyncChannel(channel_name_, + mode_, + this, + ipc_thread_.message_loop_proxy().get(), + true, + &shutdown_event_); + } + + base::Thread* ListenerThread() { + return overrided_thread_ ? overrided_thread_ : &listener_thread_; + } + + const base::Thread& ipc_thread() const { return ipc_thread_; } + + private: + // Called on the listener thread to create the sync channel. + void OnStart() { + // Link ipc_thread_, listener_thread_ and channel_ altogether. + StartThread(&ipc_thread_, base::MessageLoop::TYPE_IO); + channel_.reset(CreateChannel()); + channel_created_->Signal(); + Run(); + } + + void OnListenerThreadShutdown1(WaitableEvent* listener_event, + WaitableEvent* ipc_event) { + // SyncChannel needs to be destructed on the thread that it was created on. + channel_.reset(); + + base::RunLoop().RunUntilIdle(); + + ipc_thread_.message_loop()->PostTask( + FROM_HERE, base::Bind(&Worker::OnIPCThreadShutdown, this, + listener_event, ipc_event)); + } + + void OnIPCThreadShutdown(WaitableEvent* listener_event, + WaitableEvent* ipc_event) { + base::RunLoop().RunUntilIdle(); + ipc_event->Signal(); + + listener_thread_.message_loop()->PostTask( + FROM_HERE, base::Bind(&Worker::OnListenerThreadShutdown2, this, + listener_event)); + } + + void OnListenerThreadShutdown2(WaitableEvent* listener_event) { + base::RunLoop().RunUntilIdle(); + listener_event->Signal(); + } + + virtual bool OnMessageReceived(const Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(Worker, message) + IPC_MESSAGE_HANDLER_DELAY_REPLY(SyncChannelTestMsg_Double, OnDoubleDelay) + IPC_MESSAGE_HANDLER_DELAY_REPLY(SyncChannelTestMsg_AnswerToLife, + OnAnswerDelay) + IPC_MESSAGE_HANDLER_DELAY_REPLY(SyncChannelNestedTestMsg_String, + OnNestedTestMsg) + IPC_END_MESSAGE_MAP() + return true; + } + + void StartThread(base::Thread* thread, base::MessageLoop::Type type) { + base::Thread::Options options; + options.message_loop_type = type; + thread->StartWithOptions(options); + } + + scoped_ptr<WaitableEvent> done_; + scoped_ptr<WaitableEvent> channel_created_; + std::string channel_name_; + Channel::Mode mode_; + scoped_ptr<SyncChannel> channel_; + base::Thread ipc_thread_; + base::Thread listener_thread_; + base::Thread* overrided_thread_; + + base::WaitableEvent shutdown_event_; + + bool is_shutdown_; + + DISALLOW_COPY_AND_ASSIGN(Worker); +}; + + +// Starts the test with the given workers. This function deletes the workers +// when it's done. +void RunTest(std::vector<Worker*> workers) { + // First we create the workers that are channel servers, or else the other + // workers' channel initialization might fail because the pipe isn't created.. + for (size_t i = 0; i < workers.size(); ++i) { + if (workers[i]->mode() & Channel::MODE_SERVER_FLAG) { + workers[i]->Start(); + workers[i]->WaitForChannelCreation(); + } + } + + // now create the clients + for (size_t i = 0; i < workers.size(); ++i) { + if (workers[i]->mode() & Channel::MODE_CLIENT_FLAG) + workers[i]->Start(); + } + + // wait for all the workers to finish + for (size_t i = 0; i < workers.size(); ++i) + workers[i]->done_event()->Wait(); + + for (size_t i = 0; i < workers.size(); ++i) { + workers[i]->Shutdown(); + delete workers[i]; + } +} + +class IPCSyncChannelTest : public testing::Test { + private: + base::MessageLoop message_loop_; +}; + +//------------------------------------------------------------------------------ + +class SimpleServer : public Worker { + public: + explicit SimpleServer(bool pump_during_send) + : Worker(Channel::MODE_SERVER, "simpler_server"), + pump_during_send_(pump_during_send) { } + virtual void Run() OVERRIDE { + SendAnswerToLife(pump_during_send_, base::kNoTimeout, true); + Done(); + } + + bool pump_during_send_; +}; + +class SimpleClient : public Worker { + public: + SimpleClient() : Worker(Channel::MODE_CLIENT, "simple_client") { } + + virtual void OnAnswer(int* answer) OVERRIDE { + *answer = 42; + Done(); + } +}; + +void Simple(bool pump_during_send) { + std::vector<Worker*> workers; + workers.push_back(new SimpleServer(pump_during_send)); + workers.push_back(new SimpleClient()); + RunTest(workers); +} + +// Tests basic synchronous call +TEST_F(IPCSyncChannelTest, Simple) { + Simple(false); + Simple(true); +} + +//------------------------------------------------------------------------------ + +// Worker classes which override how the sync channel is created to use the +// two-step initialization (calling the lightweight constructor and then +// ChannelProxy::Init separately) process. +class TwoStepServer : public Worker { + public: + explicit TwoStepServer(bool create_pipe_now) + : Worker(Channel::MODE_SERVER, "simpler_server"), + create_pipe_now_(create_pipe_now) { } + + virtual void Run() OVERRIDE { + SendAnswerToLife(false, base::kNoTimeout, true); + Done(); + } + + virtual SyncChannel* CreateChannel() OVERRIDE { + SyncChannel* channel = new SyncChannel( + this, ipc_thread().message_loop_proxy().get(), shutdown_event()); + channel->Init(channel_name(), mode(), create_pipe_now_); + return channel; + } + + bool create_pipe_now_; +}; + +class TwoStepClient : public Worker { + public: + TwoStepClient(bool create_pipe_now) + : Worker(Channel::MODE_CLIENT, "simple_client"), + create_pipe_now_(create_pipe_now) { } + + virtual void OnAnswer(int* answer) OVERRIDE { + *answer = 42; + Done(); + } + + virtual SyncChannel* CreateChannel() OVERRIDE { + SyncChannel* channel = new SyncChannel( + this, ipc_thread().message_loop_proxy().get(), shutdown_event()); + channel->Init(channel_name(), mode(), create_pipe_now_); + return channel; + } + + bool create_pipe_now_; +}; + +void TwoStep(bool create_server_pipe_now, bool create_client_pipe_now) { + std::vector<Worker*> workers; + workers.push_back(new TwoStepServer(create_server_pipe_now)); + workers.push_back(new TwoStepClient(create_client_pipe_now)); + RunTest(workers); +} + +// Tests basic two-step initialization, where you call the lightweight +// constructor then Init. +TEST_F(IPCSyncChannelTest, TwoStepInitialization) { + TwoStep(false, false); + TwoStep(false, true); + TwoStep(true, false); + TwoStep(true, true); +} + +//------------------------------------------------------------------------------ + +class DelayClient : public Worker { + public: + DelayClient() : Worker(Channel::MODE_CLIENT, "delay_client") { } + + virtual void OnAnswerDelay(Message* reply_msg) OVERRIDE { + SyncChannelTestMsg_AnswerToLife::WriteReplyParams(reply_msg, 42); + Send(reply_msg); + Done(); + } +}; + +void DelayReply(bool pump_during_send) { + std::vector<Worker*> workers; + workers.push_back(new SimpleServer(pump_during_send)); + workers.push_back(new DelayClient()); + RunTest(workers); +} + +// Tests that asynchronous replies work +TEST_F(IPCSyncChannelTest, DelayReply) { + DelayReply(false); + DelayReply(true); +} + +//------------------------------------------------------------------------------ + +class NoHangServer : public Worker { + public: + NoHangServer(WaitableEvent* got_first_reply, bool pump_during_send) + : Worker(Channel::MODE_SERVER, "no_hang_server"), + got_first_reply_(got_first_reply), + pump_during_send_(pump_during_send) { } + virtual void Run() OVERRIDE { + SendAnswerToLife(pump_during_send_, base::kNoTimeout, true); + got_first_reply_->Signal(); + + SendAnswerToLife(pump_during_send_, base::kNoTimeout, false); + Done(); + } + + WaitableEvent* got_first_reply_; + bool pump_during_send_; +}; + +class NoHangClient : public Worker { + public: + explicit NoHangClient(WaitableEvent* got_first_reply) + : Worker(Channel::MODE_CLIENT, "no_hang_client"), + got_first_reply_(got_first_reply) { } + + virtual void OnAnswerDelay(Message* reply_msg) OVERRIDE { + // Use the DELAY_REPLY macro so that we can force the reply to be sent + // before this function returns (when the channel will be reset). + SyncChannelTestMsg_AnswerToLife::WriteReplyParams(reply_msg, 42); + Send(reply_msg); + got_first_reply_->Wait(); + CloseChannel(); + Done(); + } + + WaitableEvent* got_first_reply_; +}; + +void NoHang(bool pump_during_send) { + WaitableEvent got_first_reply(false, false); + std::vector<Worker*> workers; + workers.push_back(new NoHangServer(&got_first_reply, pump_during_send)); + workers.push_back(new NoHangClient(&got_first_reply)); + RunTest(workers); +} + +// Tests that caller doesn't hang if receiver dies +TEST_F(IPCSyncChannelTest, NoHang) { + NoHang(false); + NoHang(true); +} + +//------------------------------------------------------------------------------ + +class UnblockServer : public Worker { + public: + UnblockServer(bool pump_during_send, bool delete_during_send) + : Worker(Channel::MODE_SERVER, "unblock_server"), + pump_during_send_(pump_during_send), + delete_during_send_(delete_during_send) { } + virtual void Run() OVERRIDE { + if (delete_during_send_) { + // Use custom code since race conditions mean the answer may or may not be + // available. + int answer = 0; + SyncMessage* msg = new SyncChannelTestMsg_AnswerToLife(&answer); + if (pump_during_send_) + msg->EnableMessagePumping(); + Send(msg); + } else { + SendAnswerToLife(pump_during_send_, base::kNoTimeout, true); + } + Done(); + } + + virtual void OnDoubleDelay(int in, Message* reply_msg) OVERRIDE { + SyncChannelTestMsg_Double::WriteReplyParams(reply_msg, in * 2); + Send(reply_msg); + if (delete_during_send_) + ResetChannel(); + } + + bool pump_during_send_; + bool delete_during_send_; +}; + +class UnblockClient : public Worker { + public: + explicit UnblockClient(bool pump_during_send) + : Worker(Channel::MODE_CLIENT, "unblock_client"), + pump_during_send_(pump_during_send) { } + + virtual void OnAnswer(int* answer) OVERRIDE { + SendDouble(pump_during_send_, true); + *answer = 42; + Done(); + } + + bool pump_during_send_; +}; + +void Unblock(bool server_pump, bool client_pump, bool delete_during_send) { + std::vector<Worker*> workers; + workers.push_back(new UnblockServer(server_pump, delete_during_send)); + workers.push_back(new UnblockClient(client_pump)); + RunTest(workers); +} + +// Tests that the caller unblocks to answer a sync message from the receiver. +TEST_F(IPCSyncChannelTest, Unblock) { + Unblock(false, false, false); + Unblock(false, true, false); + Unblock(true, false, false); + Unblock(true, true, false); +} + +//------------------------------------------------------------------------------ + +// Tests that the the SyncChannel object can be deleted during a Send. +TEST_F(IPCSyncChannelTest, ChannelDeleteDuringSend) { + Unblock(false, false, true); + Unblock(false, true, true); + Unblock(true, false, true); + Unblock(true, true, true); +} + +//------------------------------------------------------------------------------ + +class RecursiveServer : public Worker { + public: + RecursiveServer(bool expected_send_result, bool pump_first, bool pump_second) + : Worker(Channel::MODE_SERVER, "recursive_server"), + expected_send_result_(expected_send_result), + pump_first_(pump_first), pump_second_(pump_second) {} + virtual void Run() OVERRIDE { + SendDouble(pump_first_, expected_send_result_); + Done(); + } + + virtual void OnDouble(int in, int* out) OVERRIDE { + *out = in * 2; + SendAnswerToLife(pump_second_, base::kNoTimeout, expected_send_result_); + } + + bool expected_send_result_, pump_first_, pump_second_; +}; + +class RecursiveClient : public Worker { + public: + RecursiveClient(bool pump_during_send, bool close_channel) + : Worker(Channel::MODE_CLIENT, "recursive_client"), + pump_during_send_(pump_during_send), close_channel_(close_channel) {} + + virtual void OnDoubleDelay(int in, Message* reply_msg) OVERRIDE { + SendDouble(pump_during_send_, !close_channel_); + if (close_channel_) { + delete reply_msg; + } else { + SyncChannelTestMsg_Double::WriteReplyParams(reply_msg, in * 2); + Send(reply_msg); + } + Done(); + } + + virtual void OnAnswerDelay(Message* reply_msg) OVERRIDE { + if (close_channel_) { + delete reply_msg; + CloseChannel(); + } else { + SyncChannelTestMsg_AnswerToLife::WriteReplyParams(reply_msg, 42); + Send(reply_msg); + } + } + + bool pump_during_send_, close_channel_; +}; + +void Recursive( + bool server_pump_first, bool server_pump_second, bool client_pump) { + std::vector<Worker*> workers; + workers.push_back( + new RecursiveServer(true, server_pump_first, server_pump_second)); + workers.push_back(new RecursiveClient(client_pump, false)); + RunTest(workers); +} + +// Tests a server calling Send while another Send is pending. +TEST_F(IPCSyncChannelTest, Recursive) { + Recursive(false, false, false); + Recursive(false, false, true); + Recursive(false, true, false); + Recursive(false, true, true); + Recursive(true, false, false); + Recursive(true, false, true); + Recursive(true, true, false); + Recursive(true, true, true); +} + +//------------------------------------------------------------------------------ + +void RecursiveNoHang( + bool server_pump_first, bool server_pump_second, bool client_pump) { + std::vector<Worker*> workers; + workers.push_back( + new RecursiveServer(false, server_pump_first, server_pump_second)); + workers.push_back(new RecursiveClient(client_pump, true)); + RunTest(workers); +} + +// Tests that if a caller makes a sync call during an existing sync call and +// the receiver dies, neither of the Send() calls hang. +TEST_F(IPCSyncChannelTest, RecursiveNoHang) { + RecursiveNoHang(false, false, false); + RecursiveNoHang(false, false, true); + RecursiveNoHang(false, true, false); + RecursiveNoHang(false, true, true); + RecursiveNoHang(true, false, false); + RecursiveNoHang(true, false, true); + RecursiveNoHang(true, true, false); + RecursiveNoHang(true, true, true); +} + +//------------------------------------------------------------------------------ + +class MultipleServer1 : public Worker { + public: + explicit MultipleServer1(bool pump_during_send) + : Worker("test_channel1", Channel::MODE_SERVER), + pump_during_send_(pump_during_send) { } + + virtual void Run() OVERRIDE { + SendDouble(pump_during_send_, true); + Done(); + } + + bool pump_during_send_; +}; + +class MultipleClient1 : public Worker { + public: + MultipleClient1(WaitableEvent* client1_msg_received, + WaitableEvent* client1_can_reply) : + Worker("test_channel1", Channel::MODE_CLIENT), + client1_msg_received_(client1_msg_received), + client1_can_reply_(client1_can_reply) { } + + virtual void OnDouble(int in, int* out) OVERRIDE { + client1_msg_received_->Signal(); + *out = in * 2; + client1_can_reply_->Wait(); + Done(); + } + + private: + WaitableEvent *client1_msg_received_, *client1_can_reply_; +}; + +class MultipleServer2 : public Worker { + public: + MultipleServer2() : Worker("test_channel2", Channel::MODE_SERVER) { } + + virtual void OnAnswer(int* result) OVERRIDE { + *result = 42; + Done(); + } +}; + +class MultipleClient2 : public Worker { + public: + MultipleClient2( + WaitableEvent* client1_msg_received, WaitableEvent* client1_can_reply, + bool pump_during_send) + : Worker("test_channel2", Channel::MODE_CLIENT), + client1_msg_received_(client1_msg_received), + client1_can_reply_(client1_can_reply), + pump_during_send_(pump_during_send) { } + + virtual void Run() OVERRIDE { + client1_msg_received_->Wait(); + SendAnswerToLife(pump_during_send_, base::kNoTimeout, true); + client1_can_reply_->Signal(); + Done(); + } + + private: + WaitableEvent *client1_msg_received_, *client1_can_reply_; + bool pump_during_send_; +}; + +void Multiple(bool server_pump, bool client_pump) { + std::vector<Worker*> workers; + + // A shared worker thread so that server1 and server2 run on one thread. + base::Thread worker_thread("Multiple"); + ASSERT_TRUE(worker_thread.Start()); + + // Server1 sends a sync msg to client1, which blocks the reply until + // server2 (which runs on the same worker thread as server1) responds + // to a sync msg from client2. + WaitableEvent client1_msg_received(false, false); + WaitableEvent client1_can_reply(false, false); + + Worker* worker; + + worker = new MultipleServer2(); + worker->OverrideThread(&worker_thread); + workers.push_back(worker); + + worker = new MultipleClient2( + &client1_msg_received, &client1_can_reply, client_pump); + workers.push_back(worker); + + worker = new MultipleServer1(server_pump); + worker->OverrideThread(&worker_thread); + workers.push_back(worker); + + worker = new MultipleClient1( + &client1_msg_received, &client1_can_reply); + workers.push_back(worker); + + RunTest(workers); +} + +// Tests that multiple SyncObjects on the same listener thread can unblock each +// other. +TEST_F(IPCSyncChannelTest, Multiple) { + Multiple(false, false); + Multiple(false, true); + Multiple(true, false); + Multiple(true, true); +} + +//------------------------------------------------------------------------------ + +// This class provides server side functionality to test the case where +// multiple sync channels are in use on the same thread on the client and +// nested calls are issued. +class QueuedReplyServer : public Worker { + public: + QueuedReplyServer(base::Thread* listener_thread, + const std::string& channel_name, + const std::string& reply_text) + : Worker(channel_name, Channel::MODE_SERVER), + reply_text_(reply_text) { + Worker::OverrideThread(listener_thread); + } + + virtual void OnNestedTestMsg(Message* reply_msg) OVERRIDE { + VLOG(1) << __FUNCTION__ << " Sending reply: " << reply_text_; + SyncChannelNestedTestMsg_String::WriteReplyParams(reply_msg, reply_text_); + Send(reply_msg); + Done(); + } + + private: + std::string reply_text_; +}; + +// The QueuedReplyClient class provides functionality to test the case where +// multiple sync channels are in use on the same thread and they make nested +// sync calls, i.e. while the first channel waits for a response it makes a +// sync call on another channel. +// The callstack should unwind correctly, i.e. the outermost call should +// complete first, and so on. +class QueuedReplyClient : public Worker { + public: + QueuedReplyClient(base::Thread* listener_thread, + const std::string& channel_name, + const std::string& expected_text, + bool pump_during_send) + : Worker(channel_name, Channel::MODE_CLIENT), + pump_during_send_(pump_during_send), + expected_text_(expected_text) { + Worker::OverrideThread(listener_thread); + } + + virtual void Run() OVERRIDE { + std::string response; + SyncMessage* msg = new SyncChannelNestedTestMsg_String(&response); + if (pump_during_send_) + msg->EnableMessagePumping(); + bool result = Send(msg); + DCHECK(result); + DCHECK_EQ(response, expected_text_); + + VLOG(1) << __FUNCTION__ << " Received reply: " << response; + Done(); + } + + private: + bool pump_during_send_; + std::string expected_text_; +}; + +void QueuedReply(bool client_pump) { + std::vector<Worker*> workers; + + // A shared worker thread for servers + base::Thread server_worker_thread("QueuedReply_ServerListener"); + ASSERT_TRUE(server_worker_thread.Start()); + + base::Thread client_worker_thread("QueuedReply_ClientListener"); + ASSERT_TRUE(client_worker_thread.Start()); + + Worker* worker; + + worker = new QueuedReplyServer(&server_worker_thread, + "QueuedReply_Server1", + "Got first message"); + workers.push_back(worker); + + worker = new QueuedReplyServer(&server_worker_thread, + "QueuedReply_Server2", + "Got second message"); + workers.push_back(worker); + + worker = new QueuedReplyClient(&client_worker_thread, + "QueuedReply_Server1", + "Got first message", + client_pump); + workers.push_back(worker); + + worker = new QueuedReplyClient(&client_worker_thread, + "QueuedReply_Server2", + "Got second message", + client_pump); + workers.push_back(worker); + + RunTest(workers); +} + +// While a blocking send is in progress, the listener thread might answer other +// synchronous messages. This tests that if during the response to another +// message the reply to the original messages comes, it is queued up correctly +// and the original Send is unblocked later. +// We also test that the send call stacks unwind correctly when the channel +// pumps messages while waiting for a response. +TEST_F(IPCSyncChannelTest, QueuedReply) { + QueuedReply(false); + QueuedReply(true); +} + +//------------------------------------------------------------------------------ + +class ChattyClient : public Worker { + public: + ChattyClient() : + Worker(Channel::MODE_CLIENT, "chatty_client") { } + + virtual void OnAnswer(int* answer) OVERRIDE { + // The PostMessage limit is 10k. Send 20% more than that. + const int kMessageLimit = 10000; + const int kMessagesToSend = kMessageLimit * 120 / 100; + for (int i = 0; i < kMessagesToSend; ++i) { + if (!SendDouble(false, true)) + break; + } + *answer = 42; + Done(); + } +}; + +void ChattyServer(bool pump_during_send) { + std::vector<Worker*> workers; + workers.push_back(new UnblockServer(pump_during_send, false)); + workers.push_back(new ChattyClient()); + RunTest(workers); +} + +// Tests http://b/1093251 - that sending lots of sync messages while +// the receiver is waiting for a sync reply does not overflow the PostMessage +// queue. +TEST_F(IPCSyncChannelTest, ChattyServer) { + ChattyServer(false); + ChattyServer(true); +} + +//------------------------------------------------------------------------------ + +class TimeoutServer : public Worker { + public: + TimeoutServer(int timeout_ms, + std::vector<bool> timeout_seq, + bool pump_during_send) + : Worker(Channel::MODE_SERVER, "timeout_server"), + timeout_ms_(timeout_ms), + timeout_seq_(timeout_seq), + pump_during_send_(pump_during_send) { + } + + virtual void Run() OVERRIDE { + for (std::vector<bool>::const_iterator iter = timeout_seq_.begin(); + iter != timeout_seq_.end(); ++iter) { + SendAnswerToLife(pump_during_send_, timeout_ms_, !*iter); + } + Done(); + } + + private: + int timeout_ms_; + std::vector<bool> timeout_seq_; + bool pump_during_send_; +}; + +class UnresponsiveClient : public Worker { + public: + explicit UnresponsiveClient(std::vector<bool> timeout_seq) + : Worker(Channel::MODE_CLIENT, "unresponsive_client"), + timeout_seq_(timeout_seq) { + } + + virtual void OnAnswerDelay(Message* reply_msg) OVERRIDE { + DCHECK(!timeout_seq_.empty()); + if (!timeout_seq_[0]) { + SyncChannelTestMsg_AnswerToLife::WriteReplyParams(reply_msg, 42); + Send(reply_msg); + } else { + // Don't reply. + delete reply_msg; + } + timeout_seq_.erase(timeout_seq_.begin()); + if (timeout_seq_.empty()) + Done(); + } + + private: + // Whether we should time-out or respond to the various messages we receive. + std::vector<bool> timeout_seq_; +}; + +void SendWithTimeoutOK(bool pump_during_send) { + std::vector<Worker*> workers; + std::vector<bool> timeout_seq; + timeout_seq.push_back(false); + timeout_seq.push_back(false); + timeout_seq.push_back(false); + workers.push_back(new TimeoutServer(5000, timeout_seq, pump_during_send)); + workers.push_back(new SimpleClient()); + RunTest(workers); +} + +void SendWithTimeoutTimeout(bool pump_during_send) { + std::vector<Worker*> workers; + std::vector<bool> timeout_seq; + timeout_seq.push_back(true); + timeout_seq.push_back(false); + timeout_seq.push_back(false); + workers.push_back(new TimeoutServer(100, timeout_seq, pump_during_send)); + workers.push_back(new UnresponsiveClient(timeout_seq)); + RunTest(workers); +} + +void SendWithTimeoutMixedOKAndTimeout(bool pump_during_send) { + std::vector<Worker*> workers; + std::vector<bool> timeout_seq; + timeout_seq.push_back(true); + timeout_seq.push_back(false); + timeout_seq.push_back(false); + timeout_seq.push_back(true); + timeout_seq.push_back(false); + workers.push_back(new TimeoutServer(100, timeout_seq, pump_during_send)); + workers.push_back(new UnresponsiveClient(timeout_seq)); + RunTest(workers); +} + +// Tests that SendWithTimeout does not time-out if the response comes back fast +// enough. +TEST_F(IPCSyncChannelTest, SendWithTimeoutOK) { + SendWithTimeoutOK(false); + SendWithTimeoutOK(true); +} + +// Tests that SendWithTimeout does time-out. +TEST_F(IPCSyncChannelTest, SendWithTimeoutTimeout) { + SendWithTimeoutTimeout(false); + SendWithTimeoutTimeout(true); +} + +// Sends some message that time-out and some that succeed. +TEST_F(IPCSyncChannelTest, SendWithTimeoutMixedOKAndTimeout) { + SendWithTimeoutMixedOKAndTimeout(false); + SendWithTimeoutMixedOKAndTimeout(true); +} + +//------------------------------------------------------------------------------ + +void NestedCallback(Worker* server) { + // Sleep a bit so that we wake up after the reply has been received. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(250)); + server->SendAnswerToLife(true, base::kNoTimeout, true); +} + +bool timeout_occurred = false; + +void TimeoutCallback() { + timeout_occurred = true; +} + +class DoneEventRaceServer : public Worker { + public: + DoneEventRaceServer() + : Worker(Channel::MODE_SERVER, "done_event_race_server") { } + + virtual void Run() OVERRIDE { + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&NestedCallback, this)); + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&TimeoutCallback), + base::TimeDelta::FromSeconds(9)); + // Even though we have a timeout on the Send, it will succeed since for this + // bug, the reply message comes back and is deserialized, however the done + // event wasn't set. So we indirectly use the timeout task to notice if a + // timeout occurred. + SendAnswerToLife(true, 10000, true); + DCHECK(!timeout_occurred); + Done(); + } +}; + +// Tests http://b/1474092 - that if after the done_event is set but before +// OnObjectSignaled is called another message is sent out, then after its +// reply comes back OnObjectSignaled will be called for the first message. +TEST_F(IPCSyncChannelTest, DoneEventRace) { + std::vector<Worker*> workers; + workers.push_back(new DoneEventRaceServer()); + workers.push_back(new SimpleClient()); + RunTest(workers); +} + +//------------------------------------------------------------------------------ + +class TestSyncMessageFilter : public SyncMessageFilter { + public: + TestSyncMessageFilter(base::WaitableEvent* shutdown_event, + Worker* worker, + scoped_refptr<base::MessageLoopProxy> message_loop) + : SyncMessageFilter(shutdown_event), + worker_(worker), + message_loop_(message_loop) { + } + + virtual void OnFilterAdded(Channel* channel) OVERRIDE { + SyncMessageFilter::OnFilterAdded(channel); + message_loop_->PostTask( + FROM_HERE, + base::Bind(&TestSyncMessageFilter::SendMessageOnHelperThread, this)); + } + + void SendMessageOnHelperThread() { + int answer = 0; + bool result = Send(new SyncChannelTestMsg_AnswerToLife(&answer)); + DCHECK(result); + DCHECK_EQ(answer, 42); + + worker_->Done(); + } + + private: + virtual ~TestSyncMessageFilter() {} + + Worker* worker_; + scoped_refptr<base::MessageLoopProxy> message_loop_; +}; + +class SyncMessageFilterServer : public Worker { + public: + SyncMessageFilterServer() + : Worker(Channel::MODE_SERVER, "sync_message_filter_server"), + thread_("helper_thread") { + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_DEFAULT; + thread_.StartWithOptions(options); + filter_ = new TestSyncMessageFilter(shutdown_event(), this, + thread_.message_loop_proxy()); + } + + virtual void Run() OVERRIDE { + channel()->AddFilter(filter_.get()); + } + + base::Thread thread_; + scoped_refptr<TestSyncMessageFilter> filter_; +}; + +// This class provides functionality to test the case that a Send on the sync +// channel does not crash after the channel has been closed. +class ServerSendAfterClose : public Worker { + public: + ServerSendAfterClose() + : Worker(Channel::MODE_SERVER, "simpler_server"), + send_result_(true) { + } + + bool SendDummy() { + ListenerThread()->message_loop()->PostTask( + FROM_HERE, base::Bind(base::IgnoreResult(&ServerSendAfterClose::Send), + this, new SyncChannelTestMsg_NoArgs)); + return true; + } + + bool send_result() const { + return send_result_; + } + + private: + virtual void Run() OVERRIDE { + CloseChannel(); + Done(); + } + + virtual bool Send(Message* msg) OVERRIDE { + send_result_ = Worker::Send(msg); + Done(); + return send_result_; + } + + bool send_result_; +}; + +// Tests basic synchronous call +TEST_F(IPCSyncChannelTest, SyncMessageFilter) { + std::vector<Worker*> workers; + workers.push_back(new SyncMessageFilterServer()); + workers.push_back(new SimpleClient()); + RunTest(workers); +} + +// Test the case when the channel is closed and a Send is attempted after that. +TEST_F(IPCSyncChannelTest, SendAfterClose) { + ServerSendAfterClose server; + server.Start(); + + server.done_event()->Wait(); + server.done_event()->Reset(); + + server.SendDummy(); + server.done_event()->Wait(); + + EXPECT_FALSE(server.send_result()); + + server.Shutdown(); +} + +//------------------------------------------------------------------------------ + +class RestrictedDispatchServer : public Worker { + public: + RestrictedDispatchServer(WaitableEvent* sent_ping_event, + WaitableEvent* wait_event) + : Worker("restricted_channel", Channel::MODE_SERVER), + sent_ping_event_(sent_ping_event), + wait_event_(wait_event) { } + + void OnDoPing(int ping) { + // Send an asynchronous message that unblocks the caller. + Message* msg = new SyncChannelTestMsg_Ping(ping); + msg->set_unblock(true); + Send(msg); + // Signal the event after the message has been sent on the channel, on the + // IPC thread. + ipc_thread().message_loop()->PostTask( + FROM_HERE, base::Bind(&RestrictedDispatchServer::OnPingSent, this)); + } + + void OnPingTTL(int ping, int* out) { + *out = ping; + wait_event_->Wait(); + } + + base::Thread* ListenerThread() { return Worker::ListenerThread(); } + + private: + virtual bool OnMessageReceived(const Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchServer, message) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_PingTTL, OnPingTTL) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, Done) + IPC_END_MESSAGE_MAP() + return true; + } + + void OnPingSent() { + sent_ping_event_->Signal(); + } + + void OnNoArgs() { } + WaitableEvent* sent_ping_event_; + WaitableEvent* wait_event_; +}; + +class NonRestrictedDispatchServer : public Worker { + public: + NonRestrictedDispatchServer(WaitableEvent* signal_event) + : Worker("non_restricted_channel", Channel::MODE_SERVER), + signal_event_(signal_event) {} + + base::Thread* ListenerThread() { return Worker::ListenerThread(); } + + void OnDoPingTTL(int ping) { + int value = 0; + Send(new SyncChannelTestMsg_PingTTL(ping, &value)); + signal_event_->Signal(); + } + + private: + virtual bool OnMessageReceived(const Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(NonRestrictedDispatchServer, message) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, Done) + IPC_END_MESSAGE_MAP() + return true; + } + + void OnNoArgs() { } + WaitableEvent* signal_event_; +}; + +class RestrictedDispatchClient : public Worker { + public: + RestrictedDispatchClient(WaitableEvent* sent_ping_event, + RestrictedDispatchServer* server, + NonRestrictedDispatchServer* server2, + int* success) + : Worker("restricted_channel", Channel::MODE_CLIENT), + ping_(0), + server_(server), + server2_(server2), + success_(success), + sent_ping_event_(sent_ping_event) {} + + virtual void Run() OVERRIDE { + // Incoming messages from our channel should only be dispatched when we + // send a message on that same channel. + channel()->SetRestrictDispatchChannelGroup(1); + + server_->ListenerThread()->message_loop()->PostTask( + FROM_HERE, base::Bind(&RestrictedDispatchServer::OnDoPing, server_, 1)); + sent_ping_event_->Wait(); + Send(new SyncChannelTestMsg_NoArgs); + if (ping_ == 1) + ++*success_; + else + LOG(ERROR) << "Send failed to dispatch incoming message on same channel"; + + non_restricted_channel_.reset( + new SyncChannel("non_restricted_channel", + Channel::MODE_CLIENT, + this, + ipc_thread().message_loop_proxy().get(), + true, + shutdown_event())); + + server_->ListenerThread()->message_loop()->PostTask( + FROM_HERE, base::Bind(&RestrictedDispatchServer::OnDoPing, server_, 2)); + sent_ping_event_->Wait(); + // Check that the incoming message is *not* dispatched when sending on the + // non restricted channel. + // TODO(piman): there is a possibility of a false positive race condition + // here, if the message that was posted on the server-side end of the pipe + // is not visible yet on the client side, but I don't know how to solve this + // without hooking into the internals of SyncChannel. I haven't seen it in + // practice (i.e. not setting SetRestrictDispatchToSameChannel does cause + // the following to fail). + non_restricted_channel_->Send(new SyncChannelTestMsg_NoArgs); + if (ping_ == 1) + ++*success_; + else + LOG(ERROR) << "Send dispatched message from restricted channel"; + + Send(new SyncChannelTestMsg_NoArgs); + if (ping_ == 2) + ++*success_; + else + LOG(ERROR) << "Send failed to dispatch incoming message on same channel"; + + // Check that the incoming message on the non-restricted channel is + // dispatched when sending on the restricted channel. + server2_->ListenerThread()->message_loop()->PostTask( + FROM_HERE, + base::Bind(&NonRestrictedDispatchServer::OnDoPingTTL, server2_, 3)); + int value = 0; + Send(new SyncChannelTestMsg_PingTTL(4, &value)); + if (ping_ == 3 && value == 4) + ++*success_; + else + LOG(ERROR) << "Send failed to dispatch message from unrestricted channel"; + + non_restricted_channel_->Send(new SyncChannelTestMsg_Done); + non_restricted_channel_.reset(); + Send(new SyncChannelTestMsg_Done); + Done(); + } + + private: + virtual bool OnMessageReceived(const Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchClient, message) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Ping, OnPing) + IPC_MESSAGE_HANDLER_DELAY_REPLY(SyncChannelTestMsg_PingTTL, OnPingTTL) + IPC_END_MESSAGE_MAP() + return true; + } + + void OnPing(int ping) { + ping_ = ping; + } + + void OnPingTTL(int ping, IPC::Message* reply) { + ping_ = ping; + // This message comes from the NonRestrictedDispatchServer, we have to send + // the reply back manually. + SyncChannelTestMsg_PingTTL::WriteReplyParams(reply, ping); + non_restricted_channel_->Send(reply); + } + + int ping_; + RestrictedDispatchServer* server_; + NonRestrictedDispatchServer* server2_; + int* success_; + WaitableEvent* sent_ping_event_; + scoped_ptr<SyncChannel> non_restricted_channel_; +}; + +TEST_F(IPCSyncChannelTest, RestrictedDispatch) { + WaitableEvent sent_ping_event(false, false); + WaitableEvent wait_event(false, false); + RestrictedDispatchServer* server = + new RestrictedDispatchServer(&sent_ping_event, &wait_event); + NonRestrictedDispatchServer* server2 = + new NonRestrictedDispatchServer(&wait_event); + + int success = 0; + std::vector<Worker*> workers; + workers.push_back(server); + workers.push_back(server2); + workers.push_back(new RestrictedDispatchClient( + &sent_ping_event, server, server2, &success)); + RunTest(workers); + EXPECT_EQ(4, success); +} + +//------------------------------------------------------------------------------ + +// This test case inspired by crbug.com/108491 +// We create two servers that use the same ListenerThread but have +// SetRestrictDispatchToSameChannel set to true. +// We create clients, then use some specific WaitableEvent wait/signalling to +// ensure that messages get dispatched in a way that causes a deadlock due to +// a nested dispatch and an eligible message in a higher-level dispatch's +// delayed_queue. Specifically, we start with client1 about so send an +// unblocking message to server1, while the shared listener thread for the +// servers server1 and server2 is about to send a non-unblocking message to +// client1. At the same time, client2 will be about to send an unblocking +// message to server2. Server1 will handle the client1->server1 message by +// telling server2 to send a non-unblocking message to client2. +// What should happen is that the send to server2 should find the pending, +// same-context client2->server2 message to dispatch, causing client2 to +// unblock then handle the server2->client2 message, so that the shared +// servers' listener thread can then respond to the client1->server1 message. +// Then client1 can handle the non-unblocking server1->client1 message. +// The old code would end up in a state where the server2->client2 message is +// sent, but the client2->server2 message (which is eligible for dispatch, and +// which is what client2 is waiting for) is stashed in a local delayed_queue +// that has server1's channel context, causing a deadlock. +// WaitableEvents in the events array are used to: +// event 0: indicate to client1 that server listener is in OnDoServerTask +// event 1: indicate to client1 that client2 listener is in OnDoClient2Task +// event 2: indicate to server1 that client2 listener is in OnDoClient2Task +// event 3: indicate to client2 that server listener is in OnDoServerTask + +class RestrictedDispatchDeadlockServer : public Worker { + public: + RestrictedDispatchDeadlockServer(int server_num, + WaitableEvent* server_ready_event, + WaitableEvent** events, + RestrictedDispatchDeadlockServer* peer) + : Worker(server_num == 1 ? "channel1" : "channel2", Channel::MODE_SERVER), + server_num_(server_num), + server_ready_event_(server_ready_event), + events_(events), + peer_(peer) { } + + void OnDoServerTask() { + events_[3]->Signal(); + events_[2]->Wait(); + events_[0]->Signal(); + SendMessageToClient(); + } + + virtual void Run() OVERRIDE { + channel()->SetRestrictDispatchChannelGroup(1); + server_ready_event_->Signal(); + } + + base::Thread* ListenerThread() { return Worker::ListenerThread(); } + + private: + virtual bool OnMessageReceived(const Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchDeadlockServer, message) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, Done) + IPC_END_MESSAGE_MAP() + return true; + } + + void OnNoArgs() { + if (server_num_ == 1) { + DCHECK(peer_ != NULL); + peer_->SendMessageToClient(); + } + } + + void SendMessageToClient() { + Message* msg = new SyncChannelTestMsg_NoArgs; + msg->set_unblock(false); + DCHECK(!msg->should_unblock()); + Send(msg); + } + + int server_num_; + WaitableEvent* server_ready_event_; + WaitableEvent** events_; + RestrictedDispatchDeadlockServer* peer_; +}; + +class RestrictedDispatchDeadlockClient2 : public Worker { + public: + RestrictedDispatchDeadlockClient2(RestrictedDispatchDeadlockServer* server, + WaitableEvent* server_ready_event, + WaitableEvent** events) + : Worker("channel2", Channel::MODE_CLIENT), + server_ready_event_(server_ready_event), + events_(events), + received_msg_(false), + received_noarg_reply_(false), + done_issued_(false) {} + + virtual void Run() OVERRIDE { + server_ready_event_->Wait(); + } + + void OnDoClient2Task() { + events_[3]->Wait(); + events_[1]->Signal(); + events_[2]->Signal(); + DCHECK(received_msg_ == false); + + Message* message = new SyncChannelTestMsg_NoArgs; + message->set_unblock(true); + Send(message); + received_noarg_reply_ = true; + } + + base::Thread* ListenerThread() { return Worker::ListenerThread(); } + private: + virtual bool OnMessageReceived(const Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchDeadlockClient2, message) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs) + IPC_END_MESSAGE_MAP() + return true; + } + + void OnNoArgs() { + received_msg_ = true; + PossiblyDone(); + } + + void PossiblyDone() { + if (received_noarg_reply_ && received_msg_) { + DCHECK(done_issued_ == false); + done_issued_ = true; + Send(new SyncChannelTestMsg_Done); + Done(); + } + } + + WaitableEvent* server_ready_event_; + WaitableEvent** events_; + bool received_msg_; + bool received_noarg_reply_; + bool done_issued_; +}; + +class RestrictedDispatchDeadlockClient1 : public Worker { + public: + RestrictedDispatchDeadlockClient1(RestrictedDispatchDeadlockServer* server, + RestrictedDispatchDeadlockClient2* peer, + WaitableEvent* server_ready_event, + WaitableEvent** events) + : Worker("channel1", Channel::MODE_CLIENT), + server_(server), + peer_(peer), + server_ready_event_(server_ready_event), + events_(events), + received_msg_(false), + received_noarg_reply_(false), + done_issued_(false) {} + + virtual void Run() OVERRIDE { + server_ready_event_->Wait(); + server_->ListenerThread()->message_loop()->PostTask( + FROM_HERE, + base::Bind(&RestrictedDispatchDeadlockServer::OnDoServerTask, server_)); + peer_->ListenerThread()->message_loop()->PostTask( + FROM_HERE, + base::Bind(&RestrictedDispatchDeadlockClient2::OnDoClient2Task, peer_)); + events_[0]->Wait(); + events_[1]->Wait(); + DCHECK(received_msg_ == false); + + Message* message = new SyncChannelTestMsg_NoArgs; + message->set_unblock(true); + Send(message); + received_noarg_reply_ = true; + PossiblyDone(); + } + + base::Thread* ListenerThread() { return Worker::ListenerThread(); } + private: + virtual bool OnMessageReceived(const Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchDeadlockClient1, message) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs) + IPC_END_MESSAGE_MAP() + return true; + } + + void OnNoArgs() { + received_msg_ = true; + PossiblyDone(); + } + + void PossiblyDone() { + if (received_noarg_reply_ && received_msg_) { + DCHECK(done_issued_ == false); + done_issued_ = true; + Send(new SyncChannelTestMsg_Done); + Done(); + } + } + + RestrictedDispatchDeadlockServer* server_; + RestrictedDispatchDeadlockClient2* peer_; + WaitableEvent* server_ready_event_; + WaitableEvent** events_; + bool received_msg_; + bool received_noarg_reply_; + bool done_issued_; +}; + +TEST_F(IPCSyncChannelTest, RestrictedDispatchDeadlock) { + std::vector<Worker*> workers; + + // A shared worker thread so that server1 and server2 run on one thread. + base::Thread worker_thread("RestrictedDispatchDeadlock"); + ASSERT_TRUE(worker_thread.Start()); + + WaitableEvent server1_ready(false, false); + WaitableEvent server2_ready(false, false); + + WaitableEvent event0(false, false); + WaitableEvent event1(false, false); + WaitableEvent event2(false, false); + WaitableEvent event3(false, false); + WaitableEvent* events[4] = {&event0, &event1, &event2, &event3}; + + RestrictedDispatchDeadlockServer* server1; + RestrictedDispatchDeadlockServer* server2; + RestrictedDispatchDeadlockClient1* client1; + RestrictedDispatchDeadlockClient2* client2; + + server2 = new RestrictedDispatchDeadlockServer(2, &server2_ready, events, + NULL); + server2->OverrideThread(&worker_thread); + workers.push_back(server2); + + client2 = new RestrictedDispatchDeadlockClient2(server2, &server2_ready, + events); + workers.push_back(client2); + + server1 = new RestrictedDispatchDeadlockServer(1, &server1_ready, events, + server2); + server1->OverrideThread(&worker_thread); + workers.push_back(server1); + + client1 = new RestrictedDispatchDeadlockClient1(server1, client2, + &server1_ready, events); + workers.push_back(client1); + + RunTest(workers); +} + +//------------------------------------------------------------------------------ + +// This test case inspired by crbug.com/120530 +// We create 4 workers that pipe to each other W1->W2->W3->W4->W1 then we send a +// message that recurses through 3, 4 or 5 steps to make sure, say, W1 can +// re-enter when called from W4 while it's sending a message to W2. +// The first worker drives the whole test so it must be treated specially. + +class RestrictedDispatchPipeWorker : public Worker { + public: + RestrictedDispatchPipeWorker( + const std::string &channel1, + WaitableEvent* event1, + const std::string &channel2, + WaitableEvent* event2, + int group, + int* success) + : Worker(channel1, Channel::MODE_SERVER), + event1_(event1), + event2_(event2), + other_channel_name_(channel2), + group_(group), + success_(success) { + } + + void OnPingTTL(int ping, int* ret) { + *ret = 0; + if (!ping) + return; + other_channel_->Send(new SyncChannelTestMsg_PingTTL(ping - 1, ret)); + ++*ret; + } + + void OnDone() { + if (is_first()) + return; + other_channel_->Send(new SyncChannelTestMsg_Done); + other_channel_.reset(); + Done(); + } + + virtual void Run() OVERRIDE { + channel()->SetRestrictDispatchChannelGroup(group_); + if (is_first()) + event1_->Signal(); + event2_->Wait(); + other_channel_.reset( + new SyncChannel(other_channel_name_, + Channel::MODE_CLIENT, + this, + ipc_thread().message_loop_proxy().get(), + true, + shutdown_event())); + other_channel_->SetRestrictDispatchChannelGroup(group_); + if (!is_first()) { + event1_->Signal(); + return; + } + *success_ = 0; + int value = 0; + OnPingTTL(3, &value); + *success_ += (value == 3); + OnPingTTL(4, &value); + *success_ += (value == 4); + OnPingTTL(5, &value); + *success_ += (value == 5); + other_channel_->Send(new SyncChannelTestMsg_Done); + other_channel_.reset(); + Done(); + } + + bool is_first() { return !!success_; } + + private: + virtual bool OnMessageReceived(const Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchPipeWorker, message) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_PingTTL, OnPingTTL) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, OnDone) + IPC_END_MESSAGE_MAP() + return true; + } + + scoped_ptr<SyncChannel> other_channel_; + WaitableEvent* event1_; + WaitableEvent* event2_; + std::string other_channel_name_; + int group_; + int* success_; +}; + +TEST_F(IPCSyncChannelTest, RestrictedDispatch4WayDeadlock) { + int success = 0; + std::vector<Worker*> workers; + WaitableEvent event0(true, false); + WaitableEvent event1(true, false); + WaitableEvent event2(true, false); + WaitableEvent event3(true, false); + workers.push_back(new RestrictedDispatchPipeWorker( + "channel0", &event0, "channel1", &event1, 1, &success)); + workers.push_back(new RestrictedDispatchPipeWorker( + "channel1", &event1, "channel2", &event2, 2, NULL)); + workers.push_back(new RestrictedDispatchPipeWorker( + "channel2", &event2, "channel3", &event3, 3, NULL)); + workers.push_back(new RestrictedDispatchPipeWorker( + "channel3", &event3, "channel0", &event0, 4, NULL)); + RunTest(workers); + EXPECT_EQ(3, success); +} + +//------------------------------------------------------------------------------ + +// This test case inspired by crbug.com/122443 +// We want to make sure a reply message with the unblock flag set correctly +// behaves as a reply, not a regular message. +// We have 3 workers. Server1 will send a message to Server2 (which will block), +// during which it will dispatch a message comming from Client, at which point +// it will send another message to Server2. While sending that second message it +// will receive a reply from Server1 with the unblock flag. + +class ReentrantReplyServer1 : public Worker { + public: + ReentrantReplyServer1(WaitableEvent* server_ready) + : Worker("reentrant_reply1", Channel::MODE_SERVER), + server_ready_(server_ready) { } + + virtual void Run() OVERRIDE { + server2_channel_.reset( + new SyncChannel("reentrant_reply2", + Channel::MODE_CLIENT, + this, + ipc_thread().message_loop_proxy().get(), + true, + shutdown_event())); + server_ready_->Signal(); + Message* msg = new SyncChannelTestMsg_Reentrant1(); + server2_channel_->Send(msg); + server2_channel_.reset(); + Done(); + } + + private: + virtual bool OnMessageReceived(const Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(ReentrantReplyServer1, message) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Reentrant2, OnReentrant2) + IPC_REPLY_HANDLER(OnReply) + IPC_END_MESSAGE_MAP() + return true; + } + + void OnReentrant2() { + Message* msg = new SyncChannelTestMsg_Reentrant3(); + server2_channel_->Send(msg); + } + + void OnReply(const Message& message) { + // If we get here, the Send() will never receive the reply (thus would + // hang), so abort instead. + LOG(FATAL) << "Reply message was dispatched"; + } + + WaitableEvent* server_ready_; + scoped_ptr<SyncChannel> server2_channel_; +}; + +class ReentrantReplyServer2 : public Worker { + public: + ReentrantReplyServer2() + : Worker("reentrant_reply2", Channel::MODE_SERVER), + reply_(NULL) { } + + private: + virtual bool OnMessageReceived(const Message& message) OVERRIDE { + IPC_BEGIN_MESSAGE_MAP(ReentrantReplyServer2, message) + IPC_MESSAGE_HANDLER_DELAY_REPLY( + SyncChannelTestMsg_Reentrant1, OnReentrant1) + IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Reentrant3, OnReentrant3) + IPC_END_MESSAGE_MAP() + return true; + } + + void OnReentrant1(Message* reply) { + DCHECK(!reply_); + reply_ = reply; + } + + void OnReentrant3() { + DCHECK(reply_); + Message* reply = reply_; + reply_ = NULL; + reply->set_unblock(true); + Send(reply); + Done(); + } + + Message* reply_; +}; + +class ReentrantReplyClient : public Worker { + public: + ReentrantReplyClient(WaitableEvent* server_ready) + : Worker("reentrant_reply1", Channel::MODE_CLIENT), + server_ready_(server_ready) { } + + virtual void Run() OVERRIDE { + server_ready_->Wait(); + Send(new SyncChannelTestMsg_Reentrant2()); + Done(); + } + + private: + WaitableEvent* server_ready_; +}; + +TEST_F(IPCSyncChannelTest, ReentrantReply) { + std::vector<Worker*> workers; + WaitableEvent server_ready(false, false); + workers.push_back(new ReentrantReplyServer2()); + workers.push_back(new ReentrantReplyServer1(&server_ready)); + workers.push_back(new ReentrantReplyClient(&server_ready)); + RunTest(workers); +} + +//------------------------------------------------------------------------------ + +// Generate a validated channel ID using Channel::GenerateVerifiedChannelID(). + +class VerifiedServer : public Worker { + public: + VerifiedServer(base::Thread* listener_thread, + const std::string& channel_name, + const std::string& reply_text) + : Worker(channel_name, Channel::MODE_SERVER), + reply_text_(reply_text) { + Worker::OverrideThread(listener_thread); + } + + virtual void OnNestedTestMsg(Message* reply_msg) OVERRIDE { + VLOG(1) << __FUNCTION__ << " Sending reply: " << reply_text_; + SyncChannelNestedTestMsg_String::WriteReplyParams(reply_msg, reply_text_); + Send(reply_msg); + ASSERT_EQ(channel()->peer_pid(), base::GetCurrentProcId()); + Done(); + } + + private: + std::string reply_text_; +}; + +class VerifiedClient : public Worker { + public: + VerifiedClient(base::Thread* listener_thread, + const std::string& channel_name, + const std::string& expected_text) + : Worker(channel_name, Channel::MODE_CLIENT), + expected_text_(expected_text) { + Worker::OverrideThread(listener_thread); + } + + virtual void Run() OVERRIDE { + std::string response; + SyncMessage* msg = new SyncChannelNestedTestMsg_String(&response); + bool result = Send(msg); + DCHECK(result); + DCHECK_EQ(response, expected_text_); + // expected_text_ is only used in the above DCHECK. This line suppresses the + // "unused private field" warning in release builds. + (void)expected_text_; + + VLOG(1) << __FUNCTION__ << " Received reply: " << response; + ASSERT_EQ(channel()->peer_pid(), base::GetCurrentProcId()); + Done(); + } + + private: + std::string expected_text_; +}; + +void Verified() { + std::vector<Worker*> workers; + + // A shared worker thread for servers + base::Thread server_worker_thread("Verified_ServerListener"); + ASSERT_TRUE(server_worker_thread.Start()); + + base::Thread client_worker_thread("Verified_ClientListener"); + ASSERT_TRUE(client_worker_thread.Start()); + + std::string channel_id = Channel::GenerateVerifiedChannelID("Verified"); + Worker* worker; + + worker = new VerifiedServer(&server_worker_thread, + channel_id, + "Got first message"); + workers.push_back(worker); + + worker = new VerifiedClient(&client_worker_thread, + channel_id, + "Got first message"); + workers.push_back(worker); + + RunTest(workers); +} + +// Windows needs to send an out-of-band secret to verify the client end of the +// channel. Test that we still connect correctly in that case. +TEST_F(IPCSyncChannelTest, Verified) { + Verified(); +} + +} // namespace +} // namespace IPC diff --git a/chromium/ipc/ipc_sync_message.cc b/chromium/ipc/ipc_sync_message.cc new file mode 100644 index 00000000000..9e3acf8e1fa --- /dev/null +++ b/chromium/ipc/ipc_sync_message.cc @@ -0,0 +1,145 @@ +// 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 "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif +#include <stack> + +#include "base/atomic_sequence_num.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/synchronization/waitable_event.h" +#include "ipc/ipc_sync_message.h" + +namespace { + +struct WaitableEventLazyInstanceTraits + : public base::DefaultLazyInstanceTraits<base::WaitableEvent> { + static base::WaitableEvent* New(void* instance) { + // Use placement new to initialize our instance in our preallocated space. + return new (instance) base::WaitableEvent(true, true); + } +}; + +base::LazyInstance<base::WaitableEvent, WaitableEventLazyInstanceTraits> + dummy_event = LAZY_INSTANCE_INITIALIZER; + +base::StaticAtomicSequenceNumber g_next_id; + +} // namespace + +namespace IPC { + +#define kSyncMessageHeaderSize 4 + +SyncMessage::SyncMessage( + int32 routing_id, + uint32 type, + PriorityValue priority, + MessageReplyDeserializer* deserializer) + : Message(routing_id, type, priority), + deserializer_(deserializer), + pump_messages_event_(NULL) + { + set_sync(); + set_unblock(true); + + // Add synchronous message data before the message payload. + SyncHeader header; + header.message_id = g_next_id.GetNext(); + WriteSyncHeader(this, header); +} + +SyncMessage::~SyncMessage() { +} + +MessageReplyDeserializer* SyncMessage::GetReplyDeserializer() { + DCHECK(deserializer_.get()); + return deserializer_.release(); +} + +void SyncMessage::EnableMessagePumping() { + DCHECK(!pump_messages_event_); + set_pump_messages_event(dummy_event.Pointer()); +} + +bool SyncMessage::IsMessageReplyTo(const Message& msg, int request_id) { + if (!msg.is_reply()) + return false; + + return GetMessageId(msg) == request_id; +} + +PickleIterator SyncMessage::GetDataIterator(const Message* msg) { + PickleIterator iter(*msg); + if (!iter.SkipBytes(kSyncMessageHeaderSize)) + return PickleIterator(); + else + return iter; +} + +int SyncMessage::GetMessageId(const Message& msg) { + if (!msg.is_sync() && !msg.is_reply()) + return 0; + + SyncHeader header; + if (!ReadSyncHeader(msg, &header)) + return 0; + + return header.message_id; +} + +Message* SyncMessage::GenerateReply(const Message* msg) { + DCHECK(msg->is_sync()); + + Message* reply = new Message(msg->routing_id(), IPC_REPLY_ID, + msg->priority()); + reply->set_reply(); + + SyncHeader header; + + // use the same message id, but this time reply bit is set + header.message_id = GetMessageId(*msg); + WriteSyncHeader(reply, header); + + return reply; +} + +bool SyncMessage::ReadSyncHeader(const Message& msg, SyncHeader* header) { + DCHECK(msg.is_sync() || msg.is_reply()); + + PickleIterator iter(msg); + bool result = msg.ReadInt(&iter, &header->message_id); + if (!result) { + NOTREACHED(); + return false; + } + + return true; +} + +bool SyncMessage::WriteSyncHeader(Message* msg, const SyncHeader& header) { + DCHECK(msg->is_sync() || msg->is_reply()); + DCHECK(msg->payload_size() == 0); + bool result = msg->WriteInt(header.message_id); + if (!result) { + NOTREACHED(); + return false; + } + + // Note: if you add anything here, you need to update kSyncMessageHeaderSize. + DCHECK(kSyncMessageHeaderSize == msg->payload_size()); + + return true; +} + + +bool MessageReplyDeserializer::SerializeOutputParameters(const Message& msg) { + return SerializeOutputParameters(msg, SyncMessage::GetDataIterator(&msg)); +} + +} // namespace IPC diff --git a/chromium/ipc/ipc_sync_message.h b/chromium/ipc/ipc_sync_message.h new file mode 100644 index 00000000000..a7415911641 --- /dev/null +++ b/chromium/ipc/ipc_sync_message.h @@ -0,0 +1,111 @@ +// 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. + +#ifndef IPC_IPC_SYNC_MESSAGE_H_ +#define IPC_IPC_SYNC_MESSAGE_H_ + +#if defined(OS_WIN) +#include <windows.h> +#endif +#include <string> +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "ipc/ipc_message.h" + +namespace base { +class WaitableEvent; +} + +namespace IPC { + +class MessageReplyDeserializer; + +class IPC_EXPORT SyncMessage : public Message { + public: + SyncMessage(int32 routing_id, uint32 type, PriorityValue priority, + MessageReplyDeserializer* deserializer); + virtual ~SyncMessage(); + + // Call this to get a deserializer for the output parameters. + // Note that this can only be called once, and the caller is responsible + // for deleting the deserializer when they're done. + MessageReplyDeserializer* GetReplyDeserializer(); + + // If this message can cause the receiver to block while waiting for user + // input (i.e. by calling MessageBox), then the caller needs to pump window + // messages and dispatch asynchronous messages while waiting for the reply. + // If this event is passed in, then window messages will start being pumped + // when it's set. Note that this behavior will continue even if the event is + // later reset. The event must be valid until after the Send call returns. + void set_pump_messages_event(base::WaitableEvent* event) { + pump_messages_event_ = event; + if (event) { + header()->flags |= PUMPING_MSGS_BIT; + } else { + header()->flags &= ~PUMPING_MSGS_BIT; + } + } + + // Call this if you always want to pump messages. You can call this method + // or set_pump_messages_event but not both. + void EnableMessagePumping(); + + base::WaitableEvent* pump_messages_event() const { + return pump_messages_event_; + } + + // Returns true if the message is a reply to the given request id. + static bool IsMessageReplyTo(const Message& msg, int request_id); + + // Given a reply message, returns an iterator to the beginning of the data + // (i.e. skips over the synchronous specific data). + static PickleIterator GetDataIterator(const Message* msg); + + // Given a synchronous message (or its reply), returns its id. + static int GetMessageId(const Message& msg); + + // Generates a reply message to the given message. + static Message* GenerateReply(const Message* msg); + + private: + struct SyncHeader { + // unique ID (unique per sender) + int message_id; + }; + + static bool ReadSyncHeader(const Message& msg, SyncHeader* header); + static bool WriteSyncHeader(Message* msg, const SyncHeader& header); + + scoped_ptr<MessageReplyDeserializer> deserializer_; + base::WaitableEvent* pump_messages_event_; +}; + +// Used to deserialize parameters from a reply to a synchronous message +class IPC_EXPORT MessageReplyDeserializer { + public: + virtual ~MessageReplyDeserializer() {} + bool SerializeOutputParameters(const Message& msg); + private: + // Derived classes need to implement this, using the given iterator (which + // is skipped past the header for synchronous messages). + virtual bool SerializeOutputParameters(const Message& msg, + PickleIterator iter) = 0; +}; + +// When sending a synchronous message, this structure contains an object +// that knows how to deserialize the response. +struct PendingSyncMsg { + PendingSyncMsg(int id, + MessageReplyDeserializer* d, + base::WaitableEvent* e) + : id(id), deserializer(d), done_event(e), send_result(false) { } + int id; + MessageReplyDeserializer* deserializer; + base::WaitableEvent* done_event; + bool send_result; +}; + +} // namespace IPC + +#endif // IPC_IPC_SYNC_MESSAGE_H_ diff --git a/chromium/ipc/ipc_sync_message_filter.cc b/chromium/ipc/ipc_sync_message_filter.cc new file mode 100644 index 00000000000..a534c445916 --- /dev/null +++ b/chromium/ipc/ipc_sync_message_filter.cc @@ -0,0 +1,128 @@ +// 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 "ipc/ipc_sync_message_filter.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/synchronization/waitable_event.h" +#include "ipc/ipc_sync_message.h" + +using base::MessageLoopProxy; + +namespace IPC { + +SyncMessageFilter::SyncMessageFilter(base::WaitableEvent* shutdown_event) + : channel_(NULL), + listener_loop_(MessageLoopProxy::current()), + shutdown_event_(shutdown_event) { +} + +bool SyncMessageFilter::Send(Message* message) { + { + base::AutoLock auto_lock(lock_); + if (!io_loop_.get()) { + delete message; + return false; + } + } + + if (!message->is_sync()) { + io_loop_->PostTask( + FROM_HERE, base::Bind(&SyncMessageFilter::SendOnIOThread, this, message)); + return true; + } + + base::WaitableEvent done_event(true, false); + PendingSyncMsg pending_message( + SyncMessage::GetMessageId(*message), + static_cast<SyncMessage*>(message)->GetReplyDeserializer(), + &done_event); + + { + base::AutoLock auto_lock(lock_); + // Can't use this class on the main thread or else it can lead to deadlocks. + // Also by definition, can't use this on IO thread since we're blocking it. + DCHECK(MessageLoopProxy::current().get() != listener_loop_.get()); + DCHECK(MessageLoopProxy::current().get() != io_loop_.get()); + pending_sync_messages_.insert(&pending_message); + } + + io_loop_->PostTask( + FROM_HERE, base::Bind(&SyncMessageFilter::SendOnIOThread, this, message)); + + base::WaitableEvent* events[2] = { shutdown_event_, &done_event }; + base::WaitableEvent::WaitMany(events, 2); + + { + base::AutoLock auto_lock(lock_); + delete pending_message.deserializer; + pending_sync_messages_.erase(&pending_message); + } + + return pending_message.send_result; +} + +void SyncMessageFilter::OnFilterAdded(Channel* channel) { + channel_ = channel; + base::AutoLock auto_lock(lock_); + io_loop_ = MessageLoopProxy::current(); +} + +void SyncMessageFilter::OnChannelError() { + channel_ = NULL; + SignalAllEvents(); +} + +void SyncMessageFilter::OnChannelClosing() { + channel_ = NULL; + SignalAllEvents(); +} + +bool SyncMessageFilter::OnMessageReceived(const Message& message) { + base::AutoLock auto_lock(lock_); + for (PendingSyncMessages::iterator iter = pending_sync_messages_.begin(); + iter != pending_sync_messages_.end(); ++iter) { + if (SyncMessage::IsMessageReplyTo(message, (*iter)->id)) { + if (!message.is_reply_error()) { + (*iter)->send_result = + (*iter)->deserializer->SerializeOutputParameters(message); + } + (*iter)->done_event->Signal(); + return true; + } + } + + return false; +} + +SyncMessageFilter::~SyncMessageFilter() { +} + +void SyncMessageFilter::SendOnIOThread(Message* message) { + if (channel_) { + channel_->Send(message); + return; + } + + if (message->is_sync()) { + // We don't know which thread sent it, but it doesn't matter, just signal + // them all. + SignalAllEvents(); + } + + delete message; +} + +void SyncMessageFilter::SignalAllEvents() { + base::AutoLock auto_lock(lock_); + for (PendingSyncMessages::iterator iter = pending_sync_messages_.begin(); + iter != pending_sync_messages_.end(); ++iter) { + (*iter)->done_event->Signal(); + } +} + +} // namespace IPC diff --git a/chromium/ipc/ipc_sync_message_filter.h b/chromium/ipc/ipc_sync_message_filter.h new file mode 100644 index 00000000000..dbc28345ac6 --- /dev/null +++ b/chromium/ipc/ipc_sync_message_filter.h @@ -0,0 +1,72 @@ +// 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. + +#ifndef IPC_IPC_SYNC_MESSAGE_FILTER_H_ +#define IPC_IPC_SYNC_MESSAGE_FILTER_H_ + +#include <set> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_sync_message.h" + +namespace base { +class MessageLoopProxy; +class WaitableEvent; +} + +namespace IPC { + +// This MessageFilter allows sending synchronous IPC messages from a thread +// other than the listener thread associated with the SyncChannel. It does not +// support fancy features that SyncChannel does, such as handling recursion or +// receiving messages while waiting for a response. Note that this object can +// be used to send simultaneous synchronous messages from different threads. +class IPC_EXPORT SyncMessageFilter : public ChannelProxy::MessageFilter, + public Sender { + public: + explicit SyncMessageFilter(base::WaitableEvent* shutdown_event); + + // MessageSender implementation. + virtual bool Send(Message* message) OVERRIDE; + + // ChannelProxy::MessageFilter implementation. + virtual void OnFilterAdded(Channel* channel) OVERRIDE; + virtual void OnChannelError() OVERRIDE; + virtual void OnChannelClosing() OVERRIDE; + virtual bool OnMessageReceived(const Message& message) OVERRIDE; + + protected: + virtual ~SyncMessageFilter(); + + private: + void SendOnIOThread(Message* message); + // Signal all the pending sends as done, used in an error condition. + void SignalAllEvents(); + + // The channel to which this filter was added. + Channel* channel_; + + // The process's main thread. + scoped_refptr<base::MessageLoopProxy> listener_loop_; + + // The message loop where the Channel lives. + scoped_refptr<base::MessageLoopProxy> io_loop_; + + typedef std::set<PendingSyncMsg*> PendingSyncMessages; + PendingSyncMessages pending_sync_messages_; + + // Locks data members above. + base::Lock lock_; + + base::WaitableEvent* shutdown_event_; + + DISALLOW_COPY_AND_ASSIGN(SyncMessageFilter); +}; + +} // namespace IPC + +#endif // IPC_IPC_SYNC_MESSAGE_FILTER_H_ diff --git a/chromium/ipc/ipc_sync_message_unittest.cc b/chromium/ipc/ipc_sync_message_unittest.cc new file mode 100644 index 00000000000..d170b7fcc5f --- /dev/null +++ b/chromium/ipc/ipc_sync_message_unittest.cc @@ -0,0 +1,307 @@ +// 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. +// +// Unit test to make sure that the serialization of synchronous IPC messages +// works. This ensures that the macros and templates were defined correctly. +// Doesn't test the IPC channel mechanism. + +#include <string.h> + +#include "base/basictypes.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_message_utils.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define IPC_MESSAGE_IMPL +#include "ipc/ipc_sync_message_unittest.h" + +namespace { + +static IPC::Message* g_reply; + +class TestMessageReceiver { + public: + + void On_0_1(bool* out1) { + *out1 = false; + } + + void On_0_2(bool* out1, int* out2) { + *out1 = true; + *out2 = 2; + } + + void On_0_3(bool* out1, int* out2, std::string* out3) { + *out1 = false; + *out2 = 3; + *out3 = "0_3"; + } + + void On_1_1(int in1, bool* out1) { + DCHECK_EQ(1, in1); + *out1 = true; + } + + void On_1_2(bool in1, bool* out1, int* out2) { + DCHECK(!in1); + *out1 = true; + *out2 = 12; + } + + void On_1_3(int in1, std::string* out1, int* out2, bool* out3) { + DCHECK_EQ(3, in1); + *out1 = "1_3"; + *out2 = 13; + *out3 = false; + } + + void On_2_1(int in1, bool in2, bool* out1) { + DCHECK_EQ(1, in1); + DCHECK(!in2); + *out1 = true; + } + + void On_2_2(bool in1, int in2, bool* out1, int* out2) { + DCHECK(!in1); + DCHECK_EQ(2, in2); + *out1 = true; + *out2 = 22; + } + + void On_2_3(int in1, bool in2, std::string* out1, int* out2, bool* out3) { + DCHECK_EQ(3, in1); + DCHECK(in2); + *out1 = "2_3"; + *out2 = 23; + *out3 = false; + } + + void On_3_1(int in1, bool in2, std::string in3, bool* out1) { + DCHECK_EQ(1, in1); + DCHECK(!in2); + DCHECK_EQ("3_1", in3); + *out1 = true; + } + + void On_3_2(std::string in1, bool in2, int in3, bool* out1, int* out2) { + DCHECK_EQ("3_2", in1); + DCHECK(!in2); + DCHECK_EQ(2, in3); + *out1 = true; + *out2 = 32; + } + + void On_3_3(int in1, std::string in2, bool in3, std::string* out1, int* out2, + bool* out3) { + DCHECK_EQ(3, in1); + DCHECK_EQ("3_3", in2); + DCHECK(in3); + *out1 = "3_3"; + *out2 = 33; + *out3 = false; + } + + void On_3_4(bool in1, int in2, std::string in3, int* out1, bool* out2, + std::string* out3, bool* out4) { + DCHECK(in1); + DCHECK_EQ(3, in2); + DCHECK_EQ("3_4", in3); + *out1 = 34; + *out2 = true; + *out3 = "3_4"; + *out4 = false; + } + + bool Send(IPC::Message* message) { + // gets the reply message, stash in global + DCHECK(g_reply == NULL); + g_reply = message; + return true; + } + + bool OnMessageReceived(const IPC::Message& msg) { + IPC_BEGIN_MESSAGE_MAP(TestMessageReceiver, msg) + IPC_MESSAGE_HANDLER(Msg_C_0_1, On_0_1) + IPC_MESSAGE_HANDLER(Msg_C_0_2, On_0_2) + IPC_MESSAGE_HANDLER(Msg_C_0_3, On_0_3) + IPC_MESSAGE_HANDLER(Msg_C_1_1, On_1_1) + IPC_MESSAGE_HANDLER(Msg_C_1_2, On_1_2) + IPC_MESSAGE_HANDLER(Msg_C_1_3, On_1_3) + IPC_MESSAGE_HANDLER(Msg_C_2_1, On_2_1) + IPC_MESSAGE_HANDLER(Msg_C_2_2, On_2_2) + IPC_MESSAGE_HANDLER(Msg_C_2_3, On_2_3) + IPC_MESSAGE_HANDLER(Msg_C_3_1, On_3_1) + IPC_MESSAGE_HANDLER(Msg_C_3_2, On_3_2) + IPC_MESSAGE_HANDLER(Msg_C_3_3, On_3_3) + IPC_MESSAGE_HANDLER(Msg_C_3_4, On_3_4) + IPC_MESSAGE_HANDLER(Msg_R_0_1, On_0_1) + IPC_MESSAGE_HANDLER(Msg_R_0_2, On_0_2) + IPC_MESSAGE_HANDLER(Msg_R_0_3, On_0_3) + IPC_MESSAGE_HANDLER(Msg_R_1_1, On_1_1) + IPC_MESSAGE_HANDLER(Msg_R_1_2, On_1_2) + IPC_MESSAGE_HANDLER(Msg_R_1_3, On_1_3) + IPC_MESSAGE_HANDLER(Msg_R_2_1, On_2_1) + IPC_MESSAGE_HANDLER(Msg_R_2_2, On_2_2) + IPC_MESSAGE_HANDLER(Msg_R_2_3, On_2_3) + IPC_MESSAGE_HANDLER(Msg_R_3_1, On_3_1) + IPC_MESSAGE_HANDLER(Msg_R_3_2, On_3_2) + IPC_MESSAGE_HANDLER(Msg_R_3_3, On_3_3) + IPC_MESSAGE_HANDLER(Msg_R_3_4, On_3_4) + IPC_END_MESSAGE_MAP() + return true; + } + +}; + +void Send(IPC::SyncMessage* msg) { + static TestMessageReceiver receiver; + + IPC::MessageReplyDeserializer* reply_serializer = msg->GetReplyDeserializer(); + DCHECK(reply_serializer != NULL); + + // "send" the message + receiver.OnMessageReceived(*msg); + delete msg; + + // get the reply message from the global, and deserialize the output + // parameters into the output pointers. + DCHECK(g_reply != NULL); + bool result = reply_serializer->SerializeOutputParameters(*g_reply); + DCHECK(result); + delete g_reply; + g_reply = NULL; + delete reply_serializer; +} + +TEST(IPCSyncMessageTest, Main) { + bool bool1 = true; + int int1 = 0; + std::string string1; + + Send(new Msg_C_0_1(&bool1)); + DCHECK(!bool1); + + Send(new Msg_C_0_2(&bool1, &int1)); + DCHECK(bool1); + DCHECK_EQ(2, int1); + + Send(new Msg_C_0_3(&bool1, &int1, &string1)); + DCHECK(!bool1); + DCHECK_EQ(3, int1); + DCHECK_EQ("0_3", string1); + + bool1 = false; + Send(new Msg_C_1_1(1, &bool1)); + DCHECK(bool1); + + bool1 = false; + Send(new Msg_C_1_2(false, &bool1, &int1)); + DCHECK(bool1); + DCHECK_EQ(12, int1); + + bool1 = true; + Send(new Msg_C_1_3(3, &string1, &int1, &bool1)); + DCHECK_EQ("1_3", string1); + DCHECK_EQ(13, int1); + DCHECK(!bool1); + + bool1 = false; + Send(new Msg_C_2_1(1, false, &bool1)); + DCHECK(bool1); + + bool1 = false; + Send(new Msg_C_2_2(false, 2, &bool1, &int1)); + DCHECK(bool1); + DCHECK_EQ(22, int1); + + bool1 = true; + Send(new Msg_C_2_3(3, true, &string1, &int1, &bool1)); + DCHECK_EQ("2_3", string1); + DCHECK_EQ(23, int1); + DCHECK(!bool1); + + bool1 = false; + Send(new Msg_C_3_1(1, false, "3_1", &bool1)); + DCHECK(bool1); + + bool1 = false; + Send(new Msg_C_3_2("3_2", false, 2, &bool1, &int1)); + DCHECK(bool1); + DCHECK_EQ(32, int1); + + bool1 = true; + Send(new Msg_C_3_3(3, "3_3", true, &string1, &int1, &bool1)); + DCHECK_EQ("3_3", string1); + DCHECK_EQ(33, int1); + DCHECK(!bool1); + + bool1 = false; + bool bool2 = true; + Send(new Msg_C_3_4(true, 3, "3_4", &int1, &bool1, &string1, &bool2)); + DCHECK_EQ(34, int1); + DCHECK(bool1); + DCHECK_EQ("3_4", string1); + DCHECK(!bool2); + + // Routed messages, just a copy of the above but with extra routing paramater + Send(new Msg_R_0_1(0, &bool1)); + DCHECK(!bool1); + + Send(new Msg_R_0_2(0, &bool1, &int1)); + DCHECK(bool1); + DCHECK_EQ(2, int1); + + Send(new Msg_R_0_3(0, &bool1, &int1, &string1)); + DCHECK(!bool1); + DCHECK_EQ(3, int1); + DCHECK_EQ("0_3", string1); + + bool1 = false; + Send(new Msg_R_1_1(0, 1, &bool1)); + DCHECK(bool1); + + bool1 = false; + Send(new Msg_R_1_2(0, false, &bool1, &int1)); + DCHECK(bool1); + DCHECK_EQ(12, int1); + + bool1 = true; + Send(new Msg_R_1_3(0, 3, &string1, &int1, &bool1)); + DCHECK_EQ("1_3", string1); + DCHECK_EQ(13, int1); + DCHECK(!bool1); + + bool1 = false; + Send(new Msg_R_2_1(0, 1, false, &bool1)); + DCHECK(bool1); + + bool1 = false; + Send(new Msg_R_2_2(0, false, 2, &bool1, &int1)); + DCHECK(bool1); + DCHECK_EQ(22, int1); + + bool1 = true; + Send(new Msg_R_2_3(0, 3, true, &string1, &int1, &bool1)); + DCHECK(!bool1); + DCHECK_EQ("2_3", string1); + DCHECK_EQ(23, int1); + + bool1 = false; + Send(new Msg_R_3_1(0, 1, false, "3_1", &bool1)); + DCHECK(bool1); + + bool1 = false; + Send(new Msg_R_3_2(0, "3_2", false, 2, &bool1, &int1)); + DCHECK(bool1); + DCHECK_EQ(32, int1); + + bool1 = true; + Send(new Msg_R_3_3(0, 3, "3_3", true, &string1, &int1, &bool1)); + DCHECK_EQ("3_3", string1); + DCHECK_EQ(33, int1); + DCHECK(!bool1); +} + +} // namespace diff --git a/chromium/ipc/ipc_sync_message_unittest.h b/chromium/ipc/ipc_sync_message_unittest.h new file mode 100644 index 00000000000..114aca6fb6b --- /dev/null +++ b/chromium/ipc/ipc_sync_message_unittest.h @@ -0,0 +1,120 @@ +// 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 <string> + +#include "ipc/ipc_message_macros.h" + +#define IPC_MESSAGE_START TestMsgStart + +IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_NoArgs) + +IPC_SYNC_MESSAGE_CONTROL0_1(SyncChannelTestMsg_AnswerToLife, + int /* answer */) + +IPC_SYNC_MESSAGE_CONTROL1_1(SyncChannelTestMsg_Double, + int /* in */, + int /* out */) + +IPC_SYNC_MESSAGE_CONTROL0_1(SyncChannelNestedTestMsg_String, + std::string) + +// out1 is false +IPC_SYNC_MESSAGE_CONTROL0_1(Msg_C_0_1, bool) + +// out1 is true, out2 is 2 +IPC_SYNC_MESSAGE_CONTROL0_2(Msg_C_0_2, bool, int) + +// out1 is false, out2 is 3, out3 is "0_3" +IPC_SYNC_MESSAGE_CONTROL0_3(Msg_C_0_3, bool, int, std::string) + +// in1 must be 1, out1 is true +IPC_SYNC_MESSAGE_CONTROL1_1(Msg_C_1_1, int, bool) + +// in1 must be false, out1 is true, out2 is 12 +IPC_SYNC_MESSAGE_CONTROL1_2(Msg_C_1_2, bool, bool, int) + +// in1 must be 3, out1 is "1_3", out2 is 13, out3 is false +IPC_SYNC_MESSAGE_CONTROL1_3(Msg_C_1_3, int, std::string, int, bool) + +// in1 must be 1, in2 must be false, out1 is true +IPC_SYNC_MESSAGE_CONTROL2_1(Msg_C_2_1, int, bool, bool) + +// in1 must be false, in2 must be 2, out1 is true, out2 is 22 +IPC_SYNC_MESSAGE_CONTROL2_2(Msg_C_2_2, bool, int, bool, int) + +// in1 must be 3, in2 must be true, out1 is "2_3", out2 is 23, out3 is false +IPC_SYNC_MESSAGE_CONTROL2_3(Msg_C_2_3, int, bool, std::string, int, bool) + +// in1 must be 1, in2 must be false, in3 must be "3_1", out1 is true +IPC_SYNC_MESSAGE_CONTROL3_1(Msg_C_3_1, int, bool, std::string, bool) + +// in1 must be "3_3", in2 must be false, in3 must be 2, out1 is true, out2 is +// 32 +IPC_SYNC_MESSAGE_CONTROL3_2(Msg_C_3_2, std::string, bool, int, bool, int) + +// in1 must be 3, in2 must be "3_3", in3 must be true, out1 is "3_3", out2 is +// 33, out3 is false +IPC_SYNC_MESSAGE_CONTROL3_3(Msg_C_3_3, int, std::string, bool, std::string, + int, bool) + +// in1 must be true, in2 must be 3, in3 must be "3_4", out1 is 34, out2 is +// true, out3 is "3_4", out3 is false +IPC_SYNC_MESSAGE_CONTROL3_4(Msg_C_3_4, bool, int, std::string, int, bool, + std::string, bool) + +// NOTE: routed messages are just a copy of the above... + +// out1 is false +IPC_SYNC_MESSAGE_ROUTED0_1(Msg_R_0_1, bool) + +// out1 is true, out2 is 2 +IPC_SYNC_MESSAGE_ROUTED0_2(Msg_R_0_2, bool, int) + +// out1 is false, out2 is 3, out3 is "0_3" +IPC_SYNC_MESSAGE_ROUTED0_3(Msg_R_0_3, bool, int, std::string) + +// in1 must be 1, out1 is true +IPC_SYNC_MESSAGE_ROUTED1_1(Msg_R_1_1, int, bool) + +// in1 must be false, out1 is true, out2 is 12 +IPC_SYNC_MESSAGE_ROUTED1_2(Msg_R_1_2, bool, bool, int) + +// in1 must be 3, out1 is "1_3", out2 is 13, out3 is false +IPC_SYNC_MESSAGE_ROUTED1_3(Msg_R_1_3, int, std::string, int, bool) + +// in1 must be 1, in2 must be false, out1 is true +IPC_SYNC_MESSAGE_ROUTED2_1(Msg_R_2_1, int, bool, bool) + +// in1 must be false, in2 must be 2, out1 is true, out2 is 22 +IPC_SYNC_MESSAGE_ROUTED2_2(Msg_R_2_2, bool, int, bool, int) + +// in1 must be 3, in2 must be true, out1 is "2_3", out2 is 23, out3 is false +IPC_SYNC_MESSAGE_ROUTED2_3(Msg_R_2_3, int, bool, std::string, int, bool) + +// in1 must be 1, in2 must be false, in3 must be "3_1", out1 is true +IPC_SYNC_MESSAGE_ROUTED3_1(Msg_R_3_1, int, bool, std::string, bool) + +// in1 must be "3_3", in2 must be false, in3 must be 2, out1 is true, out2 +// is 32 +IPC_SYNC_MESSAGE_ROUTED3_2(Msg_R_3_2, std::string, bool, int, bool, int) + +// in1 must be 3, in2 must be "3_3", in3 must be true, out1 is "3_3", out2 is +// 33, out3 is false +IPC_SYNC_MESSAGE_ROUTED3_3(Msg_R_3_3, int, std::string, bool, std::string, + int, bool) + +// in1 must be true, in2 must be 3, in3 must be "3_4", out1 is 34, out2 is +// true, out3 is "3_4", out4 is false +IPC_SYNC_MESSAGE_ROUTED3_4(Msg_R_3_4, bool, int, std::string, int, bool, + std::string, bool) + +IPC_MESSAGE_CONTROL1(SyncChannelTestMsg_Ping, int) +IPC_SYNC_MESSAGE_CONTROL1_1(SyncChannelTestMsg_PingTTL, int, int) +IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_Done) + +// Messages for ReentrantReply test. +IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_Reentrant1) +IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_Reentrant2) +IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_Reentrant3) diff --git a/chromium/ipc/ipc_test_base.cc b/chromium/ipc/ipc_test_base.cc new file mode 100644 index 00000000000..5e16e42cd8d --- /dev/null +++ b/chromium/ipc/ipc_test_base.cc @@ -0,0 +1,123 @@ +// 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 "build/build_config.h" + +#include "ipc/ipc_test_base.h" + +#include "base/command_line.h" +#include "base/debug/debug_on_start_win.h" +#include "base/process/kill.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "ipc/ipc_descriptors.h" +#include "ipc/ipc_switches.h" + +// static +std::string IPCTestBase::GetChannelName(const std::string& test_client_name) { + DCHECK(!test_client_name.empty()); + return test_client_name + "__Channel"; +} + +IPCTestBase::IPCTestBase() + : client_process_(base::kNullProcessHandle) { +} + +IPCTestBase::~IPCTestBase() { +} + +void IPCTestBase::SetUp() { + MultiProcessTest::SetUp(); + + // Construct a fresh IO Message loop for the duration of each test. + DCHECK(!message_loop_.get()); + message_loop_.reset(new base::MessageLoopForIO()); +} + +void IPCTestBase::TearDown() { + DCHECK(message_loop_.get()); + message_loop_.reset(); + MultiProcessTest::TearDown(); +} + +void IPCTestBase::Init(const std::string& test_client_name) { + DCHECK(!test_client_name.empty()); + DCHECK(test_client_name_.empty()); + test_client_name_ = test_client_name; +} + +void IPCTestBase::CreateChannel(IPC::Listener* listener) { + return CreateChannelFromChannelHandle(GetChannelName(test_client_name_), + listener); +} + +bool IPCTestBase::ConnectChannel() { + CHECK(channel_.get()); + return channel_->Connect(); +} + +void IPCTestBase::DestroyChannel() { + DCHECK(channel_.get()); + channel_.reset(); +} + +void IPCTestBase::CreateChannelFromChannelHandle( + const IPC::ChannelHandle& channel_handle, + IPC::Listener* listener) { + CHECK(!channel_.get()); + CHECK(!channel_proxy_.get()); + channel_.reset(new IPC::Channel(channel_handle, + IPC::Channel::MODE_SERVER, + listener)); +} + +void IPCTestBase::CreateChannelProxy( + IPC::Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner) { + CHECK(!channel_.get()); + CHECK(!channel_proxy_.get()); + channel_proxy_.reset(new IPC::ChannelProxy(GetChannelName(test_client_name_), + IPC::Channel::MODE_SERVER, + listener, + ipc_task_runner)); +} + +void IPCTestBase::DestroyChannelProxy() { + CHECK(channel_proxy_.get()); + channel_proxy_.reset(); +} + +bool IPCTestBase::StartClient() { + DCHECK(client_process_ == base::kNullProcessHandle); + + std::string test_main = test_client_name_ + "TestClientMain"; + bool debug_on_start = + CommandLine::ForCurrentProcess()->HasSwitch(switches::kDebugChildren); + +#if defined(OS_WIN) + client_process_ = MultiProcessTest::SpawnChild(test_main, debug_on_start); +#elif defined(OS_POSIX) + base::FileHandleMappingVector fds_to_map; + const int ipcfd = channel_.get() ? channel_->GetClientFileDescriptor() : + channel_proxy_->GetClientFileDescriptor(); + if (ipcfd > -1) + fds_to_map.push_back(std::pair<int, int>(ipcfd, kPrimaryIPCChannel + 3)); + + client_process_ = MultiProcessTest::SpawnChild(test_main, + fds_to_map, + debug_on_start); +#endif + + return client_process_ != base::kNullProcessHandle; +} + +bool IPCTestBase::WaitForClientShutdown() { + DCHECK(client_process_ != base::kNullProcessHandle); + + bool rv = base::WaitForSingleProcess(client_process_, + base::TimeDelta::FromSeconds(5)); + base::CloseProcessHandle(client_process_); + client_process_ = base::kNullProcessHandle; + return rv; +} diff --git a/chromium/ipc/ipc_test_base.h b/chromium/ipc/ipc_test_base.h new file mode 100644 index 00000000000..5bd3e96ab5f --- /dev/null +++ b/chromium/ipc/ipc_test_base.h @@ -0,0 +1,101 @@ +// 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. + +#ifndef IPC_IPC_TEST_BASE_H_ +#define IPC_IPC_TEST_BASE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/process/process.h" +#include "base/test/multiprocess_test.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_multiprocess_test.h" + +namespace base { +class MessageLoopForIO; +} + +// A test fixture for multiprocess IPC tests. Such tests include a "client" side +// (running in a separate process). The same client may be shared between +// several different tests. +class IPCTestBase : public base::MultiProcessTest { + public: + // The channel name is based on the client's name. This is a public static + // helper to be used by the client-side code; server-side test code should + // usually not use this (directly). + static std::string GetChannelName(const std::string& test_client_name); + + protected: + IPCTestBase(); + virtual ~IPCTestBase(); + + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + // Initializes the test to use the given client. + void Init(const std::string& test_client_name); + + // Creates a channel with the given listener and connects to the channel + // (returning true if successful), respectively. Use these to use a channel + // directly. Since the listener must outlive the channel, you must destroy the + // channel before the listener gets destroyed. + void CreateChannel(IPC::Listener* listener); + bool ConnectChannel(); + void DestroyChannel(); + + // Use this instead of CreateChannel() if you want to use some different + // channel specification (then use ConnectChannel() as usual). + void CreateChannelFromChannelHandle(const IPC::ChannelHandle& channel_handle, + IPC::Listener* listener); + + // Creates a channel proxy with the given listener and task runner. (The + // channel proxy will automatically create and connect a channel.) You must + // (manually) destroy the channel proxy before the task runner's thread is + // destroyed. + void CreateChannelProxy(IPC::Listener* listener, + base::SingleThreadTaskRunner* ipc_task_runner); + void DestroyChannelProxy(); + + // Starts the client process, returning true if successful; this should be + // done after connecting to the channel. + bool StartClient(); + + // Waits for the client to shut down, returning true if successful. Note that + // this does not initiate client shutdown; that must be done by the test + // (somehow). This must be called before the end of the test whenever + // StartClient() was called successfully. + bool WaitForClientShutdown(); + + // Use this to send IPC messages (when you don't care if you're using a + // channel or a proxy). + IPC::Sender* sender() { + return channel_.get() ? static_cast<IPC::Sender*>(channel_.get()) : + static_cast<IPC::Sender*>(channel_proxy_.get()); + } + + IPC::Channel* channel() { return channel_.get(); } + IPC::ChannelProxy* channel_proxy() { return channel_proxy_.get(); } + + const base::ProcessHandle& client_process() const { return client_process_; } + + private: + std::string test_client_name_; + scoped_ptr<base::MessageLoopForIO> message_loop_; + + scoped_ptr<IPC::Channel> channel_; + scoped_ptr<IPC::ChannelProxy> channel_proxy_; + + base::ProcessHandle client_process_; + + DISALLOW_COPY_AND_ASSIGN(IPCTestBase); +}; + +// Use this to declare the client side for tests using IPCTestBase. +#define MULTIPROCESS_IPC_TEST_CLIENT_MAIN(test_client_name) \ + MULTIPROCESS_IPC_TEST_MAIN(test_client_name ## TestClientMain) + +#endif // IPC_IPC_TEST_BASE_H_ diff --git a/chromium/ipc/ipc_test_sink.cc b/chromium/ipc/ipc_test_sink.cc new file mode 100644 index 00000000000..070fe1235d1 --- /dev/null +++ b/chromium/ipc/ipc_test_sink.cc @@ -0,0 +1,77 @@ +// 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 "ipc/ipc_test_sink.h" + +#include "ipc/ipc_listener.h" +#include "ipc/ipc_message.h" + +namespace IPC { + +TestSink::TestSink() { +} + +TestSink::~TestSink() { +} + +bool TestSink::Send(Message* message) { + OnMessageReceived(*message); + delete message; + return true; +} + +bool TestSink::OnMessageReceived(const Message& msg) { + ObserverListBase<Listener>::Iterator it(filter_list_); + Listener* observer; + while ((observer = it.GetNext()) != NULL) { + if (observer->OnMessageReceived(msg)) + return true; + } + + // No filter handled the message, so store it. + messages_.push_back(Message(msg)); + return true; +} + +void TestSink::ClearMessages() { + messages_.clear(); +} + +const Message* TestSink::GetMessageAt(size_t index) const { + if (index >= messages_.size()) + return NULL; + return &messages_[index]; +} + +const Message* TestSink::GetFirstMessageMatching(uint32 id) const { + for (size_t i = 0; i < messages_.size(); i++) { + if (messages_[i].type() == id) + return &messages_[i]; + } + return NULL; +} + +const Message* TestSink::GetUniqueMessageMatching(uint32 id) const { + size_t found_index = 0; + size_t found_count = 0; + for (size_t i = 0; i < messages_.size(); i++) { + if (messages_[i].type() == id) { + found_count++; + found_index = i; + } + } + if (found_count != 1) + return NULL; // Didn't find a unique one. + return &messages_[found_index]; +} + +void TestSink::AddFilter(Listener* filter) { + filter_list_.AddObserver(filter); +} + +void TestSink::RemoveFilter(Listener* filter) { + filter_list_.RemoveObserver(filter); +} + +} // namespace IPC diff --git a/chromium/ipc/ipc_test_sink.h b/chromium/ipc/ipc_test_sink.h new file mode 100644 index 00000000000..78be9e75269 --- /dev/null +++ b/chromium/ipc/ipc_test_sink.h @@ -0,0 +1,129 @@ +// 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. + +#ifndef IPC_IPC_TEST_SINK_H_ +#define IPC_IPC_TEST_SINK_H_ + +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "ipc/ipc_channel.h" + +namespace IPC { + +class Message; + +// This test sink provides a "sink" for IPC messages that are sent. It allows +// the caller to query messages received in various different ways. It is +// designed for tests for objects that use the IPC system. +// +// Typical usage: +// +// test_sink.ClearMessages(); +// do_something(); +// +// // We should have gotten exactly one update state message. +// EXPECT_TRUE(test_sink.GetUniqeMessageMatching(ViewHostMsg_Update::ID)); +// // ...and no start load messages. +// EXPECT_FALSE(test_sink.GetFirstMessageMatching(ViewHostMsg_Start::ID)); +// +// // Now inspect a message. This assumes a message that was declared like +// // this: IPC_MESSAGE_ROUTED2(ViewMsg_Foo, bool, int) +// IPC::Message* msg = test_sink.GetFirstMessageMatching(ViewMsg_Foo::ID)); +// ASSERT_TRUE(msg); +// bool first_param; +// int second_param; +// ViewMsg_Foo::Read(msg, &first_param, &second_param); +// +// // Go on to the next phase of the test. +// test_sink.ClearMessages(); +// +// To read a sync reply, do this: +// +// IPC::Message* msg = test_sink.GetUniqueMessageMatching(IPC_REPLY_ID); +// ASSERT_TRUE(msg); +// TupleTypes<ViewHostMsg_Foo::ReplyParam>::ValueTuple reply_data; +// EXPECT_TRUE(ViewHostMsg_Foo::ReadReplyParam(msg, &reply_data)); +// +// You can also register to be notified when messages are posted to the sink. +// This can be useful if you need to wait for a particular message that will +// be posted asynchronously. Example usage: +// +// class MyListener : public IPC::Listener { +// public: +// virtual bool OnMessageReceived(const IPC::Message& msg) { +// <do something with the message> +// MessageLoop::current()->Quit(); +// return false; // to store the message in the sink, or true to drop it +// } +// }; +// +// MyListener listener; +// test_sink.AddFilter(&listener); +// StartSomeAsynchronousProcess(&test_sink); +// MessageLoop::current()->Run(); +// <inspect the results> +// ... +// +// To hook up the sink, all you need to do is call OnMessageReceived when a +// message is received. +class TestSink : public Channel { + public: + TestSink(); + virtual ~TestSink(); + + // Interface in IPC::Channel. This copies the message to the sink and then + // deletes it. + virtual bool Send(IPC::Message* message) OVERRIDE; + + // Used by the source of the messages to send the message to the sink. This + // will make a copy of the message and store it in the list. + bool OnMessageReceived(const Message& msg); + + // Returns the number of messages in the queue. + size_t message_count() const { return messages_.size(); } + + // Clears the message queue of saved messages. + void ClearMessages(); + + // Returns the message at the given index in the queue. The index may be out + // of range, in which case the return value is NULL. The returned pointer will + // only be valid until another message is received or the list is cleared. + const Message* GetMessageAt(size_t index) const; + + // Returns the first message with the given ID in the queue. If there is no + // message with the given ID, returns NULL. The returned pointer will only be + // valid until another message is received or the list is cleared. + const Message* GetFirstMessageMatching(uint32 id) const; + + // Returns the message with the given ID in the queue. If there is no such + // message or there is more than one of that message, this will return NULL + // (with the expectation that you'll do an ASSERT_TRUE() on the result). + // The returned pointer will only be valid until another message is received + // or the list is cleared. + const Message* GetUniqueMessageMatching(uint32 id) const; + + // Adds the given listener as a filter to the TestSink. + // When a message is received by the TestSink, it will be dispatched to + // the filters, in the order they were added. If a filter returns true + // from OnMessageReceived, subsequent filters will not receive the message + // and the TestSink will not store it. + void AddFilter(Listener* filter); + + // Removes the given filter from the TestSink. + void RemoveFilter(Listener* filter); + + private: + // The actual list of received messages. + std::vector<Message> messages_; + ObserverList<Listener> filter_list_; + + DISALLOW_COPY_AND_ASSIGN(TestSink); +}; + +} // namespace IPC + +#endif // IPC_IPC_TEST_SINK_H_ diff --git a/chromium/ipc/ipc_untrusted.gyp b/chromium/ipc/ipc_untrusted.gyp new file mode 100644 index 00000000000..a502548e873 --- /dev/null +++ b/chromium/ipc/ipc_untrusted.gyp @@ -0,0 +1,35 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'includes': [ + '../build/common_untrusted.gypi', + 'ipc.gypi', + ], + 'conditions': [ + ['disable_nacl==0 and disable_nacl_untrusted==0', { + 'targets': [ + { + 'target_name': 'ipc_untrusted', + 'type': 'none', + 'variables': { + 'ipc_target': 1, + 'nacl_untrusted_build': 1, + 'nlib_target': 'libipc_untrusted.a', + 'build_glibc': 0, + 'build_newlib': 1, + 'build_irt': 1, + }, + 'dependencies': [ + '<(DEPTH)/native_client/tools.gyp:prep_toolchain', + '../base/base_untrusted.gyp:base_untrusted', + ], + }, + ], + }], + ], +} diff --git a/chromium/ipc/param_traits_log_macros.h b/chromium/ipc/param_traits_log_macros.h new file mode 100644 index 00000000000..fa50bac68ba --- /dev/null +++ b/chromium/ipc/param_traits_log_macros.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef IPC_PARAM_TRAITS_LOG_MACROS_H_ +#define IPC_PARAM_TRAITS_LOG_MACROS_H_ + +#include <string> + +// Null out all the macros that need nulling. +#include "ipc/ipc_message_null_macros.h" + +// STRUCT declarations cause corresponding STRUCT_TRAITS declarations to occur. +#undef IPC_STRUCT_BEGIN_WITH_PARENT +#undef IPC_STRUCT_MEMBER +#undef IPC_STRUCT_END +#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \ + IPC_STRUCT_TRAITS_BEGIN(struct_name) +#define IPC_STRUCT_MEMBER(type, name, ...) IPC_STRUCT_TRAITS_MEMBER(name) +#define IPC_STRUCT_END() IPC_STRUCT_TRAITS_END() + +// Set up so next include will generate log methods. +#undef IPC_STRUCT_TRAITS_BEGIN +#undef IPC_STRUCT_TRAITS_MEMBER +#undef IPC_STRUCT_TRAITS_PARENT +#undef IPC_STRUCT_TRAITS_END +#define IPC_STRUCT_TRAITS_BEGIN(struct_name) \ + void ParamTraits<struct_name>::Log(const param_type& p, std::string* l) { \ + bool needs_comma = false; \ + l->append("("); +#define IPC_STRUCT_TRAITS_MEMBER(name) \ + if (needs_comma) \ + l->append(", "); \ + LogParam(p.name, l); \ + needs_comma = true; +#define IPC_STRUCT_TRAITS_PARENT(type) \ + if (needs_comma) \ + l->append(", "); \ + ParamTraits<type>::Log(p, l); \ + needs_comma = true; +#define IPC_STRUCT_TRAITS_END() \ + l->append(")"); \ + } + +#undef IPC_ENUM_TRAITS_VALIDATE +#define IPC_ENUM_TRAITS_VALIDATE(enum_name, validation_expression) \ + void ParamTraits<enum_name>::Log(const param_type& p, std::string* l) { \ + LogParam(static_cast<int>(p), l); \ + } + +#endif // IPC_PARAM_TRAITS_LOG_MACROS_H_ + diff --git a/chromium/ipc/param_traits_macros.h b/chromium/ipc/param_traits_macros.h new file mode 100644 index 00000000000..ad87a939bb7 --- /dev/null +++ b/chromium/ipc/param_traits_macros.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef IPC_PARAM_TRAITS_MACROS_H_ +#define IPC_PARAM_TRAITS_MACROS_H_ + +#include <string> + +// Traits generation for structs. +#define IPC_STRUCT_TRAITS_BEGIN(struct_name) \ + namespace IPC { \ + template <> \ + struct IPC_MESSAGE_EXPORT ParamTraits<struct_name> { \ + typedef struct_name param_type; \ + static void Write(Message* m, const param_type& p); \ + static bool Read(const Message* m, PickleIterator* iter, param_type* p); \ + static void Log(const param_type& p, std::string* l); \ + }; \ + } + +#define IPC_STRUCT_TRAITS_MEMBER(name) +#define IPC_STRUCT_TRAITS_PARENT(type) +#define IPC_STRUCT_TRAITS_END() + +// Convenience macro for defining enumerated type traits for types which are +// not range-checked by the IPC system. The author of the message handlers +// is responsible for all validation. This macro should not need to be +// subsequently redefined. +#define IPC_ENUM_TRAITS(type) \ + IPC_ENUM_TRAITS_VALIDATE(type, true) + +// Convenience macro for defining enumerated type traits for types which are +// range-checked by the IPC system to be in the range of 0..maxvalue inclusive. +// This macro should not need to be subsequently redefined. +#define IPC_ENUM_TRAITS_MAX_VALUE(type, maxvalue) \ + IPC_ENUM_TRAITS_MIN_MAX_VALUE(type, 0, maxvalue) + +// Convenience macro for defining enumerated type traits for types which are +// range-checked by the IPC system to be in the range of minvalue..maxvalue +// inclusive. This macro should not need to be subsequently redefined. +#define IPC_ENUM_TRAITS_MIN_MAX_VALUE(type, minvalue, maxvalue) \ + IPC_ENUM_TRAITS_VALIDATE(type, (value >= (minvalue) && value <= (maxvalue))) + +// Traits generation for enums. This macro may be redefined later. +#define IPC_ENUM_TRAITS_VALIDATE(enum_name, validation_expression) \ + namespace IPC { \ + template <> \ + struct IPC_MESSAGE_EXPORT ParamTraits<enum_name> { \ + typedef enum_name param_type; \ + static void Write(Message* m, const param_type& p); \ + static bool Read(const Message* m, PickleIterator* iter, param_type* p); \ + static void Log(const param_type& p, std::string* l); \ + }; \ + } + +#endif // IPC_PARAM_TRAITS_MACROS_H_ + diff --git a/chromium/ipc/param_traits_read_macros.h b/chromium/ipc/param_traits_read_macros.h new file mode 100644 index 00000000000..1656d14228b --- /dev/null +++ b/chromium/ipc/param_traits_read_macros.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef IPC_PARAM_TRAITS_READ_MACROS_H_ +#define IPC_PARAM_TRAITS_READ_MACROS_H_ + +// Null out all the macros that need nulling. +#include "ipc/ipc_message_null_macros.h" + +// STRUCT declarations cause corresponding STRUCT_TRAITS declarations to occur. +#undef IPC_STRUCT_BEGIN_WITH_PARENT +#undef IPC_STRUCT_MEMBER +#undef IPC_STRUCT_END +#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \ + IPC_STRUCT_TRAITS_BEGIN(struct_name) +#define IPC_STRUCT_MEMBER(type, name, ...) IPC_STRUCT_TRAITS_MEMBER(name) +#define IPC_STRUCT_END() IPC_STRUCT_TRAITS_END() + +// Set up so next include will generate read methods. +#undef IPC_STRUCT_TRAITS_BEGIN +#undef IPC_STRUCT_TRAITS_MEMBER +#undef IPC_STRUCT_TRAITS_PARENT +#undef IPC_STRUCT_TRAITS_END +#define IPC_STRUCT_TRAITS_BEGIN(struct_name) \ + bool ParamTraits<struct_name>:: \ + Read(const Message* m, PickleIterator* iter, param_type* p) { \ + return +#define IPC_STRUCT_TRAITS_MEMBER(name) ReadParam(m, iter, &p->name) && +#define IPC_STRUCT_TRAITS_PARENT(type) ParamTraits<type>::Read(m, iter, p) && +#define IPC_STRUCT_TRAITS_END() 1; } + +#undef IPC_ENUM_TRAITS_VALIDATE +#define IPC_ENUM_TRAITS_VALIDATE(enum_name, validation_expression) \ + bool ParamTraits<enum_name>:: \ + Read(const Message* m, PickleIterator* iter, param_type* p) { \ + int value; \ + if (!m->ReadInt(iter, &value)) \ + return false; \ + if (!(validation_expression)) \ + return false; \ + *p = static_cast<param_type>(value); \ + return true; \ + } + +#endif // IPC_PARAM_TRAITS_READ_MACROS_H_ + diff --git a/chromium/ipc/param_traits_write_macros.h b/chromium/ipc/param_traits_write_macros.h new file mode 100644 index 00000000000..81f915ffff8 --- /dev/null +++ b/chromium/ipc/param_traits_write_macros.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef IPC_PARAM_TRAITS_WRITE_MACROS_H_ +#define IPC_PARAM_TRAITS_WRITE_MACROS_H_ + +// Null out all the macros that need nulling. +#include "ipc/ipc_message_null_macros.h" + +// STRUCT declarations cause corresponding STRUCT_TRAITS declarations to occur. +#undef IPC_STRUCT_BEGIN_WITH_PARENT +#undef IPC_STRUCT_MEMBER +#undef IPC_STRUCT_END +#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \ + IPC_STRUCT_TRAITS_BEGIN(struct_name) +#define IPC_STRUCT_MEMBER(type, name, ...) IPC_STRUCT_TRAITS_MEMBER(name) +#define IPC_STRUCT_END() IPC_STRUCT_TRAITS_END() + +// Set up so next include will generate write methods. +#undef IPC_STRUCT_TRAITS_BEGIN +#undef IPC_STRUCT_TRAITS_MEMBER +#undef IPC_STRUCT_TRAITS_PARENT +#undef IPC_STRUCT_TRAITS_END +#define IPC_STRUCT_TRAITS_BEGIN(struct_name) \ + void ParamTraits<struct_name>::Write(Message* m, const param_type& p) { +#define IPC_STRUCT_TRAITS_MEMBER(name) WriteParam(m, p.name); +#define IPC_STRUCT_TRAITS_PARENT(type) ParamTraits<type>::Write(m, p); +#define IPC_STRUCT_TRAITS_END() } + +#undef IPC_ENUM_TRAITS_VALIDATE +#define IPC_ENUM_TRAITS_VALIDATE(enum_name, validation_expression) \ + void ParamTraits<enum_name>::Write(Message* m, const param_type& value) { \ + DCHECK(validation_expression); \ + m->WriteInt(static_cast<int>(value)); \ + } + +#endif // IPC_PARAM_TRAITS_WRITE_MACROS_H_ + diff --git a/chromium/ipc/struct_constructor_macros.h b/chromium/ipc/struct_constructor_macros.h new file mode 100644 index 00000000000..43feab863b5 --- /dev/null +++ b/chromium/ipc/struct_constructor_macros.h @@ -0,0 +1,21 @@ +// 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. + +#ifndef IPC_STRUCT_CONSTRUCTOR_MACROS_H_ +#define IPC_STRUCT_CONSTRUCTOR_MACROS_H_ + +// Null out all the macros that need nulling. +#include "ipc/ipc_message_null_macros.h" + +// Set up so next include will generate constructors. +#undef IPC_STRUCT_BEGIN_WITH_PARENT +#undef IPC_STRUCT_MEMBER +#undef IPC_STRUCT_END +#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \ + struct_name::struct_name() : parent() +#define IPC_STRUCT_MEMBER(type, name, ...) , name(__VA_ARGS__) +#define IPC_STRUCT_END() {} + +#endif // IPC_STRUCT_CONSTRUCTOR_MACROS_H_ + diff --git a/chromium/ipc/struct_destructor_macros.h b/chromium/ipc/struct_destructor_macros.h new file mode 100644 index 00000000000..ccb46f94938 --- /dev/null +++ b/chromium/ipc/struct_destructor_macros.h @@ -0,0 +1,17 @@ +// 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. + +#ifndef IPC_STRUCT_DESTRUCTOR_MACROS_H_ +#define IPC_STRUCT_DESTRUCTOR_MACROS_H_ + +// Null out all the macros that need nulling. +#include "ipc/ipc_message_null_macros.h" + +// Set up so next include will generate destructors. +#undef IPC_STRUCT_BEGIN_WITH_PARENT +#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \ + struct_name::~struct_name() {} + +#endif // IPC_STRUCT_DESTRUCTOR_MACROS_H_ + diff --git a/chromium/ipc/sync_socket_unittest.cc b/chromium/ipc/sync_socket_unittest.cc new file mode 100644 index 00000000000..288860713ad --- /dev/null +++ b/chromium/ipc/sync_socket_unittest.cc @@ -0,0 +1,308 @@ +// 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 "base/sync_socket.h" + +#include <stdio.h> +#include <string> +#include <sstream> + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread.h" +#include "ipc/ipc_test_base.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#endif + +// IPC messages for testing ---------------------------------------------------- + +#define IPC_MESSAGE_IMPL +#include "ipc/ipc_message_macros.h" + +#define IPC_MESSAGE_START TestMsgStart + +// Message class to pass a base::SyncSocket::Handle to another process. This +// is not as easy as it sounds, because of the differences in transferring +// Windows HANDLEs versus posix file descriptors. +#if defined(OS_WIN) +IPC_MESSAGE_CONTROL1(MsgClassSetHandle, base::SyncSocket::Handle) +#elif defined(OS_POSIX) +IPC_MESSAGE_CONTROL1(MsgClassSetHandle, base::FileDescriptor) +#endif + +// Message class to pass a response to the server. +IPC_MESSAGE_CONTROL1(MsgClassResponse, std::string) + +// Message class to tell the server to shut down. +IPC_MESSAGE_CONTROL0(MsgClassShutdown) + +// ----------------------------------------------------------------------------- + +namespace { + +const char kHelloString[] = "Hello, SyncSocket Client"; +const size_t kHelloStringLength = arraysize(kHelloString); + +// The SyncSocket server listener class processes two sorts of +// messages from the client. +class SyncSocketServerListener : public IPC::Listener { + public: + SyncSocketServerListener() : chan_(NULL) { + } + + void Init(IPC::Channel* chan) { + chan_ = chan; + } + + virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE { + 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() + } + return true; + } + + 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. +#if defined(OS_WIN) + void OnMsgClassSetHandle(const base::SyncSocket::Handle handle) { + SetHandle(handle); + } +#elif defined(OS_POSIX) + void OnMsgClassSetHandle(const base::FileDescriptor& fd_struct) { + SetHandle(fd_struct.fd); + } +#else +# error "What platform?" +#endif // defined(OS_WIN) + + void SetHandle(base::SyncSocket::Handle handle) { + base::SyncSocket sync_socket(handle); + EXPECT_EQ(sync_socket.Send(kHelloString, kHelloStringLength), + kHelloStringLength); + IPC::Message* msg = new MsgClassResponse(kHelloString); + EXPECT_TRUE(chan_->Send(msg)); + } + + // When the client responds, it sends back a shutdown message, + // which causes the message loop to exit. + void OnMsgClassShutdown() { + base::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_IPC_TEST_CLIENT_MAIN(SyncSocketServerClient) { + base::MessageLoopForIO main_message_loop; + SyncSocketServerListener listener; + IPC::Channel channel(IPCTestBase::GetChannelName("SyncSocketServerClient"), + IPC::Channel::MODE_CLIENT, + &listener); + EXPECT_TRUE(channel.Connect()); + listener.Init(&channel); + base::MessageLoop::current()->Run(); + return 0; +} + +// The SyncSocket client listener only processes one sort of message, +// a response from the server. +class SyncSocketClientListener : public IPC::Listener { + public: + SyncSocketClientListener() { + } + + void Init(base::SyncSocket* socket, IPC::Channel* chan) { + socket_ = socket; + chan_ = chan; + } + + virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE { + if (msg.routing_id() == MSG_ROUTING_CONTROL) { + IPC_BEGIN_MESSAGE_MAP(SyncSocketClientListener, msg) + IPC_MESSAGE_HANDLER(MsgClassResponse, OnMsgClassResponse) + IPC_END_MESSAGE_MAP() + } + return true; + } + + 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) { + // We rely on the order of sync_socket.Send() and chan_->Send() in + // the SyncSocketServerListener object. + EXPECT_EQ(kHelloStringLength, socket_->Peek()); + char buf[kHelloStringLength]; + socket_->Receive(static_cast<void*>(buf), kHelloStringLength); + EXPECT_EQ(strcmp(str.c_str(), buf), 0); + // After receiving from the socket there should be no bytes left. + EXPECT_EQ(0U, socket_->Peek()); + IPC::Message* msg = new MsgClassShutdown(); + EXPECT_TRUE(chan_->Send(msg)); + base::MessageLoop::current()->Quit(); + } + + base::SyncSocket* socket_; + IPC::Channel* chan_; + + DISALLOW_COPY_AND_ASSIGN(SyncSocketClientListener); +}; + +class SyncSocketTest : public IPCTestBase { +}; + +TEST_F(SyncSocketTest, SanityTest) { + Init("SyncSocketServerClient"); + + SyncSocketClientListener listener; + CreateChannel(&listener); + ASSERT_TRUE(StartClient()); + // Create a pair of SyncSockets. + base::SyncSocket pair[2]; + base::SyncSocket::CreatePair(&pair[0], &pair[1]); + // Immediately after creation there should be no pending bytes. + EXPECT_EQ(0U, pair[0].Peek()); + EXPECT_EQ(0U, pair[1].Peek()); + base::SyncSocket::Handle target_handle; + // Connect the channel and listener. + ASSERT_TRUE(ConnectChannel()); + listener.Init(&pair[0], channel()); +#if defined(OS_WIN) + // On windows we need to duplicate the handle into the server process. + BOOL retval = DuplicateHandle(GetCurrentProcess(), pair[1].handle(), + client_process(), &target_handle, + 0, FALSE, DUPLICATE_SAME_ACCESS); + EXPECT_TRUE(retval); + // Set up a message to pass the handle to the server. + IPC::Message* msg = new MsgClassSetHandle(target_handle); +#else + target_handle = pair[1].handle(); + // Set up a message to pass the handle to the server. + base::FileDescriptor filedesc(target_handle, false); + IPC::Message* msg = new MsgClassSetHandle(filedesc); +#endif // defined(OS_WIN) + EXPECT_TRUE(sender()->Send(msg)); + // Use the current thread as the I/O thread. + base::MessageLoop::current()->Run(); + // Shut down. + pair[0].Close(); + pair[1].Close(); + EXPECT_TRUE(WaitForClientShutdown()); + DestroyChannel(); +} + +// A blocking read operation that will block the thread until it receives +// |length| bytes of packets or Shutdown() is called on another thread. +static void BlockingRead(base::SyncSocket* socket, char* buf, + size_t length, size_t* received) { + DCHECK(buf != NULL); + // Notify the parent thread that we're up and running. + socket->Send(kHelloString, kHelloStringLength); + *received = socket->Receive(buf, length); +} + +// Tests that we can safely end a blocking Receive operation on one thread +// from another thread by disconnecting (but not closing) the socket. +TEST_F(SyncSocketTest, DisconnectTest) { + base::CancelableSyncSocket pair[2]; + ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&pair[0], &pair[1])); + + base::Thread worker("BlockingThread"); + worker.Start(); + + // Try to do a blocking read from one of the sockets on the worker thread. + char buf[0xff]; + size_t received = 1U; // Initialize to an unexpected value. + worker.message_loop()->PostTask(FROM_HERE, + base::Bind(&BlockingRead, &pair[0], &buf[0], arraysize(buf), &received)); + + // Wait for the worker thread to say hello. + char hello[kHelloStringLength] = {0}; + pair[1].Receive(&hello[0], sizeof(hello)); + EXPECT_EQ(0, strcmp(hello, kHelloString)); + // Give the worker a chance to start Receive(). + base::PlatformThread::YieldCurrentThread(); + + // Now shut down the socket that the thread is issuing a blocking read on + // which should cause Receive to return with an error. + pair[0].Shutdown(); + + worker.Stop(); + + EXPECT_EQ(0U, received); +} + +// Tests that read is a blocking operation. +TEST_F(SyncSocketTest, BlockingReceiveTest) { + base::CancelableSyncSocket pair[2]; + ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&pair[0], &pair[1])); + + base::Thread worker("BlockingThread"); + worker.Start(); + + // Try to do a blocking read from one of the sockets on the worker thread. + char buf[kHelloStringLength] = {0}; + size_t received = 1U; // Initialize to an unexpected value. + worker.message_loop()->PostTask(FROM_HERE, + base::Bind(&BlockingRead, &pair[0], &buf[0], + kHelloStringLength, &received)); + + // Wait for the worker thread to say hello. + char hello[kHelloStringLength] = {0}; + pair[1].Receive(&hello[0], sizeof(hello)); + EXPECT_EQ(0, strcmp(hello, kHelloString)); + // Give the worker a chance to start Receive(). + base::PlatformThread::YieldCurrentThread(); + + // Send a message to the socket on the blocking thead, it should free the + // socket from Receive(). + pair[1].Send(kHelloString, kHelloStringLength); + worker.Stop(); + + // Verify the socket has received the message. + EXPECT_TRUE(strcmp(buf, kHelloString) == 0); + EXPECT_EQ(kHelloStringLength, received); +} + +// Tests that the write operation is non-blocking and returns immediately +// when there is insufficient space in the socket's buffer. +TEST_F(SyncSocketTest, NonBlockingWriteTest) { + base::CancelableSyncSocket pair[2]; + ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&pair[0], &pair[1])); + + // Fill up the buffer for one of the socket, Send() should not block the + // thread even when the buffer is full. + while (pair[0].Send(kHelloString, kHelloStringLength) != 0) {} + + // Data should be avialble on another socket. + size_t bytes_in_buffer = pair[1].Peek(); + EXPECT_NE(bytes_in_buffer, 0U); + + // No more data can be written to the buffer since socket has been full, + // verify that the amount of avialble data on another socket is unchanged. + EXPECT_EQ(0U, pair[0].Send(kHelloString, kHelloStringLength)); + EXPECT_EQ(bytes_in_buffer, pair[1].Peek()); + + // Read from another socket to free some space for a new write. + char hello[kHelloStringLength] = {0}; + pair[1].Receive(&hello[0], sizeof(hello)); + + // Should be able to write more data to the buffer now. + EXPECT_EQ(kHelloStringLength, pair[0].Send(kHelloString, kHelloStringLength)); +} + +} // namespace diff --git a/chromium/ipc/unix_domain_socket_util.cc b/chromium/ipc/unix_domain_socket_util.cc new file mode 100644 index 00000000000..7f513a3ab48 --- /dev/null +++ b/chromium/ipc/unix_domain_socket_util.cc @@ -0,0 +1,202 @@ +// 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 "ipc/unix_domain_socket_util.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace IPC { + +// Verify that kMaxSocketNameLength is a decent size. +COMPILE_ASSERT(sizeof(((sockaddr_un*)0)->sun_path) >= kMaxSocketNameLength, + BAD_SUN_PATH_LENGTH); + +namespace { + +// Returns fd (>= 0) on success, -1 on failure. If successful, fills in +// |unix_addr| with the appropriate data for the socket, and sets +// |unix_addr_len| to the length of the data therein. +int MakeUnixAddrForPath(const std::string& socket_name, + struct sockaddr_un* unix_addr, + size_t* unix_addr_len) { + DCHECK(unix_addr); + DCHECK(unix_addr_len); + + if (socket_name.length() == 0) { + LOG(ERROR) << "Empty socket name provided for unix socket address."; + return -1; + } + // We reject socket_name.length() == kMaxSocketNameLength to make room for + // the NUL terminator at the end of the string. + if (socket_name.length() >= kMaxSocketNameLength) { + LOG(ERROR) << "Socket name too long: " << socket_name; + return -1; + } + + // Create socket. + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + PLOG(ERROR) << "socket"; + return -1; + } + file_util::ScopedFD scoped_fd(&fd); + + // Make socket non-blocking + if (HANDLE_EINTR(fcntl(fd, F_SETFL, O_NONBLOCK)) < 0) { + PLOG(ERROR) << "fcntl(O_NONBLOCK)"; + return -1; + } + + // Create unix_addr structure. + memset(unix_addr, 0, sizeof(struct sockaddr_un)); + unix_addr->sun_family = AF_UNIX; + strncpy(unix_addr->sun_path, socket_name.c_str(), kMaxSocketNameLength); + *unix_addr_len = + offsetof(struct sockaddr_un, sun_path) + socket_name.length(); + return *scoped_fd.release(); +} + +} // namespace + +bool CreateServerUnixDomainSocket(const base::FilePath& socket_path, + int* server_listen_fd) { + DCHECK(server_listen_fd); + + std::string socket_name = socket_path.value(); + base::FilePath socket_dir = socket_path.DirName(); + + struct sockaddr_un unix_addr; + size_t unix_addr_len; + int fd = MakeUnixAddrForPath(socket_name, &unix_addr, &unix_addr_len); + if (fd < 0) + return false; + file_util::ScopedFD scoped_fd(&fd); + + // Make sure the path we need exists. + if (!file_util::CreateDirectory(socket_dir)) { + LOG(ERROR) << "Couldn't create directory: " << socket_dir.value(); + return false; + } + + // Delete any old FS instances. + if (unlink(socket_name.c_str()) < 0 && errno != ENOENT) { + PLOG(ERROR) << "unlink " << socket_name; + return false; + } + + // Bind the socket. + if (bind(fd, reinterpret_cast<const sockaddr*>(&unix_addr), + unix_addr_len) < 0) { + PLOG(ERROR) << "bind " << socket_path.value(); + return false; + } + + // Start listening on the socket. + if (listen(fd, SOMAXCONN) < 0) { + PLOG(ERROR) << "listen " << socket_path.value(); + unlink(socket_name.c_str()); + return false; + } + + *server_listen_fd = *scoped_fd.release(); + return true; +} + +bool CreateClientUnixDomainSocket(const base::FilePath& socket_path, + int* client_socket) { + DCHECK(client_socket); + + std::string socket_name = socket_path.value(); + base::FilePath socket_dir = socket_path.DirName(); + + struct sockaddr_un unix_addr; + size_t unix_addr_len; + int fd = MakeUnixAddrForPath(socket_name, &unix_addr, &unix_addr_len); + if (fd < 0) + return false; + file_util::ScopedFD scoped_fd(&fd); + + if (HANDLE_EINTR(connect(fd, reinterpret_cast<sockaddr*>(&unix_addr), + unix_addr_len)) < 0) { + PLOG(ERROR) << "connect " << socket_path.value(); + return false; + } + + *client_socket = *scoped_fd.release(); + return true; +} + +bool GetPeerEuid(int fd, uid_t* peer_euid) { + DCHECK(peer_euid); +#if defined(OS_MACOSX) || defined(OS_OPENBSD) || defined(OS_FREEBSD) + uid_t socket_euid; + gid_t socket_gid; + if (getpeereid(fd, &socket_euid, &socket_gid) < 0) { + PLOG(ERROR) << "getpeereid " << fd; + return false; + } + *peer_euid = socket_euid; + return true; +#else + struct ucred cred; + socklen_t cred_len = sizeof(cred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) < 0) { + PLOG(ERROR) << "getsockopt " << fd; + return false; + } + if (static_cast<unsigned>(cred_len) < sizeof(cred)) { + NOTREACHED() << "Truncated ucred from SO_PEERCRED?"; + return false; + } + *peer_euid = cred.uid; + return true; +#endif +} + +bool IsPeerAuthorized(int peer_fd) { + uid_t peer_euid; + if (!GetPeerEuid(peer_fd, &peer_euid)) + return false; + if (peer_euid != geteuid()) { + DLOG(ERROR) << "Client euid is not authorised"; + return false; + } + return true; +} + +bool IsRecoverableError(int err) { + return errno == ECONNABORTED || errno == EMFILE || errno == ENFILE || + errno == ENOMEM || errno == ENOBUFS; +} + +bool ServerAcceptConnection(int server_listen_fd, int* server_socket) { + DCHECK(server_socket); + *server_socket = -1; + + int accept_fd = HANDLE_EINTR(accept(server_listen_fd, NULL, 0)); + if (accept_fd < 0) + return IsRecoverableError(errno); + file_util::ScopedFD scoped_fd(&accept_fd); + if (HANDLE_EINTR(fcntl(accept_fd, F_SETFL, O_NONBLOCK)) < 0) { + PLOG(ERROR) << "fcntl(O_NONBLOCK) " << accept_fd; + // It's safe to keep listening on |server_listen_fd| even if the attempt to + // set O_NONBLOCK failed on the client fd. + return true; + } + + *server_socket = *scoped_fd.release(); + return true; +} + +} // namespace IPC diff --git a/chromium/ipc/unix_domain_socket_util.h b/chromium/ipc/unix_domain_socket_util.h new file mode 100644 index 00000000000..5752364fa10 --- /dev/null +++ b/chromium/ipc/unix_domain_socket_util.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef IPC_UNIX_DOMAIN_SOCKET_UTIL_H_ +#define IPC_UNIX_DOMAIN_SOCKET_UTIL_H_ + +#include <sys/types.h> + +#include <string> + +#include "ipc/ipc_export.h" + +namespace base { +class FilePath; +} // namespace base + +namespace IPC { + +// Creates a UNIX-domain socket at |socket_name| and bind()s it, then listen()s +// on it. If successful, |server_listen_fd| will be set to the new file +// descriptor, and the function will return true. Otherwise returns false. +// +// This function also effectively performs `mkdir -p` on the dirname of +// |socket_name| to ensure that all the directories up to |socket_name| exist. +// As a result of which this function must be run on a thread that allows +// blocking I/O, e.g. the FILE thread in Chrome's browser process. +IPC_EXPORT bool CreateServerUnixDomainSocket(const base::FilePath& socket_name, + int* server_listen_fd); + +// Opens a UNIX-domain socket at |socket_name| and connect()s to it. If +// successful, |client_socket| will be set to the new file descriptor, and the +// function will return true. Otherwise returns false. +IPC_EXPORT bool CreateClientUnixDomainSocket(const base::FilePath& socket_name, + int* client_socket); + +// Gets the effective user ID of the other end of the UNIX-domain socket +// specified by |fd|. If successful, sets |peer_euid| to the uid, and returns +// true. Otherwise returns false. +IPC_EXPORT bool GetPeerEuid(int fd, uid_t* peer_euid); + +// Checks that the process on the other end of the UNIX domain socket +// represented by |peer_fd| shares the same EUID as this process. +IPC_EXPORT bool IsPeerAuthorized(int peer_fd); + +// Accepts a client attempting to connect to |server_listen_fd|, storing the +// new file descriptor for the connection in |server_socket|. +// +// Returns false if |server_listen_fd| encounters an unrecoverable error. +// Returns true if it's valid to keep listening on |server_listen_fd|. In this +// case, it's possible that a connection wasn't successfully established; then, +// |server_socket| will be set to -1. +IPC_EXPORT bool ServerAcceptConnection(int server_listen_fd, + int* server_socket); + +// The maximum length of the name of a socket for MODE_NAMED_SERVER or +// MODE_NAMED_CLIENT if you want to pass in your own socket. +// The standard size on linux is 108, mac is 104. To maintain consistency +// across platforms we standardize on the smaller value. +static const size_t kMaxSocketNameLength = 104; + +} // namespace IPC + +#endif // IPC_UNIX_DOMAIN_SOCKET_UTIL_H_ diff --git a/chromium/ipc/unix_domain_socket_util_unittest.cc b/chromium/ipc/unix_domain_socket_util_unittest.cc new file mode 100644 index 00000000000..81d2f063d35 --- /dev/null +++ b/chromium/ipc/unix_domain_socket_util_unittest.cc @@ -0,0 +1,175 @@ +// 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 <sys/socket.h> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "base/threading/thread_restrictions.h" +#include "ipc/unix_domain_socket_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class SocketAcceptor : public base::MessageLoopForIO::Watcher { + public: + SocketAcceptor(int fd, base::MessageLoopProxy* target_thread) + : server_fd_(-1), + target_thread_(target_thread), + started_watching_event_(false, false), + accepted_event_(false, false) { + target_thread->PostTask(FROM_HERE, + base::Bind(&SocketAcceptor::StartWatching, base::Unretained(this), fd)); + } + + virtual ~SocketAcceptor() { + Close(); + } + + int server_fd() const { return server_fd_; } + + void WaitUntilReady() { + started_watching_event_.Wait(); + } + + void WaitForAccept() { + accepted_event_.Wait(); + } + + void Close() { + if (watcher_.get()) { + target_thread_->PostTask(FROM_HERE, + base::Bind(&SocketAcceptor::StopWatching, base::Unretained(this), + watcher_.release())); + } + } + + private: + void StartWatching(int fd) { + watcher_.reset(new base::MessageLoopForIO::FileDescriptorWatcher); + base::MessageLoopForIO::current()->WatchFileDescriptor( + fd, true, base::MessageLoopForIO::WATCH_READ, watcher_.get(), this); + started_watching_event_.Signal(); + } + void StopWatching(base::MessageLoopForIO::FileDescriptorWatcher* watcher) { + watcher->StopWatchingFileDescriptor(); + delete watcher; + } + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE { + ASSERT_EQ(-1, server_fd_); + IPC::ServerAcceptConnection(fd, &server_fd_); + watcher_->StopWatchingFileDescriptor(); + accepted_event_.Signal(); + } + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {} + + int server_fd_; + base::MessageLoopProxy* target_thread_; + scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> watcher_; + base::WaitableEvent started_watching_event_; + base::WaitableEvent accepted_event_; + + DISALLOW_COPY_AND_ASSIGN(SocketAcceptor); +}; + +const base::FilePath GetChannelDir() { +#if defined(OS_ANDROID) + base::FilePath tmp_dir; + PathService::Get(base::DIR_CACHE, &tmp_dir); + return tmp_dir; +#else + return base::FilePath("/var/tmp"); +#endif +} + +class TestUnixSocketConnection { + public: + TestUnixSocketConnection() + : worker_("WorkerThread"), + server_listen_fd_(-1), + server_fd_(-1), + client_fd_(-1) { + socket_name_ = GetChannelDir().Append("TestSocket"); + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + worker_.StartWithOptions(options); + } + + bool CreateServerSocket() { + IPC::CreateServerUnixDomainSocket(socket_name_, &server_listen_fd_); + if (server_listen_fd_ < 0) + return false; + struct stat socket_stat; + stat(socket_name_.value().c_str(), &socket_stat); + EXPECT_TRUE(S_ISSOCK(socket_stat.st_mode)); + acceptor_.reset(new SocketAcceptor(server_listen_fd_, + worker_.message_loop_proxy().get())); + acceptor_->WaitUntilReady(); + return true; + } + + bool CreateClientSocket() { + DCHECK(server_listen_fd_ >= 0); + IPC::CreateClientUnixDomainSocket(socket_name_, &client_fd_); + if (client_fd_ < 0) + return false; + acceptor_->WaitForAccept(); + server_fd_ = acceptor_->server_fd(); + return server_fd_ >= 0; + } + + virtual ~TestUnixSocketConnection() { + if (client_fd_ >= 0) + close(client_fd_); + if (server_fd_ >= 0) + close(server_fd_); + if (server_listen_fd_ >= 0) { + close(server_listen_fd_); + unlink(socket_name_.value().c_str()); + } + } + + int client_fd() const { return client_fd_; } + int server_fd() const { return server_fd_; } + + private: + base::Thread worker_; + base::FilePath socket_name_; + int server_listen_fd_; + int server_fd_; + int client_fd_; + scoped_ptr<SocketAcceptor> acceptor_; +}; + +// Ensure that IPC::CreateServerUnixDomainSocket creates a socket that +// IPC::CreateClientUnixDomainSocket can successfully connect to. +TEST(UnixDomainSocketUtil, Connect) { + TestUnixSocketConnection connection; + ASSERT_TRUE(connection.CreateServerSocket()); + ASSERT_TRUE(connection.CreateClientSocket()); +} + +// Ensure that messages can be sent across the resulting socket. +TEST(UnixDomainSocketUtil, SendReceive) { + TestUnixSocketConnection connection; + ASSERT_TRUE(connection.CreateServerSocket()); + ASSERT_TRUE(connection.CreateClientSocket()); + + const char buffer[] = "Hello, server!"; + size_t buf_len = sizeof(buffer); + size_t sent_bytes = + HANDLE_EINTR(send(connection.client_fd(), buffer, buf_len, 0)); + ASSERT_EQ(buf_len, sent_bytes); + char recv_buf[sizeof(buffer)]; + size_t received_bytes = + HANDLE_EINTR(recv(connection.server_fd(), recv_buf, buf_len, 0)); + ASSERT_EQ(buf_len, received_bytes); + ASSERT_EQ(0, memcmp(recv_buf, buffer, buf_len)); +} + +} // namespace |