summaryrefslogtreecommitdiffstats
path: root/mojo/edk
diff options
context:
space:
mode:
authorjam <jam@chromium.org>2015-10-02 14:01:28 -0700
committerCommit bot <commit-bot@chromium.org>2015-10-02 21:02:13 +0000
commit76bcf0c21e332c08ee7a1601d0c878d1c75541a0 (patch)
tree9a1070172b98a5c0775967858a07d55585751839 /mojo/edk
parentbe81c48e3a52a050a6d07d0eb558a41c02594e6e (diff)
downloadchromium_src-76bcf0c21e332c08ee7a1601d0c878d1c75541a0.zip
chromium_src-76bcf0c21e332c08ee7a1601d0c878d1c75541a0.tar.gz
chromium_src-76bcf0c21e332c08ee7a1601d0c878d1c75541a0.tar.bz2
Add a Mojo EDK for Chrome that uses one OS pipe per message pipe.
TODOs in future cls: -POSIX -use shared memory for MessagePipeDispatcher serialization -work in Windows sandbox (using master.mojom which is implemented but not hooked up in this change) -XP BUG=478251 NOPRESUBMIT=true Review URL: https://codereview.chromium.org/1350023003 Cr-Commit-Position: refs/heads/master@{#352123}
Diffstat (limited to 'mojo/edk')
-rw-r--r--mojo/edk/DEPS13
-rw-r--r--mojo/edk/embedder/BUILD.gn145
-rw-r--r--mojo/edk/embedder/README.md13
-rw-r--r--mojo/edk/embedder/configuration.h68
-rw-r--r--mojo/edk/embedder/embedder.cc171
-rw-r--r--mojo/edk/embedder/embedder.h111
-rw-r--r--mojo/edk/embedder/embedder_internal.h46
-rw-r--r--mojo/edk/embedder/embedder_unittest.cc560
-rw-r--r--mojo/edk/embedder/entrypoints.cc149
-rw-r--r--mojo/edk/embedder/platform_channel_pair.cc32
-rw-r--r--mojo/edk/embedder/platform_channel_pair.h94
-rw-r--r--mojo/edk/embedder/platform_channel_pair_posix.cc111
-rw-r--r--mojo/edk/embedder/platform_channel_pair_posix_unittest.cc260
-rw-r--r--mojo/edk/embedder/platform_channel_pair_win.cc111
-rw-r--r--mojo/edk/embedder/platform_channel_utils_posix.cc186
-rw-r--r--mojo/edk/embedder/platform_channel_utils_posix.h76
-rw-r--r--mojo/edk/embedder/platform_handle.cc39
-rw-r--r--mojo/edk/embedder/platform_handle.h47
-rw-r--r--mojo/edk/embedder/platform_handle_utils.h33
-rw-r--r--mojo/edk/embedder/platform_handle_utils_posix.cc22
-rw-r--r--mojo/edk/embedder/platform_handle_utils_win.cc27
-rw-r--r--mojo/edk/embedder/platform_handle_vector.h35
-rw-r--r--mojo/edk/embedder/platform_shared_buffer.h102
-rw-r--r--mojo/edk/embedder/platform_support.h43
-rw-r--r--mojo/edk/embedder/process_delegate.h32
-rw-r--r--mojo/edk/embedder/scoped_platform_handle.h62
-rw-r--r--mojo/edk/embedder/simple_platform_shared_buffer.cc108
-rw-r--r--mojo/edk/embedder/simple_platform_shared_buffer.h101
-rw-r--r--mojo/edk/embedder/simple_platform_shared_buffer_android.cc72
-rw-r--r--mojo/edk/embedder/simple_platform_shared_buffer_posix.cc158
-rw-r--r--mojo/edk/embedder/simple_platform_shared_buffer_unittest.cc187
-rw-r--r--mojo/edk/embedder/simple_platform_shared_buffer_win.cc88
-rw-r--r--mojo/edk/embedder/simple_platform_support.cc31
-rw-r--r--mojo/edk/embedder/simple_platform_support.h39
-rw-r--r--mojo/edk/embedder/system_impl_private_entrypoints.cc255
-rw-r--r--mojo/edk/embedder/test_embedder.cc56
-rw-r--r--mojo/edk/embedder/test_embedder.h28
-rw-r--r--mojo/edk/js/BUILD.gn65
-rw-r--r--mojo/edk/js/core.cc379
-rw-r--r--mojo/edk/js/core.h22
-rw-r--r--mojo/edk/js/drain_data.cc131
-rw-r--r--mojo/edk/js/drain_data.h64
-rw-r--r--mojo/edk/js/handle.cc83
-rw-r--r--mojo/edk/js/handle.h99
-rw-r--r--mojo/edk/js/handle_close_observer.h22
-rw-r--r--mojo/edk/js/handle_unittest.cc90
-rw-r--r--mojo/edk/js/mojo_runner_delegate.cc78
-rw-r--r--mojo/edk/js/mojo_runner_delegate.h33
-rw-r--r--mojo/edk/js/support.cc60
-rw-r--r--mojo/edk/js/support.h22
-rw-r--r--mojo/edk/js/test/BUILD.gn46
-rw-r--r--mojo/edk/js/test/hexdump.js34
-rw-r--r--mojo/edk/js/test/run_js_integration_tests.cc57
-rw-r--r--mojo/edk/js/test/run_js_tests.cc66
-rw-r--r--mojo/edk/js/tests/BUILD.gn31
-rw-r--r--mojo/edk/js/tests/connection_tests.js240
-rw-r--r--mojo/edk/js/tests/js_to_cpp.mojom54
-rw-r--r--mojo/edk/js/tests/js_to_cpp_tests.cc427
-rw-r--r--mojo/edk/js/tests/js_to_cpp_tests.js228
-rw-r--r--mojo/edk/js/tests/sample_service_tests.js171
-rw-r--r--mojo/edk/js/threading.cc47
-rw-r--r--mojo/edk/js/threading.h25
-rw-r--r--mojo/edk/js/waiting_callback.cc115
-rw-r--r--mojo/edk/js/waiting_callback.h68
-rw-r--r--mojo/edk/mojo_edk.gni69
-rw-r--r--mojo/edk/system/BUILD.gn208
-rw-r--r--mojo/edk/system/async_waiter.cc23
-rw-r--r--mojo/edk/system/async_waiter.h38
-rw-r--r--mojo/edk/system/awakable.h34
-rw-r--r--mojo/edk/system/awakable_list.cc74
-rw-r--r--mojo/edk/system/awakable_list.h58
-rw-r--r--mojo/edk/system/awakable_list_unittest.cc356
-rw-r--r--mojo/edk/system/configuration.cc26
-rw-r--r--mojo/edk/system/configuration.h29
-rw-r--r--mojo/edk/system/core.cc603
-rw-r--r--mojo/edk/system/core.h188
-rw-r--r--mojo/edk/system/core_test_base.cc373
-rw-r--r--mojo/edk/system/core_test_base.h112
-rw-r--r--mojo/edk/system/core_unittest.cc1265
-rw-r--r--mojo/edk/system/data_pipe.cc203
-rw-r--r--mojo/edk/system/data_pipe.h63
-rw-r--r--mojo/edk/system/data_pipe_consumer_dispatcher.cc474
-rw-r--r--mojo/edk/system/data_pipe_consumer_dispatcher.h117
-rw-r--r--mojo/edk/system/data_pipe_producer_dispatcher.cc357
-rw-r--r--mojo/edk/system/data_pipe_producer_dispatcher.h110
-rw-r--r--mojo/edk/system/data_pipe_unittest.cc1574
-rw-r--r--mojo/edk/system/dispatcher.cc493
-rw-r--r--mojo/edk/system/dispatcher.h409
-rw-r--r--mojo/edk/system/dispatcher_unittest.cc295
-rw-r--r--mojo/edk/system/handle_signals_state.h49
-rw-r--r--mojo/edk/system/handle_table.cc243
-rw-r--r--mojo/edk/system/handle_table.h144
-rw-r--r--mojo/edk/system/mapping_table.cc48
-rw-r--r--mojo/edk/system/mapping_table.h57
-rw-r--r--mojo/edk/system/master.mojom11
-rw-r--r--mojo/edk/system/master_impl.cc107
-rw-r--r--mojo/edk/system/master_impl.h43
-rw-r--r--mojo/edk/system/master_impl_unittest.cc120
-rw-r--r--mojo/edk/system/message_in_transit.cc184
-rw-r--r--mojo/edk/system/message_in_transit.h234
-rw-r--r--mojo/edk/system/message_in_transit_queue.cc33
-rw-r--r--mojo/edk/system/message_in_transit_queue.h62
-rw-r--r--mojo/edk/system/message_in_transit_queue_unittest.cc91
-rw-r--r--mojo/edk/system/message_in_transit_test_utils.cc37
-rw-r--r--mojo/edk/system/message_in_transit_test_utils.h35
-rw-r--r--mojo/edk/system/message_pipe_dispatcher.cc720
-rw-r--r--mojo/edk/system/message_pipe_dispatcher.h145
-rw-r--r--mojo/edk/system/message_pipe_perftest.cc162
-rw-r--r--mojo/edk/system/message_pipe_test_utils.cc32
-rw-r--r--mojo/edk/system/message_pipe_test_utils.h51
-rw-r--r--mojo/edk/system/message_pipe_unittest.cc409
-rw-r--r--mojo/edk/system/multiprocess_message_pipe_unittest.cc782
-rw-r--r--mojo/edk/system/options_validation.h97
-rw-r--r--mojo/edk/system/options_validation_unittest.cc130
-rw-r--r--mojo/edk/system/platform_handle_dispatcher.cc113
-rw-r--r--mojo/edk/system/platform_handle_dispatcher.h63
-rw-r--r--mojo/edk/system/platform_handle_dispatcher_unittest.cc110
-rw-r--r--mojo/edk/system/raw_channel.cc631
-rw-r--r--mojo/edk/system/raw_channel.h409
-rw-r--r--mojo/edk/system/raw_channel_posix.cc520
-rw-r--r--mojo/edk/system/raw_channel_unittest.cc661
-rw-r--r--mojo/edk/system/raw_channel_win.cc866
-rw-r--r--mojo/edk/system/run_all_unittests.cc28
-rw-r--r--mojo/edk/system/shared_buffer_dispatcher.cc270
-rw-r--r--mojo/edk/system/shared_buffer_dispatcher.h104
-rw-r--r--mojo/edk/system/shared_buffer_dispatcher_unittest.cc277
-rw-r--r--mojo/edk/system/simple_dispatcher.cc61
-rw-r--r--mojo/edk/system/simple_dispatcher.h51
-rw-r--r--mojo/edk/system/simple_dispatcher_unittest.cc604
-rw-r--r--mojo/edk/system/system_impl_export.h29
-rw-r--r--mojo/edk/system/test_utils.cc83
-rw-r--r--mojo/edk/system/test_utils.h78
-rw-r--r--mojo/edk/system/transport_data.cc336
-rw-r--r--mojo/edk/system/transport_data.h188
-rw-r--r--mojo/edk/system/waiter.cc100
-rw-r--r--mojo/edk/system/waiter.h79
-rw-r--r--mojo/edk/system/waiter_test_utils.cc70
-rw-r--r--mojo/edk/system/waiter_test_utils.h104
-rw-r--r--mojo/edk/system/waiter_unittest.cc298
-rw-r--r--mojo/edk/test/BUILD.gn159
-rw-r--r--mojo/edk/test/multiprocess_test_helper.cc101
-rw-r--r--mojo/edk/test/multiprocess_test_helper.h98
-rw-r--r--mojo/edk/test/multiprocess_test_helper_unittest.cc196
-rw-r--r--mojo/edk/test/run_all_perftests.cc20
-rw-r--r--mojo/edk/test/run_all_unittests.cc43
-rw-r--r--mojo/edk/test/scoped_ipc_support.cc59
-rw-r--r--mojo/edk/test/scoped_ipc_support.h66
-rw-r--r--mojo/edk/test/test_support_impl.cc83
-rw-r--r--mojo/edk/test/test_support_impl.h38
-rw-r--r--mojo/edk/test/test_utils.h55
-rw-r--r--mojo/edk/test/test_utils_posix.cc93
-rw-r--r--mojo/edk/test/test_utils_win.cc114
152 files changed, 25303 insertions, 0 deletions
diff --git a/mojo/edk/DEPS b/mojo/edk/DEPS
new file mode 100644
index 0000000..7fa76cd
--- /dev/null
+++ b/mojo/edk/DEPS
@@ -0,0 +1,13 @@
+include_rules = [
+ # This code is checked into the chromium repo so it's fine to depend on this.
+ "+base",
+ "+crypto",
+ "+build",
+ "+gin",
+ "+testing",
+ "+third_party/ashmem",
+ "+v8",
+
+ # internal includes.
+ "+mojo",
+]
diff --git a/mojo/edk/embedder/BUILD.gn b/mojo/edk/embedder/BUILD.gn
new file mode 100644
index 0000000..c4b486d
--- /dev/null
+++ b/mojo/edk/embedder/BUILD.gn
@@ -0,0 +1,145 @@
+# Copyright 2014 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.
+
+import("../mojo_edk.gni")
+
+mojo_edk_source_set("embedder") {
+ # This isn't really a standalone target; it must be linked into the
+ # mojo_system_impl component.
+ visibility = [ "//mojo/edk/system" ]
+
+ sources = [
+ "configuration.h",
+ "embedder.cc",
+ "embedder.h",
+ "embedder_internal.h",
+ "entrypoints.cc",
+ "system_impl_private_entrypoints.cc",
+
+ # Test-only code:
+ # TODO(vtl): It's a little unfortunate that these end up in the same
+ # component as non-test-only code. In the static build, this code should
+ # hopefully be dead-stripped.
+ "test_embedder.cc",
+ "test_embedder.h",
+ ]
+
+ defines = [
+ "MOJO_SYSTEM_IMPL_IMPLEMENTATION",
+ "MOJO_SYSTEM_IMPLEMENTATION",
+ ]
+
+ configs = [ "//mojo/edk/system:system_config" ]
+
+ public_deps = [
+ ":delegates",
+ ":platform",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ "//third_party/mojo/src/mojo/public/platform/native:system_impl_private_api",
+ ]
+
+ deps = [
+ "//base",
+ ]
+}
+
+mojo_edk_source_set("platform") {
+ # This isn't really a standalone target; it must be linked into the
+ # mojo_system_impl component.
+ visibility = [
+ ":embedder",
+ "//mojo/edk/system",
+ ]
+
+ sources = [
+ "platform_channel_pair.cc",
+ "platform_channel_pair.h",
+ "platform_channel_pair_posix.cc",
+ "platform_channel_pair_win.cc",
+ "platform_channel_utils_posix.cc",
+ "platform_channel_utils_posix.h",
+ "platform_handle.cc",
+ "platform_handle.h",
+ "platform_handle_utils.h",
+ "platform_handle_utils_posix.cc",
+ "platform_handle_utils_win.cc",
+ "platform_handle_vector.h",
+ "platform_shared_buffer.h",
+ "platform_support.h",
+ "scoped_platform_handle.h",
+ "simple_platform_shared_buffer.cc",
+ "simple_platform_shared_buffer.h",
+ "simple_platform_shared_buffer_android.cc",
+ "simple_platform_shared_buffer_posix.cc",
+ "simple_platform_shared_buffer_win.cc",
+ "simple_platform_support.cc",
+ "simple_platform_support.h",
+ ]
+
+ defines = [ "MOJO_SYSTEM_IMPL_IMPLEMENTATION" ]
+
+ configs = [ "//mojo/edk/system:system_config" ]
+
+ public_deps = [
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ ]
+
+ deps = [
+ "//base",
+ ]
+
+ if (is_android) {
+ deps += [ "//third_party/ashmem" ]
+ }
+}
+
+mojo_edk_source_set("delegates") {
+ # This isn't really a standalone target; it must be linked into the
+ # mojo_system_impl component.
+ visibility = [
+ ":embedder",
+ "//mojo/edk/system",
+ ]
+
+ sources = [
+ "process_delegate.h",
+ ]
+
+ defines = [ "MOJO_SYSTEM_IMPL_IMPLEMENTATION" ]
+
+ configs = [ "//mojo/edk/system:system_config" ]
+
+ public_deps = [
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ ]
+}
+
+# TODO(use_chrome_edk): remove "2"
+mojo_edk_source_set("embedder_unittests2") {
+ testonly = true
+
+ # TODO(use_chrome_edk): remove "2". Also enable this visibility check when we
+ # figure out why it's failing just on Android.
+ #visibility = [ "//mojo/edk/system:mojo_system_unittests2" ]
+
+ sources = [
+ "embedder_unittest.cc",
+ "platform_channel_pair_posix_unittest.cc",
+ "simple_platform_shared_buffer_unittest.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//mojo/edk/system",
+ "//mojo/message_pump",
+
+ # TODO(use_chrome_edk): temporary since the Mojo wrapper primitives are
+ # declared in third party only for now.
+ "//third_party/mojo/src/mojo/edk/system",
+ "//mojo/edk/system:test_utils",
+ "//mojo/edk/test:test_support",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/edk/embedder/README.md b/mojo/edk/embedder/README.md
new file mode 100644
index 0000000..f976fcb
--- /dev/null
+++ b/mojo/edk/embedder/README.md
@@ -0,0 +1,13 @@
+Mojo Embedder API
+=================
+
+The Mojo Embedder API is an unstable, internal API to the Mojo system
+implementation. It should be used by code running on top of the system-level
+APIs to set up the Mojo environment (instead of directly instantiating things
+from src/mojo/edk/system).
+
+Example uses: Mojo shell, to set up the Mojo environment for Mojo apps; Chromium
+code, to set up the Mojo IPC system for use between processes. Note that most
+code should use the Mojo Public API (under src/mojo/public) instead. The
+Embedder API should only be used to initialize the environment, set up the
+initial MessagePipe between two processes, etc.
diff --git a/mojo/edk/embedder/configuration.h b/mojo/edk/embedder/configuration.h
new file mode 100644
index 0000000..c63d018
--- /dev/null
+++ b/mojo/edk/embedder/configuration.h
@@ -0,0 +1,68 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_CONFIGURATION_H_
+#define MOJO_EDK_EMBEDDER_CONFIGURATION_H_
+
+#include <stddef.h>
+
+namespace mojo {
+namespace edk {
+
+// A set of constants that the Mojo system internally uses. These values should
+// be consistent across all processes on the same system.
+//
+// In general, there should be no need to change these values from their
+// defaults. However, if you do change them, you must do so before
+// initialization.
+struct Configuration {
+ // Maximum number of open (Mojo) handles. The default is 1,000,000.
+ //
+ // TODO(vtl): This doesn't count "live" handles, some of which may live in
+ // messages.
+ size_t max_handle_table_size;
+
+ // Maximum number of active memory mappings. The default is 1,000,000.
+ size_t max_mapping_table_sze;
+
+ // Upper limit of |MojoWaitMany()|'s |num_handles|. The default is 1,000,000.
+ // Must be same as or smaller than |max_handle_table_size|.
+ size_t max_wait_many_num_handles;
+
+ // Maximum data size of messages sent over message pipes, in bytes. The
+ // default is 4MB.
+ size_t max_message_num_bytes;
+
+ // Maximum number of handles that can be attached to messages sent over
+ // message pipes. The default is 10,000.
+ size_t max_message_num_handles;
+
+ // Maximum capacity of a data pipe, in bytes. The default is 256MB. This value
+ // must fit into a |uint32_t|. WARNING: If you bump it closer to 2^32, you
+ // must audit all the code to check that we don't overflow (2^31 would
+ // definitely be risky; up to 2^30 is probably okay).
+ size_t max_data_pipe_capacity_bytes;
+
+ // Default data pipe capacity, if not specified explicitly in the creation
+ // options. The default is 1MB.
+ size_t default_data_pipe_capacity_bytes;
+
+ // Alignment for the "start" of the data buffer used by data pipes. (The
+ // alignment of elements will depend on this and the element size.) The
+ // default is 16 bytes.
+ size_t data_pipe_buffer_alignment_bytes;
+
+ // Maximum size of a single shared memory segment, in bytes. The default is
+ // 1GB.
+ //
+ // TODO(vtl): Set this hard limit appropriately (e.g., higher on 64-bit).
+ // (This will also entail some auditing to make sure I'm not messing up my
+ // checks anywhere.)
+ size_t max_shared_memory_num_bytes;
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_CONFIGURATION_H_
diff --git a/mojo/edk/embedder/embedder.cc b/mojo/edk/embedder/embedder.cc
new file mode 100644
index 0000000..9f2b94a
--- /dev/null
+++ b/mojo/edk/embedder/embedder.cc
@@ -0,0 +1,171 @@
+// Copyright 2014 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 "mojo/edk/embedder/embedder.h"
+
+#include "base/atomicops.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/task_runner.h"
+#include "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/embedder/process_delegate.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/core.h"
+#include "mojo/edk/system/message_pipe_dispatcher.h"
+#include "mojo/edk/system/platform_handle_dispatcher.h"
+
+namespace mojo {
+namespace edk {
+
+// TODO(jam): move into annonymous namespace. Keep outside for debugging in VS
+// temporarily.
+int g_channel_count = 0;
+bool g_wait_for_no_more_channels = false;
+
+namespace {
+
+// Note: Called on the I/O thread.
+void ShutdownIPCSupportHelper(bool wait_for_no_more_channels) {
+ if (wait_for_no_more_channels && g_channel_count) {
+ g_wait_for_no_more_channels = true;
+ return;
+ }
+
+ internal::g_delegate_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(&ProcessDelegate::OnShutdownComplete,
+ base::Unretained(internal::g_process_delegate)));
+}
+
+} // namespace
+
+namespace internal {
+
+// Declared in embedder_internal.h.
+PlatformSupport* g_platform_support = nullptr;
+Core* g_core = nullptr;
+
+base::TaskRunner* g_delegate_thread_task_runner;
+ProcessDelegate* g_process_delegate;
+base::TaskRunner* g_io_thread_task_runner = nullptr;
+
+void ChannelStarted() {
+ DCHECK(g_io_thread_task_runner->RunsTasksOnCurrentThread());
+ g_channel_count++;
+}
+
+void ChannelShutdown() {
+ DCHECK(g_io_thread_task_runner->RunsTasksOnCurrentThread());
+ g_channel_count--;
+ if (!g_channel_count && g_wait_for_no_more_channels) {
+ // Reset g_wait_for_no_more_channels for unit tests which initialize and
+ // tear down multiple times in a process.
+ g_wait_for_no_more_channels = false;
+ ShutdownIPCSupportHelper(false);
+ }
+}
+
+} // namespace internal
+
+void SetMaxMessageSize(size_t bytes) {
+ GetMutableConfiguration()->max_message_num_bytes = bytes;
+}
+
+void Init() {
+ DCHECK(!internal::g_platform_support);
+ internal::g_platform_support = new SimplePlatformSupport();
+
+ DCHECK(!internal::g_core);
+ internal::g_core = new Core(internal::g_platform_support);
+}
+
+MojoResult AsyncWait(MojoHandle handle,
+ MojoHandleSignals signals,
+ const base::Callback<void(MojoResult)>& callback) {
+ return internal::g_core->AsyncWait(handle, signals, callback);
+}
+
+MojoResult CreatePlatformHandleWrapper(
+ ScopedPlatformHandle platform_handle,
+ MojoHandle* platform_handle_wrapper_handle) {
+ DCHECK(platform_handle_wrapper_handle);
+
+ scoped_refptr<Dispatcher> dispatcher =
+ PlatformHandleDispatcher::Create(platform_handle.Pass());
+
+ DCHECK(internal::g_core);
+ MojoHandle h = internal::g_core->AddDispatcher(dispatcher);
+ if (h == MOJO_HANDLE_INVALID) {
+ LOG(ERROR) << "Handle table full";
+ dispatcher->Close();
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ *platform_handle_wrapper_handle = h;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult PassWrappedPlatformHandle(MojoHandle platform_handle_wrapper_handle,
+ ScopedPlatformHandle* platform_handle) {
+ DCHECK(platform_handle);
+
+ DCHECK(internal::g_core);
+ scoped_refptr<Dispatcher> dispatcher(
+ internal::g_core->GetDispatcher(platform_handle_wrapper_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (dispatcher->GetType() != Dispatcher::Type::PLATFORM_HANDLE)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ *platform_handle =
+ static_cast<PlatformHandleDispatcher*>(dispatcher.get())
+ ->PassPlatformHandle()
+ .Pass();
+ return MOJO_RESULT_OK;
+}
+
+void InitIPCSupport(scoped_refptr<base::TaskRunner> delegate_thread_task_runner,
+ ProcessDelegate* process_delegate,
+ scoped_refptr<base::TaskRunner> io_thread_task_runner) {
+ // |Init()| must have already been called.
+ DCHECK(internal::g_core);
+ internal::g_delegate_thread_task_runner = delegate_thread_task_runner.get();
+ internal::g_process_delegate = process_delegate;
+ internal::g_io_thread_task_runner = io_thread_task_runner.get();
+}
+
+void ShutdownIPCSupportOnIOThread() {
+}
+
+void ShutdownIPCSupport() {
+ internal::g_io_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(&ShutdownIPCSupportHelper, false));
+}
+
+void ShutdownIPCSupportAndWaitForNoChannels() {
+ internal::g_io_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(&ShutdownIPCSupportHelper, true));
+}
+
+ScopedMessagePipeHandle CreateMessagePipe(
+ ScopedPlatformHandle platform_handle) {
+ scoped_refptr<MessagePipeDispatcher> dispatcher =
+ MessagePipeDispatcher::Create(
+ MessagePipeDispatcher::kDefaultCreateOptions);
+
+ ScopedMessagePipeHandle rv(
+ MessagePipeHandle(internal::g_core->AddDispatcher(dispatcher)));
+ CHECK(rv.is_valid());
+ dispatcher->Init(platform_handle.Pass());
+ // TODO(vtl): The |.Pass()| below is only needed due to an MSVS bug; remove it
+ // once that's fixed.
+ return rv.Pass();
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/embedder.h b/mojo/edk/embedder/embedder.h
new file mode 100644
index 0000000..30a548e
--- /dev/null
+++ b/mojo/edk/embedder/embedder.h
@@ -0,0 +1,111 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_EMBEDDER_H_
+#define MOJO_EDK_EMBEDDER_EMBEDDER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/task_runner.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+namespace edk {
+
+class ProcessDelegate;
+
+// Basic configuration/initialization ------------------------------------------
+
+// |Init()| sets up the basic Mojo system environment, making the |Mojo...()|
+// functions available and functional. This is never shut down (except in tests
+// -- see test_embedder.h).
+
+// Allows changing the default max message size. Must be called before Init.
+MOJO_SYSTEM_IMPL_EXPORT void SetMaxMessageSize(size_t bytes);
+
+// Must be called first, or just after setting configuration parameters, to
+// initialize the (global, singleton) system.
+MOJO_SYSTEM_IMPL_EXPORT void Init();
+
+// Basic functions -------------------------------------------------------------
+
+// The functions in this section are available once |Init()| has been called.
+
+// Start waiting on the handle asynchronously. On success, |callback| will be
+// called exactly once, when |handle| satisfies a signal in |signals| or it
+// becomes known that it will never do so. |callback| will be executed on an
+// arbitrary thread, so it must not call any Mojo system or embedder functions.
+MOJO_SYSTEM_IMPL_EXPORT MojoResult
+AsyncWait(MojoHandle handle,
+ MojoHandleSignals signals,
+ const base::Callback<void(MojoResult)>& callback);
+
+// Creates a |MojoHandle| that wraps the given |PlatformHandle| (taking
+// ownership of it). This |MojoHandle| can then, e.g., be passed through message
+// pipes. Note: This takes ownership (and thus closes) |platform_handle| even on
+// failure, which is different from what you'd expect from a Mojo API, but it
+// makes for a more convenient embedder API.
+MOJO_SYSTEM_IMPL_EXPORT MojoResult
+CreatePlatformHandleWrapper(ScopedPlatformHandle platform_handle,
+ MojoHandle* platform_handle_wrapper_handle);
+
+// Retrieves the |PlatformHandle| that was wrapped into a |MojoHandle| (using
+// |CreatePlatformHandleWrapper()| above). Note that the |MojoHandle| must still
+// be closed separately.
+MOJO_SYSTEM_IMPL_EXPORT MojoResult
+PassWrappedPlatformHandle(MojoHandle platform_handle_wrapper_handle,
+ ScopedPlatformHandle* platform_handle);
+
+// Initialialization/shutdown for interprocess communication (IPC) -------------
+
+// |InitIPCSupport()| sets up the subsystem for interprocess communication,
+// making the IPC functions (in the following section) available and functional.
+// (This may only be done after |Init()|.)
+//
+// This subsystem may be shut down, using |ShutdownIPCSupportOnIOThread()| or
+// |ShutdownIPCSupport()|. None of the IPC functions may be called while or
+// after either of these is called.
+
+// Initializes a process of the given type; to be called after |Init()|.
+// - |process_delegate| must be a process delegate of the appropriate type
+// corresponding to |process_type|; its methods will be called on
+// |delegate_thread_task_runner|.
+// - |delegate_thread_task_runner|, |process_delegate|, and
+// |io_thread_task_runner| should live at least until
+// |ShutdownIPCSupport()|'s callback has been run or
+// |ShutdownIPCSupportOnIOThread()| has completed.
+MOJO_SYSTEM_IMPL_EXPORT void InitIPCSupport(
+ scoped_refptr<base::TaskRunner> delegate_thread_task_runner,
+ ProcessDelegate* process_delegate,
+ scoped_refptr<base::TaskRunner> io_thread_task_runner);
+
+// Shuts down the subsystem initialized by |InitIPCSupport()|. This must be
+// called on the I/O thread (given to |InitIPCSupport()|). This completes
+// synchronously and does not result in a call to the process delegate's
+// |OnShutdownComplete()|.
+MOJO_SYSTEM_IMPL_EXPORT void ShutdownIPCSupportOnIOThread();
+
+// Like |ShutdownIPCSupportOnIOThread()|, but may be called from any thread,
+// signalling shutdown completion via the process delegate's
+// |OnShutdownComplete()|.
+MOJO_SYSTEM_IMPL_EXPORT void ShutdownIPCSupport();
+
+// Like above, but doesn't call |OnShutdownComplete| until all channels are
+// gone.
+// TODO(jam): this should be the default behavior.
+MOJO_SYSTEM_IMPL_EXPORT void ShutdownIPCSupportAndWaitForNoChannels();
+
+// Creates a message pipe from a platform handle. Safe to call from any thread.
+MOJO_SYSTEM_IMPL_EXPORT ScopedMessagePipeHandle
+CreateMessagePipe(ScopedPlatformHandle platform_handle);
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_EMBEDDER_H_
diff --git a/mojo/edk/embedder/embedder_internal.h b/mojo/edk/embedder/embedder_internal.h
new file mode 100644
index 0000000..6fd3dfc
--- /dev/null
+++ b/mojo/edk/embedder/embedder_internal.h
@@ -0,0 +1,46 @@
+// Copyright 2014 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 header contains internal details for the *implementation* of the
+// embedder API. It should not be included by any public header (nor by users of
+// the embedder API).
+
+#ifndef MOJO_EDK_EMBEDDER_EMBEDDER_INTERNAL_H_
+#define MOJO_EDK_EMBEDDER_EMBEDDER_INTERNAL_H_
+
+#include <stdint.h>
+
+namespace base {
+class TaskRunner;
+}
+
+namespace mojo {
+
+namespace edk {
+
+class Core;
+class PlatformSupport;
+class ProcessDelegate;
+
+namespace internal {
+
+// Instance of |PlatformSupport| to use.
+extern PlatformSupport* g_platform_support;
+
+// Instance of |Core| used by the system functions (|Mojo...()|).
+extern Core* g_core;
+extern base::TaskRunner* g_delegate_thread_task_runner;
+extern ProcessDelegate* g_process_delegate;
+extern base::TaskRunner* g_io_thread_task_runner;
+
+// Called on the IO thread.
+void ChannelStarted();
+void ChannelShutdown();
+} // namespace internal
+
+} // namepace edk
+
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_EMBEDDER_INTERNAL_H_
diff --git a/mojo/edk/embedder/embedder_unittest.cc b/mojo/edk/embedder/embedder_unittest.cc
new file mode 100644
index 0000000..4507328
--- /dev/null
+++ b/mojo/edk/embedder/embedder_unittest.cc
@@ -0,0 +1,560 @@
+// Copyright 2014 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 "mojo/edk/embedder/embedder.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_timeouts.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/embedder/test_embedder.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/test/multiprocess_test_helper.h"
+#include "mojo/message_pump/message_pump_mojo.h"
+#include "mojo/public/c/system/core.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+const MojoHandleSignals kSignalReadadableWritable =
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE;
+
+const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+
+class EmbedderTest : public test::MojoSystemTest {
+ public:
+ EmbedderTest() {}
+ ~EmbedderTest() override {}
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(EmbedderTest);
+};
+
+TEST_F(EmbedderTest, ChannelBasic) {
+ MojoHandle server_mp, client_mp;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateMessagePipe(nullptr, &server_mp, &client_mp));
+
+ // We can write to a message pipe handle immediately.
+ const char kHello[] = "hello";
+
+ size_t write_size = sizeof(kHello);
+ const char* write_buffer = kHello;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(server_mp, write_buffer,
+ static_cast<uint32_t>(write_size), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Now wait for the other side to become readable.
+ MojoHandleSignalsState state;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ char read_buffer[1000] = {};
+ uint32_t num_bytes = static_cast<uint32_t>(sizeof(read_buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(client_mp, read_buffer, &num_bytes, nullptr,
+ nullptr, MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(write_size, num_bytes);
+ EXPECT_STREQ(kHello, read_buffer);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp));
+}
+
+// Test sending a MP which has read messages out of the OS pipe but which have
+// not been consumed using MojoReadMessage yet.
+TEST_F(EmbedderTest, SendReadableMessagePipe) {
+ MojoHandle server_mp, client_mp;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateMessagePipe(nullptr, &server_mp, &client_mp));
+
+ MojoHandle server_mp2, client_mp2;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateMessagePipe(nullptr, &server_mp2, &client_mp2));
+
+ // Write to server2 and wait for client2 to be readable before sending it.
+ // client2's MessagePipeDispatcher will have the message below in its
+ // message_queue_. For extra measures, also verify that this pending message
+ // can contain a message pipe.
+ MojoHandle server_mp3, client_mp3;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateMessagePipe(nullptr, &server_mp3, &client_mp3));
+ const char kHello[] = "hello";
+ size_t write_size;
+ const char* write_buffer;
+ write_buffer = kHello;
+ write_size = sizeof(kHello);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(server_mp2, write_buffer,
+ static_cast<uint32_t>(write_size), &client_mp3, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ MojoHandleSignalsState state;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(client_mp2, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ // Now send client2
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(server_mp, write_buffer,
+ static_cast<uint32_t>(write_size), &client_mp2, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ char read_buffer[20000] = {};
+ uint32_t num_bytes = static_cast<uint32_t>(sizeof(read_buffer));
+ MojoHandle ports[10];
+ uint32_t num_ports;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(client_mp, read_buffer, &num_bytes, &ports[0],
+ &num_ports, MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(write_size, num_bytes);
+ EXPECT_STREQ(kHello, read_buffer);
+ EXPECT_EQ(1, num_ports);
+
+
+ client_mp2 = ports[0];
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(client_mp2, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(client_mp2, read_buffer, &num_bytes, &client_mp3,
+ &num_ports, MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(write_size, num_bytes);
+ EXPECT_STREQ(kHello, read_buffer);
+ EXPECT_EQ(1, num_ports);
+
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp3));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp3));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp2));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp2));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp));
+}
+
+// TODO(jam): fix and renable
+TEST_F(EmbedderTest, DISABLED_SendMessagePipeWithWriteQueue) {
+ MojoHandle server_mp, client_mp;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateMessagePipe(nullptr, &server_mp, &client_mp));
+
+ MojoHandle server_mp2, client_mp2;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateMessagePipe(nullptr, &server_mp2, &client_mp2));
+
+ static const size_t kNumMessages = 1001;
+ for (size_t i = 0; i < kNumMessages; i++) {
+ std::string write_buffer(i, 'A' + (i % 26));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(client_mp2, write_buffer.data(),
+ write_buffer.size(), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ }
+
+ // Now send client2
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(server_mp, nullptr, 0, &client_mp2, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ MojoHandleSignalsState state;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ /// todo: read all msgs written..
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp2));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp2));
+
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp));
+}
+
+TEST_F(EmbedderTest, ChannelsHandlePassing) {
+ MojoHandle server_mp, client_mp;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateMessagePipe(nullptr, &server_mp, &client_mp));
+ EXPECT_NE(server_mp, MOJO_HANDLE_INVALID);
+ EXPECT_NE(client_mp, MOJO_HANDLE_INVALID);
+
+ MojoHandle h0, h1;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &h0, &h1));
+
+ // Write a message to |h0| (attaching nothing).
+ const char kHello[] = "hello";
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(h0, kHello, static_cast<uint32_t>(sizeof(kHello)),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Write one message to |server_mp|, attaching |h1|.
+ const char kWorld[] = "world!!!";
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteMessage(server_mp, kWorld, static_cast<uint32_t>(sizeof(kWorld)),
+ &h1, 1, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ h1 = MOJO_HANDLE_INVALID;
+
+ // Write another message to |h0|.
+ const char kFoo[] = "foo";
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(h0, kFoo, static_cast<uint32_t>(sizeof(kFoo)),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait for |client_mp| to become readable.
+ MojoHandleSignalsState state;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ // Read a message from |client_mp|.
+ char buffer[1000] = {};
+ uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ MojoHandle handles[10] = {};
+ uint32_t num_handles = MOJO_ARRAYSIZE(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(client_mp, buffer, &num_bytes, handles,
+ &num_handles, MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(sizeof(kWorld), num_bytes);
+ EXPECT_STREQ(kWorld, buffer);
+ EXPECT_EQ(1u, num_handles);
+ EXPECT_NE(handles[0], MOJO_HANDLE_INVALID);
+ h1 = handles[0];
+
+ // Wait for |h1| to become readable.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(h1, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ // Read a message from |h1|.
+ memset(buffer, 0, sizeof(buffer));
+ num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ memset(handles, 0, sizeof(handles));
+ num_handles = MOJO_ARRAYSIZE(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(h1, buffer, &num_bytes, handles, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(sizeof(kHello), num_bytes);
+ EXPECT_STREQ(kHello, buffer);
+ EXPECT_EQ(0u, num_handles);
+
+ // Wait for |h1| to become readable (again).
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(h1, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ // Read the second message from |h1|.
+ memset(buffer, 0, sizeof(buffer));
+ num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(h1, buffer, &num_bytes, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(sizeof(kFoo), num_bytes);
+ EXPECT_STREQ(kFoo, buffer);
+
+ // Write a message to |h1|.
+ const char kBarBaz[] = "barbaz";
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteMessage(h1, kBarBaz, static_cast<uint32_t>(sizeof(kBarBaz)),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait for |h0| to become readable.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(h0, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ // Read a message from |h0|.
+ memset(buffer, 0, sizeof(buffer));
+ num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(h0, buffer, &num_bytes, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(sizeof(kBarBaz), num_bytes);
+ EXPECT_STREQ(kBarBaz, buffer);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1));
+}
+
+// The sequence of messages sent is:
+// server_mp client_mp mp0 mp1 mp2 mp3
+// 1. "hello"
+// 2. "world!"
+// 3. "FOO"
+// 4. "Bar"+mp1
+// 5. (close)
+// 6. (close)
+// 7. "baz"
+// 8. (closed)
+// 9. "quux"+mp2
+// 10. (close)
+// 11. (wait/cl.)
+// 12. (wait/cl.)
+
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_MultiprocessChannels DISABLED_MultiprocessChannels
+#else
+#define MAYBE_MultiprocessChannels MultiprocessChannels
+#endif // defined(OS_ANDROID)
+TEST_F(EmbedderTest, MAYBE_MultiprocessChannels) {
+ test::MultiprocessTestHelper multiprocess_test_helper;
+ multiprocess_test_helper.StartChild("MultiprocessChannelsClient");
+
+ {
+ MojoHandle server_mp = CreateMessagePipe(
+ multiprocess_test_helper.server_platform_handle.Pass()).release().
+ value();
+
+ // 1. Write a message to |server_mp| (attaching nothing).
+ const char kHello[] = "hello";
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(server_mp, kHello,
+ static_cast<uint32_t>(sizeof(kHello)), nullptr,
+ 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // TODO(vtl): If the scope were ended immediately here (maybe after closing
+ // |server_mp|), we die with a fatal error in |Channel::HandleLocalError()|.
+
+ // 2. Read a message from |server_mp|.
+ MojoHandleSignalsState state;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(server_mp, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ char buffer[1000] = {};
+ uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(server_mp, buffer, &num_bytes, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ const char kWorld[] = "world!";
+ EXPECT_EQ(sizeof(kWorld), num_bytes);
+ EXPECT_STREQ(kWorld, buffer);
+
+ // Create a new message pipe (endpoints |mp0| and |mp1|).
+ MojoHandle mp0, mp1;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &mp0, &mp1));
+
+ // 3. Write something to |mp0|.
+ const char kFoo[] = "FOO";
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp0, kFoo, static_cast<uint32_t>(sizeof(kFoo)),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // 4. Write a message to |server_mp|, attaching |mp1|.
+ const char kBar[] = "Bar";
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteMessage(server_mp, kBar, static_cast<uint32_t>(sizeof(kBar)),
+ &mp1, 1, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ mp1 = MOJO_HANDLE_INVALID;
+
+ // 5. Close |server_mp|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(server_mp));
+
+ // 9. Read a message from |mp0|, which should have |mp2| attached.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(mp0, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ memset(buffer, 0, sizeof(buffer));
+ num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ MojoHandle mp2 = MOJO_HANDLE_INVALID;
+ uint32_t num_handles = 1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(mp0, buffer, &num_bytes, &mp2, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ const char kQuux[] = "quux";
+ EXPECT_EQ(sizeof(kQuux), num_bytes);
+ EXPECT_STREQ(kQuux, buffer);
+ EXPECT_EQ(1u, num_handles);
+ EXPECT_NE(mp2, MOJO_HANDLE_INVALID);
+
+ // 7. Read a message from |mp2|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(mp2, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ state.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ state.satisfiable_signals);
+
+ memset(buffer, 0, sizeof(buffer));
+ num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(mp2, buffer, &num_bytes, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ const char kBaz[] = "baz";
+ EXPECT_EQ(sizeof(kBaz), num_bytes);
+ EXPECT_STREQ(kBaz, buffer);
+
+ // 10. Close |mp0|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(mp0));
+
+// 12. Wait on |mp2| (which should eventually fail) and then close it.
+// TODO(vtl): crbug.com/351768
+#if 0
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(mp2, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE,
+ &state));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, state.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, state.satisfiable_signals);
+#endif
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(mp2));
+ }
+
+ EXPECT_TRUE(multiprocess_test_helper.WaitForChildTestShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_TEST(MultiprocessChannelsClient) {
+ ScopedPlatformHandle client_platform_handle =
+ test::MultiprocessTestHelper::client_platform_handle.Pass();
+ EXPECT_TRUE(client_platform_handle.is_valid());
+
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+
+ {
+ test::ScopedIPCSupport ipc_support(test_io_thread.task_runner());
+ MojoHandle client_mp = CreateMessagePipe(
+ client_platform_handle.Pass()).release().value();
+
+ // 1. Read the first message from |client_mp|.
+ MojoHandleSignalsState state;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ char buffer[1000] = {};
+ uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(client_mp, buffer, &num_bytes, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ const char kHello[] = "hello";
+ EXPECT_EQ(sizeof(kHello), num_bytes);
+ EXPECT_STREQ(kHello, buffer);
+
+ // 2. Write a message to |client_mp| (attaching nothing).
+ const char kWorld[] = "world!";
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(client_mp, kWorld,
+ static_cast<uint32_t>(sizeof(kWorld)), nullptr,
+ 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // 4. Read a message from |client_mp|, which should have |mp1| attached.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(client_mp, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ // The other end of the handle may or may not be closed at this point, so we
+ // can't test MOJO_HANDLE_SIGNAL_WRITABLE or MOJO_HANDLE_SIGNAL_PEER_CLOSED.
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE,
+ state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE,
+ state.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ // TODO(vtl): If the scope were to end here (and |client_mp| closed), we'd
+ // die (again due to |Channel::HandleLocalError()|).
+ memset(buffer, 0, sizeof(buffer));
+ num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ MojoHandle mp1 = MOJO_HANDLE_INVALID;
+ uint32_t num_handles = 1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(client_mp, buffer, &num_bytes, &mp1, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ const char kBar[] = "Bar";
+ EXPECT_EQ(sizeof(kBar), num_bytes);
+ EXPECT_STREQ(kBar, buffer);
+ EXPECT_EQ(1u, num_handles);
+ EXPECT_NE(mp1, MOJO_HANDLE_INVALID);
+ // TODO(vtl): If the scope were to end here (and the two handles closed),
+ // we'd die due to |Channel::RunRemoteMessagePipeEndpoint()| not handling
+ // write errors (assuming the parent had closed the pipe).
+
+ // 6. Close |client_mp|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(client_mp));
+
+ // Create a new message pipe (endpoints |mp2| and |mp3|).
+ MojoHandle mp2, mp3;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &mp2, &mp3));
+
+ // 7. Write a message to |mp3|.
+ const char kBaz[] = "baz";
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp3, kBaz, static_cast<uint32_t>(sizeof(kBaz)),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // 8. Close |mp3|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(mp3));
+
+ // 9. Write a message to |mp1|, attaching |mp2|.
+ const char kQuux[] = "quux";
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp1, kQuux, static_cast<uint32_t>(sizeof(kQuux)),
+ &mp2, 1, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ mp2 = MOJO_HANDLE_INVALID;
+
+ // 3. Read a message from |mp1|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(kSignalReadadableWritable, state.satisfied_signals);
+ EXPECT_EQ(kSignalAll, state.satisfiable_signals);
+
+ memset(buffer, 0, sizeof(buffer));
+ num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(mp1, buffer, &num_bytes, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ const char kFoo[] = "FOO";
+ EXPECT_EQ(sizeof(kFoo), num_bytes);
+ EXPECT_STREQ(kFoo, buffer);
+
+ // 11. Wait on |mp1| (which should eventually fail) and then close it.
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(mp1));
+ }
+}
+
+// TODO(vtl): Test immediate write & close.
+// TODO(vtl): Test broken-connection cases.
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/entrypoints.cc b/mojo/edk/embedder/entrypoints.cc
new file mode 100644
index 0000000..6247c5f
--- /dev/null
+++ b/mojo/edk/embedder/entrypoints.cc
@@ -0,0 +1,149 @@
+// Copyright 2014 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 "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/system/core.h"
+#include "mojo/public/c/system/buffer.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/functions.h"
+#include "mojo/public/c/system/message_pipe.h"
+
+using mojo::edk::internal::g_core;
+
+// TODO(use_chrome_edk): commented out since for now we use the entrypoints in
+// third_party and that checks the command line to redirect here.
+/*
+
+// Definitions of the system functions.
+extern "C" {
+MojoTimeTicks MojoGetTimeTicksNow() {
+ return g_core->GetTimeTicksNow();
+}
+
+MojoResult MojoClose(MojoHandle handle) {
+ return g_core->Close(handle);
+}
+
+MojoResult MojoWait(MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoDeadline deadline,
+ MojoHandleSignalsState* signals_state) {
+ return g_core->Wait(handle, signals, deadline, signals_state);
+}
+
+MojoResult MojoWaitMany(const MojoHandle* handles,
+ const MojoHandleSignals* signals,
+ uint32_t num_handles,
+ MojoDeadline deadline,
+ uint32_t* result_index,
+ MojoHandleSignalsState* signals_states) {
+ return g_core->WaitMany(handles, signals, num_handles, deadline, result_index,
+ signals_states);
+}
+
+MojoResult MojoCreateMessagePipe(const MojoCreateMessagePipeOptions* options,
+ MojoHandle* message_pipe_handle0,
+ MojoHandle* message_pipe_handle1) {
+ return g_core->CreateMessagePipe(options, message_pipe_handle0,
+ message_pipe_handle1);
+}
+
+MojoResult MojoWriteMessage(MojoHandle message_pipe_handle,
+ const void* bytes,
+ uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags) {
+ return g_core->WriteMessage(message_pipe_handle, bytes, num_bytes, handles,
+ num_handles, flags);
+}
+
+MojoResult MojoReadMessage(MojoHandle message_pipe_handle,
+ void* bytes,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags) {
+ return g_core->ReadMessage(
+ message_pipe_handle, bytes, num_bytes, handles, num_handles, flags);
+}
+
+MojoResult MojoCreateDataPipe(const MojoCreateDataPipeOptions* options,
+ MojoHandle* data_pipe_producer_handle,
+ MojoHandle* data_pipe_consumer_handle) {
+ return g_core->CreateDataPipe(options, data_pipe_producer_handle,
+ data_pipe_consumer_handle);
+}
+
+MojoResult MojoWriteData(MojoHandle data_pipe_producer_handle,
+ const void* elements,
+ uint32_t* num_elements,
+ MojoWriteDataFlags flags) {
+ return g_core->WriteData(data_pipe_producer_handle, elements, num_elements,
+ flags);
+}
+
+MojoResult MojoBeginWriteData(MojoHandle data_pipe_producer_handle,
+ void** buffer,
+ uint32_t* buffer_num_elements,
+ MojoWriteDataFlags flags) {
+ return g_core->BeginWriteData(data_pipe_producer_handle, buffer,
+ buffer_num_elements, flags);
+}
+
+MojoResult MojoEndWriteData(MojoHandle data_pipe_producer_handle,
+ uint32_t num_elements_written) {
+ return g_core->EndWriteData(data_pipe_producer_handle, num_elements_written);
+}
+
+MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle,
+ void* elements,
+ uint32_t* num_elements,
+ MojoReadDataFlags flags) {
+ return g_core->ReadData(data_pipe_consumer_handle, elements, num_elements,
+ flags);
+}
+
+MojoResult MojoBeginReadData(MojoHandle data_pipe_consumer_handle,
+ const void** buffer,
+ uint32_t* buffer_num_elements,
+ MojoReadDataFlags flags) {
+ return g_core->BeginReadData(data_pipe_consumer_handle, buffer,
+ buffer_num_elements, flags);
+}
+
+MojoResult MojoEndReadData(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_elements_read) {
+ return g_core->EndReadData(data_pipe_consumer_handle, num_elements_read);
+}
+
+MojoResult MojoCreateSharedBuffer(
+ const struct MojoCreateSharedBufferOptions* options,
+ uint64_t num_bytes,
+ MojoHandle* shared_buffer_handle) {
+ return g_core->CreateSharedBuffer(options, num_bytes, shared_buffer_handle);
+}
+
+MojoResult MojoDuplicateBufferHandle(
+ MojoHandle buffer_handle,
+ const struct MojoDuplicateBufferHandleOptions* options,
+ MojoHandle* new_buffer_handle) {
+ return g_core->DuplicateBufferHandle(buffer_handle, options,
+ new_buffer_handle);
+}
+
+MojoResult MojoMapBuffer(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ void** buffer,
+ MojoMapBufferFlags flags) {
+ return g_core->MapBuffer(buffer_handle, offset, num_bytes, buffer, flags);
+}
+
+MojoResult MojoUnmapBuffer(void* buffer) {
+ return g_core->UnmapBuffer(buffer);
+}
+
+} // extern "C"
+*/
diff --git a/mojo/edk/embedder/platform_channel_pair.cc b/mojo/edk/embedder/platform_channel_pair.cc
new file mode 100644
index 0000000..b083df6
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_pair.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 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 "mojo/edk/embedder/platform_channel_pair.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace edk {
+
+const char PlatformChannelPair::kMojoPlatformChannelHandleSwitch[] =
+ "mojo-platform-channel-handle";
+
+PlatformChannelPair::~PlatformChannelPair() {
+}
+
+ScopedPlatformHandle PlatformChannelPair::PassServerHandle() {
+ return server_handle_.Pass();
+}
+
+ScopedPlatformHandle PlatformChannelPair::PassClientHandle() {
+ return client_handle_.Pass();
+}
+
+void PlatformChannelPair::ChildProcessLaunched() {
+ DCHECK(client_handle_.is_valid());
+ client_handle_.reset();
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/platform_channel_pair.h b/mojo/edk/embedder/platform_channel_pair.h
new file mode 100644
index 0000000..7487837
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_pair.h
@@ -0,0 +1,94 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/process/launch.h"
+#include "build/build_config.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace base {
+class CommandLine;
+}
+
+namespace mojo {
+namespace edk {
+
+// It would be nice to refactor base/process/launch.h to have a more platform-
+// independent way of representing handles that are passed to child processes.
+#if defined(OS_WIN)
+using HandlePassingInformation = base::HandlesToInheritVector;
+#elif defined(OS_POSIX)
+using HandlePassingInformation = base::FileHandleMappingVector;
+#else
+#error "Unsupported."
+#endif
+
+// This is used to create a pair of |PlatformHandle|s that are connected by a
+// suitable (platform-specific) bidirectional "pipe" (e.g., socket on POSIX,
+// named pipe on Windows). The resulting handles can then be used in the same
+// process (e.g., in tests) or between processes. (The "server" handle is the
+// one that will be used in the process that created the pair, whereas the
+// "client" handle is the one that will be used in a different process.)
+//
+// This class provides facilities for passing the client handle to a child
+// process. The parent should call |PrepareToPassClientHandlelToChildProcess()|
+// to get the data needed to do this, spawn the child using that data, and then
+// call |ChildProcessLaunched()|. Note that on Windows this facility (will) only
+// work on Vista and later (TODO(vtl)).
+//
+// Note: |PlatformChannelPair()|, |PassClientHandleFromParentProcess()| and
+// |PrepareToPassClientHandleToChildProcess()| have platform-specific
+// implementations.
+//
+// Note: On POSIX platforms, to write to the "pipe", use
+// |PlatformChannel{Write,Writev}()| (from platform_channel_utils_posix.h)
+// instead of |write()|, |writev()|, etc. Otherwise, you have to worry about
+// platform differences in suppressing |SIGPIPE|.
+class MOJO_SYSTEM_IMPL_EXPORT PlatformChannelPair {
+ public:
+ PlatformChannelPair();
+ ~PlatformChannelPair();
+
+ ScopedPlatformHandle PassServerHandle();
+
+ // For in-process use (e.g., in tests or to pass over another channel).
+ ScopedPlatformHandle PassClientHandle();
+
+ // To be called in the child process, after the parent process called
+ // |PrepareToPassClientHandleToChildProcess()| and launched the child (using
+ // the provided data), to create a client handle connected to the server
+ // handle (in the parent process).
+ static ScopedPlatformHandle PassClientHandleFromParentProcess(
+ const base::CommandLine& command_line);
+
+ // Prepares to pass the client channel to a new child process, to be launched
+ // using |LaunchProcess()| (from base/launch.h). Modifies |*command_line| and
+ // |*handle_passing_info| as needed.
+ // Note: For Windows, this method only works on Vista and later.
+ void PrepareToPassClientHandleToChildProcess(
+ base::CommandLine* command_line,
+ HandlePassingInformation* handle_passing_info) const;
+
+ // To be called once the child process has been successfully launched, to do
+ // any cleanup necessary.
+ void ChildProcessLaunched();
+
+ private:
+ static const char kMojoPlatformChannelHandleSwitch[];
+
+ ScopedPlatformHandle server_handle_;
+ ScopedPlatformHandle client_handle_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(PlatformChannelPair);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_
diff --git a/mojo/edk/embedder/platform_channel_pair_posix.cc b/mojo/edk/embedder/platform_channel_pair_posix.cc
new file mode 100644
index 0000000..8cdac36
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_pair_posix.cc
@@ -0,0 +1,111 @@
+// Copyright 2014 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 "mojo/edk/embedder/platform_channel_pair.h"
+
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/posix/global_descriptors.h"
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "mojo/edk/embedder/platform_handle.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+bool IsTargetDescriptorUsed(
+ const base::FileHandleMappingVector& file_handle_mapping,
+ int target_fd) {
+ for (size_t i = 0; i < file_handle_mapping.size(); i++) {
+ if (file_handle_mapping[i].second == target_fd)
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+PlatformChannelPair::PlatformChannelPair() {
+ // Create the Unix domain socket and set the ends to nonblocking.
+ int fds[2];
+ // TODO(vtl): Maybe fail gracefully if |socketpair()| fails.
+ PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0);
+ PCHECK(fcntl(fds[0], F_SETFL, O_NONBLOCK) == 0);
+ PCHECK(fcntl(fds[1], F_SETFL, O_NONBLOCK) == 0);
+
+#if defined(OS_MACOSX)
+ // This turns off |SIGPIPE| when writing to a closed socket (causing it to
+ // fail with |EPIPE| instead). On Linux, we have to use |send...()| with
+ // |MSG_NOSIGNAL| -- which is not supported on Mac -- instead.
+ int no_sigpipe = 1;
+ PCHECK(setsockopt(fds[0], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe,
+ sizeof(no_sigpipe)) == 0);
+ PCHECK(setsockopt(fds[1], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe,
+ sizeof(no_sigpipe)) == 0);
+#endif // defined(OS_MACOSX)
+
+ server_handle_.reset(PlatformHandle(fds[0]));
+ DCHECK(server_handle_.is_valid());
+ client_handle_.reset(PlatformHandle(fds[1]));
+ DCHECK(client_handle_.is_valid());
+}
+
+// static
+ScopedPlatformHandle PlatformChannelPair::PassClientHandleFromParentProcess(
+ const base::CommandLine& command_line) {
+ std::string client_fd_string =
+ command_line.GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch);
+ int client_fd = -1;
+ if (client_fd_string.empty() ||
+ !base::StringToInt(client_fd_string, &client_fd) ||
+ client_fd < base::GlobalDescriptors::kBaseDescriptor) {
+ LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch;
+ return ScopedPlatformHandle();
+ }
+
+ return ScopedPlatformHandle(PlatformHandle(client_fd));
+}
+
+void PlatformChannelPair::PrepareToPassClientHandleToChildProcess(
+ base::CommandLine* command_line,
+ base::FileHandleMappingVector* handle_passing_info) const {
+ DCHECK(command_line);
+ DCHECK(handle_passing_info);
+ // This is an arbitrary sanity check. (Note that this guarantees that the loop
+ // below will terminate sanely.)
+ CHECK_LT(handle_passing_info->size(), 1000u);
+
+ DCHECK(client_handle_.is_valid());
+
+ // Find a suitable FD to map our client handle to in the child process.
+ // This has quadratic time complexity in the size of |*handle_passing_info|,
+ // but |*handle_passing_info| should be very small (usually/often empty).
+ int target_fd = base::GlobalDescriptors::kBaseDescriptor;
+ while (IsTargetDescriptorUsed(*handle_passing_info, target_fd))
+ target_fd++;
+
+ handle_passing_info->push_back(
+ std::pair<int, int>(client_handle_.get().fd, target_fd));
+ // Log a warning if the command line already has the switch, but "clobber" it
+ // anyway, since it's reasonably likely that all the switches were just copied
+ // from the parent.
+ LOG_IF(WARNING, command_line->HasSwitch(kMojoPlatformChannelHandleSwitch))
+ << "Child command line already has switch --"
+ << kMojoPlatformChannelHandleSwitch << "="
+ << command_line->GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch);
+ // (Any existing switch won't actually be removed from the command line, but
+ // the last one appended takes precedence.)
+ command_line->AppendSwitchASCII(kMojoPlatformChannelHandleSwitch,
+ base::IntToString(target_fd));
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc b/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc
new file mode 100644
index 0000000..a0bb7fd
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc
@@ -0,0 +1,260 @@
+// Copyright 2014 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 "mojo/edk/embedder/platform_channel_pair.h"
+
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <deque>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "mojo/edk/embedder/platform_channel_utils_posix.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/test/test_utils.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+void WaitReadable(PlatformHandle h) {
+ struct pollfd pfds = {};
+ pfds.fd = h.fd;
+ pfds.events = POLLIN;
+ CHECK_EQ(poll(&pfds, 1, -1), 1);
+}
+
+class PlatformChannelPairPosixTest : public testing::Test {
+ public:
+ PlatformChannelPairPosixTest() {}
+ ~PlatformChannelPairPosixTest() override {}
+
+ void SetUp() override {
+ // Make sure |SIGPIPE| isn't being ignored.
+ struct sigaction action = {};
+ action.sa_handler = SIG_DFL;
+ ASSERT_EQ(0, sigaction(SIGPIPE, &action, &old_action_));
+ }
+
+ void TearDown() override {
+ // Restore the |SIGPIPE| handler.
+ ASSERT_EQ(0, sigaction(SIGPIPE, &old_action_, nullptr));
+ }
+
+ private:
+ struct sigaction old_action_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(PlatformChannelPairPosixTest);
+};
+
+TEST_F(PlatformChannelPairPosixTest, NoSigPipe) {
+ PlatformChannelPair channel_pair;
+ ScopedPlatformHandle server_handle = channel_pair.PassServerHandle().Pass();
+ ScopedPlatformHandle client_handle = channel_pair.PassClientHandle().Pass();
+
+ // Write to the client.
+ static const char kHello[] = "hello";
+ EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)),
+ write(client_handle.get().fd, kHello, sizeof(kHello)));
+
+ // Close the client.
+ client_handle.reset();
+
+ // Read from the server; this should be okay.
+ char buffer[100] = {};
+ EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)),
+ read(server_handle.get().fd, buffer, sizeof(buffer)));
+ EXPECT_STREQ(kHello, buffer);
+
+ // Try reading again.
+ ssize_t result = read(server_handle.get().fd, buffer, sizeof(buffer));
+ // We should probably get zero (for "end of file"), but -1 would also be okay.
+ EXPECT_TRUE(result == 0 || result == -1);
+ if (result == -1)
+ PLOG(WARNING) << "read (expected 0 for EOF)";
+
+ // Test our replacement for |write()|/|send()|.
+ result = PlatformChannelWrite(server_handle.get(), kHello, sizeof(kHello));
+ EXPECT_EQ(-1, result);
+ if (errno != EPIPE)
+ PLOG(WARNING) << "write (expected EPIPE)";
+
+ // Test our replacement for |writev()|/|sendv()|.
+ struct iovec iov[2] = {{const_cast<char*>(kHello), sizeof(kHello)},
+ {const_cast<char*>(kHello), sizeof(kHello)}};
+ result = PlatformChannelWritev(server_handle.get(), iov, 2);
+ EXPECT_EQ(-1, result);
+ if (errno != EPIPE)
+ PLOG(WARNING) << "write (expected EPIPE)";
+}
+
+TEST_F(PlatformChannelPairPosixTest, SendReceiveData) {
+ PlatformChannelPair channel_pair;
+ ScopedPlatformHandle server_handle = channel_pair.PassServerHandle().Pass();
+ ScopedPlatformHandle client_handle = channel_pair.PassClientHandle().Pass();
+
+ for (size_t i = 0; i < 10; i++) {
+ std::string send_string(1 << i, 'A' + i);
+
+ EXPECT_EQ(static_cast<ssize_t>(send_string.size()),
+ PlatformChannelWrite(server_handle.get(), send_string.data(),
+ send_string.size()));
+
+ WaitReadable(client_handle.get());
+
+ char buf[10000] = {};
+ std::deque<PlatformHandle> received_handles;
+ ssize_t result = PlatformChannelRecvmsg(client_handle.get(), buf,
+ sizeof(buf), &received_handles);
+ EXPECT_EQ(static_cast<ssize_t>(send_string.size()), result);
+ EXPECT_EQ(send_string, std::string(buf, static_cast<size_t>(result)));
+ EXPECT_TRUE(received_handles.empty());
+ }
+}
+
+TEST_F(PlatformChannelPairPosixTest, SendReceiveFDs) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ static const char kHello[] = "hello";
+
+ PlatformChannelPair channel_pair;
+ ScopedPlatformHandle server_handle = channel_pair.PassServerHandle().Pass();
+ ScopedPlatformHandle client_handle = channel_pair.PassClientHandle().Pass();
+
+// Reduce the number of FDs opened on OS X to avoid test flake.
+#if defined(OS_MACOSX)
+ const size_t kNumHandlesToSend = kPlatformChannelMaxNumHandles / 2;
+#else
+ const size_t kNumHandlesToSend = kPlatformChannelMaxNumHandles;
+#endif
+
+ for (size_t i = 1; i < kNumHandlesToSend; i++) {
+ // Make |i| files, with the j-th file consisting of j copies of the digit
+ // |c|.
+ const char c = '0' + (i % 10);
+ ScopedPlatformHandleVectorPtr platform_handles(new PlatformHandleVector);
+ for (size_t j = 1; j <= i; j++) {
+ base::FilePath unused;
+ base::ScopedFILE fp(
+ base::CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused));
+ ASSERT_TRUE(fp);
+ ASSERT_EQ(j, fwrite(std::string(j, c).data(), 1, j, fp.get()));
+ platform_handles->push_back(
+ test::PlatformHandleFromFILE(fp.Pass()).release());
+ ASSERT_TRUE(platform_handles->back().is_valid());
+ }
+
+ // Send the FDs (+ "hello").
+ struct iovec iov = {const_cast<char*>(kHello), sizeof(kHello)};
+ // We assume that the |sendmsg()| actually sends all the data.
+ EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)),
+ PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1,
+ &platform_handles->at(0),
+ platform_handles->size()));
+
+ WaitReadable(client_handle.get());
+
+ char buf[10000] = {};
+ std::deque<PlatformHandle> received_handles;
+ // We assume that the |recvmsg()| actually reads all the data.
+ EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)),
+ PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf),
+ &received_handles));
+ EXPECT_STREQ(kHello, buf);
+ EXPECT_EQ(i, received_handles.size());
+
+ for (size_t j = 0; !received_handles.empty(); j++) {
+ base::ScopedFILE fp(test::FILEFromPlatformHandle(
+ ScopedPlatformHandle(received_handles.front()), "rb"));
+ received_handles.pop_front();
+ ASSERT_TRUE(fp);
+ rewind(fp.get());
+ char read_buf[kNumHandlesToSend];
+ size_t bytes_read = fread(read_buf, 1, sizeof(read_buf), fp.get());
+ EXPECT_EQ(j + 1, bytes_read);
+ EXPECT_EQ(std::string(j + 1, c), std::string(read_buf, bytes_read));
+ }
+ }
+}
+
+TEST_F(PlatformChannelPairPosixTest, AppendReceivedFDs) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ static const char kHello[] = "hello";
+
+ PlatformChannelPair channel_pair;
+ ScopedPlatformHandle server_handle = channel_pair.PassServerHandle().Pass();
+ ScopedPlatformHandle client_handle = channel_pair.PassClientHandle().Pass();
+
+ const std::string file_contents("hello world");
+
+ {
+ base::FilePath unused;
+ base::ScopedFILE fp(
+ base::CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused));
+ ASSERT_TRUE(fp);
+ ASSERT_EQ(file_contents.size(),
+ fwrite(file_contents.data(), 1, file_contents.size(), fp.get()));
+ ScopedPlatformHandleVectorPtr platform_handles(new PlatformHandleVector);
+ platform_handles->push_back(
+ test::PlatformHandleFromFILE(fp.Pass()).release());
+ ASSERT_TRUE(platform_handles->back().is_valid());
+
+ // Send the FD (+ "hello").
+ struct iovec iov = {const_cast<char*>(kHello), sizeof(kHello)};
+ // We assume that the |sendmsg()| actually sends all the data.
+ EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)),
+ PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1,
+ &platform_handles->at(0),
+ platform_handles->size()));
+ }
+
+ WaitReadable(client_handle.get());
+
+ // Start with an invalid handle in the deque.
+ std::deque<PlatformHandle> received_handles;
+ received_handles.push_back(PlatformHandle());
+
+ char buf[100] = {};
+ // We assume that the |recvmsg()| actually reads all the data.
+ EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)),
+ PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf),
+ &received_handles));
+ EXPECT_STREQ(kHello, buf);
+ ASSERT_EQ(2u, received_handles.size());
+ EXPECT_FALSE(received_handles[0].is_valid());
+ EXPECT_TRUE(received_handles[1].is_valid());
+
+ {
+ base::ScopedFILE fp(test::FILEFromPlatformHandle(
+ ScopedPlatformHandle(received_handles[1]), "rb"));
+ received_handles[1] = PlatformHandle();
+ ASSERT_TRUE(fp);
+ rewind(fp.get());
+ char read_buf[100];
+ size_t bytes_read = fread(read_buf, 1, sizeof(read_buf), fp.get());
+ EXPECT_EQ(file_contents.size(), bytes_read);
+ EXPECT_EQ(file_contents, std::string(read_buf, bytes_read));
+ }
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/platform_channel_pair_win.cc b/mojo/edk/embedder/platform_channel_pair_win.cc
new file mode 100644
index 0000000..8ba325a
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_pair_win.cc
@@ -0,0 +1,111 @@
+// Copyright 2014 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 "mojo/edk/embedder/platform_channel_pair.h"
+
+#include <windows.h>
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/win/windows_version.h"
+#include "mojo/edk/embedder/platform_handle.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+std::wstring GeneratePipeName() {
+ return base::StringPrintf(L"\\\\.\\pipe\\mojo.%u.%u.%I64u",
+ GetCurrentProcessId(), GetCurrentThreadId(),
+ base::RandUint64());
+}
+
+} // namespace
+
+PlatformChannelPair::PlatformChannelPair() {
+ std::wstring pipe_name = GeneratePipeName();
+
+ const DWORD kOpenMode =
+ PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE;
+ const DWORD kPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE;
+ server_handle_.reset(PlatformHandle(
+ CreateNamedPipeW(pipe_name.c_str(), kOpenMode, kPipeMode,
+ 1, // Max instances.
+ 4096, // Out buffer size.
+ 4096, // In buffer size.
+ 5000, // Timeout in milliseconds.
+ nullptr))); // Default security descriptor.
+ PCHECK(server_handle_.is_valid());
+
+ const DWORD kDesiredAccess = GENERIC_READ | GENERIC_WRITE;
+ // The SECURITY_ANONYMOUS flag means that the server side cannot impersonate
+ // the client.
+ const DWORD kFlags =
+ SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS | FILE_FLAG_OVERLAPPED;
+ // Allow the handle to be inherited by child processes.
+ SECURITY_ATTRIBUTES security_attributes = {
+ sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
+ client_handle_.reset(
+ PlatformHandle(CreateFileW(pipe_name.c_str(), kDesiredAccess,
+ 0, // No sharing.
+ &security_attributes, OPEN_EXISTING, kFlags,
+ nullptr))); // No template file.
+ PCHECK(client_handle_.is_valid());
+
+ // Since a client has connected, ConnectNamedPipe() should return zero and
+ // GetLastError() should return ERROR_PIPE_CONNECTED.
+ CHECK(!ConnectNamedPipe(server_handle_.get().handle, nullptr));
+ PCHECK(GetLastError() == ERROR_PIPE_CONNECTED);
+}
+
+// static
+ScopedPlatformHandle PlatformChannelPair::PassClientHandleFromParentProcess(
+ const base::CommandLine& command_line) {
+ std::string client_handle_string =
+ command_line.GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch);
+
+ int client_handle_value = 0;
+ if (client_handle_string.empty() ||
+ !base::StringToInt(client_handle_string, &client_handle_value)) {
+ LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch;
+ return ScopedPlatformHandle();
+ }
+
+ return ScopedPlatformHandle(
+ PlatformHandle(LongToHandle(client_handle_value)));
+}
+
+void PlatformChannelPair::PrepareToPassClientHandleToChildProcess(
+ base::CommandLine* command_line,
+ base::HandlesToInheritVector* handle_passing_info) const {
+ DCHECK(command_line);
+ DCHECK(handle_passing_info);
+ DCHECK(client_handle_.is_valid());
+
+ CHECK_GE(base::win::GetVersion(), base::win::VERSION_VISTA);
+
+ handle_passing_info->push_back(client_handle_.get().handle);
+
+ // Log a warning if the command line already has the switch, but "clobber" it
+ // anyway, since it's reasonably likely that all the switches were just copied
+ // from the parent.
+ LOG_IF(WARNING, command_line->HasSwitch(kMojoPlatformChannelHandleSwitch))
+ << "Child command line already has switch --"
+ << kMojoPlatformChannelHandleSwitch << "="
+ << command_line->GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch);
+ // (Any existing switch won't actually be removed from the command line, but
+ // the last one appended takes precedence.)
+ command_line->AppendSwitchASCII(
+ kMojoPlatformChannelHandleSwitch,
+ base::IntToString(HandleToLong(client_handle_.get().handle)));
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/platform_channel_utils_posix.cc b/mojo/edk/embedder/platform_channel_utils_posix.cc
new file mode 100644
index 0000000..4af45e82
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_utils_posix.cc
@@ -0,0 +1,186 @@
+// Copyright 2014 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 "mojo/edk/embedder/platform_channel_utils_posix.h"
+
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "build/build_config.h"
+
+namespace mojo {
+namespace edk {
+
+// On Linux, |SIGPIPE| is suppressed by passing |MSG_NOSIGNAL| to
+// |send()|/|sendmsg()|. (There is no way of suppressing |SIGPIPE| on
+// |write()|/|writev().) On Mac, |SIGPIPE| is suppressed by setting the
+// |SO_NOSIGPIPE| option on the socket.
+//
+// Performance notes:
+// - On Linux, we have to use |send()|/|sendmsg()| rather than
+// |write()|/|writev()| in order to suppress |SIGPIPE|. This is okay, since
+// |send()| is (slightly) faster than |write()| (!), while |sendmsg()| is
+// quite comparable to |writev()|.
+// - On Mac, we may use |write()|/|writev()|. Here, |write()| is considerably
+// faster than |send()|, whereas |sendmsg()| is quite comparable to
+// |writev()|.
+// - On both platforms, an appropriate |sendmsg()|/|writev()| is considerably
+// faster than two |send()|s/|write()|s.
+// - Relative numbers (minimum real times from 10 runs) for one |write()| of
+// 1032 bytes, one |send()| of 1032 bytes, one |writev()| of 32+1000 bytes,
+// one |sendmsg()| of 32+1000 bytes, two |write()|s of 32 and 1000 bytes, two
+// |send()|s of 32 and 1000 bytes:
+// - Linux: 0.81 s, 0.77 s, 0.87 s, 0.89 s, 1.31 s, 1.22 s
+// - Mac: 2.21 s, 2.91 s, 2.98 s, 3.08 s, 3.59 s, 4.74 s
+
+// Flags to use with calling |send()| or |sendmsg()| (see above).
+#if defined(OS_MACOSX)
+const int kSendFlags = 0;
+#else
+const int kSendFlags = MSG_NOSIGNAL;
+#endif
+
+ssize_t PlatformChannelWrite(PlatformHandle h,
+ const void* bytes,
+ size_t num_bytes) {
+ DCHECK(h.is_valid());
+ DCHECK(bytes);
+ DCHECK_GT(num_bytes, 0u);
+
+#if defined(OS_MACOSX)
+ return HANDLE_EINTR(write(h.fd, bytes, num_bytes));
+#else
+ return send(h.fd, bytes, num_bytes, kSendFlags);
+#endif
+}
+
+ssize_t PlatformChannelWritev(PlatformHandle h,
+ struct iovec* iov,
+ size_t num_iov) {
+ DCHECK(h.is_valid());
+ DCHECK(iov);
+ DCHECK_GT(num_iov, 0u);
+
+#if defined(OS_MACOSX)
+ return HANDLE_EINTR(writev(h.fd, iov, static_cast<int>(num_iov)));
+#else
+ struct msghdr msg = {};
+ msg.msg_iov = iov;
+ msg.msg_iovlen = num_iov;
+ return HANDLE_EINTR(sendmsg(h.fd, &msg, kSendFlags));
+#endif
+}
+
+ssize_t PlatformChannelSendmsgWithHandles(PlatformHandle h,
+ struct iovec* iov,
+ size_t num_iov,
+ PlatformHandle* platform_handles,
+ size_t num_platform_handles) {
+ DCHECK(iov);
+ DCHECK_GT(num_iov, 0u);
+ DCHECK(platform_handles);
+ DCHECK_GT(num_platform_handles, 0u);
+ DCHECK_LE(num_platform_handles, kPlatformChannelMaxNumHandles);
+
+ char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))];
+ struct msghdr msg = {};
+ msg.msg_iov = iov;
+ msg.msg_iovlen = num_iov;
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = CMSG_LEN(num_platform_handles * sizeof(int));
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(num_platform_handles * sizeof(int));
+ for (size_t i = 0; i < num_platform_handles; i++) {
+ DCHECK(platform_handles[i].is_valid());
+ reinterpret_cast<int*>(CMSG_DATA(cmsg))[i] = platform_handles[i].fd;
+ }
+
+ return HANDLE_EINTR(sendmsg(h.fd, &msg, kSendFlags));
+}
+
+bool PlatformChannelSendHandles(PlatformHandle h,
+ PlatformHandle* handles,
+ size_t num_handles) {
+ DCHECK(handles);
+ DCHECK_GT(num_handles, 0u);
+ DCHECK_LE(num_handles, kPlatformChannelMaxNumHandles);
+
+ // Note: |sendmsg()| fails on Mac if we don't write at least one character.
+ struct iovec iov = {const_cast<char*>(""), 1};
+ char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))];
+ struct msghdr msg = {};
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = CMSG_LEN(num_handles * sizeof(int));
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(num_handles * sizeof(int));
+ for (size_t i = 0; i < num_handles; i++) {
+ DCHECK(handles[i].is_valid());
+ reinterpret_cast<int*>(CMSG_DATA(cmsg))[i] = handles[i].fd;
+ }
+
+ ssize_t result = HANDLE_EINTR(sendmsg(h.fd, &msg, kSendFlags));
+ if (result < 1) {
+ DCHECK_EQ(result, -1);
+ return false;
+ }
+
+ for (size_t i = 0; i < num_handles; i++)
+ handles[i].CloseIfNecessary();
+ return true;
+}
+
+ssize_t PlatformChannelRecvmsg(PlatformHandle h,
+ void* buf,
+ size_t num_bytes,
+ std::deque<PlatformHandle>* platform_handles) {
+ DCHECK(buf);
+ DCHECK_GT(num_bytes, 0u);
+ DCHECK(platform_handles);
+
+ struct iovec iov = {buf, num_bytes};
+ char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))];
+ struct msghdr msg = {};
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+
+ ssize_t result = HANDLE_EINTR(recvmsg(h.fd, &msg, MSG_DONTWAIT));
+ if (result < 0)
+ return result;
+
+ // Success; no control messages.
+ if (msg.msg_controllen == 0)
+ return result;
+
+ DCHECK(!(msg.msg_flags & MSG_CTRUNC));
+
+ for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+ size_t payload_length = cmsg->cmsg_len - CMSG_LEN(0);
+ DCHECK_EQ(payload_length % sizeof(int), 0u);
+ size_t num_fds = payload_length / sizeof(int);
+ const int* fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+ for (size_t i = 0; i < num_fds; i++) {
+ platform_handles->push_back(PlatformHandle(fds[i]));
+ DCHECK(platform_handles->back().is_valid());
+ }
+ }
+ }
+
+ return result;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/platform_channel_utils_posix.h b/mojo/edk/embedder/platform_channel_utils_posix.h
new file mode 100644
index 0000000..00e6a16
--- /dev/null
+++ b/mojo/edk/embedder/platform_channel_utils_posix.h
@@ -0,0 +1,76 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_
+
+#include <stddef.h>
+#include <sys/types.h> // For |ssize_t|.
+
+#include <deque>
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+struct iovec; // Declared in <sys/uio.h>.
+
+namespace mojo {
+namespace edk {
+
+// The maximum number of handles that can be sent "at once" using
+// |PlatformChannelSendmsgWithHandles()|.
+// TODO(vtl): This number is taken from ipc/ipc_message_attachment_set.h:
+// |IPC::MessageAttachmentSet::kMaxDescriptorsPerMessage|.
+const size_t kPlatformChannelMaxNumHandles = 128;
+
+// Use these to write to a socket created using |PlatformChannelPair| (or
+// equivalent). These are like |write()| and |writev()|, but handle |EINTR| and
+// never raise |SIGPIPE|. (Note: On Mac, the suppression of |SIGPIPE| is set up
+// by |PlatformChannelPair|.)
+MOJO_SYSTEM_IMPL_EXPORT ssize_t
+PlatformChannelWrite(PlatformHandle h, const void* bytes, size_t num_bytes);
+MOJO_SYSTEM_IMPL_EXPORT ssize_t
+PlatformChannelWritev(PlatformHandle h, struct iovec* iov, size_t num_iov);
+
+// Writes data, and the given set of |PlatformHandle|s (i.e., file descriptors)
+// over the Unix domain socket given by |h| (e.g., created using
+// |PlatformChannelPair()|). All the handles must be valid, and there must be at
+// least one and at most |kPlatformChannelMaxNumHandles| handles. The return
+// value is as for |sendmsg()|, namely -1 on failure and otherwise the number of
+// bytes of data sent on success (note that this may not be all the data
+// specified by |iov|). (The handles are not closed, regardless of success or
+// failure.)
+MOJO_SYSTEM_IMPL_EXPORT ssize_t
+PlatformChannelSendmsgWithHandles(PlatformHandle h,
+ struct iovec* iov,
+ size_t num_iov,
+ PlatformHandle* platform_handles,
+ size_t num_platform_handles);
+
+// TODO(vtl): Remove this once I've switched things over to
+// |PlatformChannelSendmsgWithHandles()|.
+// Sends |PlatformHandle|s (i.e., file descriptors) over the Unix domain socket
+// (e.g., created using PlatformChannelPair|). (These will be sent in a single
+// message having one null byte of data and one control message header with all
+// the file descriptors.) All of the handles must be valid, and there must be at
+// most |kPlatformChannelMaxNumHandles| (and at least one handle). Returns true
+// on success, in which case it closes all the handles.
+MOJO_SYSTEM_IMPL_EXPORT bool PlatformChannelSendHandles(PlatformHandle h,
+ PlatformHandle* handles,
+ size_t num_handles);
+
+// Wrapper around |recvmsg()|, which will extract any attached file descriptors
+// (in the control message) to |PlatformHandle|s (and append them to
+// |platform_handles|). (This also handles |EINTR|.)
+MOJO_SYSTEM_IMPL_EXPORT ssize_t
+PlatformChannelRecvmsg(PlatformHandle h,
+ void* buf,
+ size_t num_bytes,
+ std::deque<PlatformHandle>* platform_handles);
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_
diff --git a/mojo/edk/embedder/platform_handle.cc b/mojo/edk/embedder/platform_handle.cc
new file mode 100644
index 0000000..816c7e8
--- /dev/null
+++ b/mojo/edk/embedder/platform_handle.cc
@@ -0,0 +1,39 @@
+// 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 "mojo/edk/embedder/platform_handle.h"
+
+#include "build/build_config.h"
+#if defined(OS_POSIX)
+#include <unistd.h>
+#elif defined(OS_WIN)
+#include <windows.h>
+#else
+#error "Platform not yet supported."
+#endif
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace edk {
+
+void PlatformHandle::CloseIfNecessary() {
+ if (!is_valid())
+ return;
+
+#if defined(OS_POSIX)
+ bool success = (close(fd) == 0);
+ DPCHECK(success);
+ fd = -1;
+#elif defined(OS_WIN)
+ bool success = !!CloseHandle(handle);
+ DPCHECK(success);
+ handle = INVALID_HANDLE_VALUE;
+#else
+#error "Platform not yet supported."
+#endif
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/platform_handle.h b/mojo/edk/embedder/platform_handle.h
new file mode 100644
index 0000000..91a4eec
--- /dev/null
+++ b/mojo/edk/embedder/platform_handle.h
@@ -0,0 +1,47 @@
+// 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 MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_H_
+
+#include "build/build_config.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace mojo {
+namespace edk {
+
+#if defined(OS_POSIX)
+struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle {
+ PlatformHandle() : fd(-1) {}
+ explicit PlatformHandle(int fd) : fd(fd) {}
+
+ void CloseIfNecessary();
+
+ bool is_valid() const { return fd != -1; }
+
+ int fd;
+};
+#elif defined(OS_WIN)
+struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle {
+ PlatformHandle() : handle(INVALID_HANDLE_VALUE) {}
+ explicit PlatformHandle(HANDLE handle) : handle(handle) {}
+
+ void CloseIfNecessary();
+
+ bool is_valid() const { return handle != INVALID_HANDLE_VALUE; }
+
+ HANDLE handle;
+};
+#else
+#error "Platform not yet supported."
+#endif
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_H_
diff --git a/mojo/edk/embedder/platform_handle_utils.h b/mojo/edk/embedder/platform_handle_utils.h
new file mode 100644
index 0000000..fa683e4
--- /dev/null
+++ b/mojo/edk/embedder/platform_handle_utils.h
@@ -0,0 +1,33 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_
+
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace edk {
+
+// Closes all the |PlatformHandle|s in the given container.
+template <typename PlatformHandleContainer>
+MOJO_SYSTEM_IMPL_EXPORT inline void CloseAllPlatformHandles(
+ PlatformHandleContainer* platform_handles) {
+ for (typename PlatformHandleContainer::iterator it =
+ platform_handles->begin();
+ it != platform_handles->end(); ++it)
+ it->CloseIfNecessary();
+}
+
+// Duplicates the given |PlatformHandle| (which must be valid). (Returns an
+// invalid |ScopedPlatformHandle| on failure.)
+MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle
+DuplicatePlatformHandle(PlatformHandle platform_handle);
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_
diff --git a/mojo/edk/embedder/platform_handle_utils_posix.cc b/mojo/edk/embedder/platform_handle_utils_posix.cc
new file mode 100644
index 0000000..1f6ecce
--- /dev/null
+++ b/mojo/edk/embedder/platform_handle_utils_posix.cc
@@ -0,0 +1,22 @@
+// Copyright 2014 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 "mojo/edk/embedder/platform_handle_utils.h"
+
+#include <unistd.h>
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace edk {
+
+ScopedPlatformHandle DuplicatePlatformHandle(PlatformHandle platform_handle) {
+ DCHECK(platform_handle.is_valid());
+ // Note that |dup()| returns -1 on error (which is exactly the value we use
+ // for invalid |PlatformHandle| FDs).
+ return ScopedPlatformHandle(PlatformHandle(dup(platform_handle.fd)));
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/platform_handle_utils_win.cc b/mojo/edk/embedder/platform_handle_utils_win.cc
new file mode 100644
index 0000000..eebfdeb
--- /dev/null
+++ b/mojo/edk/embedder/platform_handle_utils_win.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 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 "mojo/edk/embedder/platform_handle_utils.h"
+
+#include <windows.h>
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace edk {
+
+ScopedPlatformHandle DuplicatePlatformHandle(PlatformHandle platform_handle) {
+ DCHECK(platform_handle.is_valid());
+
+ HANDLE new_handle;
+ if (!DuplicateHandle(GetCurrentProcess(), platform_handle.handle,
+ GetCurrentProcess(), &new_handle, 0, TRUE,
+ DUPLICATE_SAME_ACCESS))
+ return ScopedPlatformHandle();
+ DCHECK_NE(new_handle, INVALID_HANDLE_VALUE);
+ return ScopedPlatformHandle(PlatformHandle(new_handle));
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/platform_handle_vector.h b/mojo/edk/embedder/platform_handle_vector.h
new file mode 100644
index 0000000..2bea729
--- /dev/null
+++ b/mojo/edk/embedder/platform_handle_vector.h
@@ -0,0 +1,35 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/platform_handle_utils.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace edk {
+
+using PlatformHandleVector = std::vector<PlatformHandle>;
+
+// A deleter (for use with |scoped_ptr|) which closes all handles and then
+// |delete|s the |PlatformHandleVector|.
+struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandleVectorDeleter {
+ void operator()(PlatformHandleVector* platform_handles) const {
+ CloseAllPlatformHandles(platform_handles);
+ delete platform_handles;
+ }
+};
+
+using ScopedPlatformHandleVectorPtr =
+ scoped_ptr<PlatformHandleVector, PlatformHandleVectorDeleter>;
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_
diff --git a/mojo/edk/embedder/platform_shared_buffer.h b/mojo/edk/embedder/platform_shared_buffer.h
new file mode 100644
index 0000000..c7256af
--- /dev/null
+++ b/mojo/edk/embedder/platform_shared_buffer.h
@@ -0,0 +1,102 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_
+
+#include <stddef.h>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+class PlatformSharedBufferMapping;
+
+// |PlatformSharedBuffer| is an interface for a thread-safe, ref-counted wrapper
+// around OS-specific shared memory. It has the following features:
+// - A |PlatformSharedBuffer| simply represents a piece of shared memory that
+// *may* be mapped and *may* be shared to another process.
+// - A single |PlatformSharedBuffer| may be mapped multiple times. The
+// lifetime of the mapping (owned by |PlatformSharedBufferMapping|) is
+// separate from the lifetime of the |PlatformSharedBuffer|.
+// - Sizes/offsets (of the shared memory and mappings) are arbitrary, and not
+// restricted by page size. However, more memory may actually be mapped than
+// requested.
+//
+// It currently does NOT support the following:
+// - Sharing read-only. (This will probably eventually be supported.)
+//
+// TODO(vtl): Rectify this with |base::SharedMemory|.
+class MOJO_SYSTEM_IMPL_EXPORT PlatformSharedBuffer
+ : public base::RefCountedThreadSafe<PlatformSharedBuffer> {
+ public:
+ // Gets the size of shared buffer (in number of bytes).
+ virtual size_t GetNumBytes() const = 0;
+
+ // Maps (some) of the shared buffer into memory; [|offset|, |offset + length|]
+ // must be contained in [0, |num_bytes|], and |length| must be at least 1.
+ // Returns null on failure.
+ virtual scoped_ptr<PlatformSharedBufferMapping> Map(size_t offset,
+ size_t length) = 0;
+
+ // Checks if |offset| and |length| are valid arguments.
+ virtual bool IsValidMap(size_t offset, size_t length) = 0;
+
+ // Like |Map()|, but doesn't check its arguments (which should have been
+ // preflighted using |IsValidMap()|).
+ virtual scoped_ptr<PlatformSharedBufferMapping> MapNoCheck(size_t offset,
+ size_t length) = 0;
+
+ // Duplicates the underlying platform handle and passes it to the caller.
+ // TODO(vtl): On POSIX, we'll need two FDs to support sharing read-only.
+ virtual ScopedPlatformHandle DuplicatePlatformHandle() = 0;
+
+ // Passes the underlying platform handle to the caller. This should only be
+ // called if there's a unique reference to this object (owned by the caller).
+ // After calling this, this object should no longer be used, but should only
+ // be disposed of.
+ virtual ScopedPlatformHandle PassPlatformHandle() = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<PlatformSharedBuffer>;
+
+ PlatformSharedBuffer() {}
+ virtual ~PlatformSharedBuffer() {}
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(PlatformSharedBuffer);
+};
+
+// An interface for a mapping of a |PlatformSharedBuffer| (compararable to a
+// "file view" in Windows); see above. Created by (implementations of)
+// |PlatformSharedBuffer::Map()|. Automatically unmaps memory on destruction.
+//
+// Mappings are NOT thread-safe.
+//
+// Note: This is an entirely separate class (instead of
+// |PlatformSharedBuffer::Mapping|) so that it can be forward-declared.
+class MOJO_SYSTEM_IMPL_EXPORT PlatformSharedBufferMapping {
+ public:
+ // IMPORTANT: Implementations must implement a destructor that unmaps memory.
+ virtual ~PlatformSharedBufferMapping() {}
+
+ virtual void* GetBase() const = 0;
+ virtual size_t GetLength() const = 0;
+
+ protected:
+ PlatformSharedBufferMapping() {}
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(PlatformSharedBufferMapping);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_
diff --git a/mojo/edk/embedder/platform_support.h b/mojo/edk/embedder/platform_support.h
new file mode 100644
index 0000000..160ba79
--- /dev/null
+++ b/mojo/edk/embedder/platform_support.h
@@ -0,0 +1,43 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_PLATFORM_SUPPORT_H_
+#define MOJO_EDK_EMBEDDER_PLATFORM_SUPPORT_H_
+
+#include <stddef.h>
+
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+class PlatformSharedBuffer;
+
+// This class is provided by the embedder to implement (typically
+// platform-dependent) things needed by the Mojo system implementation.
+// Implementations must be thread-safe.
+class MOJO_SYSTEM_IMPL_EXPORT PlatformSupport {
+ public:
+ virtual ~PlatformSupport() {}
+
+ virtual void GetCryptoRandomBytes(void* bytes, size_t num_bytes) = 0;
+
+ virtual PlatformSharedBuffer* CreateSharedBuffer(size_t num_bytes) = 0;
+ virtual PlatformSharedBuffer* CreateSharedBufferFromHandle(
+ size_t num_bytes,
+ ScopedPlatformHandle platform_handle) = 0;
+
+ protected:
+ PlatformSupport() {}
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(PlatformSupport);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_PLATFORM_SUPPORT_H_
diff --git a/mojo/edk/embedder/process_delegate.h b/mojo/edk/embedder/process_delegate.h
new file mode 100644
index 0000000..e5f90af
--- /dev/null
+++ b/mojo/edk/embedder/process_delegate.h
@@ -0,0 +1,32 @@
+// Copyright 2015 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 MOJO_EDK_EMBEDDER_PROCESS_DELEGATE_H_
+#define MOJO_EDK_EMBEDDER_PROCESS_DELEGATE_H_
+
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// An interface for process delegates.
+class MOJO_SYSTEM_IMPL_EXPORT ProcessDelegate {
+ public:
+ // Called when |ShutdownIPCSupport()| has "completed". Note that this is NOT
+ // called if |ShutdownIPCSupportOnIOThread()| is used instead.
+ virtual void OnShutdownComplete() = 0;
+
+ protected:
+ ProcessDelegate() {}
+ virtual ~ProcessDelegate() {}
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ProcessDelegate);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_PROCESS_DELEGATE_H_
diff --git a/mojo/edk/embedder/scoped_platform_handle.h b/mojo/edk/embedder/scoped_platform_handle.h
new file mode 100644
index 0000000..ae75202
--- /dev/null
+++ b/mojo/edk/embedder/scoped_platform_handle.h
@@ -0,0 +1,62 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_
+#define MOJO_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_
+
+#include "base/move.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+class MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle {
+ MOVE_ONLY_TYPE_WITH_MOVE_CONSTRUCTOR_FOR_CPP_03(ScopedPlatformHandle)
+
+ public:
+ ScopedPlatformHandle() {}
+ explicit ScopedPlatformHandle(PlatformHandle handle) : handle_(handle) {}
+ ~ScopedPlatformHandle() { handle_.CloseIfNecessary(); }
+
+ // Move-only constructor and operator=.
+ ScopedPlatformHandle(ScopedPlatformHandle&& other)
+ : handle_(other.release()) {}
+
+ ScopedPlatformHandle& operator=(ScopedPlatformHandle&& other) {
+ if (this != &other)
+ handle_ = other.release();
+ return *this;
+ }
+
+ const PlatformHandle& get() const { return handle_; }
+
+ void swap(ScopedPlatformHandle& other) {
+ PlatformHandle temp = handle_;
+ handle_ = other.handle_;
+ other.handle_ = temp;
+ }
+
+ PlatformHandle release() MOJO_WARN_UNUSED_RESULT {
+ PlatformHandle rv = handle_;
+ handle_ = PlatformHandle();
+ return rv;
+ }
+
+ void reset(PlatformHandle handle = PlatformHandle()) {
+ handle_.CloseIfNecessary();
+ handle_ = handle;
+ }
+
+ bool is_valid() const { return handle_.is_valid(); }
+
+ private:
+ PlatformHandle handle_;
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer.cc b/mojo/edk/embedder/simple_platform_shared_buffer.cc
new file mode 100644
index 0000000..c09eb80
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer.cc
@@ -0,0 +1,108 @@
+// Copyright 2014 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 "mojo/edk/embedder/simple_platform_shared_buffer.h"
+
+#include "base/logging.h"
+#include "mojo/edk/embedder/platform_handle_utils.h"
+
+namespace mojo {
+namespace edk {
+
+// static
+SimplePlatformSharedBuffer* SimplePlatformSharedBuffer::Create(
+ size_t num_bytes) {
+ DCHECK_GT(num_bytes, 0u);
+
+ SimplePlatformSharedBuffer* rv = new SimplePlatformSharedBuffer(num_bytes);
+ if (!rv->Init()) {
+ // We can't just delete it directly, due to the "in destructor" (debug)
+ // check.
+ scoped_refptr<SimplePlatformSharedBuffer> deleter(rv);
+ return nullptr;
+ }
+
+ return rv;
+}
+
+// static
+SimplePlatformSharedBuffer*
+SimplePlatformSharedBuffer::CreateFromPlatformHandle(
+ size_t num_bytes,
+ ScopedPlatformHandle platform_handle) {
+ DCHECK_GT(num_bytes, 0u);
+
+ SimplePlatformSharedBuffer* rv = new SimplePlatformSharedBuffer(num_bytes);
+ if (!rv->InitFromPlatformHandle(platform_handle.Pass())) {
+ // We can't just delete it directly, due to the "in destructor" (debug)
+ // check.
+ scoped_refptr<SimplePlatformSharedBuffer> deleter(rv);
+ return nullptr;
+ }
+
+ return rv;
+}
+
+size_t SimplePlatformSharedBuffer::GetNumBytes() const {
+ return num_bytes_;
+}
+
+scoped_ptr<PlatformSharedBufferMapping> SimplePlatformSharedBuffer::Map(
+ size_t offset,
+ size_t length) {
+ if (!IsValidMap(offset, length))
+ return nullptr;
+
+ return MapNoCheck(offset, length);
+}
+
+bool SimplePlatformSharedBuffer::IsValidMap(size_t offset, size_t length) {
+ if (offset > num_bytes_ || length == 0)
+ return false;
+
+ // Note: This is an overflow-safe check of |offset + length > num_bytes_|
+ // (that |num_bytes >= offset| is verified above).
+ if (length > num_bytes_ - offset)
+ return false;
+
+ return true;
+}
+
+scoped_ptr<PlatformSharedBufferMapping> SimplePlatformSharedBuffer::MapNoCheck(
+ size_t offset,
+ size_t length) {
+ DCHECK(IsValidMap(offset, length));
+ return MapImpl(offset, length);
+}
+
+ScopedPlatformHandle SimplePlatformSharedBuffer::DuplicatePlatformHandle() {
+ return mojo::edk::DuplicatePlatformHandle(handle_.get());
+}
+
+ScopedPlatformHandle SimplePlatformSharedBuffer::PassPlatformHandle() {
+ DCHECK(HasOneRef());
+ return handle_.Pass();
+}
+
+SimplePlatformSharedBuffer::SimplePlatformSharedBuffer(size_t num_bytes)
+ : num_bytes_(num_bytes) {
+}
+
+SimplePlatformSharedBuffer::~SimplePlatformSharedBuffer() {
+}
+
+SimplePlatformSharedBufferMapping::~SimplePlatformSharedBufferMapping() {
+ Unmap();
+}
+
+void* SimplePlatformSharedBufferMapping::GetBase() const {
+ return base_;
+}
+
+size_t SimplePlatformSharedBufferMapping::GetLength() const {
+ return length_;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer.h b/mojo/edk/embedder/simple_platform_shared_buffer.h
new file mode 100644
index 0000000..798bc3a
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer.h
@@ -0,0 +1,101 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SHARED_BUFFER_H_
+#define MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SHARED_BUFFER_H_
+
+#include <stddef.h>
+
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// A simple implementation of |PlatformSharedBuffer|.
+class MOJO_SYSTEM_IMPL_EXPORT SimplePlatformSharedBuffer final
+ : public PlatformSharedBuffer {
+ public:
+ // Creates a shared buffer of size |num_bytes| bytes (initially zero-filled).
+ // |num_bytes| must be nonzero. Returns null on failure.
+ static SimplePlatformSharedBuffer* Create(size_t num_bytes);
+
+ static SimplePlatformSharedBuffer* CreateFromPlatformHandle(
+ size_t num_bytes,
+ ScopedPlatformHandle platform_handle);
+
+ // |PlatformSharedBuffer| implementation:
+ size_t GetNumBytes() const override;
+ scoped_ptr<PlatformSharedBufferMapping> Map(size_t offset,
+ size_t length) override;
+ bool IsValidMap(size_t offset, size_t length) override;
+ scoped_ptr<PlatformSharedBufferMapping> MapNoCheck(size_t offset,
+ size_t length) override;
+ ScopedPlatformHandle DuplicatePlatformHandle() override;
+ ScopedPlatformHandle PassPlatformHandle() override;
+
+ private:
+ explicit SimplePlatformSharedBuffer(size_t num_bytes);
+ ~SimplePlatformSharedBuffer() override;
+
+ // Implemented in simple_platform_shared_buffer_{posix,win}.cc:
+
+ // This is called by |Create()| before this object is given to anyone.
+ bool Init();
+
+ // This is like |Init()|, but for |CreateFromPlatformHandle()|. (Note: It
+ // should verify that |platform_handle| is an appropriate handle for the
+ // claimed |num_bytes_|.)
+ bool InitFromPlatformHandle(ScopedPlatformHandle platform_handle);
+
+ // The platform-dependent part of |Map()|; doesn't check arguments.
+ scoped_ptr<PlatformSharedBufferMapping> MapImpl(size_t offset, size_t length);
+
+ const size_t num_bytes_;
+
+ // This is set in |Init()|/|InitFromPlatformHandle()| and never modified
+ // (except by |PassPlatformHandle()|; see the comments above its declaration),
+ // hence does not need to be protected by a lock.
+ ScopedPlatformHandle handle_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(SimplePlatformSharedBuffer);
+};
+
+// An implementation of |PlatformSharedBufferMapping|, produced by
+// |SimplePlatformSharedBuffer|.
+class MOJO_SYSTEM_IMPL_EXPORT SimplePlatformSharedBufferMapping
+ : public PlatformSharedBufferMapping {
+ public:
+ ~SimplePlatformSharedBufferMapping() override;
+
+ void* GetBase() const override;
+ size_t GetLength() const override;
+
+ private:
+ friend class SimplePlatformSharedBuffer;
+
+ SimplePlatformSharedBufferMapping(void* base,
+ size_t length,
+ void* real_base,
+ size_t real_length)
+ : base_(base),
+ length_(length),
+ real_base_(real_base),
+ real_length_(real_length) {}
+ void Unmap();
+
+ void* const base_;
+ const size_t length_;
+
+ void* const real_base_;
+ const size_t real_length_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(SimplePlatformSharedBufferMapping);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SHARED_BUFFER_H_
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer_android.cc b/mojo/edk/embedder/simple_platform_shared_buffer_android.cc
new file mode 100644
index 0000000..d855604
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer_android.cc
@@ -0,0 +1,72 @@
+// Copyright 2014 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 "mojo/edk/embedder/simple_platform_shared_buffer.h"
+
+#include <stdint.h>
+#include <sys/mman.h> // For |PROT_...|.
+#include <sys/types.h> // For |off_t|.
+
+#include <limits>
+
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "third_party/ashmem/ashmem.h"
+
+namespace mojo {
+namespace edk {
+
+// SimplePlatformSharedBuffer --------------------------------------------------
+
+bool SimplePlatformSharedBuffer::Init() {
+ DCHECK(!handle_.is_valid());
+
+ if (static_cast<uint64_t>(num_bytes_) >
+ static_cast<uint64_t>(std::numeric_limits<off_t>::max())) {
+ return false;
+ }
+
+ base::ScopedFD fd(ashmem_create_region(nullptr, num_bytes_));
+ if (!fd.is_valid()) {
+ DPLOG(ERROR) << "ashmem_create_region()";
+ return false;
+ }
+
+ if (ashmem_set_prot_region(fd.get(), PROT_READ | PROT_WRITE) < 0) {
+ DPLOG(ERROR) << "ashmem_set_prot_region()";
+ return false;
+ }
+
+ handle_.reset(PlatformHandle(fd.release()));
+ return true;
+}
+
+bool SimplePlatformSharedBuffer::InitFromPlatformHandle(
+ ScopedPlatformHandle platform_handle) {
+ DCHECK(!handle_.is_valid());
+
+ if (static_cast<uint64_t>(num_bytes_) >
+ static_cast<uint64_t>(std::numeric_limits<off_t>::max())) {
+ return false;
+ }
+
+ int size = ashmem_get_size_region(platform_handle.get().fd);
+
+ if (size < 0) {
+ DPLOG(ERROR) << "ashmem_get_size_region()";
+ return false;
+ }
+
+ if (static_cast<size_t>(size) != num_bytes_) {
+ LOG(ERROR) << "Shared memory region has the wrong size";
+ return false;
+ }
+
+ handle_ = platform_handle.Pass();
+ return true;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer_posix.cc b/mojo/edk/embedder/simple_platform_shared_buffer_posix.cc
new file mode 100644
index 0000000..6914b39
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer_posix.cc
@@ -0,0 +1,158 @@
+// Copyright 2014 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 "mojo/edk/embedder/simple_platform_shared_buffer.h"
+
+#include <stdint.h>
+#include <stdio.h> // For |fileno()|.
+#include <sys/mman.h> // For |mmap()|/|munmap()|.
+#include <sys/stat.h>
+#include <sys/types.h> // For |off_t|.
+#include <unistd.h>
+
+#include <limits>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/sys_info.h"
+#include "base/threading/thread_restrictions.h"
+#include "mojo/edk/embedder/platform_handle.h"
+
+// We assume that |size_t| and |off_t| (type for |ftruncate()|) fits in a
+// |uint64_t|.
+static_assert(sizeof(size_t) <= sizeof(uint64_t), "size_t too big");
+static_assert(sizeof(off_t) <= sizeof(uint64_t), "off_t too big");
+
+namespace mojo {
+namespace edk {
+
+// SimplePlatformSharedBuffer --------------------------------------------------
+
+// The implementation for android uses ashmem to generate the file descriptor
+// for the shared memory. See simple_platform_shared_buffer_android.cc
+#if !defined(OS_ANDROID)
+
+bool SimplePlatformSharedBuffer::Init() {
+ DCHECK(!handle_.is_valid());
+
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ if (static_cast<uint64_t>(num_bytes_) >
+ static_cast<uint64_t>(std::numeric_limits<off_t>::max())) {
+ return false;
+ }
+
+ // TODO(vtl): This is stupid. The implementation of
+ // |CreateAndOpenTemporaryFileInDir()| starts with an FD, |fdopen()|s to get a
+ // |FILE*|, and then we have to |dup(fileno(fp))| to get back to an FD that we
+ // can own. (base/memory/shared_memory_posix.cc does this too, with more
+ // |fstat()|s thrown in for good measure.)
+ base::FilePath shared_buffer_dir;
+ if (!base::GetShmemTempDir(false, &shared_buffer_dir)) {
+ LOG(ERROR) << "Failed to get temporary directory for shared memory";
+ return false;
+ }
+ base::FilePath shared_buffer_file;
+ base::ScopedFILE fp(base::CreateAndOpenTemporaryFileInDir(
+ shared_buffer_dir, &shared_buffer_file));
+ if (!fp) {
+ LOG(ERROR) << "Failed to create/open temporary file for shared memory";
+ return false;
+ }
+ // Note: |unlink()| is not interruptible.
+ if (unlink(shared_buffer_file.value().c_str()) != 0) {
+ PLOG(WARNING) << "unlink";
+ // This isn't "fatal" (e.g., someone else may have unlinked the file first),
+ // so we may as well continue.
+ }
+
+ // Note: |dup()| is not interruptible (but |dup2()|/|dup3()| are).
+ base::ScopedFD fd(dup(fileno(fp.get())));
+ if (!fd.is_valid()) {
+ PLOG(ERROR) << "dup";
+ return false;
+ }
+
+ if (HANDLE_EINTR(ftruncate(fd.get(), static_cast<off_t>(num_bytes_))) != 0) {
+ PLOG(ERROR) << "ftruncate";
+ return false;
+ }
+
+ handle_.reset(PlatformHandle(fd.release()));
+ return true;
+}
+
+bool SimplePlatformSharedBuffer::InitFromPlatformHandle(
+ ScopedPlatformHandle platform_handle) {
+ DCHECK(!handle_.is_valid());
+
+ if (static_cast<uint64_t>(num_bytes_) >
+ static_cast<uint64_t>(std::numeric_limits<off_t>::max())) {
+ return false;
+ }
+
+ struct stat sb = {};
+ // Note: |fstat()| isn't interruptible.
+ if (fstat(platform_handle.get().fd, &sb) != 0) {
+ PLOG(ERROR) << "fstat";
+ return false;
+ }
+
+ if (!S_ISREG(sb.st_mode)) {
+ LOG(ERROR) << "Platform handle not to a regular file";
+ return false;
+ }
+
+ if (sb.st_size != static_cast<off_t>(num_bytes_)) {
+ LOG(ERROR) << "Shared memory file has the wrong size";
+ return false;
+ }
+
+ // TODO(vtl): More checks?
+
+ handle_ = platform_handle.Pass();
+ return true;
+}
+
+#endif // !defined(OS_ANDROID)
+
+scoped_ptr<PlatformSharedBufferMapping> SimplePlatformSharedBuffer::MapImpl(
+ size_t offset,
+ size_t length) {
+ size_t offset_rounding = offset % base::SysInfo::VMAllocationGranularity();
+ size_t real_offset = offset - offset_rounding;
+ size_t real_length = length + offset_rounding;
+
+ // This should hold (since we checked |num_bytes| versus the maximum value of
+ // |off_t| on creation, but it never hurts to be paranoid.
+ DCHECK_LE(static_cast<uint64_t>(real_offset),
+ static_cast<uint64_t>(std::numeric_limits<off_t>::max()));
+
+ void* real_base =
+ mmap(nullptr, real_length, PROT_READ | PROT_WRITE, MAP_SHARED,
+ handle_.get().fd, static_cast<off_t>(real_offset));
+ // |mmap()| should return |MAP_FAILED| (a.k.a. -1) on error. But it shouldn't
+ // return null either.
+ if (real_base == MAP_FAILED || !real_base) {
+ PLOG(ERROR) << "mmap";
+ return nullptr;
+ }
+
+ void* base = static_cast<char*>(real_base) + offset_rounding;
+ return make_scoped_ptr(new SimplePlatformSharedBufferMapping(
+ base, length, real_base, real_length));
+}
+
+// SimplePlatformSharedBufferMapping -------------------------------------------
+
+void SimplePlatformSharedBufferMapping::Unmap() {
+ int result = munmap(real_base_, real_length_);
+ PLOG_IF(ERROR, result != 0) << "munmap";
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer_unittest.cc b/mojo/edk/embedder/simple_platform_shared_buffer_unittest.cc
new file mode 100644
index 0000000..a0e915e
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer_unittest.cc
@@ -0,0 +1,187 @@
+// Copyright 2014 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 "mojo/edk/embedder/simple_platform_shared_buffer.h"
+
+#include <limits>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+TEST(SimplePlatformSharedBufferTest, Basic) {
+ const size_t kNumInts = 100;
+ const size_t kNumBytes = kNumInts * sizeof(int);
+ // A fudge so that we're not just writing zero bytes 75% of the time.
+ const int kFudge = 1234567890;
+
+ // Make some memory.
+ scoped_refptr<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(kNumBytes));
+ ASSERT_TRUE(buffer);
+
+ // Map it all, scribble some stuff, and then unmap it.
+ {
+ EXPECT_TRUE(buffer->IsValidMap(0, kNumBytes));
+ scoped_ptr<PlatformSharedBufferMapping> mapping(buffer->Map(0, kNumBytes));
+ ASSERT_TRUE(mapping);
+ ASSERT_TRUE(mapping->GetBase());
+ int* stuff = static_cast<int*>(mapping->GetBase());
+ for (size_t i = 0; i < kNumInts; i++)
+ stuff[i] = static_cast<int>(i) + kFudge;
+ }
+
+ // Map it all again, check that our scribbling is still there, then do a
+ // partial mapping and scribble on that, check that everything is coherent,
+ // unmap the first mapping, scribble on some of the second mapping, and then
+ // unmap it.
+ {
+ ASSERT_TRUE(buffer->IsValidMap(0, kNumBytes));
+ // Use |MapNoCheck()| this time.
+ scoped_ptr<PlatformSharedBufferMapping> mapping1(
+ buffer->MapNoCheck(0, kNumBytes));
+ ASSERT_TRUE(mapping1);
+ ASSERT_TRUE(mapping1->GetBase());
+ int* stuff1 = static_cast<int*>(mapping1->GetBase());
+ for (size_t i = 0; i < kNumInts; i++)
+ EXPECT_EQ(static_cast<int>(i) + kFudge, stuff1[i]) << i;
+
+ scoped_ptr<PlatformSharedBufferMapping> mapping2(
+ buffer->Map((kNumInts / 2) * sizeof(int), 2 * sizeof(int)));
+ ASSERT_TRUE(mapping2);
+ ASSERT_TRUE(mapping2->GetBase());
+ int* stuff2 = static_cast<int*>(mapping2->GetBase());
+ EXPECT_EQ(static_cast<int>(kNumInts / 2) + kFudge, stuff2[0]);
+ EXPECT_EQ(static_cast<int>(kNumInts / 2) + 1 + kFudge, stuff2[1]);
+
+ stuff2[0] = 123;
+ stuff2[1] = 456;
+ EXPECT_EQ(123, stuff1[kNumInts / 2]);
+ EXPECT_EQ(456, stuff1[kNumInts / 2 + 1]);
+
+ mapping1.reset();
+
+ EXPECT_EQ(123, stuff2[0]);
+ EXPECT_EQ(456, stuff2[1]);
+ stuff2[1] = 789;
+ }
+
+ // Do another partial mapping and check that everything is the way we expect
+ // it to be.
+ {
+ EXPECT_TRUE(buffer->IsValidMap(sizeof(int), kNumBytes - sizeof(int)));
+ scoped_ptr<PlatformSharedBufferMapping> mapping(
+ buffer->Map(sizeof(int), kNumBytes - sizeof(int)));
+ ASSERT_TRUE(mapping);
+ ASSERT_TRUE(mapping->GetBase());
+ int* stuff = static_cast<int*>(mapping->GetBase());
+
+ for (size_t j = 0; j < kNumInts - 1; j++) {
+ int i = static_cast<int>(j) + 1;
+ if (i == kNumInts / 2) {
+ EXPECT_EQ(123, stuff[j]);
+ } else if (i == kNumInts / 2 + 1) {
+ EXPECT_EQ(789, stuff[j]);
+ } else {
+ EXPECT_EQ(i + kFudge, stuff[j]) << i;
+ }
+ }
+ }
+}
+
+// TODO(vtl): Bigger buffers.
+
+TEST(SimplePlatformSharedBufferTest, InvalidMappings) {
+ scoped_refptr<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(100));
+ ASSERT_TRUE(buffer);
+
+ // Zero length not allowed.
+ EXPECT_FALSE(buffer->Map(0, 0));
+ EXPECT_FALSE(buffer->IsValidMap(0, 0));
+
+ // Okay:
+ EXPECT_TRUE(buffer->Map(0, 100));
+ EXPECT_TRUE(buffer->IsValidMap(0, 100));
+ // Offset + length too big.
+ EXPECT_FALSE(buffer->Map(0, 101));
+ EXPECT_FALSE(buffer->IsValidMap(0, 101));
+ EXPECT_FALSE(buffer->Map(1, 100));
+ EXPECT_FALSE(buffer->IsValidMap(1, 100));
+
+ // Okay:
+ EXPECT_TRUE(buffer->Map(50, 50));
+ EXPECT_TRUE(buffer->IsValidMap(50, 50));
+ // Offset + length too big.
+ EXPECT_FALSE(buffer->Map(50, 51));
+ EXPECT_FALSE(buffer->IsValidMap(50, 51));
+ EXPECT_FALSE(buffer->Map(51, 50));
+ EXPECT_FALSE(buffer->IsValidMap(51, 50));
+}
+
+TEST(SimplePlatformSharedBufferTest, TooBig) {
+ // If |size_t| is 32-bit, it's quite possible/likely that |Create()| succeeds
+ // (since it only involves creating a 4 GB file).
+ const size_t kMaxSizeT = std::numeric_limits<size_t>::max();
+ scoped_refptr<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(kMaxSizeT));
+ // But, assuming |sizeof(size_t) == sizeof(void*)|, mapping all of it should
+ // always fail.
+ if (buffer)
+ EXPECT_FALSE(buffer->Map(0, kMaxSizeT));
+}
+
+// Tests that separate mappings get distinct addresses.
+// Note: It's not inconceivable that the OS could ref-count identical mappings
+// and reuse the same address, in which case we'd have to be more careful about
+// using the address as the key for unmapping.
+TEST(SimplePlatformSharedBufferTest, MappingsDistinct) {
+ scoped_refptr<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(100));
+ scoped_ptr<PlatformSharedBufferMapping> mapping1(buffer->Map(0, 100));
+ scoped_ptr<PlatformSharedBufferMapping> mapping2(buffer->Map(0, 100));
+ EXPECT_NE(mapping1->GetBase(), mapping2->GetBase());
+}
+
+TEST(SimplePlatformSharedBufferTest, BufferZeroInitialized) {
+ static const size_t kSizes[] = {10, 100, 1000, 10000, 100000};
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(kSizes); i++) {
+ scoped_refptr<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(kSizes[i]));
+ scoped_ptr<PlatformSharedBufferMapping> mapping(buffer->Map(0, kSizes[i]));
+ for (size_t j = 0; j < kSizes[i]; j++) {
+ // "Assert" instead of "expect" so we don't spam the output with thousands
+ // of failures if we fail.
+ ASSERT_EQ('\0', static_cast<char*>(mapping->GetBase())[j])
+ << "size " << kSizes[i] << ", offset " << j;
+ }
+ }
+}
+
+TEST(SimplePlatformSharedBufferTest, MappingsOutliveBuffer) {
+ scoped_ptr<PlatformSharedBufferMapping> mapping1;
+ scoped_ptr<PlatformSharedBufferMapping> mapping2;
+
+ {
+ scoped_refptr<SimplePlatformSharedBuffer> buffer(
+ SimplePlatformSharedBuffer::Create(100));
+ mapping1 = buffer->Map(0, 100).Pass();
+ mapping2 = buffer->Map(50, 50).Pass();
+ static_cast<char*>(mapping1->GetBase())[50] = 'x';
+ }
+
+ EXPECT_EQ('x', static_cast<char*>(mapping2->GetBase())[0]);
+
+ static_cast<char*>(mapping2->GetBase())[1] = 'y';
+ EXPECT_EQ('y', static_cast<char*>(mapping1->GetBase())[51]);
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_shared_buffer_win.cc b/mojo/edk/embedder/simple_platform_shared_buffer_win.cc
new file mode 100644
index 0000000..a2b205e
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_shared_buffer_win.cc
@@ -0,0 +1,88 @@
+// Copyright 2014 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 "mojo/edk/embedder/simple_platform_shared_buffer.h"
+
+#include <windows.h>
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/sys_info.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+
+namespace mojo {
+namespace edk {
+
+// SimplePlatformSharedBuffer --------------------------------------------------
+
+bool SimplePlatformSharedBuffer::Init() {
+ DCHECK(!handle_.is_valid());
+
+ // TODO(vtl): Currently, we only support mapping up to 2^32-1 bytes.
+ if (static_cast<uint64_t>(num_bytes_) >
+ static_cast<uint64_t>(std::numeric_limits<DWORD>::max())) {
+ return false;
+ }
+
+ // IMPORTANT NOTE: Unnamed objects are NOT SECURABLE. Thus if we ever want to
+ // share read-only to other processes, we'll have to name our file mapping
+ // object.
+ // TODO(vtl): Unlike |base::SharedMemory|, we don't round up the size (to a
+ // multiple of 64 KB). This may cause problems with NaCl. Cross this bridge
+ // when we get there. crbug.com/210609
+ handle_.reset(PlatformHandle(
+ CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
+ static_cast<DWORD>(num_bytes_), nullptr)));
+ if (!handle_.is_valid()) {
+ PLOG(ERROR) << "CreateFileMapping";
+ return false;
+ }
+
+ return true;
+}
+
+bool SimplePlatformSharedBuffer::InitFromPlatformHandle(
+ ScopedPlatformHandle platform_handle) {
+ DCHECK(!handle_.is_valid());
+
+ handle_ = platform_handle.Pass();
+ return true;
+}
+
+scoped_ptr<PlatformSharedBufferMapping> SimplePlatformSharedBuffer::MapImpl(
+ size_t offset,
+ size_t length) {
+ size_t offset_rounding = offset % base::SysInfo::VMAllocationGranularity();
+ size_t real_offset = offset - offset_rounding;
+ size_t real_length = length + offset_rounding;
+
+ // This should hold (since we checked |num_bytes| versus the maximum value of
+ // |off_t| on creation, but it never hurts to be paranoid.
+ DCHECK_LE(static_cast<uint64_t>(real_offset),
+ static_cast<uint64_t>(std::numeric_limits<DWORD>::max()));
+
+ void* real_base =
+ MapViewOfFile(handle_.get().handle, FILE_MAP_READ | FILE_MAP_WRITE, 0,
+ static_cast<DWORD>(real_offset), real_length);
+ if (!real_base) {
+ PLOG(ERROR) << "MapViewOfFile";
+ return nullptr;
+ }
+
+ void* base = static_cast<char*>(real_base) + offset_rounding;
+ return make_scoped_ptr(new SimplePlatformSharedBufferMapping(
+ base, length, real_base, real_length));
+}
+
+// SimplePlatformSharedBufferMapping -------------------------------------------
+
+void SimplePlatformSharedBufferMapping::Unmap() {
+ BOOL result = UnmapViewOfFile(real_base_);
+ PLOG_IF(ERROR, !result) << "UnmapViewOfFile";
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_support.cc b/mojo/edk/embedder/simple_platform_support.cc
new file mode 100644
index 0000000..814cd68
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_support.cc
@@ -0,0 +1,31 @@
+// Copyright 2014 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 "mojo/edk/embedder/simple_platform_support.h"
+
+#include "base/rand_util.h"
+#include "mojo/edk/embedder/simple_platform_shared_buffer.h"
+
+namespace mojo {
+namespace edk {
+
+void SimplePlatformSupport::GetCryptoRandomBytes(void* bytes,
+ size_t num_bytes) {
+ base::RandBytes(bytes, num_bytes);
+}
+
+PlatformSharedBuffer* SimplePlatformSupport::CreateSharedBuffer(
+ size_t num_bytes) {
+ return SimplePlatformSharedBuffer::Create(num_bytes);
+}
+
+PlatformSharedBuffer* SimplePlatformSupport::CreateSharedBufferFromHandle(
+ size_t num_bytes,
+ ScopedPlatformHandle platform_handle) {
+ return SimplePlatformSharedBuffer::CreateFromPlatformHandle(
+ num_bytes, platform_handle.Pass());
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/embedder/simple_platform_support.h b/mojo/edk/embedder/simple_platform_support.h
new file mode 100644
index 0000000..ef97d91
--- /dev/null
+++ b/mojo/edk/embedder/simple_platform_support.h
@@ -0,0 +1,39 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SUPPORT_H_
+#define MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SUPPORT_H_
+
+#include "mojo/edk/embedder/platform_support.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// A simple implementation of |PlatformSupport|, when sandboxing and
+// multiprocess support are not issues (e.g., in most tests). Note: This class
+// has no state, and different instances of |SimplePlatformSupport| are mutually
+// compatible (i.e., you don't need to use a single instance of it everywhere --
+// you may simply create one whenever/wherever you need it).
+class MOJO_SYSTEM_IMPL_EXPORT SimplePlatformSupport final
+ : public PlatformSupport {
+ public:
+ SimplePlatformSupport() {}
+ ~SimplePlatformSupport() override {}
+
+ void GetCryptoRandomBytes(void* bytes, size_t num_bytes) override;
+ PlatformSharedBuffer* CreateSharedBuffer(size_t num_bytes) override;
+ PlatformSharedBuffer* CreateSharedBufferFromHandle(
+ size_t num_bytes,
+ ScopedPlatformHandle platform_handle) override;
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(SimplePlatformSupport);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_SIMPLE_PLATFORM_SUPPORT_H_
diff --git a/mojo/edk/embedder/system_impl_private_entrypoints.cc b/mojo/edk/embedder/system_impl_private_entrypoints.cc
new file mode 100644
index 0000000..0642fee1
--- /dev/null
+++ b/mojo/edk/embedder/system_impl_private_entrypoints.cc
@@ -0,0 +1,255 @@
+// Copyright 2015 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 "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/system/core.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/public/c/system/buffer.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/functions.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/platform/native/system_impl_private.h"
+
+using mojo::edk::internal::g_core;
+using mojo::edk::Core;
+using mojo::edk::Dispatcher;
+
+// TODO(use_chrome_edk): commented out since for now we use the entrypoints in
+// third_party and that checks the command line to redirect here.
+/*
+
+// Definitions of the system functions, but with an explicit parameter for the
+// core object rather than using the default singleton. Also includes functions
+// for manipulating core objects.
+extern "C" {
+
+MojoSystemImpl MojoSystemImplGetDefaultImpl() {
+ return static_cast<MojoSystemImpl>(g_core);
+}
+
+MojoSystemImpl MojoSystemImplCreateImpl() {
+ Core* created_core = new Core(g_core->platform_support());
+ return static_cast<MojoSystemImpl>(created_core);
+}
+
+MojoResult MojoSystemImplTransferHandle(MojoSystemImpl from_system,
+ MojoHandle handle,
+ MojoSystemImpl to_system,
+ MojoHandle* result_handle) {
+ Core* from_core = static_cast<Core*>(from_system);
+ if (from_core == nullptr)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (handle == MOJO_HANDLE_INVALID)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ Core* to_core = static_cast<Core*>(to_system);
+ if (to_core == nullptr)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (result_handle == nullptr)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ scoped_refptr<Dispatcher> d;
+ MojoResult result = from_core->GetAndRemoveDispatcher(handle, &d);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ MojoHandle created_handle = to_core->AddDispatcher(d);
+ if (created_handle == MOJO_HANDLE_INVALID) {
+ // The handle has been lost, unfortunately. There's no guarentee we can put
+ // it back where it came from, or get the original ID back. Holding locks
+ // for multiple cores risks deadlock, so that isn't a solution. This case
+ // should not happen for reasonable uses of this API, however.
+ LOG(ERROR) << "Could not transfer handle";
+ d->Close();
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ *result_handle = created_handle;
+ return MOJO_RESULT_OK;
+}
+
+MojoTimeTicks MojoSystemImplGetTimeTicksNow(MojoSystemImpl system) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->GetTimeTicksNow();
+}
+
+MojoResult MojoSystemImplClose(MojoSystemImpl system, MojoHandle handle) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->Close(handle);
+}
+
+MojoResult MojoSystemImplWait(MojoSystemImpl system,
+ MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoDeadline deadline,
+ MojoHandleSignalsState* signals_state) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->Wait(handle, signals, deadline, signals_state);
+}
+
+MojoResult MojoSystemImplWaitMany(MojoSystemImpl system,
+ const MojoHandle* handles,
+ const MojoHandleSignals* signals,
+ uint32_t num_handles,
+ MojoDeadline deadline,
+ uint32_t* result_index,
+ MojoHandleSignalsState* signals_states) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->WaitMany(handles, signals, num_handles, deadline, result_index,
+ signals_states);
+}
+
+MojoResult MojoSystemImplCreateMessagePipe(
+ MojoSystemImpl system,
+ const MojoCreateMessagePipeOptions* options,
+ MojoHandle* message_pipe_handle0,
+ MojoHandle* message_pipe_handle1) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->CreateMessagePipe(options, message_pipe_handle0,
+ message_pipe_handle1);
+}
+
+MojoResult MojoSystemImplWriteMessage(MojoSystemImpl system,
+ MojoHandle message_pipe_handle,
+ const void* bytes,
+ uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->WriteMessage(message_pipe_handle, bytes, num_bytes, num_bytes,
+ handles, num_handles, flags);
+}
+
+MojoResult MojoSystemImplReadMessage(MojoSystemImpl system,
+ MojoHandle message_pipe_handle,
+ void* bytes,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->ReadMessage(message_pipe_handle, bytes, num_bytes, handles,
+ num_handles, flags);
+}
+
+MojoResult MojoSystemImplCreateDataPipe(
+ MojoSystemImpl system,
+ const MojoCreateDataPipeOptions* options,
+ MojoHandle* data_pipe_producer_handle,
+ MojoHandle* data_pipe_consumer_handle) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->CreateDataPipe(options, data_pipe_producer_handle,
+ data_pipe_consumer_handle);
+}
+
+MojoResult MojoSystemImplWriteData(MojoSystemImpl system,
+ MojoHandle data_pipe_producer_handle,
+ const void* elements,
+ uint32_t* num_elements,
+ MojoWriteDataFlags flags) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->WriteData(data_pipe_producer_handle, elements, num_elements,
+ flags);
+}
+
+MojoResult MojoSystemImplBeginWriteData(MojoSystemImpl system,
+ MojoHandle data_pipe_producer_handle,
+ void** buffer,
+ uint32_t* buffer_num_elements,
+ MojoWriteDataFlags flags) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->BeginWriteData(data_pipe_producer_handle, buffer,
+ buffer_num_elements, flags);
+}
+
+MojoResult MojoSystemImplEndWriteData(MojoSystemImpl system,
+ MojoHandle data_pipe_producer_handle,
+ uint32_t num_elements_written) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->EndWriteData(data_pipe_producer_handle, num_elements_written);
+}
+
+MojoResult MojoSystemImplReadData(MojoSystemImpl system,
+ MojoHandle data_pipe_consumer_handle,
+ void* elements,
+ uint32_t* num_elements,
+ MojoReadDataFlags flags) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->ReadData(data_pipe_consumer_handle, elements, num_elements,
+ flags);
+}
+
+MojoResult MojoSystemImplBeginReadData(MojoSystemImpl system,
+ MojoHandle data_pipe_consumer_handle,
+ const void** buffer,
+ uint32_t* buffer_num_elements,
+ MojoReadDataFlags flags) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->BeginReadData(data_pipe_consumer_handle, buffer,
+ buffer_num_elements, flags);
+}
+
+MojoResult MojoSystemImplEndReadData(MojoSystemImpl system,
+ MojoHandle data_pipe_consumer_handle,
+ uint32_t num_elements_read) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->EndReadData(data_pipe_consumer_handle, num_elements_read);
+}
+
+MojoResult MojoSystemImplCreateSharedBuffer(
+ MojoSystemImpl system,
+ const MojoCreateSharedBufferOptions* options,
+ uint64_t num_bytes,
+ MojoHandle* shared_buffer_handle) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->CreateSharedBuffer(options, num_bytes, shared_buffer_handle);
+}
+
+MojoResult MojoSystemImplDuplicateBufferHandle(
+ MojoSystemImpl system,
+ MojoHandle buffer_handle,
+ const MojoDuplicateBufferHandleOptions* options,
+ MojoHandle* new_buffer_handle) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->DuplicateBufferHandle(buffer_handle, options, new_buffer_handle);
+}
+
+MojoResult MojoSystemImplMapBuffer(MojoSystemImpl system,
+ MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ void** buffer,
+ MojoMapBufferFlags flags) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->MapBuffer(buffer_handle, offset, num_bytes, buffer, flags);
+}
+
+MojoResult MojoSystemImplUnmapBuffer(MojoSystemImpl system, void* buffer) {
+ mojo::edk::Core* core = static_cast<mojo::edk::Core*>(system);
+ DCHECK(core);
+ return core->UnmapBuffer(buffer);
+}
+
+} // extern "C"
+*/
diff --git a/mojo/edk/embedder/test_embedder.cc b/mojo/edk/embedder/test_embedder.cc
new file mode 100644
index 0000000..b33ea7b
--- /dev/null
+++ b/mojo/edk/embedder/test_embedder.cc
@@ -0,0 +1,56 @@
+// Copyright 2014 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 "mojo/edk/embedder/test_embedder.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/embedder/platform_support.h"
+#include "mojo/edk/system/core.h"
+#include "mojo/edk/system/handle_table.h"
+
+namespace mojo {
+
+namespace edk {
+namespace internal {
+
+bool ShutdownCheckNoLeaks(Core* core) {
+ // No point in taking the lock.
+ const HandleTable::HandleToEntryMap& handle_to_entry_map =
+ core->handle_table_.handle_to_entry_map_;
+
+ if (handle_to_entry_map.empty())
+ return true;
+
+ for (HandleTable::HandleToEntryMap::const_iterator it =
+ handle_to_entry_map.begin();
+ it != handle_to_entry_map.end(); ++it) {
+ LOG(ERROR) << "Mojo embedder shutdown: Leaking handle " << (*it).first;
+ }
+ return false;
+}
+
+} // namespace internal
+
+namespace test {
+
+bool Shutdown() {
+ CHECK(internal::g_core);
+ bool rv = internal::ShutdownCheckNoLeaks(internal::g_core);
+ delete internal::g_core;
+ internal::g_core = nullptr;
+
+ CHECK(internal::g_platform_support);
+ delete internal::g_platform_support;
+ internal::g_platform_support = nullptr;
+
+ return rv;
+}
+
+} // namespace test
+} // namespace edk
+
+} // namespace mojo
diff --git a/mojo/edk/embedder/test_embedder.h b/mojo/edk/embedder/test_embedder.h
new file mode 100644
index 0000000..c64ba17
--- /dev/null
+++ b/mojo/edk/embedder/test_embedder.h
@@ -0,0 +1,28 @@
+// Copyright 2014 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 MOJO_EDK_EMBEDDER_TEST_EMBEDDER_H_
+#define MOJO_EDK_EMBEDDER_TEST_EMBEDDER_H_
+
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+// This shuts down the global, singleton instance. (Note: "Real" embedders are
+// not expected to ever shut down this instance. This |Shutdown()| function will
+// do more work to ensure that tests don't leak, etc.) Returns true if there
+// were no problems, false if there were leaks -- i.e., handles still open -- or
+// any other problems.
+//
+// Note: It is up to the caller to ensure that there are not outstanding
+// callbacks from |CreateChannel()| before calling this.
+MOJO_SYSTEM_IMPL_EXPORT bool Shutdown();
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_EMBEDDER_TEST_EMBEDDER_H_
diff --git a/mojo/edk/js/BUILD.gn b/mojo/edk/js/BUILD.gn
new file mode 100644
index 0000000..dc61068e
--- /dev/null
+++ b/mojo/edk/js/BUILD.gn
@@ -0,0 +1,65 @@
+# Copyright 2014 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.
+
+import("../mojo_edk.gni")
+
+# TODO(hansmuller): The organization of tests in this directory is weird:
+# * Really, js_unittests tests public stuff, so that should live in public
+# and be reworked as some sort of apptest.
+# * Both js_unittests and js_integration_tests should auto-generate their
+# tests somehow. The .cc files are just test runner stubs, including
+# explicit lists of .js files.
+group("tests") {
+ testonly = true
+ deps = [
+ # TODO(use_chrome_edk): remove "2"
+ "test:js_unittests2",
+ "test:js_integration_tests2",
+ ]
+}
+
+mojo_edk_source_set("js") {
+ sources = [
+ "core.cc",
+ "core.h",
+ "drain_data.cc",
+ "drain_data.h",
+ "handle.cc",
+ "handle.h",
+ "handle_close_observer.h",
+ "mojo_runner_delegate.cc",
+ "mojo_runner_delegate.h",
+ "support.cc",
+ "support.h",
+ "threading.cc",
+ "threading.h",
+ "waiting_callback.cc",
+ "waiting_callback.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//gin",
+ "//v8",
+ ]
+
+ deps = [
+ "//third_party/mojo/src/mojo/public/cpp/environment",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ ]
+}
+
+mojo_edk_source_set("js_unittests") {
+ testonly = true
+ sources = [
+ "handle_unittest.cc",
+ ]
+
+ deps = [
+ "//mojo/edk/js",
+ "//mojo/edk/test:test_support",
+ "//testing/gtest",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ ]
+}
diff --git a/mojo/edk/js/core.cc b/mojo/edk/js/core.cc
new file mode 100644
index 0000000..ed592b4
--- /dev/null
+++ b/mojo/edk/js/core.cc
@@ -0,0 +1,379 @@
+// Copyright 2014 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 "mojo/edk/js/core.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "gin/arguments.h"
+#include "gin/array_buffer.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+#include "gin/function_template.h"
+#include "gin/handle.h"
+#include "gin/object_template_builder.h"
+#include "gin/per_isolate_data.h"
+#include "gin/public/wrapper_info.h"
+#include "gin/wrappable.h"
+#include "mojo/edk/js/drain_data.h"
+#include "mojo/edk/js/handle.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+MojoResult CloseHandle(gin::Handle<HandleWrapper> handle) {
+ if (!handle->get().is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ handle->Close();
+ return MOJO_RESULT_OK;
+}
+
+gin::Dictionary WaitHandle(const gin::Arguments& args,
+ mojo::Handle handle,
+ MojoHandleSignals signals,
+ MojoDeadline deadline) {
+ v8::Isolate* isolate = args.isolate();
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate);
+
+ MojoHandleSignalsState signals_state;
+ MojoResult result = mojo::Wait(handle, signals, deadline, &signals_state);
+ dictionary.Set("result", result);
+
+ mojo::WaitManyResult wmv(result, 0);
+ if (!wmv.AreSignalsStatesValid()) {
+ dictionary.Set("signalsState", v8::Null(isolate).As<v8::Value>());
+ } else {
+ gin::Dictionary signalsStateDict = gin::Dictionary::CreateEmpty(isolate);
+ signalsStateDict.Set("satisfiedSignals", signals_state.satisfied_signals);
+ signalsStateDict.Set("satisfiableSignals",
+ signals_state.satisfiable_signals);
+ dictionary.Set("signalsState", signalsStateDict);
+ }
+
+ return dictionary;
+}
+
+gin::Dictionary WaitMany(const gin::Arguments& args,
+ const std::vector<mojo::Handle>& handles,
+ const std::vector<MojoHandleSignals>& signals,
+ MojoDeadline deadline) {
+ v8::Isolate* isolate = args.isolate();
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate);
+
+ std::vector<MojoHandleSignalsState> signals_states(signals.size());
+ mojo::WaitManyResult wmv =
+ mojo::WaitMany(handles, signals, deadline, &signals_states);
+ dictionary.Set("result", wmv.result);
+ if (wmv.IsIndexValid()) {
+ dictionary.Set("index", wmv.index);
+ } else {
+ dictionary.Set("index", v8::Null(isolate).As<v8::Value>());
+ }
+ if (wmv.AreSignalsStatesValid()) {
+ std::vector<gin::Dictionary> vec;
+ for (size_t i = 0; i < handles.size(); ++i) {
+ gin::Dictionary signalsStateDict = gin::Dictionary::CreateEmpty(isolate);
+ signalsStateDict.Set("satisfiedSignals",
+ signals_states[i].satisfied_signals);
+ signalsStateDict.Set("satisfiableSignals",
+ signals_states[i].satisfiable_signals);
+ vec.push_back(signalsStateDict);
+ }
+ dictionary.Set("signalsState", vec);
+ } else {
+ dictionary.Set("signalsState", v8::Null(isolate).As<v8::Value>());
+ }
+
+ return dictionary;
+}
+
+gin::Dictionary CreateMessagePipe(const gin::Arguments& args) {
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT);
+
+ MojoHandle handle0 = MOJO_HANDLE_INVALID;
+ MojoHandle handle1 = MOJO_HANDLE_INVALID;
+ MojoResult result = MOJO_RESULT_OK;
+
+ v8::Handle<v8::Value> options_value = args.PeekNext();
+ if (options_value.IsEmpty() || options_value->IsNull() ||
+ options_value->IsUndefined()) {
+ result = MojoCreateMessagePipe(NULL, &handle0, &handle1);
+ } else if (options_value->IsObject()) {
+ gin::Dictionary options_dict(args.isolate(), options_value->ToObject());
+ MojoCreateMessagePipeOptions options;
+ // For future struct_size, we can probably infer that from the presence of
+ // properties in options_dict. For now, it's always 8.
+ options.struct_size = 8;
+ // Ideally these would be optional. But the interface makes it hard to
+ // typecheck them then.
+ if (!options_dict.Get("flags", &options.flags)) {
+ return dictionary;
+ }
+
+ result = MojoCreateMessagePipe(&options, &handle0, &handle1);
+ } else {
+ return dictionary;
+ }
+
+ CHECK_EQ(MOJO_RESULT_OK, result);
+
+ dictionary.Set("result", result);
+ dictionary.Set("handle0", mojo::Handle(handle0));
+ dictionary.Set("handle1", mojo::Handle(handle1));
+ return dictionary;
+}
+
+MojoResult WriteMessage(
+ mojo::Handle handle,
+ const gin::ArrayBufferView& buffer,
+ const std::vector<gin::Handle<HandleWrapper> >& handles,
+ MojoWriteMessageFlags flags) {
+ std::vector<MojoHandle> raw_handles(handles.size());
+ for (size_t i = 0; i < handles.size(); ++i)
+ raw_handles[i] = handles[i]->get().value();
+ MojoResult rv = MojoWriteMessage(handle.value(),
+ buffer.bytes(),
+ static_cast<uint32_t>(buffer.num_bytes()),
+ raw_handles.empty() ? NULL : &raw_handles[0],
+ static_cast<uint32_t>(raw_handles.size()),
+ flags);
+ // MojoWriteMessage takes ownership of the handles upon success, so
+ // release them here.
+ if (rv == MOJO_RESULT_OK) {
+ for (size_t i = 0; i < handles.size(); ++i)
+ ignore_result(handles[i]->release());
+ }
+ return rv;
+}
+
+gin::Dictionary ReadMessage(const gin::Arguments& args,
+ mojo::Handle handle,
+ MojoReadMessageFlags flags) {
+ uint32_t num_bytes = 0;
+ uint32_t num_handles = 0;
+ MojoResult result = MojoReadMessage(
+ handle.value(), NULL, &num_bytes, NULL, &num_handles, flags);
+ if (result != MOJO_RESULT_RESOURCE_EXHAUSTED) {
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", result);
+ return dictionary;
+ }
+
+ v8::Handle<v8::ArrayBuffer> array_buffer =
+ v8::ArrayBuffer::New(args.isolate(), num_bytes);
+ std::vector<mojo::Handle> handles(num_handles);
+
+ gin::ArrayBuffer buffer;
+ ConvertFromV8(args.isolate(), array_buffer, &buffer);
+ CHECK(buffer.num_bytes() == num_bytes);
+
+ result = MojoReadMessage(handle.value(),
+ buffer.bytes(),
+ &num_bytes,
+ handles.empty() ? NULL :
+ reinterpret_cast<MojoHandle*>(&handles[0]),
+ &num_handles,
+ flags);
+
+ CHECK(buffer.num_bytes() == num_bytes);
+ CHECK(handles.size() == num_handles);
+
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", result);
+ dictionary.Set("buffer", array_buffer);
+ dictionary.Set("handles", handles);
+ return dictionary;
+}
+
+gin::Dictionary CreateDataPipe(const gin::Arguments& args) {
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT);
+
+ MojoHandle producer_handle = MOJO_HANDLE_INVALID;
+ MojoHandle consumer_handle = MOJO_HANDLE_INVALID;
+ MojoResult result = MOJO_RESULT_OK;
+
+ v8::Handle<v8::Value> options_value = args.PeekNext();
+ if (options_value.IsEmpty() || options_value->IsNull() ||
+ options_value->IsUndefined()) {
+ result = MojoCreateDataPipe(NULL, &producer_handle, &consumer_handle);
+ } else if (options_value->IsObject()) {
+ gin::Dictionary options_dict(args.isolate(), options_value->ToObject());
+ MojoCreateDataPipeOptions options;
+ // For future struct_size, we can probably infer that from the presence of
+ // properties in options_dict. For now, it's always 16.
+ options.struct_size = 16;
+ // Ideally these would be optional. But the interface makes it hard to
+ // typecheck them then.
+ if (!options_dict.Get("flags", &options.flags) ||
+ !options_dict.Get("elementNumBytes", &options.element_num_bytes) ||
+ !options_dict.Get("capacityNumBytes", &options.capacity_num_bytes)) {
+ return dictionary;
+ }
+
+ result = MojoCreateDataPipe(&options, &producer_handle, &consumer_handle);
+ } else {
+ return dictionary;
+ }
+
+ CHECK_EQ(MOJO_RESULT_OK, result);
+
+ dictionary.Set("result", result);
+ dictionary.Set("producerHandle", mojo::Handle(producer_handle));
+ dictionary.Set("consumerHandle", mojo::Handle(consumer_handle));
+ return dictionary;
+}
+
+gin::Dictionary WriteData(const gin::Arguments& args,
+ mojo::Handle handle,
+ const gin::ArrayBufferView& buffer,
+ MojoWriteDataFlags flags) {
+ uint32_t num_bytes = static_cast<uint32_t>(buffer.num_bytes());
+ MojoResult result =
+ MojoWriteData(handle.value(), buffer.bytes(), &num_bytes, flags);
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", result);
+ dictionary.Set("numBytes", num_bytes);
+ return dictionary;
+}
+
+gin::Dictionary ReadData(const gin::Arguments& args,
+ mojo::Handle handle,
+ MojoReadDataFlags flags) {
+ uint32_t num_bytes = 0;
+ MojoResult result = MojoReadData(
+ handle.value(), NULL, &num_bytes, MOJO_READ_DATA_FLAG_QUERY);
+ if (result != MOJO_RESULT_OK) {
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", result);
+ return dictionary;
+ }
+
+ v8::Handle<v8::ArrayBuffer> array_buffer =
+ v8::ArrayBuffer::New(args.isolate(), num_bytes);
+ gin::ArrayBuffer buffer;
+ ConvertFromV8(args.isolate(), array_buffer, &buffer);
+ CHECK_EQ(num_bytes, buffer.num_bytes());
+
+ result = MojoReadData(handle.value(), buffer.bytes(), &num_bytes, flags);
+ CHECK_EQ(num_bytes, buffer.num_bytes());
+
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+ dictionary.Set("result", result);
+ dictionary.Set("buffer", array_buffer);
+ return dictionary;
+}
+
+// Asynchronously read all of the data available for the specified data pipe
+// consumer handle until the remote handle is closed or an error occurs. A
+// Promise is returned whose settled value is an object like this:
+// {result: core.RESULT_OK, buffer: dataArrayBuffer}. If the read failed,
+// then the Promise is rejected, the result will be the actual error code,
+// and the buffer will contain whatever was read before the error occurred.
+// The drainData data pipe handle argument is closed automatically.
+
+v8::Handle<v8::Value> DoDrainData(gin::Arguments* args,
+ gin::Handle<HandleWrapper> handle) {
+ return (new DrainData(args->isolate(), handle->release()))->GetPromise();
+}
+
+bool IsHandle(gin::Arguments* args, v8::Handle<v8::Value> val) {
+ gin::Handle<mojo::edk::HandleWrapper> ignore_handle;
+ return gin::Converter<gin::Handle<mojo::edk::HandleWrapper>>::FromV8(
+ args->isolate(), val, &ignore_handle);
+}
+
+
+gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin };
+
+} // namespace
+
+const char Core::kModuleName[] = "mojo/public/js/core";
+
+v8::Local<v8::Value> Core::GetModule(v8::Isolate* isolate) {
+ gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
+ v8::Local<v8::ObjectTemplate> templ = data->GetObjectTemplate(
+ &g_wrapper_info);
+
+ if (templ.IsEmpty()) {
+ templ =
+ gin::ObjectTemplateBuilder(isolate)
+ // TODO(mpcomplete): Should these just be methods on the JS Handle
+ // object?
+ .SetMethod("close", CloseHandle)
+ .SetMethod("wait", WaitHandle)
+ .SetMethod("waitMany", WaitMany)
+ .SetMethod("createMessagePipe", CreateMessagePipe)
+ .SetMethod("writeMessage", WriteMessage)
+ .SetMethod("readMessage", ReadMessage)
+ .SetMethod("createDataPipe", CreateDataPipe)
+ .SetMethod("writeData", WriteData)
+ .SetMethod("readData", ReadData)
+ .SetMethod("drainData", DoDrainData)
+ .SetMethod("isHandle", IsHandle)
+
+ .SetValue("RESULT_OK", MOJO_RESULT_OK)
+ .SetValue("RESULT_CANCELLED", MOJO_RESULT_CANCELLED)
+ .SetValue("RESULT_UNKNOWN", MOJO_RESULT_UNKNOWN)
+ .SetValue("RESULT_INVALID_ARGUMENT", MOJO_RESULT_INVALID_ARGUMENT)
+ .SetValue("RESULT_DEADLINE_EXCEEDED", MOJO_RESULT_DEADLINE_EXCEEDED)
+ .SetValue("RESULT_NOT_FOUND", MOJO_RESULT_NOT_FOUND)
+ .SetValue("RESULT_ALREADY_EXISTS", MOJO_RESULT_ALREADY_EXISTS)
+ .SetValue("RESULT_PERMISSION_DENIED", MOJO_RESULT_PERMISSION_DENIED)
+ .SetValue("RESULT_RESOURCE_EXHAUSTED",
+ MOJO_RESULT_RESOURCE_EXHAUSTED)
+ .SetValue("RESULT_FAILED_PRECONDITION",
+ MOJO_RESULT_FAILED_PRECONDITION)
+ .SetValue("RESULT_ABORTED", MOJO_RESULT_ABORTED)
+ .SetValue("RESULT_OUT_OF_RANGE", MOJO_RESULT_OUT_OF_RANGE)
+ .SetValue("RESULT_UNIMPLEMENTED", MOJO_RESULT_UNIMPLEMENTED)
+ .SetValue("RESULT_INTERNAL", MOJO_RESULT_INTERNAL)
+ .SetValue("RESULT_UNAVAILABLE", MOJO_RESULT_UNAVAILABLE)
+ .SetValue("RESULT_DATA_LOSS", MOJO_RESULT_DATA_LOSS)
+ .SetValue("RESULT_BUSY", MOJO_RESULT_BUSY)
+ .SetValue("RESULT_SHOULD_WAIT", MOJO_RESULT_SHOULD_WAIT)
+
+ .SetValue("DEADLINE_INDEFINITE", MOJO_DEADLINE_INDEFINITE)
+
+ .SetValue("HANDLE_SIGNAL_NONE", MOJO_HANDLE_SIGNAL_NONE)
+ .SetValue("HANDLE_SIGNAL_READABLE", MOJO_HANDLE_SIGNAL_READABLE)
+ .SetValue("HANDLE_SIGNAL_WRITABLE", MOJO_HANDLE_SIGNAL_WRITABLE)
+ .SetValue("HANDLE_SIGNAL_PEER_CLOSED",
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED)
+
+ .SetValue("CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE",
+ MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE)
+
+ .SetValue("WRITE_MESSAGE_FLAG_NONE", MOJO_WRITE_MESSAGE_FLAG_NONE)
+
+ .SetValue("READ_MESSAGE_FLAG_NONE", MOJO_READ_MESSAGE_FLAG_NONE)
+ .SetValue("READ_MESSAGE_FLAG_MAY_DISCARD",
+ MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)
+
+ .SetValue("CREATE_DATA_PIPE_OPTIONS_FLAG_NONE",
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE)
+
+ .SetValue("WRITE_DATA_FLAG_NONE", MOJO_WRITE_DATA_FLAG_NONE)
+ .SetValue("WRITE_DATA_FLAG_ALL_OR_NONE",
+ MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)
+
+ .SetValue("READ_DATA_FLAG_NONE", MOJO_READ_DATA_FLAG_NONE)
+ .SetValue("READ_DATA_FLAG_ALL_OR_NONE",
+ MOJO_READ_DATA_FLAG_ALL_OR_NONE)
+ .SetValue("READ_DATA_FLAG_DISCARD", MOJO_READ_DATA_FLAG_DISCARD)
+ .SetValue("READ_DATA_FLAG_QUERY", MOJO_READ_DATA_FLAG_QUERY)
+ .SetValue("READ_DATA_FLAG_PEEK", MOJO_READ_DATA_FLAG_PEEK)
+ .Build();
+
+ data->SetObjectTemplate(&g_wrapper_info, templ);
+ }
+
+ return templ->NewInstance();
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/js/core.h b/mojo/edk/js/core.h
new file mode 100644
index 0000000..c13b2da
--- /dev/null
+++ b/mojo/edk/js/core.h
@@ -0,0 +1,22 @@
+// Copyright 2014 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 MOJO_EDK_JS_CORE_H_
+#define MOJO_EDK_JS_CORE_H_
+
+#include "v8/include/v8.h"
+
+namespace mojo {
+namespace edk {
+
+class Core {
+ public:
+ static const char kModuleName[];
+ static v8::Local<v8::Value> GetModule(v8::Isolate* isolate);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_JS_CORE_H_
diff --git a/mojo/edk/js/drain_data.cc b/mojo/edk/js/drain_data.cc
new file mode 100644
index 0000000..d8fa091
--- /dev/null
+++ b/mojo/edk/js/drain_data.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 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 "mojo/edk/js/drain_data.h"
+
+#include "gin/array_buffer.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+#include "gin/per_context_data.h"
+#include "gin/per_isolate_data.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace edk {
+
+DrainData::DrainData(v8::Isolate* isolate, mojo::Handle handle)
+ : isolate_(isolate),
+ handle_(DataPipeConsumerHandle(handle.value())),
+ wait_id_(0) {
+
+ v8::Handle<v8::Context> context(isolate_->GetCurrentContext());
+ runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr();
+
+ WaitForData();
+}
+
+v8::Handle<v8::Value> DrainData::GetPromise() {
+ CHECK(resolver_.IsEmpty());
+ v8::Handle<v8::Promise::Resolver> resolver(
+ v8::Promise::Resolver::New(isolate_));
+ resolver_.Reset(isolate_, resolver);
+ return resolver->GetPromise();
+}
+
+DrainData::~DrainData() {
+ if (wait_id_)
+ Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
+ resolver_.Reset();
+}
+
+void DrainData::WaitForData() {
+ wait_id_ = Environment::GetDefaultAsyncWaiter()->AsyncWait(
+ handle_.get().value(),
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE,
+ &DrainData::WaitCompleted,
+ this);
+}
+
+void DrainData::DataReady(MojoResult result) {
+ wait_id_ = 0;
+ if (result != MOJO_RESULT_OK) {
+ DeliverData(result);
+ return;
+ }
+ while (result == MOJO_RESULT_OK) {
+ result = ReadData();
+ if (result == MOJO_RESULT_SHOULD_WAIT)
+ WaitForData();
+ else if (result != MOJO_RESULT_OK)
+ DeliverData(result);
+ }
+}
+
+MojoResult DrainData::ReadData() {
+ const void* buffer;
+ uint32_t num_bytes = 0;
+ MojoResult result = BeginReadDataRaw(
+ handle_.get(), &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+ if (result != MOJO_RESULT_OK)
+ return result;
+ const char* p = static_cast<const char*>(buffer);
+ DataBuffer* data_buffer = new DataBuffer(p, p + num_bytes);
+ data_buffers_.push_back(data_buffer);
+ return EndReadDataRaw(handle_.get(), num_bytes);
+}
+
+void DrainData::DeliverData(MojoResult result) {
+ if (!runner_) {
+ delete this;
+ return;
+ }
+
+ size_t total_bytes = 0;
+ for (unsigned i = 0; i < data_buffers_.size(); i++)
+ total_bytes += data_buffers_[i]->size();
+
+ // Create a total_bytes length ArrayBuffer return value.
+ gin::Runner::Scope scope(runner_.get());
+ v8::Handle<v8::ArrayBuffer> array_buffer =
+ v8::ArrayBuffer::New(isolate_, total_bytes);
+ gin::ArrayBuffer buffer;
+ ConvertFromV8(isolate_, array_buffer, &buffer);
+ CHECK_EQ(total_bytes, buffer.num_bytes());
+
+ // Copy the data_buffers into the ArrayBuffer.
+ char* array_buffer_ptr = static_cast<char*>(buffer.bytes());
+ size_t offset = 0;
+ for (size_t i = 0; i < data_buffers_.size(); i++) {
+ size_t num_bytes = data_buffers_[i]->size();
+ if (num_bytes == 0)
+ continue;
+ const char* data_buffer_ptr = &((*data_buffers_[i])[0]);
+ memcpy(array_buffer_ptr + offset, data_buffer_ptr, num_bytes);
+ offset += num_bytes;
+ }
+
+ // The "settled" value of the promise always includes all of the data
+ // that was read before either an error occurred or the remote pipe handle
+ // was closed. The latter is indicated by MOJO_RESULT_FAILED_PRECONDITION.
+
+ v8::Handle<v8::Promise::Resolver> resolver(
+ v8::Local<v8::Promise::Resolver>::New(isolate_, resolver_));
+
+ gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate_);
+ dictionary.Set("result", result);
+ dictionary.Set("buffer", array_buffer);
+ v8::Handle<v8::Value> settled_value(ConvertToV8(isolate_, dictionary));
+
+ if (result == MOJO_RESULT_FAILED_PRECONDITION)
+ resolver->Resolve(settled_value);
+ else
+ resolver->Reject(settled_value);
+
+ delete this;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/js/drain_data.h b/mojo/edk/js/drain_data.h
new file mode 100644
index 0000000..63c9e49
--- /dev/null
+++ b/mojo/edk/js/drain_data.h
@@ -0,0 +1,64 @@
+// Copyright 2014 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 MOJO_EDK_JS_DRAIN_DATA_H_
+#define MOJO_EDK_JS_DRAIN_DATA_H_
+
+#include "base/memory/scoped_vector.h"
+#include "gin/runner.h"
+#include "mojo/public/c/environment/async_waiter.h"
+#include "mojo/public/cpp/system/core.h"
+#include "v8/include/v8.h"
+
+namespace mojo {
+namespace edk {
+
+// This class is the implementation of the Mojo JavaScript core module's
+// drainData() method. It is not intended to be used directly. The caller
+// allocates a DrainData on the heap and returns GetPromise() to JS. The
+// implementation deletes itself after reading as much data as possible
+// and rejecting or resolving the Promise.
+
+class DrainData {
+ public:
+ // Starts waiting for data on the specified data pipe consumer handle.
+ // See WaitForData(). The constructor does not block.
+ DrainData(v8::Isolate* isolate, mojo::Handle handle);
+
+ // Returns a Promise that will be settled when no more data can be read.
+ // Should be called just once on a newly allocated DrainData object.
+ v8::Handle<v8::Value> GetPromise();
+
+ private:
+ ~DrainData();
+
+ // Registers an "async waiter" that calls DataReady() via WaitCompleted().
+ void WaitForData();
+ static void WaitCompleted(void* self, MojoResult result) {
+ static_cast<DrainData*>(self)->DataReady(result);
+ }
+
+ // Use ReadData() to read whatever is availble now on handle_ and save
+ // it in data_buffers_.
+ void DataReady(MojoResult result);
+ MojoResult ReadData();
+
+ // When the remote data pipe handle is closed, or an error occurs, deliver
+ // all of the buffered data to the JS Promise and then delete this.
+ void DeliverData(MojoResult result);
+
+ using DataBuffer = std::vector<char>;
+
+ v8::Isolate* isolate_;
+ ScopedDataPipeConsumerHandle handle_;
+ MojoAsyncWaitID wait_id_;
+ base::WeakPtr<gin::Runner> runner_;
+ v8::UniquePersistent<v8::Promise::Resolver> resolver_;
+ ScopedVector<DataBuffer> data_buffers_;
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_JS_DRAIN_DATA_H_
diff --git a/mojo/edk/js/handle.cc b/mojo/edk/js/handle.cc
new file mode 100644
index 0000000..c465f46
--- /dev/null
+++ b/mojo/edk/js/handle.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 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 "mojo/edk/js/handle.h"
+
+#include "mojo/edk/js/handle_close_observer.h"
+
+namespace mojo {
+namespace edk {
+
+gin::WrapperInfo HandleWrapper::kWrapperInfo = { gin::kEmbedderNativeGin };
+
+HandleWrapper::HandleWrapper(MojoHandle handle)
+ : handle_(mojo::Handle(handle)) {
+}
+
+HandleWrapper::~HandleWrapper() {
+ NotifyCloseObservers();
+}
+
+void HandleWrapper::Close() {
+ NotifyCloseObservers();
+ handle_.reset();
+}
+
+void HandleWrapper::AddCloseObserver(HandleCloseObserver* observer) {
+ close_observers_.AddObserver(observer);
+}
+
+void HandleWrapper::RemoveCloseObserver(HandleCloseObserver* observer) {
+ close_observers_.RemoveObserver(observer);
+}
+
+void HandleWrapper::NotifyCloseObservers() {
+ if (!handle_.is_valid())
+ return;
+
+ FOR_EACH_OBSERVER(HandleCloseObserver, close_observers_, OnWillCloseHandle());
+}
+
+} // namespace edk
+} // namespace mojo
+
+namespace gin {
+
+v8::Handle<v8::Value> Converter<mojo::Handle>::ToV8(v8::Isolate* isolate,
+ const mojo::Handle& val) {
+ if (!val.is_valid())
+ return v8::Null(isolate);
+ return mojo::edk::HandleWrapper::Create(isolate, val.value()).ToV8();
+}
+
+bool Converter<mojo::Handle>::FromV8(v8::Isolate* isolate,
+ v8::Handle<v8::Value> val,
+ mojo::Handle* out) {
+ if (val->IsNull()) {
+ *out = mojo::Handle();
+ return true;
+ }
+
+ gin::Handle<mojo::edk::HandleWrapper> handle;
+ if (!Converter<gin::Handle<mojo::edk::HandleWrapper> >::FromV8(
+ isolate, val, &handle))
+ return false;
+
+ *out = handle->get();
+ return true;
+}
+
+v8::Handle<v8::Value> Converter<mojo::MessagePipeHandle>::ToV8(
+ v8::Isolate* isolate, mojo::MessagePipeHandle val) {
+ return Converter<mojo::Handle>::ToV8(isolate, val);
+}
+
+bool Converter<mojo::MessagePipeHandle>::FromV8(v8::Isolate* isolate,
+ v8::Handle<v8::Value> val,
+ mojo::MessagePipeHandle* out) {
+ return Converter<mojo::Handle>::FromV8(isolate, val, out);
+}
+
+
+} // namespace gin
diff --git a/mojo/edk/js/handle.h b/mojo/edk/js/handle.h
new file mode 100644
index 0000000..9fa92b0
--- /dev/null
+++ b/mojo/edk/js/handle.h
@@ -0,0 +1,99 @@
+// Copyright 2014 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 MOJO_EDK_JS_HANDLE_H_
+#define MOJO_EDK_JS_HANDLE_H_
+
+#include "base/observer_list.h"
+#include "gin/converter.h"
+#include "gin/handle.h"
+#include "gin/wrappable.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace edk {
+class HandleCloseObserver;
+
+// Wrapper for mojo Handles exposed to JavaScript. This ensures the Handle
+// is Closed when its JS object is garbage collected.
+class HandleWrapper : public gin::Wrappable<HandleWrapper> {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ static gin::Handle<HandleWrapper> Create(v8::Isolate* isolate,
+ MojoHandle handle) {
+ return gin::CreateHandle(isolate, new HandleWrapper(handle));
+ }
+
+ mojo::Handle get() const { return handle_.get(); }
+ mojo::Handle release() { return handle_.release(); }
+ void Close();
+
+ void AddCloseObserver(HandleCloseObserver* observer);
+ void RemoveCloseObserver(HandleCloseObserver* observer);
+
+ protected:
+ HandleWrapper(MojoHandle handle);
+ ~HandleWrapper() override;
+ void NotifyCloseObservers();
+
+ mojo::ScopedHandle handle_;
+ base::ObserverList<HandleCloseObserver> close_observers_;
+};
+
+} // namespace edk
+} // namespace mojo
+
+namespace gin {
+
+// Note: It's important to use this converter rather than the one for
+// MojoHandle, since that will do a simple int32 conversion. It's unfortunate
+// there's no way to prevent against accidental use.
+// TODO(mpcomplete): define converters for all Handle subtypes.
+template<>
+struct Converter<mojo::Handle> {
+ static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
+ const mojo::Handle& val);
+ static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val,
+ mojo::Handle* out);
+};
+
+template<>
+struct Converter<mojo::MessagePipeHandle> {
+ static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
+ mojo::MessagePipeHandle val);
+ static bool FromV8(v8::Isolate* isolate,
+ v8::Handle<v8::Value> val,
+ mojo::MessagePipeHandle* out);
+};
+
+// We need to specialize the normal gin::Handle converter in order to handle
+// converting |null| to a wrapper for an empty mojo::Handle.
+template<>
+struct Converter<gin::Handle<mojo::edk::HandleWrapper> > {
+ static v8::Handle<v8::Value> ToV8(
+ v8::Isolate* isolate,
+ const gin::Handle<mojo::edk::HandleWrapper>& val) {
+ return val.ToV8();
+ }
+
+ static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val,
+ gin::Handle<mojo::edk::HandleWrapper>* out) {
+ if (val->IsNull()) {
+ *out = mojo::edk::HandleWrapper::Create(isolate, MOJO_HANDLE_INVALID);
+ return true;
+ }
+
+ mojo::edk::HandleWrapper* object = NULL;
+ if (!Converter<mojo::edk::HandleWrapper*>::FromV8(isolate, val, &object)) {
+ return false;
+ }
+ *out = gin::Handle<mojo::edk::HandleWrapper>(val, object);
+ return true;
+ }
+};
+
+} // namespace gin
+
+#endif // MOJO_EDK_JS_HANDLE_H_
diff --git a/mojo/edk/js/handle_close_observer.h b/mojo/edk/js/handle_close_observer.h
new file mode 100644
index 0000000..b507350
--- /dev/null
+++ b/mojo/edk/js/handle_close_observer.h
@@ -0,0 +1,22 @@
+// Copyright 2014 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 MOJO_EDK_JS_HANDLE_CLOSE_OBSERVER_H_
+#define MOJO_EDK_JS_HANDLE_CLOSE_OBSERVER_H_
+
+namespace mojo {
+namespace edk {
+
+class HandleCloseObserver {
+ public:
+ virtual void OnWillCloseHandle() = 0;
+
+ protected:
+ virtual ~HandleCloseObserver() {}
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_JS_HANDLE_CLOSE_OBSERVER_H_
diff --git a/mojo/edk/js/handle_unittest.cc b/mojo/edk/js/handle_unittest.cc
new file mode 100644
index 0000000..6ee4444
--- /dev/null
+++ b/mojo/edk/js/handle_unittest.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 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/macros.h"
+#include "mojo/edk/js/handle.h"
+#include "mojo/edk/js/handle_close_observer.h"
+#include "mojo/public/cpp/system/core.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+
+class HandleWrapperTest : public testing::Test,
+ public HandleCloseObserver {
+ public:
+ HandleWrapperTest() : closes_observed_(0) {}
+
+ void OnWillCloseHandle() override { closes_observed_++; }
+
+ protected:
+ int closes_observed_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HandleWrapperTest);
+};
+
+class TestHandleWrapper : public HandleWrapper {
+ public:
+ explicit TestHandleWrapper(MojoHandle handle) : HandleWrapper(handle) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestHandleWrapper);
+};
+
+// Test that calling Close() on a HandleWrapper for an invalid handle does not
+// notify observers.
+TEST_F(HandleWrapperTest, CloseWithInvalidHandle) {
+ {
+ TestHandleWrapper wrapper(MOJO_HANDLE_INVALID);
+ wrapper.AddCloseObserver(this);
+ ASSERT_EQ(0, closes_observed_);
+ wrapper.Close();
+ EXPECT_EQ(0, closes_observed_);
+ }
+ EXPECT_EQ(0, closes_observed_);
+}
+
+// Test that destroying a HandleWrapper for an invalid handle does not notify
+// observers.
+TEST_F(HandleWrapperTest, DestroyWithInvalidHandle) {
+ {
+ TestHandleWrapper wrapper(MOJO_HANDLE_INVALID);
+ wrapper.AddCloseObserver(this);
+ ASSERT_EQ(0, closes_observed_);
+ }
+ EXPECT_EQ(0, closes_observed_);
+}
+
+// Test that calling Close on a HandleWrapper for a valid handle notifies
+// observers once.
+TEST_F(HandleWrapperTest, CloseWithValidHandle) {
+ {
+ mojo::MessagePipe pipe;
+ TestHandleWrapper wrapper(pipe.handle0.release().value());
+ wrapper.AddCloseObserver(this);
+ ASSERT_EQ(0, closes_observed_);
+ wrapper.Close();
+ EXPECT_EQ(1, closes_observed_);
+ // Check that calling close again doesn't notify observers.
+ wrapper.Close();
+ EXPECT_EQ(1, closes_observed_);
+ }
+ // Check that destroying a closed HandleWrapper doesn't notify observers.
+ EXPECT_EQ(1, closes_observed_);
+}
+
+// Test that destroying a HandleWrapper for a valid handle notifies observers.
+TEST_F(HandleWrapperTest, DestroyWithValidHandle) {
+ {
+ mojo::MessagePipe pipe;
+ TestHandleWrapper wrapper(pipe.handle0.release().value());
+ wrapper.AddCloseObserver(this);
+ ASSERT_EQ(0, closes_observed_);
+ }
+ EXPECT_EQ(1, closes_observed_);
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/js/mojo_runner_delegate.cc b/mojo/edk/js/mojo_runner_delegate.cc
new file mode 100644
index 0000000..9b18b55
--- /dev/null
+++ b/mojo/edk/js/mojo_runner_delegate.cc
@@ -0,0 +1,78 @@
+// 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 "mojo/edk/js/mojo_runner_delegate.h"
+
+#include "base/bind.h"
+#include "base/path_service.h"
+#include "gin/converter.h"
+#include "gin/modules/console.h"
+#include "gin/modules/module_registry.h"
+#include "gin/modules/timer.h"
+#include "gin/try_catch.h"
+#include "mojo/edk/js/core.h"
+#include "mojo/edk/js/handle.h"
+#include "mojo/edk/js/support.h"
+#include "mojo/edk/js/threading.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+// TODO(abarth): Rather than loading these modules from the file system, we
+// should load them from the network via Mojo IPC.
+std::vector<base::FilePath> GetModuleSearchPaths() {
+ std::vector<base::FilePath> search_paths(2);
+ PathService::Get(base::DIR_SOURCE_ROOT, &search_paths[0]);
+ PathService::Get(base::DIR_EXE, &search_paths[1]);
+ search_paths[1] = search_paths[1].AppendASCII("gen");
+ return search_paths;
+}
+
+void StartCallback(base::WeakPtr<gin::Runner> runner,
+ MojoHandle pipe,
+ v8::Handle<v8::Value> module) {
+ v8::Isolate* isolate = runner->GetContextHolder()->isolate();
+ v8::Handle<v8::Function> start;
+ CHECK(gin::ConvertFromV8(isolate, module, &start));
+
+ v8::Handle<v8::Value> args[] = {
+ gin::ConvertToV8(isolate, Handle(pipe)) };
+ runner->Call(start, runner->global(), 1, args);
+}
+
+} // namespace
+
+MojoRunnerDelegate::MojoRunnerDelegate()
+ : ModuleRunnerDelegate(GetModuleSearchPaths()) {
+ AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule);
+ AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule);
+ AddBuiltinModule(Core::kModuleName, Core::GetModule);
+ AddBuiltinModule(Support::kModuleName, Support::GetModule);
+ AddBuiltinModule(Threading::kModuleName, Threading::GetModule);
+}
+
+MojoRunnerDelegate::~MojoRunnerDelegate() {
+}
+
+void MojoRunnerDelegate::Start(gin::Runner* runner,
+ MojoHandle pipe,
+ const std::string& module) {
+ gin::Runner::Scope scope(runner);
+ gin::ModuleRegistry* registry =
+ gin::ModuleRegistry::From(runner->GetContextHolder()->context());
+ registry->LoadModule(runner->GetContextHolder()->isolate(), module,
+ base::Bind(StartCallback, runner->GetWeakPtr(), pipe));
+ AttemptToLoadMoreModules(runner);
+}
+
+void MojoRunnerDelegate::UnhandledException(gin::ShellRunner* runner,
+ gin::TryCatch& try_catch) {
+ gin::ModuleRunnerDelegate::UnhandledException(runner, try_catch);
+ LOG(ERROR) << try_catch.GetStackTrace();
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/js/mojo_runner_delegate.h b/mojo/edk/js/mojo_runner_delegate.h
new file mode 100644
index 0000000..7b50b30
--- /dev/null
+++ b/mojo/edk/js/mojo_runner_delegate.h
@@ -0,0 +1,33 @@
+// 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 MOJO_EDK_JS_MOJO_RUNNER_DELEGATE_H_
+#define MOJO_EDK_JS_MOJO_RUNNER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "gin/modules/module_runner_delegate.h"
+#include "mojo/public/c/system/core.h"
+
+namespace mojo {
+namespace edk {
+
+class MojoRunnerDelegate : public gin::ModuleRunnerDelegate {
+ public:
+ MojoRunnerDelegate();
+ ~MojoRunnerDelegate() override;
+
+ void Start(gin::Runner* runner, MojoHandle pipe, const std::string& module);
+
+ private:
+ // From ModuleRunnerDelegate:
+ void UnhandledException(gin::ShellRunner* runner,
+ gin::TryCatch& try_catch) override;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoRunnerDelegate);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_JS_MOJO_RUNNER_DELEGATE_H_
diff --git a/mojo/edk/js/support.cc b/mojo/edk/js/support.cc
new file mode 100644
index 0000000..3e4391c
--- /dev/null
+++ b/mojo/edk/js/support.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 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 "mojo/edk/js/support.h"
+
+#include "base/bind.h"
+#include "gin/arguments.h"
+#include "gin/converter.h"
+#include "gin/function_template.h"
+#include "gin/object_template_builder.h"
+#include "gin/per_isolate_data.h"
+#include "gin/public/wrapper_info.h"
+#include "gin/wrappable.h"
+#include "mojo/edk/js/handle.h"
+#include "mojo/edk/js/waiting_callback.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+WaitingCallback* AsyncWait(const gin::Arguments& args,
+ gin::Handle<HandleWrapper> handle,
+ MojoHandleSignals signals,
+ v8::Handle<v8::Function> callback) {
+ return WaitingCallback::Create(args.isolate(), callback, handle, signals)
+ .get();
+}
+
+void CancelWait(WaitingCallback* waiting_callback) {
+ waiting_callback->Cancel();
+}
+
+gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin };
+
+} // namespace
+
+const char Support::kModuleName[] = "mojo/public/js/support";
+
+v8::Local<v8::Value> Support::GetModule(v8::Isolate* isolate) {
+ gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
+ v8::Local<v8::ObjectTemplate> templ = data->GetObjectTemplate(
+ &g_wrapper_info);
+
+ if (templ.IsEmpty()) {
+ templ = gin::ObjectTemplateBuilder(isolate)
+ .SetMethod("asyncWait", AsyncWait)
+ .SetMethod("cancelWait", CancelWait)
+ .Build();
+
+ data->SetObjectTemplate(&g_wrapper_info, templ);
+ }
+
+ return templ->NewInstance();
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/js/support.h b/mojo/edk/js/support.h
new file mode 100644
index 0000000..1edc00c
--- /dev/null
+++ b/mojo/edk/js/support.h
@@ -0,0 +1,22 @@
+// Copyright 2014 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 MOJO_EDK_JS_SUPPORT_H_
+#define MOJO_EDK_JS_SUPPORT_H_
+
+#include "v8/include/v8.h"
+
+namespace mojo {
+namespace edk {
+
+class Support {
+ public:
+ static const char kModuleName[];
+ static v8::Local<v8::Value> GetModule(v8::Isolate* isolate);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_JS_SUPPORT_H_
diff --git a/mojo/edk/js/test/BUILD.gn b/mojo/edk/js/test/BUILD.gn
new file mode 100644
index 0000000..86390ba
--- /dev/null
+++ b/mojo/edk/js/test/BUILD.gn
@@ -0,0 +1,46 @@
+# Copyright 2014 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.
+
+import("//testing/test.gni")
+
+# TODO(use_chrome_edk): remove "2"
+test("js_unittests2") {
+ deps = [
+ "../../js",
+ "../../js:js_unittests",
+ "../../test:run_all_unittests",
+ "../../test:test_support",
+ "//base",
+ "//gin:gin_test",
+ "//mojo/environment:chromium",
+ "//third_party/mojo/src/mojo/public/cpp/environment",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ "//third_party/mojo/src/mojo/public/cpp/utility",
+ "//third_party/mojo/src/mojo/public/interfaces/bindings/tests:test_interfaces",
+ "//third_party/mojo/src/mojo/public/interfaces/bindings/tests:test_interfaces_experimental",
+ ]
+
+ sources = [
+ "run_js_tests.cc",
+ ]
+}
+
+# TODO(use_chrome_edk): remove "2"
+test("js_integration_tests2") {
+ deps = [
+ "../../js",
+ "../../js/tests:js_to_cpp_tests",
+ "../../test:run_all_unittests",
+ "../../test:test_support",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo/src/mojo/public/interfaces/bindings/tests:test_interfaces",
+ "//base",
+ "//gin:gin_test",
+ "//mojo/environment:chromium",
+ ]
+
+ sources = [
+ "run_js_integration_tests.cc",
+ ]
+}
diff --git a/mojo/edk/js/test/hexdump.js b/mojo/edk/js/test/hexdump.js
new file mode 100644
index 0000000..b36c47f
--- /dev/null
+++ b/mojo/edk/js/test/hexdump.js
@@ -0,0 +1,34 @@
+// 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.
+
+define(function() {
+ function hexify(value, length) {
+ var hex = value.toString(16);
+ while (hex.length < length)
+ hex = "0" + hex;
+ return hex;
+ }
+
+ function dumpArray(bytes) {
+ var dumped = "";
+ for (var i = 0; i < bytes.length; ++i) {
+ dumped += hexify(bytes[i], 2);
+
+ if (i % 16 == 15) {
+ dumped += "\n";
+ continue;
+ }
+
+ if (i % 2 == 1)
+ dumped += " ";
+ if (i % 8 == 7)
+ dumped += " ";
+ }
+ return dumped;
+ }
+
+ var exports = {};
+ exports.dumpArray = dumpArray;
+ return exports;
+});
diff --git a/mojo/edk/js/test/run_js_integration_tests.cc b/mojo/edk/js/test/run_js_integration_tests.cc
new file mode 100644
index 0000000..2512881
--- /dev/null
+++ b/mojo/edk/js/test/run_js_integration_tests.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 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/files/file_path.h"
+#include "base/path_service.h"
+#include "gin/modules/console.h"
+#include "gin/modules/module_registry.h"
+#include "gin/modules/timer.h"
+#include "gin/test/file_runner.h"
+#include "gin/test/gtest.h"
+#include "mojo/edk/js/core.h"
+#include "mojo/edk/js/support.h"
+#include "mojo/edk/js/threading.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+class TestRunnerDelegate : public gin::FileRunnerDelegate {
+ public:
+ TestRunnerDelegate() {
+ AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule);
+ AddBuiltinModule(Core::kModuleName, Core::GetModule);
+ AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule);
+ AddBuiltinModule(Threading::kModuleName, Threading::GetModule);
+ }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestRunnerDelegate);
+};
+
+void RunTest(std::string test, bool addSupportModule) {
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("mojo")
+ .AppendASCII("edk")
+ .AppendASCII("js")
+ .AppendASCII("tests")
+ .AppendASCII(test);
+ TestRunnerDelegate delegate;
+ if (addSupportModule)
+ delegate.AddBuiltinModule(Support::kModuleName, Support::GetModule);
+ gin::RunTestFromFile(path, &delegate, true);
+}
+
+TEST(JSTest, connection) {
+ RunTest("connection_tests.js", false);
+}
+
+TEST(JSTest, sample_service) {
+ RunTest("sample_service_tests.js", true);
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/js/test/run_js_tests.cc b/mojo/edk/js/test/run_js_tests.cc
new file mode 100644
index 0000000..76de3e6
--- /dev/null
+++ b/mojo/edk/js/test/run_js_tests.cc
@@ -0,0 +1,66 @@
+// Copyright 2014 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/files/file_path.h"
+#include "base/path_service.h"
+#include "gin/modules/console.h"
+#include "gin/modules/module_registry.h"
+#include "gin/test/file_runner.h"
+#include "gin/test/gtest.h"
+#include "mojo/edk/js/core.h"
+#include "mojo/edk/js/support.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+class TestRunnerDelegate : public gin::FileRunnerDelegate {
+ public:
+ TestRunnerDelegate() {
+ AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule);
+ AddBuiltinModule(Core::kModuleName, Core::GetModule);
+ AddBuiltinModule(Support::kModuleName, Support::GetModule);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestRunnerDelegate);
+};
+
+void RunTest(std::string test, bool run_until_idle) {
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("mojo")
+ .AppendASCII("public")
+ .AppendASCII("js")
+ .AppendASCII(test);
+ TestRunnerDelegate delegate;
+ gin::RunTestFromFile(path, &delegate, run_until_idle);
+}
+
+// TODO(abarth): Should we autogenerate these stubs from GYP?
+TEST(JSTest, core) {
+ RunTest("core_unittests.js", true);
+}
+
+TEST(JSTest, codec) {
+ RunTest("codec_unittests.js", true);
+}
+
+TEST(JSTest, struct) {
+ RunTest("struct_unittests.js", true);
+}
+
+TEST(JSTest, union) {
+ RunTest("union_unittests.js", true);
+}
+
+TEST(JSTest, validation) {
+ RunTest("validation_unittests.js", true);
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/js/tests/BUILD.gn b/mojo/edk/js/tests/BUILD.gn
new file mode 100644
index 0000000..a6be00f
--- /dev/null
+++ b/mojo/edk/js/tests/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright 2014 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.
+
+import("../../mojo_edk.gni")
+import("../../../../third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
+
+mojo_edk_source_set("js_to_cpp_tests") {
+ testonly = true
+
+ deps = [
+ ":js_to_cpp_bindings",
+ "//gin:gin_test",
+ "//mojo/edk/js",
+ "//mojo/edk/test:test_support",
+ "//third_party/mojo/src/mojo/public/cpp/bindings",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ "//third_party/mojo/src/mojo/public/interfaces/bindings/tests:test_interfaces",
+ "//third_party/mojo/src/mojo/public/interfaces/bindings/tests:test_interfaces_experimental",
+ ]
+
+ sources = [
+ "js_to_cpp_tests.cc",
+ ]
+}
+
+mojom("js_to_cpp_bindings") {
+ sources = [
+ "js_to_cpp.mojom",
+ ]
+}
diff --git a/mojo/edk/js/tests/connection_tests.js b/mojo/edk/js/tests/connection_tests.js
new file mode 100644
index 0000000..ff59aeb
--- /dev/null
+++ b/mojo/edk/js/tests/connection_tests.js
@@ -0,0 +1,240 @@
+// 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.
+
+// Mock out the support module to avoid depending on the message loop.
+define("mojo/public/js/support", ["timer"], function(timer) {
+ var waitingCallbacks = [];
+
+ function WaitCookie(id) {
+ this.id = id;
+ }
+
+ function asyncWait(handle, flags, callback) {
+ var id = waitingCallbacks.length;
+ waitingCallbacks.push(callback);
+ return new WaitCookie(id);
+ }
+
+ function cancelWait(cookie) {
+ waitingCallbacks[cookie.id] = null;
+ }
+
+ function numberOfWaitingCallbacks() {
+ var count = 0;
+ for (var i = 0; i < waitingCallbacks.length; ++i) {
+ if (waitingCallbacks[i])
+ ++count;
+ }
+ return count;
+ }
+
+ function pumpOnce(result) {
+ var callbacks = waitingCallbacks;
+ waitingCallbacks = [];
+ for (var i = 0; i < callbacks.length; ++i) {
+ if (callbacks[i])
+ callbacks[i](result);
+ }
+ }
+
+ // Queue up a pumpOnce call to execute after the stack unwinds. Use
+ // this to trigger a pump after all Promises are executed.
+ function queuePump(result) {
+ timer.createOneShot(0, pumpOnce.bind(undefined, result));
+ }
+
+ var exports = {};
+ exports.asyncWait = asyncWait;
+ exports.cancelWait = cancelWait;
+ exports.numberOfWaitingCallbacks = numberOfWaitingCallbacks;
+ exports.pumpOnce = pumpOnce;
+ exports.queuePump = queuePump;
+ return exports;
+});
+
+define([
+ "gin/test/expect",
+ "mojo/public/js/support",
+ "mojo/public/js/core",
+ "mojo/public/js/connection",
+ "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom",
+ "mojo/public/interfaces/bindings/tests/sample_service.mojom",
+ "mojo/public/js/threading",
+ "gc",
+], function(expect,
+ mockSupport,
+ core,
+ connection,
+ sample_interfaces,
+ sample_service,
+ threading,
+ gc) {
+ testClientServer();
+ testWriteToClosedPipe();
+ testRequestResponse().then(function() {
+ this.result = "PASS";
+ gc.collectGarbage(); // should not crash
+ threading.quit();
+ }.bind(this)).catch(function(e) {
+ this.result = "FAIL: " + (e.stack || e);
+ threading.quit();
+ }.bind(this));
+
+ function createPeerConnection(handle, stubClass, proxyClass) {
+ var c = new connection.Connection(handle, stubClass, proxyClass);
+ if (c.local)
+ c.local.peer = c.remote;
+ if (c.remote)
+ c.remote.peer = c.local;
+ return c;
+ }
+
+ function testClientServer() {
+ var receivedFrobinate = false;
+
+ // ServiceImpl ------------------------------------------------------------
+
+ function ServiceImpl() {
+ }
+
+ ServiceImpl.prototype = Object.create(
+ sample_service.Service.stubClass.prototype);
+
+ ServiceImpl.prototype.frobinate = function(foo, baz, port) {
+ receivedFrobinate = true;
+
+ expect(foo.name).toBe("Example name");
+ expect(baz).toBeTruthy();
+ expect(core.close(port)).toBe(core.RESULT_OK);
+
+ return Promise.resolve(42);
+ };
+
+ var pipe = core.createMessagePipe();
+ var anotherPipe = core.createMessagePipe();
+ var sourcePipe = core.createMessagePipe();
+
+ var connection0 = createPeerConnection(
+ pipe.handle0, ServiceImpl);
+
+ var connection1 = createPeerConnection(
+ pipe.handle1, undefined, sample_service.Service.proxyClass);
+
+ var foo = new sample_service.Foo();
+ foo.bar = new sample_service.Bar();
+ foo.name = "Example name";
+ foo.source = sourcePipe.handle0;
+ connection1.remote.frobinate(foo, true, anotherPipe.handle0);
+
+ mockSupport.pumpOnce(core.RESULT_OK);
+
+ expect(receivedFrobinate).toBeTruthy();
+
+ connection0.close();
+ connection1.close();
+
+ expect(mockSupport.numberOfWaitingCallbacks()).toBe(0);
+
+ // sourcePipe.handle0 was closed automatically when sent over IPC.
+ expect(core.close(sourcePipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
+ // sourcePipe.handle1 hasn't been closed yet.
+ expect(core.close(sourcePipe.handle1)).toBe(core.RESULT_OK);
+
+ // anotherPipe.handle0 was closed automatically when sent over IPC.
+ expect(core.close(anotherPipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
+ // anotherPipe.handle1 hasn't been closed yet.
+ expect(core.close(anotherPipe.handle1)).toBe(core.RESULT_OK);
+
+ // The Connection object is responsible for closing these handles.
+ expect(core.close(pipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
+ expect(core.close(pipe.handle1)).toBe(core.RESULT_INVALID_ARGUMENT);
+ }
+
+ function testWriteToClosedPipe() {
+ var pipe = core.createMessagePipe();
+
+ var connection1 = createPeerConnection(
+ pipe.handle1, function() {}, sample_service.Service.proxyClass);
+
+ // Close the other end of the pipe.
+ core.close(pipe.handle0);
+
+ // Not observed yet because we haven't pumped events yet.
+ expect(connection1.encounteredError()).toBeFalsy();
+
+ var foo = new sample_service.Foo();
+ foo.bar = new sample_service.Bar();
+ connection1.remote.frobinate(null, true, null);
+
+ // Write failures are not reported.
+ expect(connection1.encounteredError()).toBeFalsy();
+
+ // Pump events, and then we should start observing the closed pipe.
+ mockSupport.pumpOnce(core.RESULT_OK);
+
+ expect(connection1.encounteredError()).toBeTruthy();
+
+ connection1.close();
+ }
+
+ function testRequestResponse() {
+
+ // ProviderImpl ------------------------------------------------------------
+
+ function ProviderImpl() {
+ }
+
+ ProviderImpl.prototype =
+ Object.create(sample_interfaces.Provider.stubClass.prototype);
+
+ ProviderImpl.prototype.echoString = function(a) {
+ mockSupport.queuePump(core.RESULT_OK);
+ return Promise.resolve({a: a});
+ };
+
+ ProviderImpl.prototype.echoStrings = function(a, b) {
+ mockSupport.queuePump(core.RESULT_OK);
+ return Promise.resolve({a: a, b: b});
+ };
+
+ var pipe = core.createMessagePipe();
+
+ var connection0 = createPeerConnection(
+ pipe.handle0,
+ ProviderImpl);
+
+ var connection1 = createPeerConnection(
+ pipe.handle1,
+ undefined,
+ sample_interfaces.Provider.proxyClass);
+
+ var origReadMessage = core.readMessage;
+ // echoString
+ mockSupport.queuePump(core.RESULT_OK);
+ return connection1.remote.echoString("hello").then(function(response) {
+ expect(response.a).toBe("hello");
+ }).then(function() {
+ // echoStrings
+ mockSupport.queuePump(core.RESULT_OK);
+ return connection1.remote.echoStrings("hello", "world");
+ }).then(function(response) {
+ expect(response.a).toBe("hello");
+ expect(response.b).toBe("world");
+ }).then(function() {
+ // Mock a read failure, expect it to fail.
+ core.readMessage = function() {
+ return { result: core.RESULT_UNKNOWN };
+ };
+ mockSupport.queuePump(core.RESULT_OK);
+ return connection1.remote.echoString("goodbye");
+ }).then(function() {
+ throw Error("Expected echoString to fail.");
+ }, function(error) {
+ expect(error.message).toBe("Connection error: " + core.RESULT_UNKNOWN);
+
+ // Clean up.
+ core.readMessage = origReadMessage;
+ });
+ }
+});
diff --git a/mojo/edk/js/tests/js_to_cpp.mojom b/mojo/edk/js/tests/js_to_cpp.mojom
new file mode 100644
index 0000000..688b22b
--- /dev/null
+++ b/mojo/edk/js/tests/js_to_cpp.mojom
@@ -0,0 +1,54 @@
+module js_to_cpp;
+
+// This struct encompasses all of the basic types, so that they
+// may be sent from C++ to JS and back for validation.
+struct EchoArgs {
+ int64 si64;
+ int32 si32;
+ int16 si16;
+ int8 si8;
+ uint64 ui64;
+ uint32 ui32;
+ uint16 ui16;
+ uint8 ui8;
+ float float_val;
+ float float_inf;
+ float float_nan;
+ double double_val;
+ double double_inf;
+ double double_nan;
+ string? name;
+ array<string>? string_array;
+ handle<message_pipe>? message_handle;
+ handle<data_pipe_consumer>? data_handle;
+};
+
+struct EchoArgsList {
+ EchoArgsList? next;
+ EchoArgs? item;
+};
+
+// Note: For messages which control test flow, pick numbers that are unlikely
+// to be hit as a result of our deliberate corruption of response messages.
+interface CppSide {
+ // Sent for all tests to notify that the JS side is now ready.
+ StartTest@88888888();
+
+ // Indicates end for echo, bit-flip, and back-pointer tests.
+ TestFinished@99999999();
+
+ // Responses from specific tests.
+ PingResponse();
+ EchoResponse(EchoArgsList list);
+ BitFlipResponse(EchoArgsList arg);
+ BackPointerResponse(EchoArgsList arg);
+};
+
+interface JsSide {
+ SetCppSide(CppSide cpp);
+
+ Ping();
+ Echo(int32 numIterations, EchoArgs arg);
+ BitFlip(EchoArgs arg);
+ BackPointer(EchoArgs arg);
+};
diff --git a/mojo/edk/js/tests/js_to_cpp_tests.cc b/mojo/edk/js/tests/js_to_cpp_tests.cc
new file mode 100644
index 0000000..f9a9b92
--- /dev/null
+++ b/mojo/edk/js/tests/js_to_cpp_tests.cc
@@ -0,0 +1,427 @@
+// Copyright 2014 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/at_exit.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gin/array_buffer.h"
+#include "gin/public/isolate_holder.h"
+#include "mojo/edk/js/mojo_runner_delegate.h"
+#include "mojo/edk/js/tests/js_to_cpp.mojom.h"
+#include "mojo/edk/test/test_utils.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+
+// Global value updated by some checks to prevent compilers from optimizing
+// reads out of existence.
+uint32 g_waste_accumulator = 0;
+
+namespace {
+
+// Negative numbers with different values in each byte, the last of
+// which can survive promotion to double and back.
+const int8 kExpectedInt8Value = -65;
+const int16 kExpectedInt16Value = -16961;
+const int32 kExpectedInt32Value = -1145258561;
+const int64 kExpectedInt64Value = -77263311946305LL;
+
+// Positive numbers with different values in each byte, the last of
+// which can survive promotion to double and back.
+const uint8 kExpectedUInt8Value = 65;
+const uint16 kExpectedUInt16Value = 16961;
+const uint32 kExpectedUInt32Value = 1145258561;
+const uint64 kExpectedUInt64Value = 77263311946305LL;
+
+// Double/float values, including special case constants.
+const double kExpectedDoubleVal = 3.14159265358979323846;
+const double kExpectedDoubleInf = std::numeric_limits<double>::infinity();
+const double kExpectedDoubleNan = std::numeric_limits<double>::quiet_NaN();
+const float kExpectedFloatVal = static_cast<float>(kExpectedDoubleVal);
+const float kExpectedFloatInf = std::numeric_limits<float>::infinity();
+const float kExpectedFloatNan = std::numeric_limits<float>::quiet_NaN();
+
+// NaN has the property that it is not equal to itself.
+#define EXPECT_NAN(x) EXPECT_NE(x, x)
+
+void CheckDataPipe(MojoHandle data_pipe_handle) {
+ unsigned char buffer[100];
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ MojoResult result = MojoReadData(
+ data_pipe_handle, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(64u, buffer_size);
+ for (int i = 0; i < 64; ++i) {
+ EXPECT_EQ(i, buffer[i]);
+ }
+}
+
+void CheckMessagePipe(MojoHandle message_pipe_handle) {
+ unsigned char buffer[100];
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ MojoResult result = MojoReadMessage(
+ message_pipe_handle, buffer, &buffer_size, 0, 0, 0);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(64u, buffer_size);
+ for (int i = 0; i < 64; ++i) {
+ EXPECT_EQ(255 - i, buffer[i]);
+ }
+}
+
+js_to_cpp::EchoArgsPtr BuildSampleEchoArgs() {
+ js_to_cpp::EchoArgsPtr args(js_to_cpp::EchoArgs::New());
+ args->si64 = kExpectedInt64Value;
+ args->si32 = kExpectedInt32Value;
+ args->si16 = kExpectedInt16Value;
+ args->si8 = kExpectedInt8Value;
+ args->ui64 = kExpectedUInt64Value;
+ args->ui32 = kExpectedUInt32Value;
+ args->ui16 = kExpectedUInt16Value;
+ args->ui8 = kExpectedUInt8Value;
+ args->float_val = kExpectedFloatVal;
+ args->float_inf = kExpectedFloatInf;
+ args->float_nan = kExpectedFloatNan;
+ args->double_val = kExpectedDoubleVal;
+ args->double_inf = kExpectedDoubleInf;
+ args->double_nan = kExpectedDoubleNan;
+ args->name = "coming";
+ Array<String> string_array(3);
+ string_array[0] = "one";
+ string_array[1] = "two";
+ string_array[2] = "three";
+ args->string_array = string_array.Pass();
+ return args.Pass();
+}
+
+void CheckSampleEchoArgs(const js_to_cpp::EchoArgs& arg) {
+ EXPECT_EQ(kExpectedInt64Value, arg.si64);
+ EXPECT_EQ(kExpectedInt32Value, arg.si32);
+ EXPECT_EQ(kExpectedInt16Value, arg.si16);
+ EXPECT_EQ(kExpectedInt8Value, arg.si8);
+ EXPECT_EQ(kExpectedUInt64Value, arg.ui64);
+ EXPECT_EQ(kExpectedUInt32Value, arg.ui32);
+ EXPECT_EQ(kExpectedUInt16Value, arg.ui16);
+ EXPECT_EQ(kExpectedUInt8Value, arg.ui8);
+ EXPECT_EQ(kExpectedFloatVal, arg.float_val);
+ EXPECT_EQ(kExpectedFloatInf, arg.float_inf);
+ EXPECT_NAN(arg.float_nan);
+ EXPECT_EQ(kExpectedDoubleVal, arg.double_val);
+ EXPECT_EQ(kExpectedDoubleInf, arg.double_inf);
+ EXPECT_NAN(arg.double_nan);
+ EXPECT_EQ(std::string("coming"), arg.name.get());
+ EXPECT_EQ(std::string("one"), arg.string_array[0].get());
+ EXPECT_EQ(std::string("two"), arg.string_array[1].get());
+ EXPECT_EQ(std::string("three"), arg.string_array[2].get());
+ CheckDataPipe(arg.data_handle.get().value());
+ CheckMessagePipe(arg.message_handle.get().value());
+}
+
+void CheckSampleEchoArgsList(const js_to_cpp::EchoArgsListPtr& list) {
+ if (list.is_null())
+ return;
+ CheckSampleEchoArgs(*list->item);
+ CheckSampleEchoArgsList(list->next);
+}
+
+// More forgiving checks are needed in the face of potentially corrupt
+// messages. The values don't matter so long as all accesses are within
+// bounds.
+void CheckCorruptedString(const String& arg) {
+ if (arg.is_null())
+ return;
+ for (size_t i = 0; i < arg.size(); ++i)
+ g_waste_accumulator += arg[i];
+}
+
+void CheckCorruptedStringArray(const Array<String>& string_array) {
+ if (string_array.is_null())
+ return;
+ for (size_t i = 0; i < string_array.size(); ++i)
+ CheckCorruptedString(string_array[i]);
+}
+
+void CheckCorruptedDataPipe(MojoHandle data_pipe_handle) {
+ unsigned char buffer[100];
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ MojoResult result = MojoReadData(
+ data_pipe_handle, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE);
+ if (result != MOJO_RESULT_OK)
+ return;
+ for (uint32_t i = 0; i < buffer_size; ++i)
+ g_waste_accumulator += buffer[i];
+}
+
+void CheckCorruptedMessagePipe(MojoHandle message_pipe_handle) {
+ unsigned char buffer[100];
+ uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ MojoResult result = MojoReadMessage(
+ message_pipe_handle, buffer, &buffer_size, 0, 0, 0);
+ if (result != MOJO_RESULT_OK)
+ return;
+ for (uint32_t i = 0; i < buffer_size; ++i)
+ g_waste_accumulator += buffer[i];
+}
+
+void CheckCorruptedEchoArgs(const js_to_cpp::EchoArgsPtr& arg) {
+ if (arg.is_null())
+ return;
+ CheckCorruptedString(arg->name);
+ CheckCorruptedStringArray(arg->string_array);
+ if (arg->data_handle.is_valid())
+ CheckCorruptedDataPipe(arg->data_handle.get().value());
+ if (arg->message_handle.is_valid())
+ CheckCorruptedMessagePipe(arg->message_handle.get().value());
+}
+
+void CheckCorruptedEchoArgsList(const js_to_cpp::EchoArgsListPtr& list) {
+ if (list.is_null())
+ return;
+ CheckCorruptedEchoArgs(list->item);
+ CheckCorruptedEchoArgsList(list->next);
+}
+
+// Base Provider implementation class. It's expected that tests subclass and
+// override the appropriate Provider functions. When test is done quit the
+// run_loop().
+class CppSideConnection : public js_to_cpp::CppSide {
+ public:
+ CppSideConnection()
+ : run_loop_(nullptr),
+ js_side_(nullptr),
+ mishandled_messages_(0),
+ binding_(this) {}
+ ~CppSideConnection() override {}
+
+ void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; }
+ base::RunLoop* run_loop() { return run_loop_; }
+
+ void set_js_side(js_to_cpp::JsSide* js_side) { js_side_ = js_side; }
+ js_to_cpp::JsSide* js_side() { return js_side_; }
+
+ void Bind(InterfaceRequest<js_to_cpp::CppSide> request) {
+ binding_.Bind(request.Pass());
+ // Keep the pipe open even after validation errors.
+ binding_.internal_router()->EnableTestingMode();
+ }
+
+ // js_to_cpp::CppSide:
+ void StartTest() override { NOTREACHED(); }
+
+ void TestFinished() override { NOTREACHED(); }
+
+ void PingResponse() override { mishandled_messages_ += 1; }
+
+ void EchoResponse(js_to_cpp::EchoArgsListPtr list) override {
+ mishandled_messages_ += 1;
+ }
+
+ void BitFlipResponse(js_to_cpp::EchoArgsListPtr list) override {
+ mishandled_messages_ += 1;
+ }
+
+ void BackPointerResponse(js_to_cpp::EchoArgsListPtr list) override {
+ mishandled_messages_ += 1;
+ }
+
+ protected:
+ base::RunLoop* run_loop_;
+ js_to_cpp::JsSide* js_side_;
+ int mishandled_messages_;
+ mojo::Binding<js_to_cpp::CppSide> binding_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CppSideConnection);
+};
+
+// Trivial test to verify a message sent from JS is received.
+class PingCppSideConnection : public CppSideConnection {
+ public:
+ PingCppSideConnection() : got_message_(false) {}
+ ~PingCppSideConnection() override {}
+
+ // js_to_cpp::CppSide:
+ void StartTest() override { js_side_->Ping(); }
+
+ void PingResponse() override {
+ got_message_ = true;
+ run_loop()->Quit();
+ }
+
+ bool DidSucceed() {
+ return got_message_ && !mishandled_messages_;
+ }
+
+ private:
+ bool got_message_;
+ DISALLOW_COPY_AND_ASSIGN(PingCppSideConnection);
+};
+
+// Test that parameters are passed with correct values.
+class EchoCppSideConnection : public CppSideConnection {
+ public:
+ EchoCppSideConnection() :
+ message_count_(0),
+ termination_seen_(false) {
+ }
+ ~EchoCppSideConnection() override {}
+
+ // js_to_cpp::CppSide:
+ void StartTest() override {
+ js_side_->Echo(kExpectedMessageCount, BuildSampleEchoArgs());
+ }
+
+ void EchoResponse(js_to_cpp::EchoArgsListPtr list) override {
+ const js_to_cpp::EchoArgsPtr& special_arg = list->item;
+ message_count_ += 1;
+ EXPECT_EQ(-1, special_arg->si64);
+ EXPECT_EQ(-1, special_arg->si32);
+ EXPECT_EQ(-1, special_arg->si16);
+ EXPECT_EQ(-1, special_arg->si8);
+ EXPECT_EQ(std::string("going"), special_arg->name.To<std::string>());
+ CheckSampleEchoArgsList(list->next);
+ }
+
+ void TestFinished() override {
+ termination_seen_ = true;
+ run_loop()->Quit();
+ }
+
+ bool DidSucceed() {
+ return termination_seen_ &&
+ !mishandled_messages_ &&
+ message_count_ == kExpectedMessageCount;
+ }
+
+ private:
+ static const int kExpectedMessageCount = 10;
+ int message_count_;
+ bool termination_seen_;
+ DISALLOW_COPY_AND_ASSIGN(EchoCppSideConnection);
+};
+
+// Test that corrupted messages don't wreak havoc.
+class BitFlipCppSideConnection : public CppSideConnection {
+ public:
+ BitFlipCppSideConnection() : termination_seen_(false) {}
+ ~BitFlipCppSideConnection() override {}
+
+ // js_to_cpp::CppSide:
+ void StartTest() override { js_side_->BitFlip(BuildSampleEchoArgs()); }
+
+ void BitFlipResponse(js_to_cpp::EchoArgsListPtr list) override {
+ CheckCorruptedEchoArgsList(list);
+ }
+
+ void TestFinished() override {
+ termination_seen_ = true;
+ run_loop()->Quit();
+ }
+
+ bool DidSucceed() {
+ return termination_seen_;
+ }
+
+ private:
+ bool termination_seen_;
+ DISALLOW_COPY_AND_ASSIGN(BitFlipCppSideConnection);
+};
+
+// Test that severely random messages don't wreak havoc.
+class BackPointerCppSideConnection : public CppSideConnection {
+ public:
+ BackPointerCppSideConnection() : termination_seen_(false) {}
+ ~BackPointerCppSideConnection() override {}
+
+ // js_to_cpp::CppSide:
+ void StartTest() override { js_side_->BackPointer(BuildSampleEchoArgs()); }
+
+ void BackPointerResponse(js_to_cpp::EchoArgsListPtr list) override {
+ CheckCorruptedEchoArgsList(list);
+ }
+
+ void TestFinished() override {
+ termination_seen_ = true;
+ run_loop()->Quit();
+ }
+
+ bool DidSucceed() {
+ return termination_seen_;
+ }
+
+ private:
+ bool termination_seen_;
+ DISALLOW_COPY_AND_ASSIGN(BackPointerCppSideConnection);
+};
+
+} // namespace
+
+class JsToCppTest : public testing::Test {
+ public:
+ JsToCppTest() {}
+
+ void RunTest(const std::string& test, CppSideConnection* cpp_side) {
+ cpp_side->set_run_loop(&run_loop_);
+
+ js_to_cpp::JsSidePtr js_side;
+ auto js_side_proxy = GetProxy(&js_side);
+
+ cpp_side->set_js_side(js_side.get());
+ js_to_cpp::CppSidePtr cpp_side_ptr;
+ cpp_side->Bind(GetProxy(&cpp_side_ptr));
+
+ js_side->SetCppSide(cpp_side_ptr.Pass());
+
+ gin::IsolateHolder::Initialize(gin::IsolateHolder::kStrictMode,
+ gin::ArrayBufferAllocator::SharedInstance());
+ gin::IsolateHolder instance;
+ MojoRunnerDelegate delegate;
+ gin::ShellRunner runner(&delegate, instance.isolate());
+ delegate.Start(&runner, js_side_proxy.PassMessagePipe().release().value(),
+ test);
+
+ run_loop_.Run();
+ }
+
+ private:
+ base::ShadowingAtExitManager at_exit_;
+ base::MessageLoop loop;
+ base::RunLoop run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(JsToCppTest);
+};
+
+TEST_F(JsToCppTest, Ping) {
+ PingCppSideConnection cpp_side_connection;
+ RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection);
+ EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+TEST_F(JsToCppTest, Echo) {
+ EchoCppSideConnection cpp_side_connection;
+ RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection);
+ EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+TEST_F(JsToCppTest, BitFlip) {
+ BitFlipCppSideConnection cpp_side_connection;
+ RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection);
+ EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+TEST_F(JsToCppTest, BackPointer) {
+ BackPointerCppSideConnection cpp_side_connection;
+ RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection);
+ EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/js/tests/js_to_cpp_tests.js b/mojo/edk/js/tests/js_to_cpp_tests.js
new file mode 100644
index 0000000..ddecc4b
--- /dev/null
+++ b/mojo/edk/js/tests/js_to_cpp_tests.js
@@ -0,0 +1,228 @@
+// Copyright 2014 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.
+
+define('mojo/edk/js/tests/js_to_cpp_tests', [
+ 'console',
+ 'mojo/edk/js/tests/js_to_cpp.mojom',
+ 'mojo/public/js/bindings',
+ 'mojo/public/js/connection',
+ 'mojo/public/js/connector',
+ 'mojo/public/js/core',
+], function (console, jsToCpp, bindings, connection, connector, core) {
+ var retainedJsSide;
+ var retainedJsSideStub;
+ var sampleData;
+ var sampleMessage;
+ var BAD_VALUE = 13;
+ var DATA_PIPE_PARAMS = {
+ flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
+ elementNumBytes: 1,
+ capacityNumBytes: 64
+ };
+
+ function JsSideConnection() {
+ }
+
+ JsSideConnection.prototype =
+ Object.create(jsToCpp.JsSide.stubClass.prototype);
+
+ JsSideConnection.prototype.setCppSide = function(cppSide) {
+ this.cppSide_ = cppSide;
+ this.cppSide_.startTest();
+ };
+
+ JsSideConnection.prototype.ping = function (arg) {
+ this.cppSide_.pingResponse();
+ };
+
+ JsSideConnection.prototype.echo = function (numIterations, arg) {
+ var dataPipe1;
+ var dataPipe2;
+ var i;
+ var messagePipe1;
+ var messagePipe2;
+ var specialArg;
+
+ // Ensure expected negative values are negative.
+ if (arg.si64 > 0)
+ arg.si64 = BAD_VALUE;
+
+ if (arg.si32 > 0)
+ arg.si32 = BAD_VALUE;
+
+ if (arg.si16 > 0)
+ arg.si16 = BAD_VALUE;
+
+ if (arg.si8 > 0)
+ arg.si8 = BAD_VALUE;
+
+ for (i = 0; i < numIterations; ++i) {
+ dataPipe1 = core.createDataPipe(DATA_PIPE_PARAMS);
+ dataPipe2 = core.createDataPipe(DATA_PIPE_PARAMS);
+ messagePipe1 = core.createMessagePipe();
+ messagePipe2 = core.createMessagePipe();
+
+ arg.data_handle = dataPipe1.consumerHandle;
+ arg.message_handle = messagePipe1.handle1;
+
+ specialArg = new jsToCpp.EchoArgs();
+ specialArg.si64 = -1;
+ specialArg.si32 = -1;
+ specialArg.si16 = -1;
+ specialArg.si8 = -1;
+ specialArg.name = 'going';
+ specialArg.data_handle = dataPipe2.consumerHandle;
+ specialArg.message_handle = messagePipe2.handle1;
+
+ writeDataPipe(dataPipe1, sampleData);
+ writeDataPipe(dataPipe2, sampleData);
+ writeMessagePipe(messagePipe1, sampleMessage);
+ writeMessagePipe(messagePipe2, sampleMessage);
+
+ this.cppSide_.echoResponse(createEchoArgsList(specialArg, arg));
+
+ core.close(dataPipe1.producerHandle);
+ core.close(dataPipe2.producerHandle);
+ core.close(messagePipe1.handle0);
+ core.close(messagePipe2.handle0);
+ }
+ this.cppSide_.testFinished();
+ };
+
+ JsSideConnection.prototype.bitFlip = function (arg) {
+ var iteration = 0;
+ var dataPipe;
+ var messagePipe;
+ var proto = connector.Connector.prototype;
+ var stopSignalled = false;
+
+ proto.realAccept = proto.accept;
+ proto.accept = function (message) {
+ var offset = iteration / 8;
+ var mask;
+ var value;
+ if (offset < message.buffer.arrayBuffer.byteLength) {
+ mask = 1 << (iteration % 8);
+ value = message.buffer.getUint8(offset) ^ mask;
+ message.buffer.setUint8(offset, value);
+ return this.realAccept(message);
+ }
+ stopSignalled = true;
+ return false;
+ };
+
+ while (!stopSignalled) {
+ dataPipe = core.createDataPipe(DATA_PIPE_PARAMS);
+ messagePipe = core.createMessagePipe();
+ writeDataPipe(dataPipe, sampleData);
+ writeMessagePipe(messagePipe, sampleMessage);
+ arg.data_handle = dataPipe.consumerHandle;
+ arg.message_handle = messagePipe.handle1;
+
+ this.cppSide_.bitFlipResponse(createEchoArgsList(arg));
+
+ core.close(dataPipe.producerHandle);
+ core.close(messagePipe.handle0);
+ iteration += 1;
+ }
+
+ proto.accept = proto.realAccept;
+ proto.realAccept = null;
+ this.cppSide_.testFinished();
+ };
+
+ JsSideConnection.prototype.backPointer = function (arg) {
+ var iteration = 0;
+ var dataPipe;
+ var messagePipe;
+ var proto = connector.Connector.prototype;
+ var stopSignalled = false;
+
+ proto.realAccept = proto.accept;
+ proto.accept = function (message) {
+ var delta = 8 * (1 + iteration % 32);
+ var offset = 8 * ((iteration / 32) | 0);
+ if (offset < message.buffer.arrayBuffer.byteLength - 4) {
+ message.buffer.dataView.setUint32(offset, 0x100000000 - delta, true);
+ message.buffer.dataView.setUint32(offset + 4, 0xffffffff, true);
+ return this.realAccept(message);
+ }
+ stopSignalled = true;
+ return false;
+ };
+
+ while (!stopSignalled) {
+ dataPipe = core.createDataPipe(DATA_PIPE_PARAMS);
+ messagePipe = core.createMessagePipe();
+ writeDataPipe(dataPipe, sampleData);
+ writeMessagePipe(messagePipe, sampleMessage);
+ arg.data_handle = dataPipe.consumerHandle;
+ arg.message_handle = messagePipe.handle1;
+
+ this.cppSide_.backPointerResponse(createEchoArgsList(arg));
+
+ core.close(dataPipe.producerHandle);
+ core.close(messagePipe.handle0);
+ iteration += 1;
+ }
+
+ proto.accept = proto.realAccept;
+ proto.realAccept = null;
+ this.cppSide_.testFinished();
+ };
+
+ function writeDataPipe(pipe, data) {
+ var writeResult = core.writeData(
+ pipe.producerHandle, data, core.WRITE_DATA_FLAG_ALL_OR_NONE);
+
+ if (writeResult.result != core.RESULT_OK) {
+ console.log('ERROR: Data pipe write result was ' + writeResult.result);
+ return false;
+ }
+ if (writeResult.numBytes != data.length) {
+ console.log('ERROR: Data pipe write length was ' + writeResult.numBytes);
+ return false;
+ }
+ return true;
+ }
+
+ function writeMessagePipe(pipe, arrayBuffer) {
+ var result = core.writeMessage(pipe.handle0, arrayBuffer, [], 0);
+ if (result != core.RESULT_OK) {
+ console.log('ERROR: Message pipe write result was ' + result);
+ return false;
+ }
+ return true;
+ }
+
+ function createEchoArgsListElement(item, next) {
+ var list = new jsToCpp.EchoArgsList();
+ list.item = item;
+ list.next = next;
+ return list;
+ }
+
+ function createEchoArgsList() {
+ var genuineArray = Array.prototype.slice.call(arguments);
+ return genuineArray.reduceRight(function (previous, current) {
+ return createEchoArgsListElement(current, previous);
+ }, null);
+ }
+
+ return function(jsSideRequestHandle) {
+ var i;
+ sampleData = new Uint8Array(DATA_PIPE_PARAMS.capacityNumBytes);
+ for (i = 0; i < sampleData.length; ++i) {
+ sampleData[i] = i;
+ }
+ sampleMessage = new Uint8Array(DATA_PIPE_PARAMS.capacityNumBytes);
+ for (i = 0; i < sampleMessage.length; ++i) {
+ sampleMessage[i] = 255 - i;
+ }
+ retainedJsSideStub =
+ connection.bindHandleToStub(jsSideRequestHandle, jsToCpp.JsSide);
+ retainedJsSide = new JsSideConnection;
+ bindings.StubBindings(retainedJsSideStub).delegate = retainedJsSide;
+ };
+});
diff --git a/mojo/edk/js/tests/sample_service_tests.js b/mojo/edk/js/tests/sample_service_tests.js
new file mode 100644
index 0000000..ac8ce2e
--- /dev/null
+++ b/mojo/edk/js/tests/sample_service_tests.js
@@ -0,0 +1,171 @@
+// 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.
+
+define([
+ "console",
+ "mojo/edk/js/test/hexdump",
+ "gin/test/expect",
+ "mojo/public/interfaces/bindings/tests/sample_service.mojom",
+ "mojo/public/interfaces/bindings/tests/sample_import.mojom",
+ "mojo/public/interfaces/bindings/tests/sample_import2.mojom",
+ "mojo/public/js/core",
+ ], function(console, hexdump, expect, sample, imported, imported2, core) {
+
+ var global = this;
+
+ // Set this variable to true to print the binary message in hex.
+ var dumpMessageAsHex = false;
+
+ function makeFoo() {
+ var bar = new sample.Bar();
+ bar.alpha = 20;
+ bar.beta = 40;
+ bar.gamma = 60;
+ bar.type = sample.Bar.Type.VERTICAL;
+
+ var extra_bars = new Array(3);
+ for (var i = 0; i < extra_bars.length; ++i) {
+ var base = i * 100;
+ var type = i % 2 ?
+ sample.Bar.Type.VERTICAL : sample.Bar.Type.HORIZONTAL;
+ extra_bars[i] = new sample.Bar();
+ extra_bars[i].alpha = base;
+ extra_bars[i].beta = base + 20;
+ extra_bars[i].gamma = base + 40;
+ extra_bars[i].type = type;
+ }
+
+ var data = new Array(10);
+ for (var i = 0; i < data.length; ++i) {
+ data[i] = data.length - i;
+ }
+
+ var source = 0xFFFF; // Invent a dummy handle.
+
+ var foo = new sample.Foo();
+ foo.name = "foopy";
+ foo.x = 1;
+ foo.y = 2;
+ foo.a = false;
+ foo.b = true;
+ foo.c = false;
+ foo.bar = bar;
+ foo.extra_bars = extra_bars;
+ foo.data = data;
+ foo.source = source;
+ return foo;
+ }
+
+ // Check that the given |Foo| is identical to the one made by |MakeFoo()|.
+ function checkFoo(foo) {
+ expect(foo.name).toBe("foopy");
+ expect(foo.x).toBe(1);
+ expect(foo.y).toBe(2);
+ expect(foo.a).toBeFalsy();
+ expect(foo.b).toBeTruthy();
+ expect(foo.c).toBeFalsy();
+ expect(foo.bar.alpha).toBe(20);
+ expect(foo.bar.beta).toBe(40);
+ expect(foo.bar.gamma).toBe(60);
+ expect(foo.bar.type).toBe(sample.Bar.Type.VERTICAL);
+
+ expect(foo.extra_bars.length).toBe(3);
+ for (var i = 0; i < foo.extra_bars.length; ++i) {
+ var base = i * 100;
+ var type = i % 2 ?
+ sample.Bar.Type.VERTICAL : sample.Bar.Type.HORIZONTAL;
+ expect(foo.extra_bars[i].alpha).toBe(base);
+ expect(foo.extra_bars[i].beta).toBe(base + 20);
+ expect(foo.extra_bars[i].gamma).toBe(base + 40);
+ expect(foo.extra_bars[i].type).toBe(type);
+ }
+
+ expect(foo.data.length).toBe(10);
+ for (var i = 0; i < foo.data.length; ++i)
+ expect(foo.data[i]).toBe(foo.data.length - i);
+
+ expect(foo.source).toBe(0xFFFF);
+ }
+
+ // Check that values are set to the defaults if we don't override them.
+ function checkDefaultValues() {
+ var bar = new sample.Bar();
+ expect(bar.alpha).toBe(255);
+ expect(bar.type).toBe(sample.Bar.Type.VERTICAL);
+
+ var foo = new sample.Foo();
+ expect(foo.name).toBe("Fooby");
+ expect(foo.a).toBeTruthy();
+ expect(foo.data).toBeNull();
+
+ var defaults = new sample.DefaultsTest();
+ expect(defaults.a0).toBe(-12);
+ expect(defaults.a1).toBe(sample.kTwelve);
+ expect(defaults.a2).toBe(1234);
+ expect(defaults.a3).toBe(34567);
+ expect(defaults.a4).toBe(123456);
+ expect(defaults.a5).toBe(3456789012);
+ expect(defaults.a6).toBe(-111111111111);
+ // JS doesn't have a 64 bit integer type so this is just checking that the
+ // expected and actual values have the same closest double value.
+ expect(defaults.a7).toBe(9999999999999999999);
+ expect(defaults.a8).toBe(0x12345);
+ expect(defaults.a9).toBe(-0x12345);
+ expect(defaults.a10).toBe(1234);
+ expect(defaults.a11).toBe(true);
+ expect(defaults.a12).toBe(false);
+ expect(defaults.a13).toBe(123.25);
+ expect(defaults.a14).toBe(1234567890.123);
+ expect(defaults.a15).toBe(1E10);
+ expect(defaults.a16).toBe(-1.2E+20);
+ expect(defaults.a17).toBe(1.23E-20);
+ expect(defaults.a20).toBe(sample.Bar.Type.BOTH);
+ expect(defaults.a21).toBeNull();
+ expect(defaults.a22).toBeTruthy();
+ expect(defaults.a22.shape).toBe(imported.Shape.RECTANGLE);
+ expect(defaults.a22.color).toBe(imported2.Color.BLACK);
+ expect(defaults.a21).toBeNull();
+ expect(defaults.a23).toBe(0xFFFFFFFFFFFFFFFF);
+ expect(defaults.a24).toBe(0x123456789);
+ expect(defaults.a25).toBe(-0x123456789);
+ }
+
+ function ServiceImpl() {
+ }
+
+ ServiceImpl.prototype = Object.create(sample.Service.stubClass.prototype);
+
+ ServiceImpl.prototype.frobinate = function(foo, baz, port) {
+ checkFoo(foo);
+ expect(baz).toBe(sample.Service.BazOptions.EXTRA);
+ expect(core.isHandle(port)).toBeTruthy();
+ global.result = "PASS";
+ };
+
+ function SimpleMessageReceiver() {
+ }
+
+ SimpleMessageReceiver.prototype.acceptAndExpectResponse = function(message) {
+ if (dumpMessageAsHex) {
+ var uint8Array = new Uint8Array(message.buffer.arrayBuffer);
+ console.log(hexdump.dumpArray(uint8Array));
+ }
+ // Imagine some IPC happened here.
+ var serviceImpl = new ServiceImpl();
+ return serviceImpl.acceptWithResponder(message, { accept: function() {} });
+ };
+
+ var serviceProxy = new sample.Service.proxyClass;
+ serviceProxy.receiver_ = new SimpleMessageReceiver;
+
+ checkDefaultValues();
+
+ var foo = makeFoo();
+ checkFoo(foo);
+
+ var pipe = core.createMessagePipe();
+ serviceProxy.frobinate(foo, sample.Service.BazOptions.EXTRA, pipe.handle0);
+ expect(core.close(pipe.handle0)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.handle1)).toBe(core.RESULT_OK);
+});
diff --git a/mojo/edk/js/threading.cc b/mojo/edk/js/threading.cc
new file mode 100644
index 0000000..1e6c9a6
--- /dev/null
+++ b/mojo/edk/js/threading.cc
@@ -0,0 +1,47 @@
+// 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 "mojo/edk/js/threading.h"
+
+#include "base/message_loop/message_loop.h"
+#include "gin/object_template_builder.h"
+#include "gin/per_isolate_data.h"
+#include "mojo/edk/js/handle.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+void Quit() {
+ base::MessageLoop::current()->QuitNow();
+}
+
+gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin };
+
+} // namespace
+
+const char Threading::kModuleName[] = "mojo/public/js/threading";
+
+v8::Local<v8::Value> Threading::GetModule(v8::Isolate* isolate) {
+ gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
+ v8::Local<v8::ObjectTemplate> templ = data->GetObjectTemplate(
+ &g_wrapper_info);
+
+ if (templ.IsEmpty()) {
+ templ = gin::ObjectTemplateBuilder(isolate)
+ .SetMethod("quit", Quit)
+ .Build();
+
+ data->SetObjectTemplate(&g_wrapper_info, templ);
+ }
+
+ return templ->NewInstance();
+}
+
+Threading::Threading() {
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/js/threading.h b/mojo/edk/js/threading.h
new file mode 100644
index 0000000..1a1803d
--- /dev/null
+++ b/mojo/edk/js/threading.h
@@ -0,0 +1,25 @@
+// 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 MOJO_EDK_JS_THREADING_H_
+#define MOJO_EDK_JS_THREADING_H_
+
+#include "gin/public/wrapper_info.h"
+#include "v8/include/v8.h"
+
+namespace mojo {
+namespace edk {
+
+class Threading {
+ public:
+ static const char kModuleName[];
+ static v8::Local<v8::Value> GetModule(v8::Isolate* isolate);
+ private:
+ Threading();
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_JS_THREADING_H_
diff --git a/mojo/edk/js/waiting_callback.cc b/mojo/edk/js/waiting_callback.cc
new file mode 100644
index 0000000..0bc875d
--- /dev/null
+++ b/mojo/edk/js/waiting_callback.cc
@@ -0,0 +1,115 @@
+// Copyright 2014 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 "mojo/edk/js/waiting_callback.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "gin/per_context_data.h"
+#include "mojo/public/cpp/environment/environment.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+v8::Handle<v8::String> GetHiddenPropertyName(v8::Isolate* isolate) {
+ return gin::StringToSymbol(isolate, "::mojo::js::WaitingCallback");
+}
+
+} // namespace
+
+gin::WrapperInfo WaitingCallback::kWrapperInfo = { gin::kEmbedderNativeGin };
+
+// static
+gin::Handle<WaitingCallback> WaitingCallback::Create(
+ v8::Isolate* isolate,
+ v8::Handle<v8::Function> callback,
+ gin::Handle<HandleWrapper> handle_wrapper,
+ MojoHandleSignals signals) {
+ gin::Handle<WaitingCallback> waiting_callback = gin::CreateHandle(
+ isolate, new WaitingCallback(isolate, callback, handle_wrapper));
+ waiting_callback->wait_id_ = Environment::GetDefaultAsyncWaiter()->AsyncWait(
+ handle_wrapper->get().value(),
+ signals,
+ MOJO_DEADLINE_INDEFINITE,
+ &WaitingCallback::CallOnHandleReady,
+ waiting_callback.get());
+ return waiting_callback;
+}
+
+void WaitingCallback::Cancel() {
+ if (!wait_id_)
+ return;
+
+ handle_wrapper_->RemoveCloseObserver(this);
+ handle_wrapper_ = NULL;
+ Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
+ wait_id_ = 0;
+}
+
+WaitingCallback::WaitingCallback(v8::Isolate* isolate,
+ v8::Handle<v8::Function> callback,
+ gin::Handle<HandleWrapper> handle_wrapper)
+ : wait_id_(0), handle_wrapper_(handle_wrapper.get()), weak_factory_(this) {
+ handle_wrapper_->AddCloseObserver(this);
+ v8::Handle<v8::Context> context = isolate->GetCurrentContext();
+ runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr();
+ GetWrapper(isolate)->SetHiddenValue(GetHiddenPropertyName(isolate), callback);
+}
+
+WaitingCallback::~WaitingCallback() {
+ Cancel();
+}
+
+// static
+void WaitingCallback::CallOnHandleReady(void* closure, MojoResult result) {
+ static_cast<WaitingCallback*>(closure)->OnHandleReady(result);
+}
+
+void WaitingCallback::ClearWaitId() {
+ wait_id_ = 0;
+ handle_wrapper_->RemoveCloseObserver(this);
+ handle_wrapper_ = nullptr;
+}
+
+void WaitingCallback::OnHandleReady(MojoResult result) {
+ ClearWaitId();
+ CallCallback(result);
+}
+
+void WaitingCallback::CallCallback(MojoResult result) {
+ // ClearWaitId must already have been called.
+ DCHECK(!wait_id_);
+ DCHECK(!handle_wrapper_);
+
+ if (!runner_)
+ return;
+
+ gin::Runner::Scope scope(runner_.get());
+ v8::Isolate* isolate = runner_->GetContextHolder()->isolate();
+
+ v8::Handle<v8::Value> hidden_value =
+ GetWrapper(isolate)->GetHiddenValue(GetHiddenPropertyName(isolate));
+ v8::Handle<v8::Function> callback;
+ CHECK(gin::ConvertFromV8(isolate, hidden_value, &callback));
+
+ v8::Handle<v8::Value> args[] = { gin::ConvertToV8(isolate, result) };
+ runner_->Call(callback, runner_->global(), 1, args);
+}
+
+void WaitingCallback::OnWillCloseHandle() {
+ Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
+
+ // This may be called from GC, so we can't execute Javascript now, call
+ // ClearWaitId explicitly, and CallCallback asynchronously.
+ ClearWaitId();
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&WaitingCallback::CallCallback, weak_factory_.GetWeakPtr(),
+ MOJO_RESULT_INVALID_ARGUMENT));
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/js/waiting_callback.h b/mojo/edk/js/waiting_callback.h
new file mode 100644
index 0000000..c1b76aa
--- /dev/null
+++ b/mojo/edk/js/waiting_callback.h
@@ -0,0 +1,68 @@
+// Copyright 2014 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 MOJO_EDK_JS_WAITING_CALLBACK_H_
+#define MOJO_EDK_JS_WAITING_CALLBACK_H_
+
+#include "base/memory/weak_ptr.h"
+#include "gin/handle.h"
+#include "gin/runner.h"
+#include "gin/wrappable.h"
+#include "mojo/edk/js/handle.h"
+#include "mojo/edk/js/handle_close_observer.h"
+#include "mojo/public/c/environment/async_waiter.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace edk {
+
+class WaitingCallback : public gin::Wrappable<WaitingCallback>,
+ public HandleCloseObserver {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ // Creates a new WaitingCallback.
+ static gin::Handle<WaitingCallback> Create(
+ v8::Isolate* isolate,
+ v8::Handle<v8::Function> callback,
+ gin::Handle<HandleWrapper> handle_wrapper,
+ MojoHandleSignals signals);
+
+ // Cancels the callback. Does nothing if a callback is not pending. This is
+ // implicitly invoked from the destructor but can be explicitly invoked as
+ // necessary.
+ void Cancel();
+
+ private:
+ WaitingCallback(v8::Isolate* isolate,
+ v8::Handle<v8::Function> callback,
+ gin::Handle<HandleWrapper> handle_wrapper);
+ ~WaitingCallback() override;
+
+ // Callback from MojoAsyncWaiter. |closure| is the WaitingCallback.
+ static void CallOnHandleReady(void* closure, MojoResult result);
+
+ // Invoked from CallOnHandleReady() (CallOnHandleReady() must be static).
+ void OnHandleReady(MojoResult result);
+
+ // Invoked by the HandleWrapper if the handle is closed while this wait is
+ // still in progress.
+ void OnWillCloseHandle() override;
+
+ void ClearWaitId();
+ void CallCallback(MojoResult result);
+
+ base::WeakPtr<gin::Runner> runner_;
+ MojoAsyncWaitID wait_id_;
+
+ HandleWrapper* handle_wrapper_;
+ base::WeakPtrFactory<WaitingCallback> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WaitingCallback);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_JS_WAITING_CALLBACK_H_
diff --git a/mojo/edk/mojo_edk.gni b/mojo/edk/mojo_edk.gni
new file mode 100644
index 0000000..efb2a5f
--- /dev/null
+++ b/mojo/edk/mojo_edk.gni
@@ -0,0 +1,69 @@
+# Copyright 2014 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.
+
+import("../../third_party/mojo/src/mojo/public/mojo_sdk.gni")
+
+# A mojo_edk_source_set is a mojo_sdk_source_set that does not restrict
+# external dependencies and understands the following additional variables, all
+# of which admit a list of the relevant elements specified relative to the
+# location of the Mojo EDK:
+# allow_circular_mojo_edk_includes_from
+template("mojo_edk_source_set") {
+ mojo_sdk_source_set(target_name) {
+ if (defined(invoker.public_deps) || defined(invoker.deps)) {
+ restrict_external_deps = false
+ }
+
+ if (defined(invoker.visibility)) {
+ visibility = invoker.visibility
+ }
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ if (defined(invoker.sources)) {
+ sources = invoker.sources
+ }
+ if (defined(invoker.defines)) {
+ defines = invoker.defines
+ }
+ if (defined(invoker.public_configs)) {
+ public_configs = invoker.public_configs
+ }
+
+ configs = []
+ if (defined(invoker.configs)) {
+ configs = invoker.configs
+ }
+
+ allow_circular_includes_from = []
+ if (defined(invoker.allow_circular_includes_from)) {
+ allow_circular_includes_from += invoker.allow_circular_includes_from
+ }
+ if (defined(invoker.allow_circular_mojo_edk_includes_from)) {
+ foreach(edk_target, invoker.allow_circular_mojo_edk_includes_from) {
+ # Check that the EDK target was not mistakenly given as an absolute
+ # path.
+ assert(get_path_info(edk_target, "abspath") != edk_target)
+ allow_circular_includes_from +=
+ [ rebase_path(edk_target, ".", mojo_root) ]
+ }
+ }
+
+ if (defined(invoker.public_deps)) {
+ public_deps = invoker.public_deps
+ }
+ mojo_sdk_public_deps = []
+ if (defined(invoker.mojo_sdk_public_deps)) {
+ mojo_sdk_public_deps += invoker.mojo_sdk_public_deps
+ }
+
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ }
+ mojo_sdk_deps = []
+ if (defined(invoker.mojo_sdk_deps)) {
+ mojo_sdk_deps += invoker.mojo_sdk_deps
+ }
+ }
+}
diff --git a/mojo/edk/system/BUILD.gn b/mojo/edk/system/BUILD.gn
new file mode 100644
index 0000000..b2df53a
--- /dev/null
+++ b/mojo/edk/system/BUILD.gn
@@ -0,0 +1,208 @@
+# Copyright 2014 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.
+
+import("../mojo_edk.gni")
+import("//testing/test.gni")
+import("../../../third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
+
+if (is_android) {
+ import("//build/config/android/config.gni")
+ import("//build/config/android/rules.gni")
+}
+
+config("system_config") {
+ defines = [
+ # Ensures that dependent projects import the core functions on Windows.
+ "MOJO_USE_SYSTEM_IMPL",
+ ]
+}
+
+mojom("master") {
+ sources = [
+ "master.mojom",
+ ]
+}
+
+static_library("system") {
+# TODO(use_chrome_edk): this should be a component to match third_party,
+# but since third_party includes it, we either make it a static library
+# or we have to change the export macros to be different than third_party.
+#component("system") {
+ # TODO(use_chrome_edk): remove "2"
+ output_name = "mojo_system_impl2"
+
+ sources = [
+ "async_waiter.cc",
+ "async_waiter.h",
+ "awakable.h",
+ "awakable_list.cc",
+ "awakable_list.h",
+ "configuration.cc",
+ "configuration.h",
+ "core.cc",
+ "core.h",
+ "data_pipe.cc",
+ "data_pipe.h",
+ "data_pipe_consumer_dispatcher.cc",
+ "data_pipe_consumer_dispatcher.h",
+ "data_pipe_producer_dispatcher.cc",
+ "data_pipe_producer_dispatcher.h",
+ "dispatcher.cc",
+ "dispatcher.h",
+ "handle_signals_state.h",
+ "handle_table.cc",
+ "handle_table.h",
+ "mapping_table.cc",
+ "mapping_table.h",
+ "master_impl.cc",
+ "master_impl.h",
+ "message_in_transit.cc",
+ "message_in_transit.h",
+ "message_in_transit_queue.cc",
+ "message_in_transit_queue.h",
+ "message_pipe_dispatcher.cc",
+ "message_pipe_dispatcher.h",
+ "options_validation.h",
+ "platform_handle_dispatcher.cc",
+ "platform_handle_dispatcher.h",
+ "raw_channel.cc",
+ "raw_channel.h",
+ "raw_channel_posix.cc",
+ "raw_channel_win.cc",
+ "shared_buffer_dispatcher.cc",
+ "shared_buffer_dispatcher.h",
+ "simple_dispatcher.cc",
+ "simple_dispatcher.h",
+ "transport_data.cc",
+ "transport_data.h",
+ "waiter.cc",
+ "waiter.h",
+ ]
+
+ defines = [
+ "MOJO_SYSTEM_IMPL_IMPLEMENTATION",
+ "MOJO_SYSTEM_IMPLEMENTATION",
+ ]
+
+ all_dependent_configs = [ ":system_config" ]
+
+ public_deps = [
+ "../embedder",
+ "../embedder:delegates",
+ "../embedder:platform",
+ "../../../third_party/mojo/src/mojo/public/c/system",
+ "../../../third_party/mojo/src/mojo/public/cpp/system",
+ ]
+
+ deps = [
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ ":master",
+ ]
+
+ if (is_win) {
+ cflags = [ "/wd4324" ] # Structure was padded due to __declspec(align()),
+ # which is uninteresting.
+ }
+
+ allow_circular_includes_from = [ "../embedder" ]
+}
+
+group("tests") {
+ testonly = true
+ deps = [
+ # TODO(use_chrome_edk): remove "2"
+ ":mojo_system_unittests2",
+ ":mojo_message_pipe_perftests2",
+ ]
+}
+
+mojo_edk_source_set("test_utils") {
+ testonly = true
+
+ sources = [
+ "test_utils.cc",
+ "test_utils.h",
+ ]
+
+ public_deps = [
+ "//third_party/mojo/src/mojo/public/c/system",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//mojo/edk/test:test_support",
+ "//testing/gtest:gtest",
+ ]
+}
+
+# TODO(use_chrome_edk): remove "2"
+test("mojo_system_unittests2") {
+ sources = [
+ "../test/multiprocess_test_helper_unittest.cc",
+ "awakable_list_unittest.cc",
+ "core_test_base.cc",
+ "core_test_base.h",
+ "core_unittest.cc",
+ "data_pipe_unittest.cc",
+ "dispatcher_unittest.cc",
+ "master_impl_unittest.cc",
+
+ "message_in_transit_queue_unittest.cc",
+ "message_in_transit_test_utils.cc",
+ "message_in_transit_test_utils.h",
+ "message_pipe_test_utils.cc",
+ "message_pipe_test_utils.h",
+ "message_pipe_unittest.cc",
+ "multiprocess_message_pipe_unittest.cc",
+ "options_validation_unittest.cc",
+ "platform_handle_dispatcher_unittest.cc",
+
+ "raw_channel_unittest.cc",
+ "run_all_unittests.cc",
+ "shared_buffer_dispatcher_unittest.cc",
+ "simple_dispatcher_unittest.cc",
+ "waiter_test_utils.cc",
+ "waiter_test_utils.h",
+ "waiter_unittest.cc",
+ ]
+
+ deps = [
+ ":system",
+ ":test_utils",
+
+ # TODO(use_chrome_edk): remove "2"
+ "../embedder:embedder_unittests2",
+ "../../../third_party/mojo/src/mojo/public/cpp/environment:standalone",
+ "../test:test_support",
+ "//base",
+ "//base/test:test_support",
+ "//testing/gtest",
+ ]
+
+ # TODO(use_chrome_edk): remove "2"
+ allow_circular_includes_from = [ "../embedder:embedder_unittests2" ]
+}
+
+# TODO(use_chrome_edk): remove "2"
+test("mojo_message_pipe_perftests2") {
+ sources = [
+ "message_pipe_perftest.cc",
+ "message_pipe_test_utils.cc",
+ "message_pipe_test_utils.h",
+ ]
+
+ deps = [
+ ":system",
+ ":test_utils",
+ "../test:test_support",
+ "../test:run_all_perftests",
+ "../../../third_party/mojo/src/mojo/public/cpp/environment:standalone",
+ "//base",
+ "//base/test:test_support",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/edk/system/async_waiter.cc b/mojo/edk/system/async_waiter.cc
new file mode 100644
index 0000000..d2e7000
--- /dev/null
+++ b/mojo/edk/system/async_waiter.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 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 "mojo/edk/system/async_waiter.h"
+
+namespace mojo {
+namespace edk {
+
+AsyncWaiter::AsyncWaiter(const AwakeCallback& callback) : callback_(callback) {
+}
+
+AsyncWaiter::~AsyncWaiter() {
+}
+
+bool AsyncWaiter::Awake(MojoResult result, uintptr_t context) {
+ callback_.Run(result);
+ delete this;
+ return false;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/async_waiter.h b/mojo/edk/system/async_waiter.h
new file mode 100644
index 0000000..19ef5d6
--- /dev/null
+++ b/mojo/edk/system/async_waiter.h
@@ -0,0 +1,38 @@
+// Copyright 2014 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 MOJO_EDK_SYSTEM_ASYNC_WAITER_H_
+#define MOJO_EDK_SYSTEM_ASYNC_WAITER_H_
+
+#include "base/callback.h"
+#include "mojo/edk/system/awakable.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// An |Awakable| implementation that just calls a given callback object.
+class MOJO_SYSTEM_IMPL_EXPORT AsyncWaiter final : public Awakable {
+ public:
+ using AwakeCallback = base::Callback<void(MojoResult)>;
+
+ // |callback| must satisfy the same contract as |Awakable::Awake()|.
+ explicit AsyncWaiter(const AwakeCallback& callback);
+ virtual ~AsyncWaiter();
+
+ private:
+ // |Awakable| implementation:
+ bool Awake(MojoResult result, uintptr_t context) override;
+
+ AwakeCallback callback_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(AsyncWaiter);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_ASYNC_WAITER_H_
diff --git a/mojo/edk/system/awakable.h b/mojo/edk/system/awakable.h
new file mode 100644
index 0000000..2cb10f5
--- /dev/null
+++ b/mojo/edk/system/awakable.h
@@ -0,0 +1,34 @@
+// 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 MOJO_EDK_SYSTEM_AWAKABLE_H_
+#define MOJO_EDK_SYSTEM_AWAKABLE_H_
+
+#include <stdint.h>
+
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace edk {
+
+// An interface that may be waited on |AwakableList|.
+class MOJO_SYSTEM_IMPL_EXPORT Awakable {
+ public:
+ // |Awake()| must satisfy the following contract:
+ // * As this is called from any thread, this must be thread-safe.
+ // * As this is called inside a lock, this must not call anything that takes
+ // "non-terminal" locks, i.e., those which are always safe to take.
+ // This should return false if this must not be called again for the same
+ // reason (e.g., for the same call to |AwakableList::Add()|).
+ virtual bool Awake(MojoResult result, uintptr_t context) = 0;
+
+ protected:
+ Awakable() {}
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_AWAKABLE_H_
diff --git a/mojo/edk/system/awakable_list.cc b/mojo/edk/system/awakable_list.cc
new file mode 100644
index 0000000..84f7e45
--- /dev/null
+++ b/mojo/edk/system/awakable_list.cc
@@ -0,0 +1,74 @@
+// 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 "mojo/edk/system/awakable_list.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "mojo/edk/system/awakable.h"
+#include "mojo/edk/system/handle_signals_state.h"
+
+namespace mojo {
+namespace edk {
+
+AwakableList::AwakableList() {
+}
+
+AwakableList::~AwakableList() {
+ DCHECK(awakables_.empty());
+}
+
+void AwakableList::AwakeForStateChange(const HandleSignalsState& state) {
+ // Instead of deleting elements in-place, swap them with the last element and
+ // erase the elements from the end.
+ auto last = awakables_.end();
+ for (AwakeInfoList::iterator it = awakables_.begin(); it != last;) {
+ bool keep = true;
+ if (state.satisfies(it->signals))
+ keep = it->awakable->Awake(MOJO_RESULT_OK, it->context);
+ else if (!state.can_satisfy(it->signals))
+ keep = it->awakable->Awake(MOJO_RESULT_FAILED_PRECONDITION, it->context);
+
+ if (!keep) {
+ --last;
+ std::swap(*it, *last);
+ } else {
+ ++it;
+ }
+ }
+ awakables_.erase(last, awakables_.end());
+}
+
+void AwakableList::CancelAll() {
+ for (AwakeInfoList::iterator it = awakables_.begin(); it != awakables_.end();
+ ++it) {
+ it->awakable->Awake(MOJO_RESULT_CANCELLED, it->context);
+ }
+ awakables_.clear();
+}
+
+void AwakableList::Add(Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context) {
+ awakables_.push_back(AwakeInfo(awakable, signals, context));
+}
+
+void AwakableList::Remove(Awakable* awakable) {
+ // We allow a thread to wait on the same handle multiple times simultaneously,
+ // so we need to scan the entire list and remove all occurrences of |waiter|.
+ auto last = awakables_.end();
+ for (AwakeInfoList::iterator it = awakables_.begin(); it != last;) {
+ if (it->awakable == awakable) {
+ --last;
+ std::swap(*it, *last);
+ } else {
+ ++it;
+ }
+ }
+ awakables_.erase(last, awakables_.end());
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/awakable_list.h b/mojo/edk/system/awakable_list.h
new file mode 100644
index 0000000..7f8a6d6
--- /dev/null
+++ b/mojo/edk/system/awakable_list.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 MOJO_EDK_SYSTEM_AWAKABLE_LIST_H_
+#define MOJO_EDK_SYSTEM_AWAKABLE_LIST_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+class Awakable;
+struct HandleSignalsState;
+
+// |AwakableList| tracks all the |Waiter|s that are waiting on a given
+// handle/|Dispatcher|. There should be a |AwakableList| for each handle that
+// can be waited on (in any way). In the simple case, the |AwakableList| is
+// owned by the |Dispatcher|, whereas in more complex cases it is owned by the
+// secondary object (see simple_dispatcher.* and the explanatory comment in
+// core.cc). This class is thread-unsafe (all concurrent access must be
+// protected by some lock).
+class MOJO_SYSTEM_IMPL_EXPORT AwakableList {
+ public:
+ AwakableList();
+ ~AwakableList();
+
+ void AwakeForStateChange(const HandleSignalsState& state);
+ void CancelAll();
+ void Add(Awakable* awakable, MojoHandleSignals signals, uint32_t context);
+ void Remove(Awakable* awakable);
+
+ private:
+ struct AwakeInfo {
+ AwakeInfo(Awakable* awakable, MojoHandleSignals signals, uint32_t context)
+ : awakable(awakable), signals(signals), context(context) {}
+
+ Awakable* awakable;
+ MojoHandleSignals signals;
+ uint32_t context;
+ };
+ using AwakeInfoList = std::vector<AwakeInfo>;
+
+ AwakeInfoList awakables_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(AwakableList);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_AWAKABLE_LIST_H_
diff --git a/mojo/edk/system/awakable_list_unittest.cc b/mojo/edk/system/awakable_list_unittest.cc
new file mode 100644
index 0000000..5b5ba8f
--- /dev/null
+++ b/mojo/edk/system/awakable_list_unittest.cc
@@ -0,0 +1,356 @@
+// 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.
+
+// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a
+// heavily-loaded system). Sorry. |test::EpsilonDeadline()| may be increased to
+// increase tolerance and reduce observed flakiness (though doing so reduces the
+// meaningfulness of the test).
+
+#include "mojo/edk/system/awakable_list.h"
+
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/edk/system/waiter_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+TEST(AwakableListTest, BasicCancel) {
+ MojoResult result;
+ uint32_t context;
+
+ // Cancel immediately after thread start.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread(&result, &context);
+ awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1);
+ thread.Start();
+ awakable_list.CancelAll();
+ // Double-remove okay:
+ awakable_list.Remove(thread.waiter());
+ } // Join |thread|.
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
+ EXPECT_EQ(1u, context);
+
+ // Cancel before after thread start.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread(&result, &context);
+ awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2);
+ awakable_list.CancelAll();
+ thread.Start();
+ } // Join |thread|.
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
+ EXPECT_EQ(2u, context);
+
+ // Cancel some time after thread start.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread(&result, &context);
+ awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3);
+ thread.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ awakable_list.CancelAll();
+ } // Join |thread|.
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
+ EXPECT_EQ(3u, context);
+}
+
+TEST(AwakableListTest, BasicAwakeSatisfied) {
+ MojoResult result;
+ uint32_t context;
+
+ // Awake immediately after thread start.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread(&result, &context);
+ awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1);
+ thread.Start();
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
+ awakable_list.Remove(thread.waiter());
+ } // Join |thread|.
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(1u, context);
+
+ // Awake before after thread start.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread(&result, &context);
+ awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2);
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
+ awakable_list.Remove(thread.waiter());
+ // Double-remove okay:
+ awakable_list.Remove(thread.waiter());
+ thread.Start();
+ } // Join |thread|.
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(2u, context);
+
+ // Awake some time after thread start.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread(&result, &context);
+ awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3);
+ thread.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
+ awakable_list.Remove(thread.waiter());
+ } // Join |thread|.
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(3u, context);
+}
+
+TEST(AwakableListTest, BasicAwakeUnsatisfiable) {
+ MojoResult result;
+ uint32_t context;
+
+ // Awake (for unsatisfiability) immediately after thread start.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread(&result, &context);
+ awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1);
+ thread.Start();
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_WRITABLE));
+ awakable_list.Remove(thread.waiter());
+ } // Join |thread|.
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
+ EXPECT_EQ(1u, context);
+
+ // Awake (for unsatisfiability) before after thread start.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread(&result, &context);
+ awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2);
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_READABLE, MOJO_HANDLE_SIGNAL_READABLE));
+ awakable_list.Remove(thread.waiter());
+ thread.Start();
+ } // Join |thread|.
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
+ EXPECT_EQ(2u, context);
+
+ // Awake (for unsatisfiability) some time after thread start.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread(&result, &context);
+ awakable_list.Add(thread.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3);
+ thread.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_WRITABLE));
+ awakable_list.Remove(thread.waiter());
+ // Double-remove okay:
+ awakable_list.Remove(thread.waiter());
+ } // Join |thread|.
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
+ EXPECT_EQ(3u, context);
+}
+
+TEST(AwakableListTest, MultipleAwakables) {
+ MojoResult result1;
+ MojoResult result2;
+ MojoResult result3;
+ MojoResult result4;
+ uint32_t context1;
+ uint32_t context2;
+ uint32_t context3;
+ uint32_t context4;
+
+ // Cancel two awakables.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread1(&result1, &context1);
+ awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 1);
+ thread1.Start();
+ test::SimpleWaiterThread thread2(&result2, &context2);
+ awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 2);
+ thread2.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ awakable_list.CancelAll();
+ } // Join threads.
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result1);
+ EXPECT_EQ(1u, context1);
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result2);
+ EXPECT_EQ(2u, context2);
+
+ // Awake one awakable, cancel other.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread1(&result1, &context1);
+ awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 3);
+ thread1.Start();
+ test::SimpleWaiterThread thread2(&result2, &context2);
+ awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 4);
+ thread2.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
+ awakable_list.Remove(thread1.waiter());
+ awakable_list.CancelAll();
+ } // Join threads.
+ EXPECT_EQ(MOJO_RESULT_OK, result1);
+ EXPECT_EQ(3u, context1);
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result2);
+ EXPECT_EQ(4u, context2);
+
+ // Cancel one awakable, awake other for unsatisfiability.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread1(&result1, &context1);
+ awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 5);
+ thread1.Start();
+ test::SimpleWaiterThread thread2(&result2, &context2);
+ awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 6);
+ thread2.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_READABLE));
+ awakable_list.Remove(thread2.waiter());
+ awakable_list.CancelAll();
+ } // Join threads.
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result1);
+ EXPECT_EQ(5u, context1);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result2);
+ EXPECT_EQ(6u, context2);
+
+ // Cancel one awakable, awake other for unsatisfiability.
+ {
+ AwakableList awakable_list;
+ test::SimpleWaiterThread thread1(&result1, &context1);
+ awakable_list.Add(thread1.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 7);
+ thread1.Start();
+
+ test::Sleep(1 * test::EpsilonDeadline());
+
+ // Should do nothing.
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
+
+ test::SimpleWaiterThread thread2(&result2, &context2);
+ awakable_list.Add(thread2.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 8);
+ thread2.Start();
+
+ test::Sleep(1 * test::EpsilonDeadline());
+
+ // Awake #1.
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE));
+ awakable_list.Remove(thread1.waiter());
+
+ test::Sleep(1 * test::EpsilonDeadline());
+
+ test::SimpleWaiterThread thread3(&result3, &context3);
+ awakable_list.Add(thread3.waiter(), MOJO_HANDLE_SIGNAL_WRITABLE, 9);
+ thread3.Start();
+
+ test::SimpleWaiterThread thread4(&result4, &context4);
+ awakable_list.Add(thread4.waiter(), MOJO_HANDLE_SIGNAL_READABLE, 10);
+ thread4.Start();
+
+ test::Sleep(1 * test::EpsilonDeadline());
+
+ // Awake #2 and #3 for unsatisfiability.
+ awakable_list.AwakeForStateChange(HandleSignalsState(
+ MOJO_HANDLE_SIGNAL_NONE, MOJO_HANDLE_SIGNAL_READABLE));
+ awakable_list.Remove(thread2.waiter());
+ awakable_list.Remove(thread3.waiter());
+
+ // Cancel #4.
+ awakable_list.CancelAll();
+ } // Join threads.
+ EXPECT_EQ(MOJO_RESULT_OK, result1);
+ EXPECT_EQ(7u, context1);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result2);
+ EXPECT_EQ(8u, context2);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result3);
+ EXPECT_EQ(9u, context3);
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result4);
+ EXPECT_EQ(10u, context4);
+}
+
+class KeepAwakable : public Awakable {
+ public:
+ KeepAwakable() : awake_count(0) {}
+
+ bool Awake(MojoResult result, uintptr_t context) override {
+ awake_count++;
+ return true;
+ }
+
+ int awake_count;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(KeepAwakable);
+};
+
+class RemoveAwakable : public Awakable {
+ public:
+ RemoveAwakable() : awake_count(0) {}
+
+ bool Awake(MojoResult result, uintptr_t context) override {
+ awake_count++;
+ return false;
+ }
+
+ int awake_count;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RemoveAwakable);
+};
+
+TEST(AwakableListTest, KeepAwakablesReturningTrue) {
+ KeepAwakable keep0;
+ KeepAwakable keep1;
+ RemoveAwakable remove0;
+ RemoveAwakable remove1;
+ RemoveAwakable remove2;
+
+ HandleSignalsState hss(MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ AwakableList remove_all;
+ remove_all.Add(&remove0, MOJO_HANDLE_SIGNAL_WRITABLE, 0);
+ remove_all.Add(&remove1, MOJO_HANDLE_SIGNAL_WRITABLE, 0);
+
+ remove_all.AwakeForStateChange(hss);
+ EXPECT_EQ(remove0.awake_count, 1);
+ EXPECT_EQ(remove1.awake_count, 1);
+
+ remove_all.AwakeForStateChange(hss);
+ EXPECT_EQ(remove0.awake_count, 1);
+ EXPECT_EQ(remove1.awake_count, 1);
+
+ AwakableList remove_first;
+ remove_first.Add(&remove2, MOJO_HANDLE_SIGNAL_WRITABLE, 0);
+ remove_first.Add(&keep0, MOJO_HANDLE_SIGNAL_WRITABLE, 0);
+ remove_first.Add(&keep1, MOJO_HANDLE_SIGNAL_WRITABLE, 0);
+
+ remove_first.AwakeForStateChange(hss);
+ EXPECT_EQ(keep0.awake_count, 1);
+ EXPECT_EQ(keep1.awake_count, 1);
+ EXPECT_EQ(remove2.awake_count, 1);
+
+ remove_first.AwakeForStateChange(hss);
+ EXPECT_EQ(keep0.awake_count, 2);
+ EXPECT_EQ(keep1.awake_count, 2);
+ EXPECT_EQ(remove2.awake_count, 1);
+
+ remove_first.Remove(&keep0);
+ remove_first.Remove(&keep1);
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/configuration.cc b/mojo/edk/system/configuration.cc
new file mode 100644
index 0000000..9aaed31
--- /dev/null
+++ b/mojo/edk/system/configuration.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 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 "mojo/edk/system/configuration.h"
+
+namespace mojo {
+namespace edk {
+namespace internal {
+
+// These default values should be synced with the documentation in
+// mojo/edk/embedder/configuration.h.
+Configuration g_configuration = {
+ 1000000, // max_handle_table_size
+ 1000000, // max_mapping_table_sze
+ 1000000, // max_wait_many_num_handles
+ 4 * 1024 * 1024, // max_message_num_bytes
+ 10000, // max_message_num_handles
+ 256 * 1024 * 1024, // max_data_pipe_capacity_bytes
+ 1024 * 1024, // default_data_pipe_capacity_bytes
+ 16, // data_pipe_buffer_alignment_bytes
+ 1024 * 1024 * 1024}; // max_shared_memory_num_bytes
+
+} // namespace internal
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/configuration.h b/mojo/edk/system/configuration.h
new file mode 100644
index 0000000..038835f
--- /dev/null
+++ b/mojo/edk/system/configuration.h
@@ -0,0 +1,29 @@
+// Copyright 2014 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 MOJO_EDK_SYSTEM_CONFIGURATION_H_
+#define MOJO_EDK_SYSTEM_CONFIGURATION_H_
+
+#include "mojo/edk/embedder/configuration.h"
+#include "mojo/edk/system/system_impl_export.h"
+
+namespace mojo {
+namespace edk {
+
+namespace internal {
+MOJO_SYSTEM_IMPL_EXPORT extern Configuration g_configuration;
+} // namespace internal
+
+MOJO_SYSTEM_IMPL_EXPORT inline const Configuration& GetConfiguration() {
+ return internal::g_configuration;
+}
+
+MOJO_SYSTEM_IMPL_EXPORT inline Configuration* GetMutableConfiguration() {
+ return &internal::g_configuration;
+}
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_CONFIGURATION_H_
diff --git a/mojo/edk/system/core.cc b/mojo/edk/system/core.cc
new file mode 100644
index 0000000..47872e0
--- /dev/null
+++ b/mojo/edk/system/core.cc
@@ -0,0 +1,603 @@
+// 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 "mojo/edk/system/core.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/embedder/platform_support.h"
+#include "mojo/edk/system/async_waiter.h"
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/data_pipe.h"
+#include "mojo/edk/system/data_pipe_consumer_dispatcher.h"
+#include "mojo/edk/system/data_pipe_producer_dispatcher.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/message_pipe_dispatcher.h"
+#include "mojo/edk/system/shared_buffer_dispatcher.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// Implementation notes
+//
+// Mojo primitives are implemented by the singleton |Core| object. Most calls
+// are for a "primary" handle (the first argument). |Core::GetDispatcher()| is
+// used to look up a |Dispatcher| object for a given handle. That object
+// implements most primitives for that object. The wait primitives are not
+// attached to objects and are implemented by |Core| itself.
+//
+// Some objects have multiple handles associated to them, e.g., message pipes
+// (which have two). In such a case, there is still a |Dispatcher| (e.g.,
+// |MessagePipeDispatcher|) for each handle, with each handle having a strong
+// reference to the common "secondary" object (e.g., |MessagePipe|). This
+// secondary object does NOT have any references to the |Dispatcher|s (even if
+// it did, it wouldn't be able to do anything with them due to lock order
+// requirements -- see below).
+//
+// Waiting is implemented by having the thread that wants to wait call the
+// |Dispatcher|s for the handles that it wants to wait on with a |Waiter|
+// object; this |Waiter| object may be created on the stack of that thread or be
+// kept in thread local storage for that thread (TODO(vtl): future improvement).
+// The |Dispatcher| then adds the |Waiter| to an |AwakableList| that's either
+// owned by that |Dispatcher| (see |SimpleDispatcher|) or by a secondary object
+// (e.g., |MessagePipe|). To signal/wake a |Waiter|, the object in question --
+// either a |SimpleDispatcher| or a secondary object -- talks to its
+// |AwakableList|.
+
+// Thread-safety notes
+//
+// Mojo primitives calls are thread-safe. We achieve this with relatively
+// fine-grained locking. There is a global handle table lock. This lock should
+// be held as briefly as possible (TODO(vtl): a future improvement would be to
+// switch it to a reader-writer lock). Each |Dispatcher| object then has a lock
+// (which subclasses can use to protect their data).
+//
+// The lock ordering is as follows:
+// 1. global handle table lock, global mapping table lock
+// 2. |Dispatcher| locks
+// 3. secondary object locks
+// ...
+// INF. |Waiter| locks
+//
+// Notes:
+// - While holding a |Dispatcher| lock, you may not unconditionally attempt
+// to take another |Dispatcher| lock. (This has consequences on the
+// concurrency semantics of |MojoWriteMessage()| when passing handles.)
+// Doing so would lead to deadlock.
+// - Locks at the "INF" level may not have any locks taken while they are
+// held.
+
+// TODO(vtl): This should take a |scoped_ptr<PlatformSupport>| as a parameter.
+Core::Core(PlatformSupport* platform_support)
+ : platform_support_(platform_support) {
+}
+
+Core::~Core() {
+}
+
+MojoHandle Core::AddDispatcher(const scoped_refptr<Dispatcher>& dispatcher) {
+ base::AutoLock locker(handle_table_lock_);
+ return handle_table_.AddDispatcher(dispatcher);
+}
+
+scoped_refptr<Dispatcher> Core::GetDispatcher(MojoHandle handle) {
+ if (handle == MOJO_HANDLE_INVALID)
+ return nullptr;
+
+ base::AutoLock locker(handle_table_lock_);
+ return handle_table_.GetDispatcher(handle);
+}
+
+MojoResult Core::GetAndRemoveDispatcher(MojoHandle handle,
+ scoped_refptr<Dispatcher>* dispatcher) {
+ if (handle == MOJO_HANDLE_INVALID)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ base::AutoLock locker(handle_table_lock_);
+ return handle_table_.GetAndRemoveDispatcher(handle, dispatcher);
+}
+
+MojoResult Core::AsyncWait(MojoHandle handle,
+ MojoHandleSignals signals,
+ const base::Callback<void(MojoResult)>& callback) {
+ scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handle);
+ DCHECK(dispatcher);
+
+ scoped_ptr<AsyncWaiter> waiter = make_scoped_ptr(new AsyncWaiter(callback));
+ MojoResult rv = dispatcher->AddAwakable(waiter.get(), signals, 0, nullptr);
+ if (rv == MOJO_RESULT_OK)
+ ignore_result(waiter.release());
+ return rv;
+}
+
+MojoTimeTicks Core::GetTimeTicksNow() {
+ return base::TimeTicks::Now().ToInternalValue();
+}
+
+MojoResult Core::Close(MojoHandle handle) {
+ if (handle == MOJO_HANDLE_INVALID)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ scoped_refptr<Dispatcher> dispatcher;
+ {
+ base::AutoLock locker(handle_table_lock_);
+ MojoResult result =
+ handle_table_.GetAndRemoveDispatcher(handle, &dispatcher);
+ if (result != MOJO_RESULT_OK)
+ return result;
+ }
+
+ // The dispatcher doesn't have a say in being closed, but gets notified of it.
+ // Note: This is done outside of |handle_table_lock_|. As a result, there's a
+ // race condition that the dispatcher must handle; see the comment in
+ // |Dispatcher| in dispatcher.h.
+ return dispatcher->Close();
+}
+
+MojoResult Core::Wait(MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoDeadline deadline,
+ MojoHandleSignalsState* signals_state) {
+ uint32_t unused = static_cast<uint32_t>(-1);
+ HandleSignalsState hss;
+ MojoResult rv = WaitManyInternal(&handle, &signals, 1, deadline, &unused,
+ signals_state ? &hss : nullptr);
+ if (rv != MOJO_RESULT_INVALID_ARGUMENT && signals_state)
+ *signals_state = hss;
+ return rv;
+}
+
+MojoResult Core::WaitMany(const MojoHandle* handles,
+ const MojoHandleSignals* signals,
+ uint32_t num_handles,
+ MojoDeadline deadline,
+ uint32_t* result_index,
+ MojoHandleSignalsState* signals_state) {
+ if (num_handles < 1)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (num_handles > GetConfiguration().max_wait_many_num_handles)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ uint32_t index = static_cast<uint32_t>(-1);
+ MojoResult rv;
+ if (!signals_state) {
+ rv = WaitManyInternal(handles, signals, num_handles, deadline, &index,
+ nullptr);
+ } else {
+ // Note: The |reinterpret_cast| is safe, since |HandleSignalsState| is a
+ // subclass of |MojoHandleSignalsState| that doesn't add any data members.
+ rv = WaitManyInternal(handles, signals, num_handles, deadline, &index,
+ reinterpret_cast<HandleSignalsState*>(signals_state));
+ }
+ if (index != static_cast<uint32_t>(-1) && result_index)
+ *result_index = index;
+ return rv;
+}
+
+MojoResult Core::CreateMessagePipe(
+ const MojoCreateMessagePipeOptions* options,
+ MojoHandle* message_pipe_handle0,
+ MojoHandle* message_pipe_handle1) {
+ CHECK(message_pipe_handle0);
+ CHECK(message_pipe_handle1);
+ MojoCreateMessagePipeOptions validated_options = {};
+ MojoResult result =
+ MessagePipeDispatcher::ValidateCreateOptions(options, &validated_options);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ scoped_refptr<MessagePipeDispatcher> dispatcher0 =
+ MessagePipeDispatcher::Create(validated_options);
+ scoped_refptr<MessagePipeDispatcher> dispatcher1 =
+ MessagePipeDispatcher::Create(validated_options);
+
+ std::pair<MojoHandle, MojoHandle> handle_pair;
+ {
+ base::AutoLock locker(handle_table_lock_);
+ handle_pair = handle_table_.AddDispatcherPair(dispatcher0, dispatcher1);
+ }
+ if (handle_pair.first == MOJO_HANDLE_INVALID) {
+ DCHECK_EQ(handle_pair.second, MOJO_HANDLE_INVALID);
+ LOG(ERROR) << "Handle table full";
+ dispatcher0->Close();
+ dispatcher1->Close();
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ PlatformChannelPair channel_pair;
+ dispatcher0->Init(channel_pair.PassServerHandle());
+ dispatcher1->Init(channel_pair.PassClientHandle());
+
+ *message_pipe_handle0 = handle_pair.first;
+ *message_pipe_handle1 = handle_pair.second;
+ return MOJO_RESULT_OK;
+}
+
+// Implementation note: To properly cancel waiters and avoid other races, this
+// does not transfer dispatchers from one handle to another, even when sending a
+// message in-process. Instead, it must transfer the "contents" of the
+// dispatcher to a new dispatcher, and then close the old dispatcher. If this
+// isn't done, in the in-process case, calls on the old handle may complete
+// after the the message has been received and a new handle created (and
+// possibly even after calls have been made on the new handle).
+MojoResult Core::WriteMessage(MojoHandle message_pipe_handle,
+ const void* bytes,
+ uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(GetDispatcher(message_pipe_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ // Easy case: not sending any handles.
+ if (num_handles == 0)
+ return dispatcher->WriteMessage(bytes, num_bytes, nullptr, flags);
+
+ // We have to handle |handles| here, since we have to mark them busy in the
+ // global handle table. We can't delegate this to the dispatcher, since the
+ // handle table lock must be acquired before the dispatcher lock.
+ //
+ // (This leads to an oddity: |handles|/|num_handles| are always verified for
+ // validity, even for dispatchers that don't support |WriteMessage()| and will
+ // simply return failure unconditionally. It also breaks the usual
+ // left-to-right verification order of arguments.)
+ if (num_handles > GetConfiguration().max_message_num_handles)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ // We'll need to hold on to the dispatchers so that we can pass them on to
+ // |WriteMessage()| and also so that we can unlock their locks afterwards
+ // without accessing the handle table. These can be dumb pointers, since their
+ // entries in the handle table won't get removed (since they'll be marked as
+ // busy).
+ std::vector<DispatcherTransport> transports(num_handles);
+
+ // When we pass handles, we have to try to take all their dispatchers' locks
+ // and mark the handles as busy. If the call succeeds, we then remove the
+ // handles from the handle table.
+ {
+ base::AutoLock locker(handle_table_lock_);
+ MojoResult result = handle_table_.MarkBusyAndStartTransport(
+ message_pipe_handle, handles, num_handles, &transports);
+ if (result != MOJO_RESULT_OK)
+ return result;
+ }
+
+ MojoResult rv =
+ dispatcher->WriteMessage(bytes, num_bytes, &transports, flags);
+
+ // We need to release the dispatcher locks before we take the handle table
+ // lock.
+ for (uint32_t i = 0; i < num_handles; i++)
+ transports[i].End();
+
+ {
+ base::AutoLock locker(handle_table_lock_);
+ if (rv == MOJO_RESULT_OK) {
+ handle_table_.RemoveBusyHandles(handles, num_handles);
+ } else {
+ handle_table_.RestoreBusyHandles(handles, num_handles);
+ }
+ }
+
+ return rv;
+}
+
+MojoResult Core::ReadMessage(MojoHandle message_pipe_handle,
+ void* bytes,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(GetDispatcher(message_pipe_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ MojoResult rv;
+ uint32_t num_handles_value = num_handles ? *num_handles : 0;
+ if (num_handles_value == 0) {
+ // Easy case: won't receive any handles.
+ rv = dispatcher->ReadMessage(bytes, num_bytes, nullptr, &num_handles_value,
+ flags);
+ } else {
+ DispatcherVector dispatchers;
+ rv = dispatcher->ReadMessage(bytes, num_bytes, &dispatchers,
+ &num_handles_value, flags);
+ if (!dispatchers.empty()) {
+ DCHECK_EQ(rv, MOJO_RESULT_OK);
+ DCHECK(num_handles);
+ DCHECK_LE(dispatchers.size(), static_cast<size_t>(num_handles_value));
+
+ bool success;
+ {
+ base::AutoLock locker(handle_table_lock_);
+ success = handle_table_.AddDispatcherVector(dispatchers, handles);
+ }
+ if (!success) {
+ LOG(ERROR) << "Received message with " << dispatchers.size()
+ << " handles, but handle table full";
+ // Close dispatchers (outside the lock).
+ for (size_t i = 0; i < dispatchers.size(); i++) {
+ if (dispatchers[i])
+ dispatchers[i]->Close();
+ }
+ if (rv == MOJO_RESULT_OK)
+ rv = MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+ }
+ }
+
+ if (num_handles)
+ *num_handles = num_handles_value;
+ return rv;
+}
+
+MojoResult Core::CreateDataPipe(
+ const MojoCreateDataPipeOptions* options,
+ MojoHandle* data_pipe_producer_handle,
+ MojoHandle* data_pipe_consumer_handle) {
+ MojoCreateDataPipeOptions validated_options = {};
+ MojoResult result =
+ DataPipe::ValidateCreateOptions(options, &validated_options);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ scoped_refptr<DataPipeProducerDispatcher> producer_dispatcher =
+ DataPipeProducerDispatcher::Create(validated_options);
+ scoped_refptr<DataPipeConsumerDispatcher> consumer_dispatcher =
+ DataPipeConsumerDispatcher::Create(validated_options);
+
+ std::pair<MojoHandle, MojoHandle> handle_pair;
+ {
+ base::AutoLock locker(handle_table_lock_);
+ handle_pair = handle_table_.AddDispatcherPair(producer_dispatcher,
+ consumer_dispatcher);
+ }
+ if (handle_pair.first == MOJO_HANDLE_INVALID) {
+ DCHECK_EQ(handle_pair.second, MOJO_HANDLE_INVALID);
+ LOG(ERROR) << "Handle table full";
+ producer_dispatcher->Close();
+ consumer_dispatcher->Close();
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+ DCHECK_NE(handle_pair.second, MOJO_HANDLE_INVALID);
+
+ PlatformChannelPair channel_pair;
+ producer_dispatcher->Init(channel_pair.PassServerHandle());
+ consumer_dispatcher->Init(channel_pair.PassClientHandle());
+
+ *data_pipe_producer_handle = handle_pair.first;
+ *data_pipe_consumer_handle = handle_pair.second;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::WriteData(MojoHandle data_pipe_producer_handle,
+ const void* elements,
+ uint32_t* num_bytes,
+ MojoWriteDataFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_producer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->WriteData(elements, num_bytes, flags);
+}
+
+MojoResult Core::BeginWriteData(MojoHandle data_pipe_producer_handle,
+ void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoWriteDataFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_producer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->BeginWriteData(buffer, buffer_num_bytes, flags);
+}
+
+MojoResult Core::EndWriteData(MojoHandle data_pipe_producer_handle,
+ uint32_t num_bytes_written) {
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_producer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->EndWriteData(num_bytes_written);
+}
+
+MojoResult Core::ReadData(MojoHandle data_pipe_consumer_handle,
+ void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_consumer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->ReadData(elements, num_bytes, flags);
+}
+
+MojoResult Core::BeginReadData(MojoHandle data_pipe_consumer_handle,
+ const void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoReadDataFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_consumer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->BeginReadData(buffer, buffer_num_bytes, flags);
+}
+
+MojoResult Core::EndReadData(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_bytes_read) {
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_consumer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->EndReadData(num_bytes_read);
+}
+
+MojoResult Core::CreateSharedBuffer(
+ const MojoCreateSharedBufferOptions* options,
+ uint64_t num_bytes,
+ MojoHandle* shared_buffer_handle) {
+ MojoCreateSharedBufferOptions validated_options = {};
+ MojoResult result = SharedBufferDispatcher::ValidateCreateOptions(
+ options, &validated_options);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ result = SharedBufferDispatcher::Create(platform_support_, validated_options,
+ num_bytes, &dispatcher);
+ if (result != MOJO_RESULT_OK) {
+ DCHECK(!dispatcher);
+ return result;
+ }
+
+ *shared_buffer_handle = AddDispatcher(dispatcher);
+ if (*shared_buffer_handle == MOJO_HANDLE_INVALID) {
+ LOG(ERROR) << "Handle table full";
+ dispatcher->Close();
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::DuplicateBufferHandle(
+ MojoHandle buffer_handle,
+ const MojoDuplicateBufferHandleOptions* options,
+ MojoHandle* new_buffer_handle) {
+ scoped_refptr<Dispatcher> dispatcher(GetDispatcher(buffer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ // Don't verify |options| here; that's the dispatcher's job.
+ scoped_refptr<Dispatcher> new_dispatcher;
+ MojoResult result =
+ dispatcher->DuplicateBufferHandle(options, &new_dispatcher);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ *new_buffer_handle = AddDispatcher(new_dispatcher);
+ if (*new_buffer_handle == MOJO_HANDLE_INVALID) {
+ LOG(ERROR) << "Handle table full";
+ dispatcher->Close();
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::MapBuffer(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ void** buffer,
+ MojoMapBufferFlags flags) {
+ scoped_refptr<Dispatcher> dispatcher(GetDispatcher(buffer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ scoped_ptr<PlatformSharedBufferMapping> mapping;
+ MojoResult result = dispatcher->MapBuffer(offset, num_bytes, flags, &mapping);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ DCHECK(mapping);
+ void* address = mapping->GetBase();
+ {
+ base::AutoLock locker(mapping_table_lock_);
+ result = mapping_table_.AddMapping(mapping.Pass());
+ }
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ *buffer = address;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::UnmapBuffer(void* buffer) {
+ base::AutoLock locker(mapping_table_lock_);
+ return mapping_table_.RemoveMapping(buffer);
+}
+
+// Note: We allow |handles| to repeat the same handle multiple times, since
+// different flags may be specified.
+// TODO(vtl): This incurs a performance cost in |Remove()|. Analyze this
+// more carefully and address it if necessary.
+MojoResult Core::WaitManyInternal(const MojoHandle* handles,
+ const MojoHandleSignals* signals,
+ uint32_t num_handles,
+ MojoDeadline deadline,
+ uint32_t* result_index,
+ HandleSignalsState* signals_states) {
+ CHECK(handles);
+ CHECK(signals);
+ DCHECK_GT(num_handles, 0u);
+ if (result_index) {
+ DCHECK_EQ(*result_index, static_cast<uint32_t>(-1));
+ }
+
+ DispatcherVector dispatchers;
+ dispatchers.reserve(num_handles);
+ for (uint32_t i = 0; i < num_handles; i++) {
+ scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handles[i]);
+ if (!dispatcher) {
+ if (result_index && result_index)
+ *result_index = i;
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+ dispatchers.push_back(dispatcher);
+ }
+
+ // TODO(vtl): Should make the waiter live (permanently) in TLS.
+ Waiter waiter;
+ waiter.Init();
+
+ uint32_t i;
+ MojoResult rv = MOJO_RESULT_OK;
+ for (i = 0; i < num_handles; i++) {
+ rv = dispatchers[i]->AddAwakable(
+ &waiter, signals[i], i, signals_states ? &signals_states[i] : nullptr);
+ if (rv != MOJO_RESULT_OK) {
+ if (result_index)
+ *result_index = i;
+ break;
+ }
+ }
+ uint32_t num_added = i;
+
+ if (rv == MOJO_RESULT_ALREADY_EXISTS)
+ rv = MOJO_RESULT_OK; // The i-th one is already "triggered".
+ else if (rv == MOJO_RESULT_OK)
+ rv = waiter.Wait(deadline, result_index);
+
+ // Make sure no other dispatchers try to wake |waiter| for the current
+ // |Wait()|/|WaitMany()| call. (Only after doing this can |waiter| be
+ // destroyed, but this would still be required if the waiter were in TLS.)
+ for (i = 0; i < num_added; i++) {
+ dispatchers[i]->RemoveAwakable(
+ &waiter, signals_states ? &signals_states[i] : nullptr);
+ }
+ if (signals_states) {
+ for (; i < num_handles; i++)
+ signals_states[i] = dispatchers[i]->GetHandleSignalsState();
+ }
+
+ return rv;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/core.h b/mojo/edk/system/core.h
new file mode 100644
index 0000000..3f710fa
--- /dev/null
+++ b/mojo/edk/system/core.h
@@ -0,0 +1,188 @@
+// 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 MOJO_EDK_SYSTEM_CORE_H_
+#define MOJO_EDK_SYSTEM_CORE_H_
+
+#include <stdint.h>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/system/handle_table.h"
+#include "mojo/edk/system/mapping_table.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/buffer.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+namespace edk {
+
+class Dispatcher;
+class PlatformSupport;
+struct HandleSignalsState;
+
+// |Core| is an object that implements the Mojo system calls. All public methods
+// are thread-safe.
+class MOJO_SYSTEM_IMPL_EXPORT Core {
+ public:
+ // ---------------------------------------------------------------------------
+
+ // These methods are only to be used by via the embedder API (and internally):
+
+ // |*platform_support| must outlive this object.
+ explicit Core(PlatformSupport* platform_support);
+ virtual ~Core();
+
+ // Adds |dispatcher| to the handle table, returning the handle for it. Returns
+ // |MOJO_HANDLE_INVALID| on failure, namely if the handle table is full.
+ MojoHandle AddDispatcher(const scoped_refptr<Dispatcher>& dispatcher);
+
+ // Looks up the dispatcher for the given handle. Returns null if the handle is
+ // invalid.
+ scoped_refptr<Dispatcher> GetDispatcher(MojoHandle handle);
+
+ // Like |GetDispatcher()|, but also removes the handle from the handle table.
+ // On success, gets the dispatcher for a given handle (which should not be
+ // |MOJO_HANDLE_INVALID|) and removes it. (On failure, returns an appropriate
+ // result (and leaves |dispatcher| alone), namely
+ // |MOJO_RESULT_INVALID_ARGUMENT| if there's no dispatcher for the given
+ // handle or |MOJO_RESULT_BUSY| if the handle is marked as busy.)
+ MojoResult GetAndRemoveDispatcher(MojoHandle handle,
+ scoped_refptr<Dispatcher>* dispatcher);
+
+ // Watches on the given handle for the given signals, calling |callback| when
+ // a signal is satisfied or when all signals become unsatisfiable. |callback|
+ // must satisfy stringent requirements -- see |Awakable::Awake()| in
+ // awakable.h. In particular, it must not call any Mojo system functions.
+ MojoResult AsyncWait(MojoHandle handle,
+ MojoHandleSignals signals,
+ const base::Callback<void(MojoResult)>& callback);
+
+ PlatformSupport* platform_support() const {
+ return platform_support_;
+ }
+
+ // ---------------------------------------------------------------------------
+
+ // The following methods are essentially implementations of the Mojo Core
+ // functions of the Mojo API, with the C interface translated to C++ by
+ // "mojo/edk/embedder/entrypoints.cc". The best way to understand the contract
+ // of these methods is to look at the header files defining the corresponding
+ // API functions, referenced below.
+
+ // These methods correspond to the API functions defined in
+ // "mojo/public/c/system/functions.h":
+ MojoTimeTicks GetTimeTicksNow();
+ MojoResult Close(MojoHandle handle);
+ MojoResult Wait(MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoDeadline deadline,
+ MojoHandleSignalsState* signals_state);
+ MojoResult WaitMany(const MojoHandle* handles,
+ const MojoHandleSignals* signals,
+ uint32_t num_handles,
+ MojoDeadline deadline,
+ uint32_t* result_index,
+ MojoHandleSignalsState* signals_states);
+
+ // These methods correspond to the API functions defined in
+ // "mojo/public/c/system/message_pipe.h":
+ MojoResult CreateMessagePipe(
+ const MojoCreateMessagePipeOptions* options,
+ MojoHandle* message_pipe_handle0,
+ MojoHandle* message_pipe_handle1);
+ MojoResult WriteMessage(MojoHandle message_pipe_handle,
+ const void* bytes,
+ uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags);
+ MojoResult ReadMessage(MojoHandle message_pipe_handle,
+ void* bytes,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags);
+
+ // These methods correspond to the API functions defined in
+ // "mojo/public/c/system/data_pipe.h":
+ MojoResult CreateDataPipe(
+ const MojoCreateDataPipeOptions* options,
+ MojoHandle* data_pipe_producer_handle,
+ MojoHandle* data_pipe_consumer_handle);
+ MojoResult WriteData(MojoHandle data_pipe_producer_handle,
+ const void* elements,
+ uint32_t* num_bytes,
+ MojoWriteDataFlags flags);
+ MojoResult BeginWriteData(MojoHandle data_pipe_producer_handle,
+ void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoWriteDataFlags flags);
+ MojoResult EndWriteData(MojoHandle data_pipe_producer_handle,
+ uint32_t num_bytes_written);
+ MojoResult ReadData(MojoHandle data_pipe_consumer_handle,
+ void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags);
+ MojoResult BeginReadData(MojoHandle data_pipe_consumer_handle,
+ const void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoReadDataFlags flags);
+ MojoResult EndReadData(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_bytes_read);
+
+ // These methods correspond to the API functions defined in
+ // "mojo/public/c/system/buffer.h":
+ MojoResult CreateSharedBuffer(
+ const MojoCreateSharedBufferOptions* options,
+ uint64_t num_bytes,
+ MojoHandle* shared_buffer_handle);
+ MojoResult DuplicateBufferHandle(
+ MojoHandle buffer_handle,
+ const MojoDuplicateBufferHandleOptions* options,
+ MojoHandle* new_buffer_handle);
+ MojoResult MapBuffer(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ void** buffer,
+ MojoMapBufferFlags flags);
+ MojoResult UnmapBuffer(void* buffer);
+
+ private:
+ friend bool internal::ShutdownCheckNoLeaks(Core*);
+
+ // Internal implementation of |Wait()| and |WaitMany()|; doesn't do basic
+ // validation of arguments. |*result_index| is only set if the result (whether
+ // success or failure) applies to a specific handle, so its value should be
+ // preinitialized to |static_cast<uint32_t>(-1)|.
+ MojoResult WaitManyInternal(const MojoHandle* handles,
+ const MojoHandleSignals* signals,
+ uint32_t num_handles,
+ MojoDeadline deadline,
+ uint32_t* result_index,
+ HandleSignalsState* signals_states);
+
+ PlatformSupport* const platform_support_;
+
+ // TODO(vtl): |handle_table_lock_| should be a reader-writer lock (if only we
+ // had them).
+ base::Lock handle_table_lock_; // Protects |handle_table_|.
+ HandleTable handle_table_;
+
+ base::Lock mapping_table_lock_; // Protects |mapping_table_|.
+ MappingTable mapping_table_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_CORE_H_
diff --git a/mojo/edk/system/core_test_base.cc b/mojo/edk/system/core_test_base.cc
new file mode 100644
index 0000000..9962767
--- /dev/null
+++ b/mojo/edk/system/core_test_base.cc
@@ -0,0 +1,373 @@
+// 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 "mojo/edk/system/core_test_base.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/core.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+namespace {
+
+// MockDispatcher --------------------------------------------------------------
+
+class MockDispatcher : public Dispatcher {
+ public:
+ static scoped_refptr<MockDispatcher> Create(
+ CoreTestBase::MockHandleInfo* info) {
+ return make_scoped_refptr(new MockDispatcher(info));
+ }
+
+ // |Dispatcher| private methods:
+ Type GetType() const override { return Type::UNKNOWN; }
+
+ private:
+ explicit MockDispatcher(CoreTestBase::MockHandleInfo* info) : info_(info) {
+ CHECK(info_);
+ info_->IncrementCtorCallCount();
+ }
+
+ ~MockDispatcher() override { info_->IncrementDtorCallCount(); }
+
+ // |Dispatcher| protected methods:
+ void CloseImplNoLock() override {
+ info_->IncrementCloseCallCount();
+ lock().AssertAcquired();
+ }
+
+ MojoResult WriteMessageImplNoLock(
+ const void* bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags /*flags*/) override {
+ info_->IncrementWriteMessageCallCount();
+ lock().AssertAcquired();
+
+ if (num_bytes > GetConfiguration().max_message_num_bytes)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ if (transports)
+ return MOJO_RESULT_UNIMPLEMENTED;
+
+ return MOJO_RESULT_OK;
+ }
+
+ MojoResult ReadMessageImplNoLock(void* bytes,
+ uint32_t* num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags /*flags*/) override {
+ info_->IncrementReadMessageCallCount();
+ lock().AssertAcquired();
+
+ if (num_dispatchers) {
+ *num_dispatchers = 1;
+ if (dispatchers) {
+ // Okay to leave an invalid dispatcher.
+ dispatchers->resize(1);
+ }
+ }
+
+ return MOJO_RESULT_OK;
+ }
+
+ MojoResult WriteDataImplNoLock(const void* /*elements*/,
+ uint32_t* /*num_bytes*/,
+ MojoWriteDataFlags /*flags*/) override {
+ info_->IncrementWriteDataCallCount();
+ lock().AssertAcquired();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ MojoResult BeginWriteDataImplNoLock(
+ void** /*buffer*/,
+ uint32_t* /*buffer_num_bytes*/,
+ MojoWriteDataFlags /*flags*/) override {
+ info_->IncrementBeginWriteDataCallCount();
+ lock().AssertAcquired();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ MojoResult EndWriteDataImplNoLock(uint32_t /*num_bytes_written*/) override {
+ info_->IncrementEndWriteDataCallCount();
+ lock().AssertAcquired();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ MojoResult ReadDataImplNoLock(void* /*elements*/,
+ uint32_t* /*num_bytes*/,
+ MojoReadDataFlags /*flags*/) override {
+ info_->IncrementReadDataCallCount();
+ lock().AssertAcquired();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ MojoResult BeginReadDataImplNoLock(const void** /*buffer*/,
+ uint32_t* /*buffer_num_bytes*/,
+ MojoReadDataFlags /*flags*/) override {
+ info_->IncrementBeginReadDataCallCount();
+ lock().AssertAcquired();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ MojoResult EndReadDataImplNoLock(uint32_t /*num_bytes_read*/) override {
+ info_->IncrementEndReadDataCallCount();
+ lock().AssertAcquired();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ MojoResult AddAwakableImplNoLock(Awakable* awakable,
+ MojoHandleSignals /*signals*/,
+ uint32_t /*context*/,
+ HandleSignalsState* signals_state) override {
+ info_->IncrementAddAwakableCallCount();
+ lock().AssertAcquired();
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+ if (info_->IsAddAwakableAllowed()) {
+ info_->AwakableWasAdded(awakable);
+ return MOJO_RESULT_OK;
+ }
+
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ void RemoveAwakableImplNoLock(Awakable* /*awakable*/,
+ HandleSignalsState* signals_state) override {
+ info_->IncrementRemoveAwakableCallCount();
+ lock().AssertAcquired();
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+ }
+
+ void CancelAllAwakablesNoLock() override {
+ info_->IncrementCancelAllAwakablesCallCount();
+ lock().AssertAcquired();
+ }
+
+ scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndCloseImplNoLock()
+ override {
+ return Create(info_);
+ }
+
+ CoreTestBase::MockHandleInfo* const info_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MockDispatcher);
+};
+
+} // namespace
+
+// CoreTestBase ----------------------------------------------------------------
+
+CoreTestBase::CoreTestBase() {
+}
+
+CoreTestBase::~CoreTestBase() {
+}
+
+MojoHandle CoreTestBase::CreateMockHandle(CoreTestBase::MockHandleInfo* info) {
+ scoped_refptr<MockDispatcher> dispatcher = MockDispatcher::Create(info);
+ return core()->AddDispatcher(dispatcher);
+}
+
+// CoreTestBase_MockHandleInfo -------------------------------------------------
+
+CoreTestBase_MockHandleInfo::CoreTestBase_MockHandleInfo()
+ : ctor_call_count_(0),
+ dtor_call_count_(0),
+ close_call_count_(0),
+ write_message_call_count_(0),
+ read_message_call_count_(0),
+ write_data_call_count_(0),
+ begin_write_data_call_count_(0),
+ end_write_data_call_count_(0),
+ read_data_call_count_(0),
+ begin_read_data_call_count_(0),
+ end_read_data_call_count_(0),
+ add_awakable_call_count_(0),
+ remove_awakable_call_count_(0),
+ cancel_all_awakables_call_count_(0),
+ add_awakable_allowed_(false) {
+}
+
+CoreTestBase_MockHandleInfo::~CoreTestBase_MockHandleInfo() {
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetCtorCallCount() const {
+ base::AutoLock locker(lock_);
+ return ctor_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetDtorCallCount() const {
+ base::AutoLock locker(lock_);
+ return dtor_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetCloseCallCount() const {
+ base::AutoLock locker(lock_);
+ return close_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetWriteMessageCallCount() const {
+ base::AutoLock locker(lock_);
+ return write_message_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetReadMessageCallCount() const {
+ base::AutoLock locker(lock_);
+ return read_message_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetWriteDataCallCount() const {
+ base::AutoLock locker(lock_);
+ return write_data_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetBeginWriteDataCallCount() const {
+ base::AutoLock locker(lock_);
+ return begin_write_data_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetEndWriteDataCallCount() const {
+ base::AutoLock locker(lock_);
+ return end_write_data_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetReadDataCallCount() const {
+ base::AutoLock locker(lock_);
+ return read_data_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetBeginReadDataCallCount() const {
+ base::AutoLock locker(lock_);
+ return begin_read_data_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetEndReadDataCallCount() const {
+ base::AutoLock locker(lock_);
+ return end_read_data_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetAddAwakableCallCount() const {
+ base::AutoLock locker(lock_);
+ return add_awakable_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetRemoveAwakableCallCount() const {
+ base::AutoLock locker(lock_);
+ return remove_awakable_call_count_;
+}
+
+unsigned CoreTestBase_MockHandleInfo::GetCancelAllAwakablesCallCount() const {
+ base::AutoLock locker(lock_);
+ return cancel_all_awakables_call_count_;
+}
+
+size_t CoreTestBase_MockHandleInfo::GetAddedAwakableSize() const {
+ base::AutoLock locker(lock_);
+ return added_awakables_.size();
+}
+
+Awakable* CoreTestBase_MockHandleInfo::GetAddedAwakableAt(unsigned i) const {
+ base::AutoLock locker(lock_);
+ return added_awakables_[i];
+}
+
+void CoreTestBase_MockHandleInfo::IncrementCtorCallCount() {
+ base::AutoLock locker(lock_);
+ ctor_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementDtorCallCount() {
+ base::AutoLock locker(lock_);
+ dtor_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementCloseCallCount() {
+ base::AutoLock locker(lock_);
+ close_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementWriteMessageCallCount() {
+ base::AutoLock locker(lock_);
+ write_message_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementReadMessageCallCount() {
+ base::AutoLock locker(lock_);
+ read_message_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementWriteDataCallCount() {
+ base::AutoLock locker(lock_);
+ write_data_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementBeginWriteDataCallCount() {
+ base::AutoLock locker(lock_);
+ begin_write_data_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementEndWriteDataCallCount() {
+ base::AutoLock locker(lock_);
+ end_write_data_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementReadDataCallCount() {
+ base::AutoLock locker(lock_);
+ read_data_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementBeginReadDataCallCount() {
+ base::AutoLock locker(lock_);
+ begin_read_data_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementEndReadDataCallCount() {
+ base::AutoLock locker(lock_);
+ end_read_data_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementAddAwakableCallCount() {
+ base::AutoLock locker(lock_);
+ add_awakable_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementRemoveAwakableCallCount() {
+ base::AutoLock locker(lock_);
+ remove_awakable_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::IncrementCancelAllAwakablesCallCount() {
+ base::AutoLock locker(lock_);
+ cancel_all_awakables_call_count_++;
+}
+
+void CoreTestBase_MockHandleInfo::AllowAddAwakable(bool alllow) {
+ base::AutoLock locker(lock_);
+ add_awakable_allowed_ = alllow;
+}
+
+bool CoreTestBase_MockHandleInfo::IsAddAwakableAllowed() const {
+ base::AutoLock locker(lock_);
+ return add_awakable_allowed_;
+}
+
+void CoreTestBase_MockHandleInfo::AwakableWasAdded(Awakable* awakable) {
+ base::AutoLock locker(lock_);
+ added_awakables_.push_back(awakable);
+}
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/core_test_base.h b/mojo/edk/system/core_test_base.h
new file mode 100644
index 0000000..bbdaa5c
--- /dev/null
+++ b/mojo/edk/system/core_test_base.h
@@ -0,0 +1,112 @@
+// 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 MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_
+#define MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_
+
+#include "base/synchronization/lock.h"
+#include "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+
+class Core;
+class Awakable;
+
+namespace test {
+
+class CoreTestBase_MockHandleInfo;
+
+class CoreTestBase : public MojoSystemTest {
+ public:
+ using MockHandleInfo = CoreTestBase_MockHandleInfo;
+
+ CoreTestBase();
+ ~CoreTestBase() override;
+
+ protected:
+ // |info| must remain alive until the returned handle is closed.
+ MojoHandle CreateMockHandle(MockHandleInfo* info);
+
+ Core* core() { return mojo::edk::internal::g_core; }
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(CoreTestBase);
+};
+
+class CoreTestBase_MockHandleInfo {
+ public:
+ CoreTestBase_MockHandleInfo();
+ ~CoreTestBase_MockHandleInfo();
+
+ unsigned GetCtorCallCount() const;
+ unsigned GetDtorCallCount() const;
+ unsigned GetCloseCallCount() const;
+ unsigned GetWriteMessageCallCount() const;
+ unsigned GetReadMessageCallCount() const;
+ unsigned GetWriteDataCallCount() const;
+ unsigned GetBeginWriteDataCallCount() const;
+ unsigned GetEndWriteDataCallCount() const;
+ unsigned GetReadDataCallCount() const;
+ unsigned GetBeginReadDataCallCount() const;
+ unsigned GetEndReadDataCallCount() const;
+ unsigned GetAddAwakableCallCount() const;
+ unsigned GetRemoveAwakableCallCount() const;
+ unsigned GetCancelAllAwakablesCallCount() const;
+
+ size_t GetAddedAwakableSize() const;
+ Awakable* GetAddedAwakableAt(unsigned i) const;
+
+ // For use by |MockDispatcher|:
+ void IncrementCtorCallCount();
+ void IncrementDtorCallCount();
+ void IncrementCloseCallCount();
+ void IncrementWriteMessageCallCount();
+ void IncrementReadMessageCallCount();
+ void IncrementWriteDataCallCount();
+ void IncrementBeginWriteDataCallCount();
+ void IncrementEndWriteDataCallCount();
+ void IncrementReadDataCallCount();
+ void IncrementBeginReadDataCallCount();
+ void IncrementEndReadDataCallCount();
+ void IncrementAddAwakableCallCount();
+ void IncrementRemoveAwakableCallCount();
+ void IncrementCancelAllAwakablesCallCount();
+
+ void AllowAddAwakable(bool alllow);
+ bool IsAddAwakableAllowed() const;
+ void AwakableWasAdded(Awakable*);
+
+ private:
+ mutable base::Lock lock_; // Protects the following members.
+ unsigned ctor_call_count_;
+ unsigned dtor_call_count_;
+ unsigned close_call_count_;
+ unsigned write_message_call_count_;
+ unsigned read_message_call_count_;
+ unsigned write_data_call_count_;
+ unsigned begin_write_data_call_count_;
+ unsigned end_write_data_call_count_;
+ unsigned read_data_call_count_;
+ unsigned begin_read_data_call_count_;
+ unsigned end_read_data_call_count_;
+ unsigned add_awakable_call_count_;
+ unsigned remove_awakable_call_count_;
+ unsigned cancel_all_awakables_call_count_;
+
+ bool add_awakable_allowed_;
+ std::vector<Awakable*> added_awakables_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(CoreTestBase_MockHandleInfo);
+};
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_
diff --git a/mojo/edk/system/core_unittest.cc b/mojo/edk/system/core_unittest.cc
new file mode 100644
index 0000000..bd7dfd9
--- /dev/null
+++ b/mojo/edk/system/core_unittest.cc
@@ -0,0 +1,1265 @@
+// 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 "mojo/edk/system/core.h"
+
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/bind.h"
+#include "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/system/awakable.h"
+#include "mojo/edk/system/core_test_base.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+const MojoHandleSignalsState kEmptyMojoHandleSignalsState = {0u, 0u};
+const MojoHandleSignalsState kFullMojoHandleSignalsState = {~0u, ~0u};
+const MojoHandleSignals kAllSignals = MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+
+using CoreTest = test::CoreTestBase;
+
+TEST_F(CoreTest, GetTimeTicksNow) {
+ const MojoTimeTicks start = core()->GetTimeTicksNow();
+ EXPECT_NE(static_cast<MojoTimeTicks>(0), start)
+ << "GetTimeTicksNow should return nonzero value";
+ test::Sleep(test::DeadlineFromMilliseconds(15));
+ const MojoTimeTicks finish = core()->GetTimeTicksNow();
+ // Allow for some fuzz in sleep.
+ EXPECT_GE((finish - start), static_cast<MojoTimeTicks>(8000))
+ << "Sleeping should result in increasing time ticks";
+}
+
+TEST_F(CoreTest, Basic) {
+ MockHandleInfo info;
+
+ EXPECT_EQ(0u, info.GetCtorCallCount());
+ MojoHandle h = CreateMockHandle(&info);
+ EXPECT_EQ(1u, info.GetCtorCallCount());
+ EXPECT_NE(h, MOJO_HANDLE_INVALID);
+
+ EXPECT_EQ(0u, info.GetWriteMessageCallCount());
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h, nullptr, 0, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetWriteMessageCallCount());
+
+ EXPECT_EQ(0u, info.GetReadMessageCallCount());
+ uint32_t num_bytes = 0;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ core()->ReadMessage(h, nullptr, &num_bytes, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetReadMessageCallCount());
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h, nullptr, nullptr, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(2u, info.GetReadMessageCallCount());
+
+ EXPECT_EQ(0u, info.GetWriteDataCallCount());
+ EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ core()->WriteData(h, nullptr, nullptr, MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetWriteDataCallCount());
+
+ EXPECT_EQ(0u, info.GetBeginWriteDataCallCount());
+ EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ core()->BeginWriteData(h, nullptr, nullptr,
+ MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetBeginWriteDataCallCount());
+
+ EXPECT_EQ(0u, info.GetEndWriteDataCallCount());
+ EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, core()->EndWriteData(h, 0));
+ EXPECT_EQ(1u, info.GetEndWriteDataCallCount());
+
+ EXPECT_EQ(0u, info.GetReadDataCallCount());
+ EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ core()->ReadData(h, nullptr, nullptr, MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetReadDataCallCount());
+
+ EXPECT_EQ(0u, info.GetBeginReadDataCallCount());
+ EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ core()->BeginReadData(h, nullptr, nullptr,
+ MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetBeginReadDataCallCount());
+
+ EXPECT_EQ(0u, info.GetEndReadDataCallCount());
+ EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, core()->EndReadData(h, 0));
+ EXPECT_EQ(1u, info.GetEndReadDataCallCount());
+
+ EXPECT_EQ(0u, info.GetAddAwakableCallCount());
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, MOJO_DEADLINE_INDEFINITE,
+ nullptr));
+ EXPECT_EQ(1u, info.GetAddAwakableCallCount());
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 0, nullptr));
+ EXPECT_EQ(2u, info.GetAddAwakableCallCount());
+ MojoHandleSignalsState hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, MOJO_DEADLINE_INDEFINITE,
+ &hss));
+ EXPECT_EQ(3u, info.GetAddAwakableCallCount());
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 10 * 1000, nullptr));
+ EXPECT_EQ(4u, info.GetAddAwakableCallCount());
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h, ~MOJO_HANDLE_SIGNAL_NONE, 10 * 1000, &hss));
+ EXPECT_EQ(5u, info.GetAddAwakableCallCount());
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ MojoHandleSignals handle_signals = ~MOJO_HANDLE_SIGNAL_NONE;
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE,
+ nullptr, nullptr));
+ EXPECT_EQ(6u, info.GetAddAwakableCallCount());
+ uint32_t result_index = static_cast<uint32_t>(-1);
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE,
+ &result_index, nullptr));
+ EXPECT_EQ(7u, info.GetAddAwakableCallCount());
+ EXPECT_EQ(0u, result_index);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE,
+ nullptr, &hss));
+ EXPECT_EQ(8u, info.GetAddAwakableCallCount());
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ result_index = static_cast<uint32_t>(-1);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(&h, &handle_signals, 1, MOJO_DEADLINE_INDEFINITE,
+ &result_index, &hss));
+ EXPECT_EQ(9u, info.GetAddAwakableCallCount());
+ EXPECT_EQ(0u, result_index);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ EXPECT_EQ(0u, info.GetDtorCallCount());
+ EXPECT_EQ(0u, info.GetCloseCallCount());
+ EXPECT_EQ(0u, info.GetCancelAllAwakablesCallCount());
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h));
+ EXPECT_EQ(1u, info.GetCancelAllAwakablesCallCount());
+ EXPECT_EQ(1u, info.GetCloseCallCount());
+ EXPECT_EQ(1u, info.GetDtorCallCount());
+
+ // No awakables should ever have ever been added.
+ EXPECT_EQ(0u, info.GetRemoveAwakableCallCount());
+}
+
+TEST_F(CoreTest, InvalidArguments) {
+ // |Close()|:
+ {
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(MOJO_HANDLE_INVALID));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(10));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(1000000000));
+
+ // Test a double-close.
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h));
+ EXPECT_EQ(1u, info.GetCloseCallCount());
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(h));
+ EXPECT_EQ(1u, info.GetCloseCallCount());
+ }
+
+ // |Wait()|:
+ {
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->Wait(MOJO_HANDLE_INVALID, ~MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_DEADLINE_INDEFINITE, nullptr));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->Wait(10, ~MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_DEADLINE_INDEFINITE, nullptr));
+
+ MojoHandleSignalsState hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->Wait(MOJO_HANDLE_INVALID, ~MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ // On invalid argument, it shouldn't modify the handle signals state.
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
+ hss.satisfied_signals);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
+ hss.satisfiable_signals);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->Wait(10, ~MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ // On invalid argument, it shouldn't modify the handle signals state.
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
+ hss.satisfied_signals);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
+ hss.satisfiable_signals);
+ }
+
+ // |WaitMany()|:
+ {
+ MojoHandle handles[2] = {MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID};
+ MojoHandleSignals signals[2] = {~MOJO_HANDLE_SIGNAL_NONE,
+ ~MOJO_HANDLE_SIGNAL_NONE};
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(handles, signals, 0, MOJO_DEADLINE_INDEFINITE,
+ nullptr, nullptr));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(nullptr, signals, 0, MOJO_DEADLINE_INDEFINITE,
+ nullptr, nullptr));
+ // If |num_handles| is invalid, it should leave |result_index| and
+ // |signals_states| alone.
+ // (We use -1 internally; make sure that doesn't leak.)
+ uint32_t result_index = 123;
+ MojoHandleSignalsState hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(nullptr, signals, 0, MOJO_DEADLINE_INDEFINITE,
+ &result_index, &hss));
+ EXPECT_EQ(123u, result_index);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
+ hss.satisfied_signals);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
+ hss.satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(handles, nullptr, 0, MOJO_DEADLINE_INDEFINITE,
+ nullptr, nullptr));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(handles, signals, 1, MOJO_DEADLINE_INDEFINITE, nullptr,
+ nullptr));
+ // But if a handle is bad, then it should set |result_index| but still leave
+ // |signals_states| alone.
+ result_index = static_cast<uint32_t>(-1);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(
+ handles, signals, 1, MOJO_DEADLINE_INDEFINITE, &result_index,
+ &hss));
+ EXPECT_EQ(0u, result_index);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
+ hss.satisfied_signals);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
+ hss.satisfiable_signals);
+
+ MockHandleInfo info[2];
+ handles[0] = CreateMockHandle(&info[0]);
+
+ result_index = static_cast<uint32_t>(-1);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(
+ handles, signals, 1, MOJO_DEADLINE_INDEFINITE, &result_index,
+ &hss));
+ EXPECT_EQ(0u, result_index);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ // On invalid argument, it'll leave |signals_states| alone.
+ result_index = static_cast<uint32_t>(-1);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(
+ handles, signals, 2, MOJO_DEADLINE_INDEFINITE, &result_index,
+ &hss));
+ EXPECT_EQ(1u, result_index);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfied_signals,
+ hss.satisfied_signals);
+ EXPECT_EQ(kFullMojoHandleSignalsState.satisfiable_signals,
+ hss.satisfiable_signals);
+ handles[1] = handles[0] + 1; // Invalid handle.
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WaitMany(handles, signals, 2, MOJO_DEADLINE_INDEFINITE, nullptr,
+ nullptr));
+ handles[1] = CreateMockHandle(&info[1]);
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WaitMany(handles, signals, 2, MOJO_DEADLINE_INDEFINITE, nullptr,
+ nullptr));
+
+ // TODO(vtl): Test one where we get "failed precondition" only for the
+ // second handle (and the first one is valid to wait on).
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(handles[0]));
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(handles[1]));
+ }
+
+ // |CreateMessagePipe()|: Nothing to check (apart from things that cause
+ // death).
+
+ // |WriteMessage()|:
+ // Only check arguments checked by |Core|, namely |handle|, |handles|, and
+ // |num_handles|.
+ {
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WriteMessage(MOJO_HANDLE_INVALID, nullptr, 0,
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+ MojoHandle handles[2] = {MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID};
+
+ // Huge handle count (implausibly big on some systems -- more than can be
+ // stored in a 32-bit address space).
+ // Note: This may return either |MOJO_RESULT_INVALID_ARGUMENT| or
+ // |MOJO_RESULT_RESOURCE_EXHAUSTED|, depending on whether it's plausible or
+ // not.
+ EXPECT_NE(
+ MOJO_RESULT_OK,
+ core()->WriteMessage(h, nullptr, 0, handles,
+ std::numeric_limits<uint32_t>::max(),
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(0u, info.GetWriteMessageCallCount());
+
+ // Huge handle count (plausibly big).
+ EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ core()->WriteMessage(
+ h, nullptr, 0, handles,
+ std::numeric_limits<uint32_t>::max() / sizeof(handles[0]),
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(0u, info.GetWriteMessageCallCount());
+
+ // Invalid handle in |handles|.
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WriteMessage(h, nullptr, 0, handles, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(0u, info.GetWriteMessageCallCount());
+
+ // Two invalid handles in |handles|.
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WriteMessage(h, nullptr, 0, handles, 2,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(0u, info.GetWriteMessageCallCount());
+
+ // Can't send a handle over itself.
+ handles[0] = h;
+ EXPECT_EQ(
+ MOJO_RESULT_BUSY,
+ core()->WriteMessage(h, nullptr, 0, handles, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(0u, info.GetWriteMessageCallCount());
+
+ MockHandleInfo info2;
+ MojoHandle h2 = CreateMockHandle(&info2);
+
+ // This is "okay", but |MockDispatcher| doesn't implement it.
+ handles[0] = h2;
+ EXPECT_EQ(
+ MOJO_RESULT_UNIMPLEMENTED,
+ core()->WriteMessage(h, nullptr, 0, handles, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetWriteMessageCallCount());
+
+ // One of the |handles| is still invalid.
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WriteMessage(h, nullptr, 0, handles, 2,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetWriteMessageCallCount());
+
+ // One of the |handles| is the same as |handle|.
+ handles[1] = h;
+ EXPECT_EQ(
+ MOJO_RESULT_BUSY,
+ core()->WriteMessage(h, nullptr, 0, handles, 2,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetWriteMessageCallCount());
+
+ // Can't send a handle twice in the same message.
+ handles[1] = h2;
+ EXPECT_EQ(
+ MOJO_RESULT_BUSY,
+ core()->WriteMessage(h, nullptr, 0, handles, 2,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(1u, info.GetWriteMessageCallCount());
+
+ // Note: Since we never successfully sent anything with it, |h2| should
+ // still be valid.
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h2));
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h));
+ }
+
+ // |ReadMessage()|:
+ // Only check arguments checked by |Core|, namely |handle|, |handles|, and
+ // |num_handles|.
+ {
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ core()->ReadMessage(MOJO_HANDLE_INVALID, nullptr, nullptr, nullptr,
+ nullptr, MOJO_READ_MESSAGE_FLAG_NONE));
+
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+
+ // Okay.
+ uint32_t handle_count = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(
+ h, nullptr, nullptr, nullptr, &handle_count,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ // Checked by |Core|, shouldn't go through to the dispatcher.
+ EXPECT_EQ(1u, info.GetReadMessageCallCount());
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h));
+ }
+}
+
+// These test invalid arguments that should cause death if we're being paranoid
+// about checking arguments (which we would want to do if, e.g., we were in a
+// true "kernel" situation, but we might not want to do otherwise for
+// performance reasons). Probably blatant errors like passing in null pointers
+// (for required pointer arguments) will still cause death, but perhaps not
+// predictably.
+TEST_F(CoreTest, InvalidArgumentsDeath) {
+ const char kMemoryCheckFailedRegex[] = "Check failed";
+
+ // |WaitMany()|:
+ {
+ MojoHandle handle = MOJO_HANDLE_INVALID;
+ MojoHandleSignals signals = ~MOJO_HANDLE_SIGNAL_NONE;
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->WaitMany(nullptr, &signals, 1, MOJO_DEADLINE_INDEFINITE,
+ nullptr, nullptr),
+ kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->WaitMany(&handle, nullptr, 1, MOJO_DEADLINE_INDEFINITE, nullptr,
+ nullptr),
+ kMemoryCheckFailedRegex);
+ // TODO(vtl): |result_index| and |signals_states| are optional. Test them
+ // with non-null invalid pointers?
+ }
+
+ // |CreateMessagePipe()|:
+ {
+ MojoHandle h;
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->CreateMessagePipe(nullptr, nullptr, nullptr),
+ kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->CreateMessagePipe(nullptr, &h, nullptr),
+ kMemoryCheckFailedRegex);
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->CreateMessagePipe(nullptr, nullptr, &h),
+ kMemoryCheckFailedRegex);
+ }
+
+ // |WriteMessage()|:
+ // Only check arguments checked by |Core|, namely |handle|, |handles|, and
+ // |num_handles|.
+ {
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+
+ // Null |handles| with nonzero |num_handles|.
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->WriteMessage(h, nullptr, 0, nullptr, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE),
+ kMemoryCheckFailedRegex);
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h));
+ }
+
+ // |ReadMessage()|:
+ // Only check arguments checked by |Core|, namely |handle|, |handles|, and
+ // |num_handles|.
+ {
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+
+ uint32_t handle_count = 1;
+ EXPECT_DEATH_IF_SUPPORTED(
+ core()->ReadMessage(h, nullptr, nullptr, nullptr, &handle_count,
+ MOJO_READ_MESSAGE_FLAG_NONE),
+ kMemoryCheckFailedRegex);
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h));
+ }
+}
+
+// TODO(vtl): test |Wait()| and |WaitMany()| properly
+// - including |WaitMany()| with the same handle more than once (with
+// same/different signals)
+
+TEST_F(CoreTest, MessagePipe) {
+ MojoHandle h[2];
+ MojoHandleSignalsState hss[2];
+ uint32_t result_index;
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->CreateMessagePipe(nullptr, &h[0], &h[1]));
+ // Should get two distinct, valid handles.
+ EXPECT_NE(h[0], MOJO_HANDLE_INVALID);
+ EXPECT_NE(h[1], MOJO_HANDLE_INVALID);
+ EXPECT_NE(h[0], h[1]);
+
+ // Neither should be readable.
+ MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_HANDLE_SIGNAL_READABLE};
+ result_index = static_cast<uint32_t>(-1);
+ hss[0] = kEmptyMojoHandleSignalsState;
+ hss[1] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_DEADLINE_EXCEEDED,
+ core()->WaitMany(h, signals, 2, 0, &result_index, hss));
+ EXPECT_EQ(static_cast<uint32_t>(-1), result_index);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss[0].satisfiable_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss[1].satisfiable_signals);
+
+ // Try to read anyway.
+ char buffer[1] = {'a'};
+ uint32_t buffer_size = 1;
+ EXPECT_EQ(
+ MOJO_RESULT_SHOULD_WAIT,
+ core()->ReadMessage(h[0], buffer, &buffer_size, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ // Check that it left its inputs alone.
+ EXPECT_EQ('a', buffer[0]);
+ EXPECT_EQ(1u, buffer_size);
+
+ // Both should be writable.
+ hss[0] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Wait(h[0], MOJO_HANDLE_SIGNAL_WRITABLE,
+ 1000000000, &hss[0]));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss[0].satisfiable_signals);
+ hss[0] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Wait(h[1], MOJO_HANDLE_SIGNAL_WRITABLE,
+ 1000000000, &hss[0]));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss[0].satisfiable_signals);
+
+ // Also check that |h[1]| is writable using |WaitMany()|.
+ signals[0] = MOJO_HANDLE_SIGNAL_READABLE;
+ signals[1] = MOJO_HANDLE_SIGNAL_WRITABLE;
+ result_index = static_cast<uint32_t>(-1);
+ hss[0] = kEmptyMojoHandleSignalsState;
+ hss[1] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ core()->WaitMany(h, signals, 2, MOJO_DEADLINE_INDEFINITE, &result_index,
+ hss));
+ EXPECT_EQ(1u, result_index);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss[0].satisfiable_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss[1].satisfiable_signals);
+
+ // Write to |h[1]|.
+ buffer[0] = 'b';
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ core()->WriteMessage(h[1], buffer, 1, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Check that |h[0]| is now readable.
+ signals[0] = MOJO_HANDLE_SIGNAL_READABLE;
+ signals[1] = MOJO_HANDLE_SIGNAL_READABLE;
+ result_index = static_cast<uint32_t>(-1);
+ hss[0] = kEmptyMojoHandleSignalsState;
+ hss[1] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ core()->WaitMany(h, signals, 2, MOJO_DEADLINE_INDEFINITE, &result_index,
+ hss));
+ EXPECT_EQ(0u, result_index);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss[0].satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss[0].satisfiable_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss[1].satisfiable_signals);
+
+ // Read from |h[0]|.
+ // First, get only the size.
+ buffer_size = 0;
+ EXPECT_EQ(
+ MOJO_RESULT_RESOURCE_EXHAUSTED,
+ core()->ReadMessage(h[0], nullptr, &buffer_size, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(1u, buffer_size);
+ // Then actually read it.
+ buffer[0] = 'c';
+ buffer_size = 1;
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ core()->ReadMessage(h[0], buffer, &buffer_size, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ('b', buffer[0]);
+ EXPECT_EQ(1u, buffer_size);
+
+ // |h[0]| should no longer be readable.
+ hss[0] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ core()->Wait(h[0], MOJO_HANDLE_SIGNAL_READABLE, 0, &hss[0]));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss[0].satisfiable_signals);
+
+ // Write to |h[0]|.
+ buffer[0] = 'd';
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ core()->WriteMessage(h[0], buffer, 1, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Close |h[0]|.
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h[0]));
+
+ // Wait for |h[1]| to learn about the other end's closure.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h[1], MOJO_HANDLE_SIGNAL_PEER_CLOSED, 1000000000,
+ &hss[0]));
+
+ // Check that |h[1]| is no longer writable (and will never be).
+ hss[0] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h[1], MOJO_HANDLE_SIGNAL_WRITABLE, 1000000000,
+ &hss[0]));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss[0].satisfiable_signals);
+
+ // Check that |h[1]| is still readable (for the moment).
+ hss[0] = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Wait(h[1], MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000, &hss[0]));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss[0].satisfiable_signals);
+
+ // Discard a message from |h[1]|.
+ EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ core()->ReadMessage(h[1], nullptr, nullptr, nullptr, nullptr,
+ MOJO_READ_MESSAGE_FLAG_MAY_DISCARD));
+
+ // |h[1]| is no longer readable (and will never be).
+ hss[0] = kFullMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(h[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ &hss[0]));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[0].satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[0].satisfiable_signals);
+
+ // Try writing to |h[1]|.
+ buffer[0] = 'e';
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WriteMessage(h[1], buffer, 1, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h[1]));
+}
+
+// Tests passing a message pipe handle.
+TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing1) {
+ const char kHello[] = "hello";
+ const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
+ const char kWorld[] = "world!!!";
+ const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
+ char buffer[100];
+ const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer));
+ uint32_t num_bytes;
+ MojoHandle handles[10];
+ uint32_t num_handles;
+ MojoHandleSignalsState hss;
+ MojoHandle h_received;
+
+ MojoHandle h_passing[2];
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->CreateMessagePipe(nullptr, &h_passing[0], &h_passing[1]));
+
+ // Make sure that |h_passing[]| work properly.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passing[0], kHello, kHelloSize, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = MOJO_ARRAYSIZE(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(
+ h_passing[1], buffer, &num_bytes, handles, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, num_bytes);
+ EXPECT_STREQ(kHello, buffer);
+ EXPECT_EQ(0u, num_handles);
+
+ // Make sure that you can't pass either of the message pipe's handles over
+ // itself.
+ EXPECT_EQ(MOJO_RESULT_BUSY,
+ core()->WriteMessage(h_passing[0], kHello, kHelloSize,
+ &h_passing[0], 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WriteMessage(h_passing[0], kHello, kHelloSize,
+ &h_passing[1], 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ MojoHandle h_passed[2];
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->CreateMessagePipe(nullptr, &h_passed[0], &h_passed[1]));
+
+ // Make sure that |h_passed[]| work properly.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passed[0], kHello, kHelloSize, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passed[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = MOJO_ARRAYSIZE(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(
+ h_passed[1], buffer, &num_bytes, handles, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, num_bytes);
+ EXPECT_STREQ(kHello, buffer);
+ EXPECT_EQ(0u, num_handles);
+
+ // Send |h_passed[1]| from |h_passing[0]| to |h_passing[1]|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passing[0], kWorld, kWorldSize,
+ &h_passed[1], 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = MOJO_ARRAYSIZE(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(
+ h_passing[1], buffer, &num_bytes, handles, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kWorldSize, num_bytes);
+ EXPECT_STREQ(kWorld, buffer);
+ EXPECT_EQ(1u, num_handles);
+ h_received = handles[0];
+ EXPECT_NE(h_received, MOJO_HANDLE_INVALID);
+ EXPECT_NE(h_received, h_passing[0]);
+ EXPECT_NE(h_received, h_passing[1]);
+ EXPECT_NE(h_received, h_passed[0]);
+
+ // Note: We rely on the Mojo system not re-using handle values very often.
+ EXPECT_NE(h_received, h_passed[1]);
+
+ // |h_passed[1]| should no longer be valid; check that trying to close it
+ // fails. See above note.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(h_passed[1]));
+
+ // Write to |h_passed[0]|. Should receive on |h_received|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passed[0], kHello, kHelloSize, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = MOJO_ARRAYSIZE(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(
+ h_received, buffer, &num_bytes, handles, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, num_bytes);
+ EXPECT_STREQ(kHello, buffer);
+ EXPECT_EQ(0u, num_handles);
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[0]));
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[1]));
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_passed[0]));
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_received));
+}
+
+TEST_F(CoreTest, DataPipe) {
+ MojoHandle ph, ch; // p is for producer and c is for consumer.
+ MojoHandleSignalsState hss;
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->CreateDataPipe(nullptr, &ph, &ch));
+ // Should get two distinct, valid handles.
+ EXPECT_NE(ph, MOJO_HANDLE_INVALID);
+ EXPECT_NE(ch, MOJO_HANDLE_INVALID);
+ EXPECT_NE(ph, ch);
+
+ // Producer should be never-readable, but already writable.
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(ph, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Wait(ph, MOJO_HANDLE_SIGNAL_WRITABLE, 0,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Consumer should be never-writable, and not yet readable.
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_DEADLINE_EXCEEDED,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Write.
+ signed char elements[2] = {'A', 'B'};
+ uint32_t num_bytes = 2u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteData(ph, elements, &num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(2u, num_bytes);
+
+ // Wait for the data to arrive to the consumer.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, &hss));
+
+ // Consumer should now be readable.
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Peek one character.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = 1u;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(
+ ch, elements, &num_bytes,
+ MOJO_READ_DATA_FLAG_NONE | MOJO_READ_DATA_FLAG_PEEK));
+ EXPECT_EQ('A', elements[0]);
+ EXPECT_EQ(-1, elements[1]);
+
+ // Read one character.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = 1u;
+ EXPECT_EQ(MOJO_RESULT_OK, core()->ReadData(ch, elements, &num_bytes,
+ MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ('A', elements[0]);
+ EXPECT_EQ(-1, elements[1]);
+
+ // Two-phase write.
+ void* write_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->BeginWriteData(ph, &write_ptr, &num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE));
+ // We count on the default options providing a decent buffer size.
+ ASSERT_GE(num_bytes, 3u);
+
+ // Trying to do a normal write during a two-phase write should fail.
+ elements[0] = 'X';
+ num_bytes = 1u;
+ EXPECT_EQ(MOJO_RESULT_BUSY,
+ core()->WriteData(ph, elements, &num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE));
+
+ // Actually write the data, and complete it now.
+ static_cast<char*>(write_ptr)[0] = 'C';
+ static_cast<char*>(write_ptr)[1] = 'D';
+ static_cast<char*>(write_ptr)[2] = 'E';
+ EXPECT_EQ(MOJO_RESULT_OK, core()->EndWriteData(ph, 3u));
+
+ // Wait for the data to arrive to the consumer.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, &hss));
+
+ // Query how much data we have.
+ num_bytes = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(ch, nullptr, &num_bytes,
+ MOJO_READ_DATA_FLAG_QUERY));
+ EXPECT_GE(num_bytes, 1u);
+
+ // Try to query with peek. Should fail.
+ num_bytes = 0;
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ core()->ReadData(ch, nullptr, &num_bytes,
+ MOJO_READ_DATA_FLAG_QUERY | MOJO_READ_DATA_FLAG_PEEK));
+ EXPECT_EQ(0u, num_bytes);
+
+ // Try to discard ten characters, in all-or-none mode. Should fail.
+ num_bytes = 10;
+ EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE,
+ core()->ReadData(
+ ch, nullptr, &num_bytes,
+ MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_ALL_OR_NONE));
+
+ // Try to discard two characters, in peek mode. Should fail.
+ num_bytes = 2;
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ core()->ReadData(ch, nullptr, &num_bytes,
+ MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_PEEK));
+
+ // Discard a character.
+ num_bytes = 1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(
+ ch, nullptr, &num_bytes,
+ MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_ALL_OR_NONE));
+
+ // Ensure the 3 bytes were read.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 1000000000, &hss));
+
+ // Try a two-phase read of the remaining three bytes with peek. Should fail.
+ const void* read_ptr = nullptr;
+ num_bytes = 3;
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->BeginReadData(ch, &read_ptr, &num_bytes,
+ MOJO_READ_DATA_FLAG_PEEK));
+
+ // Read the remaining two characters, in two-phase mode (all-or-none).
+ num_bytes = 3;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->BeginReadData(ch, &read_ptr, &num_bytes,
+ MOJO_READ_DATA_FLAG_ALL_OR_NONE));
+ // Note: Count on still being able to do the contiguous read here.
+ ASSERT_EQ(3u, num_bytes);
+
+ // Discarding right now should fail.
+ num_bytes = 1;
+ EXPECT_EQ(MOJO_RESULT_BUSY,
+ core()->ReadData(ch, nullptr, &num_bytes,
+ MOJO_READ_DATA_FLAG_DISCARD));
+
+ // Actually check our data and end the two-phase read.
+ EXPECT_EQ('C', static_cast<const char*>(read_ptr)[0]);
+ EXPECT_EQ('D', static_cast<const char*>(read_ptr)[1]);
+ EXPECT_EQ('E', static_cast<const char*>(read_ptr)[2]);
+ EXPECT_EQ(MOJO_RESULT_OK, core()->EndReadData(ch, 3u));
+
+ // Consumer should now be no longer readable.
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_DEADLINE_EXCEEDED,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // TODO(vtl): More.
+
+ // Close the producer.
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(ph));
+
+ // Wait for this to get to the consumer.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_PEER_CLOSED, 1000000000, &hss));
+
+ // The consumer should now be never-readable.
+ hss = kFullMojoHandleSignalsState;
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(ch));
+}
+
+// Tests passing data pipe producer and consumer handles.
+TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) {
+ const char kHello[] = "hello";
+ const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
+ const char kWorld[] = "world!!!";
+ const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
+ char buffer[100];
+ const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer));
+ uint32_t num_bytes;
+ MojoHandle handles[10];
+ uint32_t num_handles;
+ MojoHandleSignalsState hss;
+
+ MojoHandle h_passing[2];
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->CreateMessagePipe(nullptr, &h_passing[0], &h_passing[1]));
+
+ MojoHandle ph, ch;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->CreateDataPipe(nullptr, &ph, &ch));
+
+ // Send |ch| from |h_passing[0]| to |h_passing[1]|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = MOJO_ARRAYSIZE(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(
+ h_passing[1], buffer, &num_bytes, handles, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, num_bytes);
+ EXPECT_STREQ(kHello, buffer);
+ EXPECT_EQ(1u, num_handles);
+ MojoHandle ch_received = handles[0];
+ EXPECT_NE(ch_received, MOJO_HANDLE_INVALID);
+ EXPECT_NE(ch_received, h_passing[0]);
+ EXPECT_NE(ch_received, h_passing[1]);
+ EXPECT_NE(ch_received, ph);
+
+ // Note: We rely on the Mojo system not re-using handle values very often.
+ EXPECT_NE(ch_received, ch);
+
+ // |ch| should no longer be valid; check that trying to close it fails. See
+ // above note.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(ch));
+
+ // Write to |ph|. Should receive on |ch_received|.
+ num_bytes = kWorldSize;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteData(ph, kWorld, &num_bytes,
+ MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(ch_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(ch_received, buffer, &num_bytes,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kWorldSize, num_bytes);
+ EXPECT_STREQ(kWorld, buffer);
+
+ // Now pass |ph| in the same direction.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passing[0], kWorld, kWorldSize, &ph, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = MOJO_ARRAYSIZE(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(
+ h_passing[1], buffer, &num_bytes, handles, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kWorldSize, num_bytes);
+ EXPECT_STREQ(kWorld, buffer);
+ EXPECT_EQ(1u, num_handles);
+ MojoHandle ph_received = handles[0];
+ EXPECT_NE(ph_received, MOJO_HANDLE_INVALID);
+ EXPECT_NE(ph_received, h_passing[0]);
+ EXPECT_NE(ph_received, h_passing[1]);
+ EXPECT_NE(ph_received, ch_received);
+
+ // Again, rely on the Mojo system not re-using handle values very often.
+ EXPECT_NE(ph_received, ph);
+
+ // |ph| should no longer be valid; check that trying to close it fails. See
+ // above note.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(ph));
+
+ // Write to |ph_received|. Should receive on |ch_received|.
+ num_bytes = kHelloSize;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteData(ph_received, kHello, &num_bytes,
+ MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(ch_received, MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(ch_received, buffer, &num_bytes,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, num_bytes);
+ EXPECT_STREQ(kHello, buffer);
+
+ ph = ph_received;
+ ph_received = MOJO_HANDLE_INVALID;
+ ch = ch_received;
+ ch_received = MOJO_HANDLE_INVALID;
+
+ // Make sure that |ph| can't be sent if it's in a two-phase write.
+ void* write_ptr = nullptr;
+ num_bytes = 0;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->BeginWriteData(ph, &write_ptr, &num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE));
+ ASSERT_GE(num_bytes, 1u);
+ EXPECT_EQ(MOJO_RESULT_BUSY,
+ core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ph, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // But |ch| can, even if |ph| is in a two-phase write.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ ch = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ nullptr));
+ num_bytes = kBufferSize;
+ num_handles = MOJO_ARRAYSIZE(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(
+ h_passing[1], buffer, &num_bytes, handles, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, num_bytes);
+ EXPECT_STREQ(kHello, buffer);
+ EXPECT_EQ(1u, num_handles);
+ ch = handles[0];
+ EXPECT_NE(ch, MOJO_HANDLE_INVALID);
+
+ // Complete the two-phase write.
+ static_cast<char*>(write_ptr)[0] = 'x';
+ EXPECT_EQ(MOJO_RESULT_OK, core()->EndWriteData(ph, 1));
+
+ // Wait for |ch| to be readable.
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Wait(ch, MOJO_HANDLE_SIGNAL_READABLE,
+ 1000000000, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Make sure that |ch| can't be sent if it's in a two-phase read.
+ const void* read_ptr = nullptr;
+ num_bytes = 1;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->BeginReadData(ch, &read_ptr, &num_bytes,
+ MOJO_READ_DATA_FLAG_ALL_OR_NONE));
+ EXPECT_EQ(MOJO_RESULT_BUSY,
+ core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // But |ph| can, even if |ch| is in a two-phase read.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passing[0], kWorld, kWorldSize, &ph, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ ph = MOJO_HANDLE_INVALID;
+ hss = kEmptyMojoHandleSignalsState;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->Wait(h_passing[1], MOJO_HANDLE_SIGNAL_READABLE, 1000000000,
+ &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+ num_bytes = kBufferSize;
+ num_handles = MOJO_ARRAYSIZE(handles);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(
+ h_passing[1], buffer, &num_bytes, handles, &num_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kWorldSize, num_bytes);
+ EXPECT_STREQ(kWorld, buffer);
+ EXPECT_EQ(1u, num_handles);
+ ph = handles[0];
+ EXPECT_NE(ph, MOJO_HANDLE_INVALID);
+
+ // Complete the two-phase read.
+ EXPECT_EQ('x', static_cast<const char*>(read_ptr)[0]);
+ EXPECT_EQ(MOJO_RESULT_OK, core()->EndReadData(ch, 1));
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[0]));
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[1]));
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(ph));
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(ch));
+}
+
+struct TestAsyncWaiter {
+ TestAsyncWaiter() : result(MOJO_RESULT_UNKNOWN) {}
+
+ void Awake(MojoResult r) { result = r; }
+
+ MojoResult result;
+};
+
+TEST_F(CoreTest, AsyncWait) {
+ TestAsyncWaiter waiter;
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->AsyncWait(h, MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(&TestAsyncWaiter::Awake,
+ base::Unretained(&waiter))));
+ EXPECT_EQ(0u, info.GetAddedAwakableSize());
+
+ info.AllowAddAwakable(true);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ core()->AsyncWait(h, MOJO_HANDLE_SIGNAL_READABLE,
+ base::Bind(&TestAsyncWaiter::Awake,
+ base::Unretained(&waiter))));
+ EXPECT_EQ(1u, info.GetAddedAwakableSize());
+
+ EXPECT_FALSE(info.GetAddedAwakableAt(0)->Awake(MOJO_RESULT_BUSY, 0));
+ EXPECT_EQ(MOJO_RESULT_BUSY, waiter.result);
+
+ EXPECT_EQ(MOJO_RESULT_OK, core()->Close(h));
+}
+
+// TODO(vtl): Test |DuplicateBufferHandle()| and |MapBuffer()|.
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/data_pipe.cc b/mojo/edk/system/data_pipe.cc
new file mode 100644
index 0000000..ce04bf9
--- /dev/null
+++ b/mojo/edk/system/data_pipe.cc
@@ -0,0 +1,203 @@
+// Copyright 2015 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 "mojo/edk/system/data_pipe.h"
+
+#include <string.h>
+
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/options_validation.h"
+#include "mojo/edk/system/raw_channel.h"
+#include "mojo/edk/system/transport_data.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+const size_t kInvalidDataPipeHandleIndex = static_cast<size_t>(-1);
+
+struct MOJO_ALIGNAS(8) SerializedDataPipeHandleDispatcher {
+ size_t platform_handle_index; // (Or |kInvalidDataPipeHandleIndex|.)
+
+ // These are from MojoCreateDataPipeOptions
+ MojoCreateDataPipeOptionsFlags flags;
+ uint32_t element_num_bytes;
+ uint32_t capacity_num_bytes;
+
+ size_t shared_memory_handle_index; // (Or |kInvalidDataPipeHandleIndex|.)
+ uint32_t shared_memory_size;
+};
+
+} // namespace
+
+MojoCreateDataPipeOptions DataPipe::GetDefaultCreateOptions() {
+ MojoCreateDataPipeOptions result = {
+ static_cast<uint32_t>(sizeof(MojoCreateDataPipeOptions)),
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
+ 1u,
+ static_cast<uint32_t>(
+ GetConfiguration().default_data_pipe_capacity_bytes)};
+ return result;
+}
+
+MojoResult DataPipe::ValidateCreateOptions(
+ const MojoCreateDataPipeOptions* in_options,
+ MojoCreateDataPipeOptions* out_options) {
+ const MojoCreateDataPipeOptionsFlags kKnownFlags =
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
+
+ *out_options = GetDefaultCreateOptions();
+ if (!in_options)
+ return MOJO_RESULT_OK;
+
+ UserOptionsReader<MojoCreateDataPipeOptions> reader(in_options);
+ if (!reader.is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(MojoCreateDataPipeOptions, flags, reader))
+ return MOJO_RESULT_OK;
+ if ((reader.options().flags & ~kKnownFlags))
+ return MOJO_RESULT_UNIMPLEMENTED;
+ out_options->flags = reader.options().flags;
+
+ // Checks for fields beyond |flags|:
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(MojoCreateDataPipeOptions, element_num_bytes,
+ reader))
+ return MOJO_RESULT_OK;
+ if (reader.options().element_num_bytes == 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ out_options->element_num_bytes = reader.options().element_num_bytes;
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(MojoCreateDataPipeOptions, capacity_num_bytes,
+ reader) ||
+ reader.options().capacity_num_bytes == 0) {
+ // Round the default capacity down to a multiple of the element size (but at
+ // least one element).
+ size_t default_data_pipe_capacity_bytes =
+ GetConfiguration().default_data_pipe_capacity_bytes;
+ out_options->capacity_num_bytes =
+ std::max(static_cast<uint32_t>(default_data_pipe_capacity_bytes -
+ (default_data_pipe_capacity_bytes %
+ out_options->element_num_bytes)),
+ out_options->element_num_bytes);
+ return MOJO_RESULT_OK;
+ }
+ if (reader.options().capacity_num_bytes % out_options->element_num_bytes != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (reader.options().capacity_num_bytes >
+ GetConfiguration().max_data_pipe_capacity_bytes)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ out_options->capacity_num_bytes = reader.options().capacity_num_bytes;
+
+ return MOJO_RESULT_OK;
+}
+
+void DataPipe::StartSerialize(bool have_channel_handle,
+ bool have_shared_memory,
+ size_t* max_size,
+ size_t* max_platform_handles) {
+ *max_size = sizeof(SerializedDataPipeHandleDispatcher);
+ *max_platform_handles = 0;
+ if (have_channel_handle)
+ (*max_platform_handles)++;
+ if (have_shared_memory)
+ (*max_platform_handles)++;
+ DCHECK_LE(*max_size, TransportData::kMaxSerializedDispatcherSize);
+}
+
+void DataPipe::EndSerialize(const MojoCreateDataPipeOptions& options,
+ ScopedPlatformHandle channel_handle,
+ ScopedPlatformHandle shared_memory_handle,
+ size_t shared_memory_size,
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) {
+ SerializedDataPipeHandleDispatcher* serialization =
+ static_cast<SerializedDataPipeHandleDispatcher*>(destination);
+ if (channel_handle.is_valid()) {
+ serialization->platform_handle_index = platform_handles->size();
+ platform_handles->push_back(channel_handle.release());
+ } else {
+ serialization->platform_handle_index = kInvalidDataPipeHandleIndex;
+ }
+
+ serialization->flags = options.flags;
+ serialization->element_num_bytes = options.element_num_bytes;
+ serialization->capacity_num_bytes = options.capacity_num_bytes;
+
+ serialization->shared_memory_size = static_cast<uint32_t>(shared_memory_size);
+ if (serialization->shared_memory_size) {
+ serialization->shared_memory_handle_index = platform_handles->size();
+ platform_handles->push_back(shared_memory_handle.release());
+ }
+
+ *actual_size = sizeof(SerializedDataPipeHandleDispatcher);
+}
+
+ScopedPlatformHandle DataPipe::Deserialize(
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles,
+ MojoCreateDataPipeOptions* options,
+ ScopedPlatformHandle* shared_memory_handle,
+ size_t* shared_memory_size) {
+ if (size != sizeof(SerializedDataPipeHandleDispatcher)) {
+ LOG(ERROR) << "Invalid serialized platform handle dispatcher (bad size)";
+ return ScopedPlatformHandle();
+ }
+
+ const SerializedDataPipeHandleDispatcher* serialization =
+ static_cast<const SerializedDataPipeHandleDispatcher*>(source);
+ size_t platform_handle_index = serialization->platform_handle_index;
+
+ // Starts off invalid, which is what we want.
+ PlatformHandle platform_handle;
+ if (platform_handle_index != kInvalidDataPipeHandleIndex) {
+ if (!platform_handles ||
+ platform_handle_index >= platform_handles->size()) {
+ LOG(ERROR)
+ << "Invalid serialized platform handle dispatcher (missing handles)";
+ return ScopedPlatformHandle();
+ }
+
+ // We take ownership of the handle, so we have to invalidate the one in
+ // |platform_handles|.
+ std::swap(platform_handle, (*platform_handles)[platform_handle_index]);
+ }
+
+ options->struct_size = sizeof(MojoCreateDataPipeOptions);
+ options->flags = serialization->flags;
+ options->element_num_bytes = serialization->element_num_bytes;
+ options->capacity_num_bytes = serialization->capacity_num_bytes;
+
+ if (shared_memory_size) {
+ *shared_memory_size = serialization->shared_memory_size;
+ if (*shared_memory_size) {
+ DCHECK(serialization->shared_memory_handle_index !=
+ kInvalidDataPipeHandleIndex);
+ if (!serialization->shared_memory_handle_index ||
+ serialization->shared_memory_handle_index >=
+ platform_handles->size()) {
+ LOG(ERROR) << "Invalid serialized platform handle dispatcher "
+ << "(missing handles)";
+ return ScopedPlatformHandle();
+ }
+
+ PlatformHandle temp_shared_memory_handle;
+ std::swap(temp_shared_memory_handle,
+ (*platform_handles)[serialization->shared_memory_handle_index]);
+ *shared_memory_handle =
+ ScopedPlatformHandle(temp_shared_memory_handle);
+ }
+ }
+
+ size -= sizeof(SerializedDataPipeHandleDispatcher);
+
+ return ScopedPlatformHandle(platform_handle);
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/data_pipe.h b/mojo/edk/system/data_pipe.h
new file mode 100644
index 0000000..f17416b
--- /dev/null
+++ b/mojo/edk/system/data_pipe.h
@@ -0,0 +1,63 @@
+// Copyright 2015 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 MOJO_EDK_SYSTEM_DATA_PIPE_H_
+#define MOJO_EDK_SYSTEM_DATA_PIPE_H_
+
+#include "base/compiler_specific.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+class RawChannel;
+
+// Shared code between DataPipeConsumerDispatcher and
+// DataPipeProducerDispatcher.
+class MOJO_SYSTEM_IMPL_EXPORT DataPipe {
+ public:
+ // The default options for |MojoCreateDataPipe()|. (Real uses should obtain
+ // this via |ValidateCreateOptions()| with a null |in_options|; this is
+ // exposed directly for testing convenience.)
+ static MojoCreateDataPipeOptions GetDefaultCreateOptions();
+
+ // Validates and/or sets default options for |MojoCreateDataPipeOptions|. If
+ // non-null, |in_options| must point to a struct of at least
+ // |in_options->struct_size| bytes. |out_options| must point to a (current)
+ // |MojoCreateDataPipeOptions| and will be entirely overwritten on success (it
+ // may be partly overwritten on failure).
+ static MojoResult ValidateCreateOptions(
+ const MojoCreateDataPipeOptions* in_options,
+ MojoCreateDataPipeOptions* out_options);
+
+ // Helper methods used by DataPipeConsumerDispatcher and
+ // DataPipeProducerDispatcher for serialization and deserialization.
+ static void StartSerialize(bool have_channel_handle,
+ bool have_shared_memory,
+ size_t* max_size,
+ size_t* max_platform_handles);
+ static void EndSerialize(const MojoCreateDataPipeOptions& options,
+ ScopedPlatformHandle channel_handle,
+ ScopedPlatformHandle shared_memory_handle,
+ size_t shared_memory_size,
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles);
+ static ScopedPlatformHandle Deserialize(
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles,
+ MojoCreateDataPipeOptions* options,
+ ScopedPlatformHandle* shared_memory_handle,
+ size_t* shared_memory_size);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_DATA_PIPE_H_
diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.cc b/mojo/edk/system/data_pipe_consumer_dispatcher.cc
new file mode 100644
index 0000000..1cf4367
--- /dev/null
+++ b/mojo/edk/system/data_pipe_consumer_dispatcher.cc
@@ -0,0 +1,474 @@
+// 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 "mojo/edk/system/data_pipe_consumer_dispatcher.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/embedder/platform_support.h"
+#include "mojo/edk/system/data_pipe.h"
+
+namespace mojo {
+namespace edk {
+
+struct SharedMemoryHeader {
+ uint32_t data_size;
+ uint32_t read_buffer_size;
+};
+
+void DataPipeConsumerDispatcher::Init(ScopedPlatformHandle message_pipe) {
+ if (message_pipe.is_valid()) {
+ channel_ = RawChannel::Create(message_pipe.Pass());
+ if (!serialized_read_buffer_.empty())
+ channel_->SetInitialReadBufferData(
+ &serialized_read_buffer_[0], serialized_read_buffer_.size());
+ serialized_read_buffer_.clear();
+ internal::g_io_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(&DataPipeConsumerDispatcher::InitOnIO, this));
+ }
+}
+
+void DataPipeConsumerDispatcher::InitOnIO() {
+ base::AutoLock locker(lock());
+ calling_init_ = true;
+ if (channel_)
+ channel_->Init(this);
+ calling_init_ = false;
+}
+
+void DataPipeConsumerDispatcher::CloseOnIO() {
+ base::AutoLock locker(lock());
+ if (channel_) {
+ channel_->Shutdown();
+ channel_ = nullptr;
+ }
+}
+
+Dispatcher::Type DataPipeConsumerDispatcher::GetType() const {
+ return Type::DATA_PIPE_CONSUMER;
+}
+
+scoped_refptr<DataPipeConsumerDispatcher>
+DataPipeConsumerDispatcher::Deserialize(
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles) {
+ MojoCreateDataPipeOptions options;
+ ScopedPlatformHandle shared_memory_handle;
+ size_t shared_memory_size = 0;
+
+ ScopedPlatformHandle platform_handle =
+ DataPipe::Deserialize(source, size, platform_handles, &options,
+ &shared_memory_handle, &shared_memory_size);
+
+ scoped_refptr<DataPipeConsumerDispatcher> rv(Create(options));
+
+ if (shared_memory_size) {
+ scoped_refptr<PlatformSharedBuffer> shared_buffer(
+ internal::g_platform_support->CreateSharedBufferFromHandle(
+ shared_memory_size, shared_memory_handle.Pass()));;
+ scoped_ptr<PlatformSharedBufferMapping> mapping(
+ shared_buffer->Map(0, shared_memory_size));
+ char* buffer = static_cast<char*>(mapping->GetBase());
+ SharedMemoryHeader* header = reinterpret_cast<SharedMemoryHeader*>(buffer);
+ buffer += sizeof(SharedMemoryHeader);
+ if (header->data_size) {
+ rv->data_.resize(header->data_size);
+ memcpy(&rv->data_[0], buffer, header->data_size);
+ buffer += header->data_size;
+ }
+ if (header->read_buffer_size) {
+ rv->serialized_read_buffer_.resize(header->read_buffer_size);
+ memcpy(&rv->serialized_read_buffer_[0], buffer, header->read_buffer_size);
+ buffer += header->read_buffer_size;
+ }
+
+ }
+
+ if (platform_handle.is_valid())
+ rv->Init(platform_handle.Pass());
+ return rv;
+}
+
+DataPipeConsumerDispatcher::DataPipeConsumerDispatcher(
+ const MojoCreateDataPipeOptions& options)
+ : options_(options),
+ channel_(nullptr),
+ calling_init_(false),
+ in_two_phase_read_(false),
+ two_phase_max_bytes_read_(0),
+ error_(false),
+ serialized_(false) {
+}
+
+DataPipeConsumerDispatcher::~DataPipeConsumerDispatcher() {
+ // |Close()|/|CloseImplNoLock()| should have taken care of the channel.
+ DCHECK(!channel_);
+}
+
+void DataPipeConsumerDispatcher::CancelAllAwakablesNoLock() {
+ lock().AssertAcquired();
+ awakable_list_.CancelAll();
+}
+
+void DataPipeConsumerDispatcher::CloseImplNoLock() {
+ lock().AssertAcquired();
+ internal::g_io_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(&DataPipeConsumerDispatcher::CloseOnIO, this));
+}
+
+scoped_refptr<Dispatcher>
+DataPipeConsumerDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() {
+ lock().AssertAcquired();
+
+ SerializeInternal();
+
+ scoped_refptr<DataPipeConsumerDispatcher> rv = Create(options_);
+ rv->channel_ = channel_;
+ channel_ = nullptr;
+ rv->options_ = options_;
+ data_.swap(rv->data_);
+ serialized_read_buffer_.swap(rv->serialized_read_buffer_);
+ rv->serialized_platform_handle_ = serialized_platform_handle_.Pass();
+ rv->serialized_ = true;
+
+ return scoped_refptr<Dispatcher>(rv.get());
+}
+
+MojoResult DataPipeConsumerDispatcher::ReadDataImplNoLock(
+ void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags) {
+ lock().AssertAcquired();
+ if (in_two_phase_read_)
+ return MOJO_RESULT_BUSY;
+
+ if ((flags & MOJO_READ_DATA_FLAG_QUERY)) {
+ if ((flags & MOJO_READ_DATA_FLAG_PEEK) ||
+ (flags & MOJO_READ_DATA_FLAG_DISCARD))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ DCHECK(!(flags & MOJO_READ_DATA_FLAG_DISCARD)); // Handled above.
+ DVLOG_IF(2, elements)
+ << "Query mode: ignoring non-null |elements|";
+ *num_bytes = static_cast<uint32_t>(data_.size());
+ return MOJO_RESULT_OK;
+ }
+
+ bool discard = false;
+ if ((flags & MOJO_READ_DATA_FLAG_DISCARD)) {
+ // These flags are mutally exclusive.
+ if (flags & MOJO_READ_DATA_FLAG_PEEK)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ DVLOG_IF(2, elements)
+ << "Discard mode: ignoring non-null |elements|";
+ discard = true;
+ }
+
+ uint32_t max_num_bytes_to_read = *num_bytes;
+ if (max_num_bytes_to_read % options_.element_num_bytes != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ bool all_or_none = flags & MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ uint32_t min_num_bytes_to_read =
+ all_or_none ? max_num_bytes_to_read : 0;
+
+ if (min_num_bytes_to_read > data_.size())
+ return error_ ? MOJO_RESULT_FAILED_PRECONDITION : MOJO_RESULT_OUT_OF_RANGE;
+
+ uint32_t bytes_to_read = std::min(max_num_bytes_to_read,
+ static_cast<uint32_t>(data_.size()));
+ if (bytes_to_read == 0)
+ return error_ ? MOJO_RESULT_FAILED_PRECONDITION : MOJO_RESULT_SHOULD_WAIT;
+
+ if (!discard)
+ memcpy(elements, &data_[0], bytes_to_read);
+ *num_bytes = bytes_to_read;
+
+ bool peek = !!(flags & MOJO_READ_DATA_FLAG_PEEK);
+ if (discard || !peek)
+ data_.erase(data_.begin(), data_.begin() + bytes_to_read);
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult DataPipeConsumerDispatcher::BeginReadDataImplNoLock(
+ const void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoReadDataFlags flags) {
+ lock().AssertAcquired();
+ if (in_two_phase_read_)
+ return MOJO_RESULT_BUSY;
+
+ // These flags may not be used in two-phase mode.
+ if ((flags & MOJO_READ_DATA_FLAG_DISCARD) ||
+ (flags & MOJO_READ_DATA_FLAG_QUERY) ||
+ (flags & MOJO_READ_DATA_FLAG_PEEK))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ bool all_or_none = flags & MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ uint32_t min_num_bytes_to_read = 0;
+ if (all_or_none) {
+ min_num_bytes_to_read = *buffer_num_bytes;
+ if (min_num_bytes_to_read % options_.element_num_bytes != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ uint32_t max_num_bytes_to_read = static_cast<uint32_t>(data_.size());
+ if (min_num_bytes_to_read > max_num_bytes_to_read)
+ return error_ ? MOJO_RESULT_FAILED_PRECONDITION : MOJO_RESULT_OUT_OF_RANGE;
+ if (max_num_bytes_to_read == 0)
+ return error_ ? MOJO_RESULT_FAILED_PRECONDITION : MOJO_RESULT_SHOULD_WAIT;
+
+ in_two_phase_read_ = true;
+ *buffer = &data_[0];
+ *buffer_num_bytes = max_num_bytes_to_read;
+ two_phase_max_bytes_read_ = max_num_bytes_to_read;
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult DataPipeConsumerDispatcher::EndReadDataImplNoLock(
+ uint32_t num_bytes_read) {
+ lock().AssertAcquired();
+ if (!in_two_phase_read_)
+ return MOJO_RESULT_FAILED_PRECONDITION;
+
+ MojoResult rv;
+ if (num_bytes_read > two_phase_max_bytes_read_ ||
+ num_bytes_read % options_.element_num_bytes != 0) {
+ rv = MOJO_RESULT_INVALID_ARGUMENT;
+ } else {
+ rv = MOJO_RESULT_OK;
+ data_.erase(data_.begin(), data_.begin() + num_bytes_read);
+ }
+
+ in_two_phase_read_ = false;
+ two_phase_max_bytes_read_ = 0;
+
+ // If we're now readable, we *became* readable (since we weren't readable
+ // during the two-phase read), so awake consumer awakables.
+ HandleSignalsState new_state = GetHandleSignalsStateImplNoLock();
+ if (new_state.satisfies(MOJO_HANDLE_SIGNAL_READABLE))
+ awakable_list_.AwakeForStateChange(new_state);
+
+ return rv;
+}
+
+HandleSignalsState DataPipeConsumerDispatcher::GetHandleSignalsStateImplNoLock()
+ const {
+ lock().AssertAcquired();
+
+ HandleSignalsState rv;
+ if (!data_.empty()) {
+ if (!in_two_phase_read_)
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_READABLE;
+ rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE;
+ } else if (!error_) {
+ rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE;
+ }
+
+ if (error_)
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+ rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+ return rv;
+}
+
+MojoResult DataPipeConsumerDispatcher::AddAwakableImplNoLock(
+ Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ HandleSignalsState state = GetHandleSignalsStateImplNoLock();
+ if (state.satisfies(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_ALREADY_EXISTS;
+ }
+ if (!state.can_satisfy(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ awakable_list_.Add(awakable, signals, context);
+ return MOJO_RESULT_OK;
+}
+
+void DataPipeConsumerDispatcher::RemoveAwakableImplNoLock(
+ Awakable* awakable,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ awakable_list_.Remove(awakable);
+ if (signals_state)
+ *signals_state = GetHandleSignalsStateImplNoLock();
+}
+
+void DataPipeConsumerDispatcher::StartSerializeImplNoLock(
+ size_t* max_size,
+ size_t* max_platform_handles) {
+ if (!serialized_) {
+ // Handles the case where we have messages read off RawChannel but not ready
+ // by MojoReadMessage.
+ SerializeInternal();
+ }
+
+ DataPipe::StartSerialize(serialized_platform_handle_.is_valid(),
+ !data_.empty(),
+ max_size, max_platform_handles);
+}
+
+bool DataPipeConsumerDispatcher::EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) {
+ ScopedPlatformHandle shared_memory_handle;
+ size_t shared_memory_size = data_.size() + serialized_read_buffer_.size();
+ if (shared_memory_size) {
+ shared_memory_size += sizeof(SharedMemoryHeader);
+ SharedMemoryHeader header;
+ header.data_size = static_cast<uint32_t>(data_.size());
+ header.read_buffer_size =
+ static_cast<uint32_t>(serialized_read_buffer_.size());
+
+ scoped_refptr<PlatformSharedBuffer> shared_buffer(
+ internal::g_platform_support->CreateSharedBuffer(
+ shared_memory_size));
+ scoped_ptr<PlatformSharedBufferMapping> mapping(
+ shared_buffer->Map(0, shared_memory_size));
+
+ char* start = static_cast<char*>(mapping->GetBase());
+ memcpy(start, &header, sizeof(SharedMemoryHeader));
+ start += sizeof(SharedMemoryHeader);
+
+ if (!data_.empty()) {
+ memcpy(start, &data_[0], data_.size());
+ start += data_.size();
+ }
+
+ if (!serialized_read_buffer_.empty()) {
+ memcpy(start, &serialized_read_buffer_[0],
+ serialized_read_buffer_.size());
+ start += serialized_read_buffer_.size();
+ }
+
+ shared_memory_handle.reset(shared_buffer->PassPlatformHandle().release());
+ }
+
+ DataPipe::EndSerialize(
+ options_,
+ serialized_platform_handle_.Pass(),
+ shared_memory_handle.Pass(),
+ shared_memory_size, destination, actual_size,
+ platform_handles);
+ CloseImplNoLock();
+ return true;
+}
+
+void DataPipeConsumerDispatcher::TransportStarted() {
+ started_transport_.Acquire();
+}
+
+void DataPipeConsumerDispatcher::TransportEnded() {
+ started_transport_.Release();
+
+ base::AutoLock locker(lock());
+
+ // If transporting of DP failed, we might have got more data and didn't awake
+ // for.
+ // TODO(jam): should we care about only alerting if it was empty before
+ // TransportStarted?
+ if (!data_.empty())
+ awakable_list_.AwakeForStateChange(GetHandleSignalsStateImplNoLock());
+}
+
+bool DataPipeConsumerDispatcher::IsBusyNoLock() const {
+ lock().AssertAcquired();
+ return in_two_phase_read_;
+}
+
+void DataPipeConsumerDispatcher::OnReadMessage(
+ const MessageInTransit::View& message_view,
+ ScopedPlatformHandleVectorPtr platform_handles) {
+ scoped_ptr<MessageInTransit> message(new MessageInTransit(message_view));
+
+ if (started_transport_.Try()) {
+ // We're not in the middle of being sent.
+
+ // Can get synchronously called back in Init if there was initial data.
+ scoped_ptr<base::AutoLock> locker;
+ if (!calling_init_) {
+ locker.reset(new base::AutoLock(lock()));
+ }
+
+ size_t old_size = data_.size();
+ data_.resize(old_size + message->num_bytes());
+ memcpy(&data_[old_size], message->bytes(), message->num_bytes());
+ if (!old_size)
+ awakable_list_.AwakeForStateChange(GetHandleSignalsStateImplNoLock());
+ started_transport_.Release();
+ } else {
+ size_t old_size = data_.size();
+ data_.resize(old_size + message->num_bytes());
+ memcpy(&data_[old_size], message->bytes(), message->num_bytes());
+ }
+}
+
+void DataPipeConsumerDispatcher::OnError(Error error) {
+ switch (error) {
+ case ERROR_READ_SHUTDOWN:
+ // The other side was cleanly closed, so this isn't actually an error.
+ DVLOG(1) << "DataPipeConsumerDispatcher read error (shutdown)";
+ break;
+ case ERROR_READ_BROKEN:
+ LOG(ERROR) << "DataPipeConsumerDispatcher read error (connection broken)";
+ break;
+ case ERROR_READ_BAD_MESSAGE:
+ // Receiving a bad message means either a bug, data corruption, or
+ // malicious attack (probably due to some other bug).
+ LOG(ERROR) << "DataPipeConsumerDispatcher read error (received bad "
+ << "message)";
+ break;
+ case ERROR_READ_UNKNOWN:
+ LOG(ERROR) << "DataPipeConsumerDispatcher read error (unknown)";
+ break;
+ case ERROR_WRITE:
+ LOG(ERROR) << "DataPipeConsumerDispatcher shouldn't write messages";
+ break;
+ }
+
+ error_ = true;
+ if (started_transport_.Try()) {
+ base::AutoLock locker(lock());
+ awakable_list_.AwakeForStateChange(GetHandleSignalsStateImplNoLock());
+ started_transport_.Release();
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&RawChannel::Shutdown, base::Unretained(channel_)));
+ channel_ = nullptr;
+ } else {
+ // We must be waiting to call ReleaseHandle. It will call Shutdown.
+ }
+}
+
+void DataPipeConsumerDispatcher::SerializeInternal() {
+ // need to stop watching handle immediately, even tho not on IO thread, so
+ // that other messages aren't read after this.
+ if (channel_) {
+ serialized_platform_handle_ =
+ channel_->ReleaseHandle(&serialized_read_buffer_);
+
+ channel_ = nullptr;
+ serialized_ = true;
+ }
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.h b/mojo/edk/system/data_pipe_consumer_dispatcher.h
new file mode 100644
index 0000000..76ca69e
--- /dev/null
+++ b/mojo/edk/system/data_pipe_consumer_dispatcher.h
@@ -0,0 +1,117 @@
+// 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 MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_
+
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/system/awakable_list.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/raw_channel.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// This is the |Dispatcher| implementation for the consumer handle for data
+// pipes (created by the Mojo primitive |MojoCreateDataPipe()|). This class is
+// thread-safe.
+class MOJO_SYSTEM_IMPL_EXPORT DataPipeConsumerDispatcher final
+ : public Dispatcher, public RawChannel::Delegate {
+ public:
+ static scoped_refptr<DataPipeConsumerDispatcher> Create(
+ const MojoCreateDataPipeOptions& options) {
+ return make_scoped_refptr(new DataPipeConsumerDispatcher(options));
+ }
+
+ // Must be called before any other methods.
+ void Init(ScopedPlatformHandle message_pipe);
+
+ // |Dispatcher| public methods:
+ Type GetType() const override;
+
+ // The "opposite" of |SerializeAndClose()|. (Typically this is called by
+ // |Dispatcher::Deserialize()|.)
+ static scoped_refptr<DataPipeConsumerDispatcher>
+ Deserialize(const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles);
+
+ private:
+ DataPipeConsumerDispatcher(const MojoCreateDataPipeOptions& options);
+ ~DataPipeConsumerDispatcher() override;
+
+ void InitOnIO();
+ void CloseOnIO();
+
+ // |Dispatcher| protected methods:
+ void CancelAllAwakablesNoLock() override;
+ void CloseImplNoLock() override;
+ scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndCloseImplNoLock()
+ override;
+ MojoResult ReadDataImplNoLock(void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags) override;
+ MojoResult BeginReadDataImplNoLock(const void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoReadDataFlags flags) override;
+ MojoResult EndReadDataImplNoLock(uint32_t num_bytes_read) override;
+ HandleSignalsState GetHandleSignalsStateImplNoLock() const override;
+ MojoResult AddAwakableImplNoLock(Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) override;
+ void RemoveAwakableImplNoLock(Awakable* awakable,
+ HandleSignalsState* signals_state) override;
+ void StartSerializeImplNoLock(size_t* max_size,
+ size_t* max_platform_handles) override;
+ bool EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) override;
+ void TransportStarted() override;
+ void TransportEnded() override;
+ bool IsBusyNoLock() const override;
+
+ // |RawChannel::Delegate methods:
+ void OnReadMessage(
+ const MessageInTransit::View& message_view,
+ ScopedPlatformHandleVectorPtr platform_handles) override;
+ void OnError(Error error) override;
+
+ // See comment in MessagePipeDispatcher for this method.
+ void SerializeInternal();
+
+ MojoCreateDataPipeOptions options_;
+
+ // Protected by |lock()|:
+ RawChannel* channel_; // This will be null if closed.
+
+ // Queue of incoming messages.
+ std::vector<char> data_;
+ AwakableList awakable_list_;
+
+ // If DispatcherTransport is created. Must be set before lock() is called to
+ // avoid deadlocks with RawChannel calling us.
+ base::Lock started_transport_;
+
+ bool calling_init_;
+
+ bool in_two_phase_read_;
+ uint32_t two_phase_max_bytes_read_;
+
+ bool error_;
+
+ bool serialized_;
+ std::vector<char> serialized_read_buffer_;
+ ScopedPlatformHandle serialized_platform_handle_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(DataPipeConsumerDispatcher);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_
diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.cc b/mojo/edk/system/data_pipe_producer_dispatcher.cc
new file mode 100644
index 0000000..5da76d5
--- /dev/null
+++ b/mojo/edk/system/data_pipe_producer_dispatcher.cc
@@ -0,0 +1,357 @@
+// 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 "mojo/edk/system/data_pipe_producer_dispatcher.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/data_pipe.h"
+
+namespace mojo {
+namespace edk {
+
+void DataPipeProducerDispatcher::Init(ScopedPlatformHandle message_pipe) {
+ if (message_pipe.is_valid()) {
+ channel_ = RawChannel::Create(message_pipe.Pass());
+ internal::g_io_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(&DataPipeProducerDispatcher::InitOnIO, this));
+ }
+}
+
+void DataPipeProducerDispatcher::InitOnIO() {
+ base::AutoLock locker(lock());
+ if (channel_)
+ channel_->Init(this);
+}
+
+void DataPipeProducerDispatcher::CloseOnIO() {
+ base::AutoLock locker(lock());
+ if (channel_) {
+ channel_->Shutdown();
+ channel_ = nullptr;
+ }
+}
+
+Dispatcher::Type DataPipeProducerDispatcher::GetType() const {
+ return Type::DATA_PIPE_PRODUCER;
+}
+
+scoped_refptr<DataPipeProducerDispatcher>
+DataPipeProducerDispatcher::Deserialize(
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles) {
+ MojoCreateDataPipeOptions options;
+ ScopedPlatformHandle platform_handle =
+ DataPipe::Deserialize(source, size, platform_handles, &options,
+ nullptr, 0);
+
+ scoped_refptr<DataPipeProducerDispatcher> rv(Create(options));
+ if (platform_handle.is_valid())
+ rv->Init(platform_handle.Pass());
+ return rv;
+}
+
+DataPipeProducerDispatcher::DataPipeProducerDispatcher(
+ const MojoCreateDataPipeOptions& options)
+ : options_(options), channel_(nullptr), error_(false) {
+}
+
+DataPipeProducerDispatcher::~DataPipeProducerDispatcher() {
+ // |Close()|/|CloseImplNoLock()| should have taken care of the channel.
+ DCHECK(!channel_);
+}
+
+void DataPipeProducerDispatcher::CancelAllAwakablesNoLock() {
+ lock().AssertAcquired();
+ awakable_list_.CancelAll();
+}
+
+void DataPipeProducerDispatcher::CloseImplNoLock() {
+ lock().AssertAcquired();
+ internal::g_io_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(&DataPipeProducerDispatcher::CloseOnIO, this));
+}
+
+scoped_refptr<Dispatcher>
+DataPipeProducerDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() {
+ lock().AssertAcquired();
+
+ scoped_refptr<DataPipeProducerDispatcher> rv = Create(options_);
+ rv->channel_ = channel_;
+ channel_ = nullptr;
+ rv->options_ = options_;
+ return scoped_refptr<Dispatcher>(rv.get());
+}
+
+MojoResult DataPipeProducerDispatcher::WriteDataImplNoLock(
+ const void* elements,
+ uint32_t* num_bytes,
+ MojoWriteDataFlags flags) {
+ lock().AssertAcquired();
+ if (InTwoPhaseWrite())
+ return MOJO_RESULT_BUSY;
+ if (error_)
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ if (*num_bytes % options_.element_num_bytes != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (*num_bytes == 0)
+ return MOJO_RESULT_OK; // Nothing to do.
+
+ // For now, we ignore options.capacity_num_bytes as a total of all pending
+ // writes (and just treat it per message). We will implement that later if
+ // we need to. All current uses want all their data to be sent, and it's not
+ // clear that this backpressure should be done at the mojo layer or at a
+ // higher application layer.
+ bool all_or_none = flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE;
+ uint32_t min_num_bytes_to_write = all_or_none ? *num_bytes : 0;
+ if (min_num_bytes_to_write > options_.capacity_num_bytes) {
+ // Don't return "should wait" since you can't wait for a specified amount of
+ // data.
+ return MOJO_RESULT_OUT_OF_RANGE;
+ }
+
+ uint32_t num_bytes_to_write =
+ std::min(*num_bytes, options_.capacity_num_bytes);
+ if (num_bytes_to_write == 0)
+ return MOJO_RESULT_SHOULD_WAIT;
+
+ HandleSignalsState old_state = GetHandleSignalsStateImplNoLock();
+
+ *num_bytes = num_bytes_to_write;
+ WriteDataIntoMessages(elements, num_bytes_to_write);
+
+ HandleSignalsState new_state = GetHandleSignalsStateImplNoLock();
+ if (!new_state.equals(old_state))
+ awakable_list_.AwakeForStateChange(new_state);
+ return MOJO_RESULT_OK;
+}
+
+MojoResult DataPipeProducerDispatcher::BeginWriteDataImplNoLock(
+ void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoWriteDataFlags flags) {
+ lock().AssertAcquired();
+ if (InTwoPhaseWrite())
+ return MOJO_RESULT_BUSY;
+ if (error_)
+ return MOJO_RESULT_FAILED_PRECONDITION;
+
+ bool all_or_none = flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE;
+ uint32_t min_num_bytes_to_write = 0;
+ if (all_or_none) {
+ min_num_bytes_to_write = *buffer_num_bytes;
+ if (min_num_bytes_to_write % options_.element_num_bytes != 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (min_num_bytes_to_write > options_.capacity_num_bytes) {
+ // Don't return "should wait" since you can't wait for a specified amount
+ // of data.
+ return MOJO_RESULT_OUT_OF_RANGE;
+ }
+ }
+
+ // See comment in WriteDataImplNoLock about ignoring capacity_num_bytes.
+ if (*buffer_num_bytes == 0)
+ *buffer_num_bytes = options_.capacity_num_bytes;
+
+ two_phase_data_.resize(*buffer_num_bytes);
+ *buffer = &two_phase_data_[0];
+
+ // TODO: if buffer_num_bytes.Get() > GetConfiguration().max_message_num_bytes
+ // we can construct a MessageInTransit here. But then we need to make
+ // MessageInTransit support changing its data size later.
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult DataPipeProducerDispatcher::EndWriteDataImplNoLock(
+ uint32_t num_bytes_written) {
+ lock().AssertAcquired();
+ if (!InTwoPhaseWrite())
+ return MOJO_RESULT_FAILED_PRECONDITION;
+
+ // Note: Allow successful completion of the two-phase write even if the other
+ // side has been closed.
+ MojoResult rv = MOJO_RESULT_OK;
+ if (num_bytes_written > two_phase_data_.size() ||
+ num_bytes_written % options_.element_num_bytes != 0) {
+ rv = MOJO_RESULT_INVALID_ARGUMENT;
+ } else if (channel_) {
+ WriteDataIntoMessages(&two_phase_data_[0], num_bytes_written);
+ }
+
+ // Two-phase write ended even on failure.
+ two_phase_data_.clear();
+ // If we're now writable, we *became* writable (since we weren't writable
+ // during the two-phase write), so awake producer awakables.
+ HandleSignalsState new_state = GetHandleSignalsStateImplNoLock();
+ if (new_state.satisfies(MOJO_HANDLE_SIGNAL_WRITABLE))
+ awakable_list_.AwakeForStateChange(new_state);
+
+ return rv;
+}
+
+HandleSignalsState DataPipeProducerDispatcher::GetHandleSignalsStateImplNoLock()
+ const {
+ lock().AssertAcquired();
+
+ HandleSignalsState rv;
+ if (!error_) {
+ if (!InTwoPhaseWrite())
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE;
+ rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_WRITABLE;
+ } else {
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+ }
+ rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+ return rv;
+}
+
+MojoResult DataPipeProducerDispatcher::AddAwakableImplNoLock(
+ Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ HandleSignalsState state = GetHandleSignalsStateImplNoLock();
+ if (state.satisfies(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_ALREADY_EXISTS;
+ }
+ if (!state.can_satisfy(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ awakable_list_.Add(awakable, signals, context);
+ return MOJO_RESULT_OK;
+}
+
+void DataPipeProducerDispatcher::RemoveAwakableImplNoLock(
+ Awakable* awakable,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ awakable_list_.Remove(awakable);
+ if (signals_state)
+ *signals_state = GetHandleSignalsStateImplNoLock();
+}
+
+void DataPipeProducerDispatcher::StartSerializeImplNoLock(
+ size_t* max_size,
+ size_t* max_platform_handles) {
+ DCHECK(HasOneRef()); // Only one ref => no need to take the lock.
+
+ if (channel_) {
+ std::vector<char> temp;
+ serialized_platform_handle_ = channel_->ReleaseHandle(&temp);
+ channel_ = nullptr;
+ DCHECK(temp.empty());
+ }
+ DataPipe::StartSerialize(serialized_platform_handle_.is_valid(),
+ false, max_size, max_platform_handles);
+}
+
+bool DataPipeProducerDispatcher::EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) {
+ DCHECK(HasOneRef()); // Only one ref => no need to take the lock.
+
+ DataPipe::EndSerialize(
+ options_,
+ serialized_platform_handle_.Pass(),
+ ScopedPlatformHandle(), 0,
+ destination, actual_size, platform_handles);
+ CloseImplNoLock();
+ return true;
+}
+
+void DataPipeProducerDispatcher::TransportStarted() {
+ started_transport_.Acquire();
+}
+
+void DataPipeProducerDispatcher::TransportEnded() {
+ started_transport_.Release();
+}
+
+bool DataPipeProducerDispatcher::IsBusyNoLock() const {
+ lock().AssertAcquired();
+ return InTwoPhaseWrite();
+}
+
+void DataPipeProducerDispatcher::OnReadMessage(
+ const MessageInTransit::View& message_view,
+ ScopedPlatformHandleVectorPtr platform_handles) {
+ NOTREACHED();
+}
+
+void DataPipeProducerDispatcher::OnError(Error error) {
+ switch (error) {
+ case ERROR_READ_SHUTDOWN:
+ case ERROR_READ_BROKEN:
+ case ERROR_READ_BAD_MESSAGE:
+ case ERROR_READ_UNKNOWN:
+ LOG(ERROR) << "DataPipeProducerDispatcher shouldn't read messages";
+ break;
+ case ERROR_WRITE:
+ // Write errors are slightly notable: they probably shouldn't happen under
+ // normal operation (but maybe the other side crashed).
+ LOG(WARNING) << "DataPipeProducerDispatcher write error";
+ break;
+ }
+
+ error_ = true;
+ if (started_transport_.Try()) {
+ base::AutoLock locker(lock());
+ awakable_list_.AwakeForStateChange(GetHandleSignalsStateImplNoLock());
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&RawChannel::Shutdown, base::Unretained(channel_)));
+ channel_ = nullptr;
+ started_transport_.Release();
+ } else {
+ // We must be waiting to call ReleaseHandle. It will call Shutdown.
+ }
+}
+
+bool DataPipeProducerDispatcher::InTwoPhaseWrite() const {
+ return !two_phase_data_.empty();
+}
+
+bool DataPipeProducerDispatcher::WriteDataIntoMessages(
+ const void* elements,
+ uint32_t num_bytes) {
+ // The maximum amount of data to send per message (make it a multiple of the
+ // element size.
+ size_t max_message_num_bytes = GetConfiguration().max_message_num_bytes;
+ max_message_num_bytes -= max_message_num_bytes % options_.element_num_bytes;
+ DCHECK_GT(max_message_num_bytes, 0u);
+
+ uint32_t offset = 0;
+ while (offset < num_bytes) {
+ uint32_t message_num_bytes =
+ std::min(static_cast<uint32_t>(max_message_num_bytes),
+ num_bytes - offset);
+ scoped_ptr<MessageInTransit> message(new MessageInTransit(
+ MessageInTransit::Type::MESSAGE, message_num_bytes,
+ static_cast<const char*>(elements) + offset));
+ if (!channel_->WriteMessage(message.Pass())) {
+ error_ = true;
+ return false;
+ }
+
+ offset += message_num_bytes;
+ }
+
+ return true;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.h b/mojo/edk/system/data_pipe_producer_dispatcher.h
new file mode 100644
index 0000000..7c28ae6
--- /dev/null
+++ b/mojo/edk/system/data_pipe_producer_dispatcher.h
@@ -0,0 +1,110 @@
+// 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 MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_
+
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/system/awakable_list.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/raw_channel.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// This is the |Dispatcher| implementation for the producer handle for data
+// pipes (created by the Mojo primitive |MojoCreateDataPipe()|). This class is
+// thread-safe.
+class MOJO_SYSTEM_IMPL_EXPORT DataPipeProducerDispatcher final
+ : public Dispatcher, public RawChannel::Delegate {
+ public:
+ static scoped_refptr<DataPipeProducerDispatcher> Create(
+ const MojoCreateDataPipeOptions& options) {
+ return make_scoped_refptr(new DataPipeProducerDispatcher(options));
+ }
+
+ // Must be called before any other methods.
+ void Init(ScopedPlatformHandle message_pipe);
+
+ // |Dispatcher| public methods:
+ Type GetType() const override;
+
+ // The "opposite" of |SerializeAndClose()|. (Typically this is called by
+ // |Dispatcher::Deserialize()|.)
+ static scoped_refptr<DataPipeProducerDispatcher>
+ Deserialize(const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles);
+
+ private:
+ DataPipeProducerDispatcher(const MojoCreateDataPipeOptions& options);
+ ~DataPipeProducerDispatcher() override;
+
+ void InitOnIO();
+ void CloseOnIO();
+
+ // |Dispatcher| protected methods:
+ void CancelAllAwakablesNoLock() override;
+ void CloseImplNoLock() override;
+ scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndCloseImplNoLock()
+ override;
+ MojoResult WriteDataImplNoLock(const void* elements,
+ uint32_t* num_bytes,
+ MojoWriteDataFlags flags) override;
+ MojoResult BeginWriteDataImplNoLock(void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoWriteDataFlags flags) override;
+ MojoResult EndWriteDataImplNoLock(uint32_t num_bytes_written) override;
+ HandleSignalsState GetHandleSignalsStateImplNoLock() const override;
+ MojoResult AddAwakableImplNoLock(Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) override;
+ void RemoveAwakableImplNoLock(Awakable* awakable,
+ HandleSignalsState* signals_state) override;
+ void StartSerializeImplNoLock(size_t* max_size,
+ size_t* max_platform_handles) override;
+ bool EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) override;
+ void TransportStarted() override;
+ void TransportEnded() override;
+ bool IsBusyNoLock() const override;
+
+ // |RawChannel::Delegate methods:
+ void OnReadMessage(
+ const MessageInTransit::View& message_view,
+ ScopedPlatformHandleVectorPtr platform_handles) override;
+ void OnError(Error error) override;
+
+ bool InTwoPhaseWrite() const;
+ bool WriteDataIntoMessages(const void* elements, uint32_t num_bytes);
+
+ MojoCreateDataPipeOptions options_;
+
+ // Protected by |lock()|:
+ RawChannel* channel_; // This will be null if closed.
+
+ AwakableList awakable_list_;
+
+ // If DispatcherTransport is created. Must be set before lock() is called to
+ // avoid deadlocks with RawChannel calling us.
+ base::Lock started_transport_;
+
+ bool error_;
+
+ ScopedPlatformHandle serialized_platform_handle_;
+
+ std::vector<char> two_phase_data_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(DataPipeProducerDispatcher);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_
diff --git a/mojo/edk/system/data_pipe_unittest.cc b/mojo/edk/system/data_pipe_unittest.cc
new file mode 100644
index 0000000..edf7bcb
--- /dev/null
+++ b/mojo/edk/system/data_pipe_unittest.cc
@@ -0,0 +1,1574 @@
+// Copyright 2015 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 <stdint.h>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/functions.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+const uint32_t kSizeOfOptions =
+ static_cast<uint32_t>(sizeof(MojoCreateDataPipeOptions));
+
+// In various places, we have to poll (since, e.g., we can't yet wait for a
+// certain amount of data to be available). This is the maximum number of
+// iterations (separated by a short sleep).
+// TODO(vtl): Get rid of this.
+const size_t kMaxPoll = 100;
+
+class DataPipeTest : public test::MojoSystemTest {
+ public:
+ DataPipeTest() : producer_(MOJO_HANDLE_INVALID),
+ consumer_(MOJO_HANDLE_INVALID) {}
+
+ ~DataPipeTest() override {
+ if (producer_ != MOJO_HANDLE_INVALID)
+ CHECK_EQ(MOJO_RESULT_OK, MojoClose(producer_));
+ if (consumer_ != MOJO_HANDLE_INVALID)
+ CHECK_EQ(MOJO_RESULT_OK, MojoClose(consumer_));
+ }
+
+ MojoResult Create(const MojoCreateDataPipeOptions* options) {
+ return MojoCreateDataPipe(options, &producer_, &consumer_);
+ }
+
+ MojoResult WriteData(const void* elements,
+ uint32_t* num_bytes,
+ bool all_or_none = false) {
+ return MojoWriteData(producer_, elements, num_bytes,
+ all_or_none ? MOJO_READ_DATA_FLAG_ALL_OR_NONE :
+ MOJO_WRITE_DATA_FLAG_NONE);
+ }
+
+ MojoResult ReadData(void* elements,
+ uint32_t* num_bytes,
+ bool all_or_none = false,
+ bool peek = false) {
+ MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_NONE;
+ if (all_or_none)
+ flags |= MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ if (peek)
+ flags |= MOJO_READ_DATA_FLAG_PEEK;
+ return MojoReadData(consumer_, elements, num_bytes, flags);
+ }
+
+ MojoResult QueryData(uint32_t* num_bytes) {
+ return MojoReadData(consumer_, nullptr, num_bytes,
+ MOJO_READ_DATA_FLAG_QUERY);
+ }
+
+ MojoResult DiscardData(uint32_t* num_bytes, bool all_or_none = false) {
+ MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_DISCARD;
+ if (all_or_none)
+ flags |= MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ return MojoReadData(consumer_, nullptr, num_bytes, flags);
+ }
+
+ MojoResult BeginReadData(const void** elements,
+ uint32_t* num_bytes,
+ bool all_or_none = false) {
+ MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_NONE;
+ if (all_or_none)
+ flags |= MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ return MojoBeginReadData(consumer_, elements, num_bytes, flags);
+ }
+
+ MojoResult EndReadData(uint32_t num_bytes_read) {
+ return MojoEndReadData(consumer_, num_bytes_read);
+ }
+
+ MojoResult BeginWriteData(void** elements,
+ uint32_t* num_bytes,
+ bool all_or_none = false) {
+ MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_NONE;
+ if (all_or_none)
+ flags |= MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ return MojoBeginWriteData(producer_, elements, num_bytes, flags);
+ }
+
+ MojoResult EndWriteData(uint32_t num_bytes_written) {
+ return MojoEndWriteData(producer_, num_bytes_written);
+ }
+
+ MojoResult CloseProducer() {
+ MojoResult rv = MojoClose(producer_);
+ producer_ = MOJO_HANDLE_INVALID;
+ return rv;
+ }
+
+ MojoResult CloseConsumer() {
+ MojoResult rv = MojoClose(consumer_);
+ consumer_ = MOJO_HANDLE_INVALID;
+ return rv;
+ }
+
+ MojoHandle producer_, consumer_;
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(DataPipeTest);
+};
+
+TEST_F(DataPipeTest, Basic) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 1000 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+
+ // We can write to a data pipe handle immediately.
+ int32_t elements[10] = {};
+ uint32_t num_bytes = 0;
+
+ num_bytes =
+ static_cast<uint32_t>(MOJO_ARRAYSIZE(elements) * sizeof(elements[0]));
+
+ elements[0] = 123;
+ elements[1] = 456;
+ num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(&elements[0], &num_bytes));
+
+ // Now wait for the other side to become readable.
+ MojoHandleSignalsState state;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, state.satisfied_signals);
+
+ elements[0] = -1;
+ elements[1] = -1;
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(&elements[0], &num_bytes));
+ ASSERT_EQ(static_cast<uint32_t>(2u * sizeof(elements[0])), num_bytes);
+ ASSERT_EQ(elements[0], 123);
+ ASSERT_EQ(elements[1], 456);
+}
+
+// Tests creation of data pipes with various (valid) options.
+TEST_F(DataPipeTest, CreateAndMaybeTransfer) {
+ MojoCreateDataPipeOptions test_options[] = {
+ // Default options.
+ {},
+ // Trivial element size, non-default capacity.
+ {kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ 1, // |element_num_bytes|.
+ 1000}, // |capacity_num_bytes|.
+ // Nontrivial element size, non-default capacity.
+ {kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ 4, // |element_num_bytes|.
+ 4000}, // |capacity_num_bytes|.
+ // Nontrivial element size, default capacity.
+ {kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ 100, // |element_num_bytes|.
+ 0} // |capacity_num_bytes|.
+ };
+ for (size_t i = 0; i < arraysize(test_options); i++) {
+ MojoHandle producer_handle, consumer_handle;
+ MojoCreateDataPipeOptions* options =
+ i ? &test_options[i] : nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateDataPipe(options, &producer_handle, &consumer_handle));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(producer_handle));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(consumer_handle));
+ }
+}
+
+TEST_F(DataPipeTest, SimpleReadWrite) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 1000 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ int32_t elements[10] = {};
+ uint32_t num_bytes = 0;
+
+ // Try reading; nothing there yet.
+ num_bytes =
+ static_cast<uint32_t>(MOJO_ARRAYSIZE(elements) * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadData(elements, &num_bytes));
+
+ // Query; nothing there yet.
+ num_bytes = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(0u, num_bytes);
+
+ // Discard; nothing there yet.
+ num_bytes = static_cast<uint32_t>(5u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, DiscardData(&num_bytes));
+
+ // Read with invalid |num_bytes|.
+ num_bytes = sizeof(elements[0]) + 1;
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, ReadData(elements, &num_bytes));
+
+ // Write two elements.
+ elements[0] = 123;
+ elements[1] = 456;
+ num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes));
+ // It should have written everything (even without "all or none").
+ ASSERT_EQ(2u * sizeof(elements[0]), num_bytes);
+
+ // Wait.
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Query.
+ // TODO(vtl): It's theoretically possible (though not with the current
+ // implementation/configured limits) that not all the data has arrived yet.
+ // (The theoretically-correct assertion here is that |num_bytes| is |1 * ...|
+ // or |2 * ...|.)
+ num_bytes = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(2 * sizeof(elements[0]), num_bytes);
+
+ // Read one element.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes));
+ ASSERT_EQ(1u * sizeof(elements[0]), num_bytes);
+ ASSERT_EQ(123, elements[0]);
+ ASSERT_EQ(-1, elements[1]);
+
+ // Query.
+ // TODO(vtl): See previous TODO. (If we got 2 elements there, however, we
+ // should get 1 here.)
+ num_bytes = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(1 * sizeof(elements[0]), num_bytes);
+
+ // Peek one element.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, false, true));
+ ASSERT_EQ(1u * sizeof(elements[0]), num_bytes);
+ ASSERT_EQ(456, elements[0]);
+ ASSERT_EQ(-1, elements[1]);
+
+ // Query. Still has 1 element remaining.
+ num_bytes = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(1 * sizeof(elements[0]), num_bytes);
+
+ // Try to read two elements, with "all or none".
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE,
+ ReadData(elements, &num_bytes, true, false));
+ ASSERT_EQ(-1, elements[0]);
+ ASSERT_EQ(-1, elements[1]);
+
+ // Try to read two elements, without "all or none".
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, false, false));
+ ASSERT_EQ(1u * sizeof(elements[0]), num_bytes);
+ ASSERT_EQ(456, elements[0]);
+ ASSERT_EQ(-1, elements[1]);
+
+ // Query.
+ num_bytes = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(0u, num_bytes);
+}
+
+// Note: The "basic" waiting tests test that the "wait states" are correct in
+// various situations; they don't test that waiters are properly awoken on state
+// changes. (For that, we need to use multiple threads.)
+TEST_F(DataPipeTest, BasicProducerWaiting) {
+ // Note: We take advantage of the fact that current for current
+ // implementations capacities are strict maximums. This is not guaranteed by
+ // the API.
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 2 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+ Create(&options);
+ MojoHandleSignalsState hss;
+
+ // Never readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(producer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Already writable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+
+ // Write two elements.
+ int32_t elements[2] = {123, 456};
+ uint32_t num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true));
+ ASSERT_EQ(static_cast<uint32_t>(2u * sizeof(elements[0])), num_bytes);
+
+ // Wait for data to become available to the consumer.
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Peek one element.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, true));
+ ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+ ASSERT_EQ(123, elements[0]);
+ ASSERT_EQ(-1, elements[1]);
+
+ // Read one element.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, false));
+ ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+ ASSERT_EQ(123, elements[0]);
+ ASSERT_EQ(-1, elements[1]);
+
+ // Try writing, using a two-phase write.
+ void* buffer = nullptr;
+ num_bytes = static_cast<uint32_t>(3u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&buffer, &num_bytes));
+ EXPECT_TRUE(buffer);
+ ASSERT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(elements[0])));
+
+ static_cast<int32_t*>(buffer)[0] = 789;
+ ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(static_cast<uint32_t>(
+ 1u * sizeof(elements[0]))));
+
+ // Read one element, using a two-phase read.
+ const void* read_buffer = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ BeginReadData(&read_buffer, &num_bytes, false));
+ EXPECT_TRUE(read_buffer);
+ // Since we only read one element (after having written three in all), the
+ // two-phase read should only allow us to read one. This checks an
+ // implementation detail!
+ ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+ ASSERT_EQ(456, static_cast<const int32_t*>(read_buffer)[0]);
+ ASSERT_EQ(MOJO_RESULT_OK, EndReadData(static_cast<uint32_t>(
+ 1u * sizeof(elements[0]))));
+
+ // Write one element.
+ elements[0] = 123;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes));
+ ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+
+ // Close the consumer.
+ CloseConsumer();
+
+ // It should now be never-writable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+}
+
+TEST_F(DataPipeTest, PeerClosedProducerWaiting) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 2 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ // Close the consumer.
+ CloseConsumer();
+
+ // It should be signaled.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+}
+
+TEST_F(DataPipeTest, PeerClosedConsumerWaiting) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 2 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ // Close the producer.
+ CloseProducer();
+
+ // It should be signaled.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+}
+
+TEST_F(DataPipeTest, BasicConsumerWaiting) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 1000 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ // Never writable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(0u, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Write two elements.
+ int32_t elements[2] = {123, 456};
+ uint32_t num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true));
+
+ // Wait for readability.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Discard one element.
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true));
+ ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+
+ // Should still be readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Peek one element.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, true));
+ ASSERT_EQ(456, elements[0]);
+ ASSERT_EQ(-1, elements[1]);
+
+ // Should still be readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Read one element.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true));
+ ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+ ASSERT_EQ(456, elements[0]);
+ ASSERT_EQ(-1, elements[1]);
+
+ // Write one element.
+ elements[0] = 789;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true));
+
+ // Waiting should now succeed.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Close the producer.
+ CloseProducer();
+
+ // Should still be readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Wait for the peer closed signal.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_CLOSED) != 0);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Read one element.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true));
+ ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+ ASSERT_EQ(789, elements[0]);
+ ASSERT_EQ(-1, elements[1]);
+
+ // Should be never-readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+}
+
+// Test with two-phase APIs and also closing the producer with an active
+// consumer waiter.
+TEST_F(DataPipeTest, ConsumerWaitingTwoPhase) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 1000 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ // Write two elements.
+ int32_t* elements = nullptr;
+ void* buffer = nullptr;
+ // Request room for three (but we'll only write two).
+ uint32_t num_bytes = static_cast<uint32_t>(3u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&buffer, &num_bytes, true));
+ EXPECT_TRUE(buffer);
+ EXPECT_GE(num_bytes, static_cast<uint32_t>(3u * sizeof(elements[0])));
+ elements = static_cast<int32_t*>(buffer);
+ elements[0] = 123;
+ elements[1] = 456;
+ ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(2u * sizeof(elements[0])));
+
+ // Wait for readability.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Read one element.
+ // Request two in all-or-none mode, but only read one.
+ const void* read_buffer = nullptr;
+ num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes, true));
+ EXPECT_TRUE(read_buffer);
+ ASSERT_EQ(static_cast<uint32_t>(2u * sizeof(elements[0])), num_bytes);
+ const int32_t* read_elements = static_cast<const int32_t*>(read_buffer);
+ ASSERT_EQ(123, read_elements[0]);
+ ASSERT_EQ(MOJO_RESULT_OK, EndReadData(1u * sizeof(elements[0])));
+
+ // Should still be readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Read one element.
+ // Request three, but not in all-or-none mode.
+ read_buffer = nullptr;
+ num_bytes = static_cast<uint32_t>(3u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes));
+ EXPECT_TRUE(read_buffer);
+ ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+ read_elements = static_cast<const int32_t*>(read_buffer);
+ ASSERT_EQ(456, read_elements[0]);
+ ASSERT_EQ(MOJO_RESULT_OK, EndReadData(1u * sizeof(elements[0])));
+
+ // Close the producer.
+ CloseProducer();
+
+ // Should be never-readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+}
+
+// Tests that data pipes aren't writable/readable during two-phase writes/reads.
+TEST_F(DataPipeTest, BasicTwoPhaseWaiting) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 1000 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ // It should be writable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ uint32_t num_bytes = static_cast<uint32_t>(1u * sizeof(int32_t));
+ void* write_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes));
+ EXPECT_TRUE(write_ptr);
+ EXPECT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(int32_t)));
+
+ // At this point, it shouldn't be writable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+ ASSERT_EQ(0u, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // It shouldn't be readable yet either (we'll wait later).
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ ASSERT_EQ(0u, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ static_cast<int32_t*>(write_ptr)[0] = 123;
+ ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(1u * sizeof(int32_t)));
+
+ // It should immediately be writable again.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // It should become readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Start another two-phase write and check that it's readable even in the
+ // middle of it.
+ num_bytes = static_cast<uint32_t>(1u * sizeof(int32_t));
+ write_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes));
+ EXPECT_TRUE(write_ptr);
+ EXPECT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(int32_t)));
+
+ // It should be readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // End the two-phase write without writing anything.
+ ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(0u));
+
+ // Start a two-phase read.
+ num_bytes = static_cast<uint32_t>(1u * sizeof(int32_t));
+ const void* read_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes));
+ EXPECT_TRUE(read_ptr);
+ ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(int32_t)), num_bytes);
+
+ // At this point, it should still be writable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // But not readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ ASSERT_EQ(0u, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // End the two-phase read without reading anything.
+ ASSERT_EQ(MOJO_RESULT_OK, EndReadData(0u));
+
+ // It should be readable again.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+}
+
+void Seq(int32_t start, size_t count, int32_t* out) {
+ for (size_t i = 0; i < count; i++)
+ out[i] = start + static_cast<int32_t>(i);
+}
+
+TEST_F(DataPipeTest, AllOrNone) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 10 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ // Try writing way too much.
+ uint32_t num_bytes = 20u * sizeof(int32_t);
+ int32_t buffer[100];
+ Seq(0, MOJO_ARRAYSIZE(buffer), buffer);
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, WriteData(buffer, &num_bytes, true));
+
+ // Should still be empty.
+ num_bytes = ~0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(0u, num_bytes);
+
+ // Write some data.
+ num_bytes = 5u * sizeof(int32_t);
+ Seq(100, MOJO_ARRAYSIZE(buffer), buffer);
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(buffer, &num_bytes, true));
+ ASSERT_EQ(5u * sizeof(int32_t), num_bytes);
+
+ // Wait for data.
+ // TODO(vtl): There's no real guarantee that all the data will become
+ // available at once (except that in current implementations, with reasonable
+ // limits, it will). Eventually, we'll be able to wait for a specified amount
+ // of data to become available.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Half full.
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(5u * sizeof(int32_t), num_bytes);
+
+ /* TODO(jam): enable if we end up observing max capacity
+ // Too much.
+ num_bytes = 6u * sizeof(int32_t);
+ Seq(200, MOJO_ARRAYSIZE(buffer), buffer);
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, WriteData(buffer, &num_bytes, true));
+ */
+
+ // Try reading too much.
+ num_bytes = 11u * sizeof(int32_t);
+ memset(buffer, 0xab, sizeof(buffer));
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, ReadData(buffer, &num_bytes, true));
+ int32_t expected_buffer[100];
+ memset(expected_buffer, 0xab, sizeof(expected_buffer));
+ ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer)));
+
+ // Try discarding too much.
+ num_bytes = 11u * sizeof(int32_t);
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, DiscardData(&num_bytes, true));
+
+ // Just a little.
+ num_bytes = 2u * sizeof(int32_t);
+ Seq(300, MOJO_ARRAYSIZE(buffer), buffer);
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(buffer, &num_bytes, true));
+ ASSERT_EQ(2u * sizeof(int32_t), num_bytes);
+
+ // Just right.
+ num_bytes = 3u * sizeof(int32_t);
+ Seq(400, MOJO_ARRAYSIZE(buffer), buffer);
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(buffer, &num_bytes, true));
+ ASSERT_EQ(3u * sizeof(int32_t), num_bytes);
+
+ // TODO(vtl): Hack (see also the TODO above): We can't currently wait for a
+ // specified amount of data to be available, so poll.
+ for (size_t i = 0; i < kMaxPoll; i++) {
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ if (num_bytes >= 10u * sizeof(int32_t))
+ break;
+
+ test::Sleep(test::EpsilonDeadline());
+ }
+ ASSERT_EQ(10u * sizeof(int32_t), num_bytes);
+
+ // Read half.
+ num_bytes = 5u * sizeof(int32_t);
+ memset(buffer, 0xab, sizeof(buffer));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes, true));
+ ASSERT_EQ(5u * sizeof(int32_t), num_bytes);
+ memset(expected_buffer, 0xab, sizeof(expected_buffer));
+ Seq(100, 5, expected_buffer);
+ ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer)));
+
+ // Try reading too much again.
+ num_bytes = 6u * sizeof(int32_t);
+ memset(buffer, 0xab, sizeof(buffer));
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, ReadData(buffer, &num_bytes, true));
+ memset(expected_buffer, 0xab, sizeof(expected_buffer));
+ ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer)));
+
+ // Try discarding too much again.
+ num_bytes = 6u * sizeof(int32_t);
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, DiscardData(&num_bytes, true));
+
+ // Discard a little.
+ num_bytes = 2u * sizeof(int32_t);
+ ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true));
+ ASSERT_EQ(2u * sizeof(int32_t), num_bytes);
+
+ // Three left.
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(3u * sizeof(int32_t), num_bytes);
+
+ // Close the producer, then test producer-closed cases.
+ CloseProducer();
+
+ // Wait.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Try reading too much; "failed precondition" since the producer is closed.
+ num_bytes = 4u * sizeof(int32_t);
+ memset(buffer, 0xab, sizeof(buffer));
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ ReadData(buffer, &num_bytes, true));
+ memset(expected_buffer, 0xab, sizeof(expected_buffer));
+ ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer)));
+
+ // Try discarding too much; "failed precondition" again.
+ num_bytes = 4u * sizeof(int32_t);
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, DiscardData(&num_bytes, true));
+
+ // Read a little.
+ num_bytes = 2u * sizeof(int32_t);
+ memset(buffer, 0xab, sizeof(buffer));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes, true));
+ ASSERT_EQ(2u * sizeof(int32_t), num_bytes);
+ memset(expected_buffer, 0xab, sizeof(expected_buffer));
+ Seq(400, 2, expected_buffer);
+ ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer)));
+
+ // Discard the remaining element.
+ num_bytes = 1u * sizeof(int32_t);
+ ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true));
+ ASSERT_EQ(1u * sizeof(int32_t), num_bytes);
+
+ // Empty again.
+ num_bytes = ~0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(0u, num_bytes);
+}
+
+TEST_F(DataPipeTest, DISABLED_TwoPhaseAllOrNone) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 10 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ // Try writing way too much (two-phase).
+ uint32_t num_bytes = 20u * sizeof(int32_t);
+ void* write_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE,
+ BeginWriteData(&write_ptr, &num_bytes, true));
+
+ // Try writing an amount which isn't a multiple of the element size
+ // (two-phase).
+ static_assert(sizeof(int32_t) > 1u, "Wow! int32_t's have size 1");
+ num_bytes = 1u;
+ write_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ BeginWriteData(&write_ptr, &num_bytes, true));
+
+ // Try reading way too much (two-phase).
+ num_bytes = 20u * sizeof(int32_t);
+ const void* read_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE,
+ BeginReadData(&read_ptr, &num_bytes, true));
+
+ // Write half (two-phase).
+ num_bytes = 5u * sizeof(int32_t);
+ write_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes, true));
+ // May provide more space than requested.
+ EXPECT_GE(num_bytes, 5u * sizeof(int32_t));
+ EXPECT_TRUE(write_ptr);
+ Seq(0, 5, static_cast<int32_t*>(write_ptr));
+ ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(5u * sizeof(int32_t)));
+
+ // Wait for data.
+ // TODO(vtl): (See corresponding TODO in AllOrNone.)
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Try reading an amount which isn't a multiple of the element size
+ // (two-phase).
+ num_bytes = 1u;
+ read_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ BeginReadData(&read_ptr, &num_bytes, true));
+
+ // Read one (two-phase).
+ num_bytes = 1u * sizeof(int32_t);
+ read_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes, true));
+ EXPECT_GE(num_bytes, 1u * sizeof(int32_t));
+ ASSERT_EQ(0, static_cast<const int32_t*>(read_ptr)[0]);
+ ASSERT_EQ(MOJO_RESULT_OK, EndReadData(1u * sizeof(int32_t)));
+
+ // We should have four left, leaving room for six.
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(4u * sizeof(int32_t), num_bytes);
+
+ // Assuming a tight circular buffer of the specified capacity, we can't do a
+ // two-phase write of six now.
+ num_bytes = 6u * sizeof(int32_t);
+ write_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE,
+ BeginWriteData(&write_ptr, &num_bytes, true));
+
+ // TODO(vtl): Hack (see also the TODO above): We can't currently wait for a
+ // specified amount of space to be available, so poll.
+ for (size_t i = 0; i < kMaxPoll; i++) {
+ // Write six elements (simple), filling the buffer.
+ num_bytes = 6u * sizeof(int32_t);
+ int32_t buffer[100];
+ Seq(100, 6, buffer);
+ MojoResult result = WriteData(buffer, &num_bytes, true);
+ if (result == MOJO_RESULT_OK)
+ break;
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, result);
+
+ test::Sleep(test::EpsilonDeadline());
+ }
+ ASSERT_EQ(6u * sizeof(int32_t), num_bytes);
+
+ // TODO(vtl): Hack: poll again.
+ for (size_t i = 0; i < kMaxPoll; i++) {
+ // We have ten.
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ if (num_bytes >= 10u * sizeof(int32_t))
+ break;
+
+ test::Sleep(test::EpsilonDeadline());
+ }
+ ASSERT_EQ(10u * sizeof(int32_t), num_bytes);
+
+ // Note: Whether a two-phase read of ten would fail here or not is
+ // implementation-dependent.
+
+ // Close the producer.
+ CloseProducer();
+
+ // A two-phase read of nine should work.
+ num_bytes = 9u * sizeof(int32_t);
+ read_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes, true));
+ EXPECT_GE(num_bytes, 9u * sizeof(int32_t));
+ ASSERT_EQ(1, static_cast<const int32_t*>(read_ptr)[0]);
+ ASSERT_EQ(2, static_cast<const int32_t*>(read_ptr)[1]);
+ ASSERT_EQ(3, static_cast<const int32_t*>(read_ptr)[2]);
+ ASSERT_EQ(4, static_cast<const int32_t*>(read_ptr)[3]);
+ ASSERT_EQ(100, static_cast<const int32_t*>(read_ptr)[4]);
+ ASSERT_EQ(101, static_cast<const int32_t*>(read_ptr)[5]);
+ ASSERT_EQ(102, static_cast<const int32_t*>(read_ptr)[6]);
+ ASSERT_EQ(103, static_cast<const int32_t*>(read_ptr)[7]);
+ ASSERT_EQ(104, static_cast<const int32_t*>(read_ptr)[8]);
+ ASSERT_EQ(MOJO_RESULT_OK, EndReadData(9u * sizeof(int32_t)));
+
+ // Wait for peer closed.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // A two-phase read of two should fail, with "failed precondition".
+ num_bytes = 2u * sizeof(int32_t);
+ read_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ BeginReadData(&read_ptr, &num_bytes, true));
+}
+
+/*
+jam: this is testing that the implementation uses a circular buffer, which we
+don't use currently.
+// Tests that |ProducerWriteData()| and |ConsumerReadData()| writes and reads,
+// respectively, as much as possible, even if it may have to "wrap around" the
+// internal circular buffer. (Note that the two-phase write and read need not do
+// this.)
+TYPED_TEST(DataPipeImplTest, WrapAround) {
+ unsigned char test_data[1000];
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(test_data); i++)
+ test_data[i] = static_cast<unsigned char>(i);
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ 1u, // |element_num_bytes|.
+ 100u // |capacity_num_bytes|.
+ };
+ MojoCreateDataPipeOptions validated_options = {};
+ // This test won't be valid if |ValidateCreateOptions()| decides to give the
+ // pipe more space.
+ ASSERT_EQ(MOJO_RESULT_OK, DataPipe::ValidateCreateOptions(
+ &options, &validated_options));
+ ASSERT_EQ(100u, validated_options.capacity_num_bytes);
+ this->Create(options);
+ this->DoTransfer();
+
+ Waiter waiter;
+ HandleSignalsState hss;
+
+ // Add waiter.
+ waiter.Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ this->ConsumerAddAwakable(&waiter, MOJO_HANDLE_SIGNAL_READABLE, 1,
+ nullptr));
+
+ // Write 20 bytes.
+ uint32_t num_bytes = 20u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ this->ProducerWriteData(&test_data[0], &num_bytes, false));
+ ASSERT_EQ(20u, num_bytes);
+
+ // Wait for data.
+ // TODO(vtl): (See corresponding TODO in AllOrNone.)
+ ASSERT_EQ(MOJO_RESULT_OK, waiter.Wait(test::TinyDeadline(), nullptr));
+ hss = HandleSignalsState();
+ this->ConsumerRemoveAwakable(&waiter, &hss);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Read 10 bytes.
+ unsigned char read_buffer[1000] = {0};
+ num_bytes = 10u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ this->ConsumerReadData(read_buffer, &num_bytes, false, false));
+ ASSERT_EQ(10u, num_bytes);
+ ASSERT_EQ(0, memcmp(read_buffer, &test_data[0], 10u));
+
+ if (this->IsStrictCircularBuffer()) {
+ // Check that a two-phase write can now only write (at most) 80 bytes. (This
+ // checks an implementation detail; this behavior is not guaranteed.)
+ void* write_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ this->ProducerBeginWriteData(&write_buffer_ptr, &num_bytes,
+ false));
+ EXPECT_TRUE(write_buffer_ptr);
+ ASSERT_EQ(80u, num_bytes);
+ ASSERT_EQ(MOJO_RESULT_OK, this->ProducerEndWriteData(0u));
+ }
+
+ // TODO(vtl): (See corresponding TODO in TwoPhaseAllOrNone.)
+ size_t total_num_bytes = 0;
+ for (size_t i = 0; i < kMaxPoll; i++) {
+ // Write as much data as we can (using |ProducerWriteData()|). We should
+ // write 90 bytes (eventually).
+ num_bytes = 200u;
+ MojoResult result = this->ProducerWriteData(
+ &test_data[20 + total_num_bytes], &num_bytes, false);
+ if (result == MOJO_RESULT_OK) {
+ total_num_bytes += num_bytes;
+ if (total_num_bytes >= 90u)
+ break;
+ } else {
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, result);
+ }
+
+ test::Sleep(test::EpsilonDeadline());
+ }
+ ASSERT_EQ(90u, total_num_bytes);
+
+ // TODO(vtl): (See corresponding TODO in TwoPhaseAllOrNone.)
+ for (size_t i = 0; i < kMaxPoll; i++) {
+ // We have 100.
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ this->ConsumerQueryData(&num_bytes));
+ if (num_bytes >= 100u)
+ break;
+
+ test::Sleep(test::EpsilonDeadline());
+ }
+ ASSERT_EQ(100u, num_bytes);
+
+ if (this->IsStrictCircularBuffer()) {
+ // Check that a two-phase read can now only read (at most) 90 bytes. (This
+ // checks an implementation detail; this behavior is not guaranteed.)
+ const void* read_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ this->ConsumerBeginReadData(&read_buffer_ptr, &num_bytes, false));
+ EXPECT_TRUE(read_buffer_ptr);
+ ASSERT_EQ(90u, num_bytes);
+ ASSERT_EQ(MOJO_RESULT_OK, this->ConsumerEndReadData(0u));
+ }
+
+ // Read as much as possible (using |ConsumerReadData()|). We should read 100
+ // bytes.
+ num_bytes = static_cast<uint32_t>(MOJO_ARRAYSIZE(read_buffer) *
+ sizeof(read_buffer[0]));
+ memset(read_buffer, 0, num_bytes);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ this->ConsumerReadData(read_buffer, &num_bytes, false, false));
+ ASSERT_EQ(100u, num_bytes);
+ ASSERT_EQ(0, memcmp(read_buffer, &test_data[10], 100u));
+
+ this->ProducerClose();
+ this->ConsumerClose();
+}
+*/
+
+// Tests the behavior of writing (simple and two-phase), closing the producer,
+// then reading (simple and two-phase).
+TEST_F(DataPipeTest, WriteCloseProducerRead) {
+ const char kTestData[] = "hello world";
+ const uint32_t kTestDataSize = static_cast<uint32_t>(sizeof(kTestData));
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ 1u, // |element_num_bytes|.
+ 1000u // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+
+ // Write some data, so we'll have something to read.
+ uint32_t num_bytes = kTestDataSize;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes, false));
+ ASSERT_EQ(kTestDataSize, num_bytes);
+
+ // Write it again, so we'll have something left over.
+ num_bytes = kTestDataSize;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes, false));
+ ASSERT_EQ(kTestDataSize, num_bytes);
+
+ // Start two-phase write.
+ void* write_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ BeginWriteData(&write_buffer_ptr, &num_bytes, false));
+ EXPECT_TRUE(write_buffer_ptr);
+ EXPECT_GT(num_bytes, 0u);
+
+ // TODO(vtl): (See corresponding TODO in TwoPhaseAllOrNone.)
+ for (size_t i = 0; i < kMaxPoll; i++) {
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ if (num_bytes >= 2u * kTestDataSize)
+ break;
+
+ test::Sleep(test::EpsilonDeadline());
+ }
+ ASSERT_EQ(2u * kTestDataSize, num_bytes);
+
+ // Start two-phase read.
+ const void* read_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ BeginReadData(&read_buffer_ptr, &num_bytes));
+ EXPECT_TRUE(read_buffer_ptr);
+ ASSERT_EQ(2u * kTestDataSize, num_bytes);
+
+ // Close the producer.
+ CloseProducer();
+
+ // The consumer can finish its two-phase read.
+ ASSERT_EQ(0, memcmp(read_buffer_ptr, kTestData, kTestDataSize));
+ ASSERT_EQ(MOJO_RESULT_OK, EndReadData(kTestDataSize));
+
+ // And start another.
+ read_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ BeginReadData(&read_buffer_ptr, &num_bytes));
+ EXPECT_TRUE(read_buffer_ptr);
+ ASSERT_EQ(kTestDataSize, num_bytes);
+}
+
+
+// Tests the behavior of interrupting a two-phase read and write by closing the
+// consumer.
+TEST_F(DataPipeTest, TwoPhaseWriteReadCloseConsumer) {
+ const char kTestData[] = "hello world";
+ const uint32_t kTestDataSize = static_cast<uint32_t>(sizeof(kTestData));
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ 1u, // |element_num_bytes|.
+ 1000u // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ // Write some data, so we'll have something to read.
+ uint32_t num_bytes = kTestDataSize;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes));
+ ASSERT_EQ(kTestDataSize, num_bytes);
+
+ // Start two-phase write.
+ void* write_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_buffer_ptr, &num_bytes));
+ EXPECT_TRUE(write_buffer_ptr);
+ ASSERT_GT(num_bytes, kTestDataSize);
+
+ // Wait for data.
+ // TODO(vtl): (See corresponding TODO in AllOrNone.)
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Start two-phase read.
+ const void* read_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer_ptr, &num_bytes));
+ EXPECT_TRUE(read_buffer_ptr);
+ ASSERT_EQ(kTestDataSize, num_bytes);
+
+ // Close the consumer.
+ CloseConsumer();
+
+ // Wait for producer to know that the consumer is closed.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+
+ // Actually write some data. (Note: Premature freeing of the buffer would
+ // probably only be detected under ASAN or similar.)
+ memcpy(write_buffer_ptr, kTestData, kTestDataSize);
+ // Note: Even though the consumer has been closed, ending the two-phase
+ // write will report success.
+ ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(kTestDataSize));
+
+ // But trying to write should result in failure.
+ num_bytes = kTestDataSize;
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, WriteData(kTestData, &num_bytes));
+
+ // As will trying to start another two-phase write.
+ write_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ BeginWriteData(&write_buffer_ptr, &num_bytes));
+}
+
+// Tests the behavior of "interrupting" a two-phase write by closing both the
+// producer and the consumer.
+TEST_F(DataPipeTest, TwoPhaseWriteCloseBoth) {
+ const uint32_t kTestDataSize = 15u;
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ 1u, // |element_num_bytes|.
+ 1000u // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+
+ // Start two-phase write.
+ void* write_buffer_ptr = nullptr;
+ uint32_t num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_buffer_ptr, &num_bytes));
+ EXPECT_TRUE(write_buffer_ptr);
+ ASSERT_GT(num_bytes, kTestDataSize);
+}
+
+// Tests the behavior of writing, closing the producer, and then reading (with
+// and without data remaining).
+TEST_F(DataPipeTest, WriteCloseProducerReadNoData) {
+ const char kTestData[] = "hello world";
+ const uint32_t kTestDataSize = static_cast<uint32_t>(sizeof(kTestData));
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ 1u, // |element_num_bytes|.
+ 1000u // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ // Write some data, so we'll have something to read.
+ uint32_t num_bytes = kTestDataSize;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes));
+ ASSERT_EQ(kTestDataSize, num_bytes);
+
+ // Close the producer.
+ CloseProducer();
+
+ // Wait. (Note that once the consumer knows that the producer is closed, it
+ // must also know about all the data that was sent.)
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Peek that data.
+ char buffer[1000];
+ num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes, false, true));
+ ASSERT_EQ(kTestDataSize, num_bytes);
+ ASSERT_EQ(0, memcmp(buffer, kTestData, kTestDataSize));
+
+ // Read that data.
+ memset(buffer, 0, 1000);
+ num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes));
+ ASSERT_EQ(kTestDataSize, num_bytes);
+ ASSERT_EQ(0, memcmp(buffer, kTestData, kTestDataSize));
+
+ // A second read should fail.
+ num_bytes = static_cast<uint32_t>(sizeof(buffer));
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ReadData(buffer, &num_bytes));
+
+ // A two-phase read should also fail.
+ const void* read_buffer_ptr = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ ReadData(&read_buffer_ptr, &num_bytes));
+
+ // Ditto for discard.
+ num_bytes = 10u;
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, DiscardData(&num_bytes));
+}
+
+// Test that two-phase reads/writes behave correctly when given invalid
+// arguments.
+TEST_F(DataPipeTest, TwoPhaseMoreInvalidArguments) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 10 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+ MojoHandleSignalsState hss;
+
+ // No data.
+ uint32_t num_bytes = 1000u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(0u, num_bytes);
+
+ // Try "ending" a two-phase write when one isn't active.
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ EndWriteData(1u * sizeof(int32_t)));
+
+ // Wait a bit, to make sure that if a signal were (incorrectly) sent, it'd
+ // have time to propagate.
+ test::Sleep(test::EpsilonDeadline());
+
+ // Still no data.
+ num_bytes = 1000u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(0u, num_bytes);
+
+ // Try ending a two-phase write with an invalid amount (too much).
+ num_bytes = 0u;
+ void* write_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes));
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ EndWriteData(num_bytes + static_cast<uint32_t>(sizeof(int32_t))));
+
+ // But the two-phase write still ended.
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, EndWriteData(0u));
+
+ // Wait a bit (as above).
+ test::Sleep(test::EpsilonDeadline());
+
+ // Still no data.
+ num_bytes = 1000u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(0u, num_bytes);
+
+ // Try ending a two-phase write with an invalid amount (not a multiple of the
+ // element size).
+ num_bytes = 0u;
+ write_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes));
+ EXPECT_GE(num_bytes, 1u);
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, EndWriteData(1u));
+
+ // But the two-phase write still ended.
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, EndWriteData(0u));
+
+ // Wait a bit (as above).
+ test::Sleep(test::EpsilonDeadline());
+
+ // Still no data.
+ num_bytes = 1000u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(0u, num_bytes);
+
+ // Now write some data, so we'll be able to try reading.
+ int32_t element = 123;
+ num_bytes = 1u * sizeof(int32_t);
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(&element, &num_bytes));
+
+ // Wait for data.
+ // TODO(vtl): (See corresponding TODO in AllOrNone.)
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(consumer_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // One element available.
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(1u * sizeof(int32_t), num_bytes);
+
+ // Try "ending" a two-phase read when one isn't active.
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, EndReadData(1u * sizeof(int32_t)));
+
+ // Still one element available.
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(1u * sizeof(int32_t), num_bytes);
+
+ // Try ending a two-phase read with an invalid amount (too much).
+ num_bytes = 0u;
+ const void* read_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes));
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ EndReadData(num_bytes + static_cast<uint32_t>(sizeof(int32_t))));
+
+ // Still one element available.
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(1u * sizeof(int32_t), num_bytes);
+
+ // Try ending a two-phase read with an invalid amount (not a multiple of the
+ // element size).
+ num_bytes = 0u;
+ read_ptr = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes));
+ ASSERT_EQ(1u * sizeof(int32_t), num_bytes);
+ ASSERT_EQ(123, static_cast<const int32_t*>(read_ptr)[0]);
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, EndReadData(1u));
+
+ // Still one element available.
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes));
+ ASSERT_EQ(1u * sizeof(int32_t), num_bytes);
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/dispatcher.cc b/mojo/edk/system/dispatcher.cc
new file mode 100644
index 0000000..e7426e5
--- /dev/null
+++ b/mojo/edk/system/dispatcher.cc
@@ -0,0 +1,493 @@
+// 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 "mojo/edk/system/dispatcher.h"
+
+#include "base/logging.h"
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/data_pipe_consumer_dispatcher.h"
+#include "mojo/edk/system/data_pipe_producer_dispatcher.h"
+#include "mojo/edk/system/message_pipe_dispatcher.h"
+#include "mojo/edk/system/platform_handle_dispatcher.h"
+#include "mojo/edk/system/shared_buffer_dispatcher.h"
+
+namespace mojo {
+namespace edk {
+
+namespace test {
+
+// TODO(vtl): Maybe this should be defined in a test-only file instead.
+DispatcherTransport DispatcherTryStartTransport(Dispatcher* dispatcher) {
+ return Dispatcher::HandleTableAccess::TryStartTransport(dispatcher);
+}
+
+} // namespace test
+
+// Dispatcher ------------------------------------------------------------------
+
+// static
+DispatcherTransport Dispatcher::HandleTableAccess::TryStartTransport(
+ Dispatcher* dispatcher) {
+ DCHECK(dispatcher);
+
+ // Our dispatcher implementations hop to IO thread on initialization, so it's
+ // valid that while their RawChannel os being initialized on IO thread, the
+ // dispatcher is being sent. We handle this by just acquiring the lock.
+
+ // See comment in header for why we need this.
+ dispatcher->TransportStarted();
+
+ dispatcher->lock_.Acquire();
+
+ // We shouldn't race with things that close dispatchers, since closing can
+ // only take place either under |handle_table_lock_| or when the handle is
+ // marked as busy.
+ DCHECK(!dispatcher->is_closed_);
+
+ return DispatcherTransport(dispatcher);
+}
+
+// static
+void Dispatcher::TransportDataAccess::StartSerialize(
+ Dispatcher* dispatcher,
+ size_t* max_size,
+ size_t* max_platform_handles) {
+ DCHECK(dispatcher);
+ dispatcher->StartSerialize(max_size, max_platform_handles);
+}
+
+// static
+bool Dispatcher::TransportDataAccess::EndSerializeAndClose(
+ Dispatcher* dispatcher,
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) {
+ DCHECK(dispatcher);
+ return dispatcher->EndSerializeAndClose(destination, actual_size,
+ platform_handles);
+}
+
+// static
+scoped_refptr<Dispatcher> Dispatcher::TransportDataAccess::Deserialize(
+ int32_t type,
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles) {
+ switch (static_cast<Dispatcher::Type>(type)) {
+ case Type::UNKNOWN:
+ DVLOG(2) << "Deserializing invalid handle";
+ return nullptr;
+ case Type::MESSAGE_PIPE:
+ return scoped_refptr<Dispatcher>(MessagePipeDispatcher::Deserialize(
+ source, size, platform_handles));
+ case Type::DATA_PIPE_PRODUCER:
+ return scoped_refptr<Dispatcher>(
+ DataPipeProducerDispatcher::Deserialize(
+ source, size, platform_handles));
+ case Type::DATA_PIPE_CONSUMER:
+ return scoped_refptr<Dispatcher>(
+ DataPipeConsumerDispatcher::Deserialize(
+ source, size, platform_handles));
+ case Type::SHARED_BUFFER:
+ return scoped_refptr<Dispatcher>(SharedBufferDispatcher::Deserialize(
+ source, size, platform_handles));
+ case Type::PLATFORM_HANDLE:
+ return scoped_refptr<Dispatcher>(PlatformHandleDispatcher::Deserialize(
+ source, size, platform_handles));
+ }
+ LOG(WARNING) << "Unknown dispatcher type " << type;
+ return nullptr;
+}
+
+MojoResult Dispatcher::Close() {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ CloseNoLock();
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Dispatcher::WriteMessage(
+ const void* bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags flags) {
+ DCHECK(!transports ||
+ (transports->size() > 0 &&
+ transports->size() < GetConfiguration().max_message_num_handles));
+
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return WriteMessageImplNoLock(bytes, num_bytes, transports, flags);
+}
+
+MojoResult Dispatcher::ReadMessage(void* bytes,
+ uint32_t* num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags) {
+ DCHECK(!num_dispatchers || *num_dispatchers == 0 ||
+ (dispatchers && dispatchers->empty()));
+
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return ReadMessageImplNoLock(bytes, num_bytes, dispatchers, num_dispatchers,
+ flags);
+}
+
+MojoResult Dispatcher::WriteData(const void* elements,
+ uint32_t* num_bytes,
+ MojoWriteDataFlags flags) {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return WriteDataImplNoLock(elements, num_bytes, flags);
+}
+
+MojoResult Dispatcher::BeginWriteData(void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoWriteDataFlags flags) {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return BeginWriteDataImplNoLock(buffer, buffer_num_bytes, flags);
+}
+
+MojoResult Dispatcher::EndWriteData(uint32_t num_bytes_written) {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return EndWriteDataImplNoLock(num_bytes_written);
+}
+
+MojoResult Dispatcher::ReadData(void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags) {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return ReadDataImplNoLock(elements, num_bytes, flags);
+}
+
+MojoResult Dispatcher::BeginReadData(const void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoReadDataFlags flags) {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return BeginReadDataImplNoLock(buffer, buffer_num_bytes, flags);
+}
+
+MojoResult Dispatcher::EndReadData(uint32_t num_bytes_read) {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return EndReadDataImplNoLock(num_bytes_read);
+}
+
+MojoResult Dispatcher::DuplicateBufferHandle(
+ const MojoDuplicateBufferHandleOptions* options,
+ scoped_refptr<Dispatcher>* new_dispatcher) {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return DuplicateBufferHandleImplNoLock(options, new_dispatcher);
+}
+
+MojoResult Dispatcher::MapBuffer(
+ uint64_t offset,
+ uint64_t num_bytes,
+ MojoMapBufferFlags flags,
+ scoped_ptr<PlatformSharedBufferMapping>* mapping) {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return MapBufferImplNoLock(offset, num_bytes, flags, mapping);
+}
+
+HandleSignalsState Dispatcher::GetHandleSignalsState() const {
+ base::AutoLock locker(lock_);
+ if (is_closed_)
+ return HandleSignalsState();
+
+ return GetHandleSignalsStateImplNoLock();
+}
+
+MojoResult Dispatcher::AddAwakable(Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ base::AutoLock locker(lock_);
+ if (is_closed_) {
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ return AddAwakableImplNoLock(awakable, signals, context, signals_state);
+}
+
+void Dispatcher::RemoveAwakable(Awakable* awakable,
+ HandleSignalsState* handle_signals_state) {
+ base::AutoLock locker(lock_);
+ if (is_closed_) {
+ if (handle_signals_state)
+ *handle_signals_state = HandleSignalsState();
+ return;
+ }
+
+ RemoveAwakableImplNoLock(awakable, handle_signals_state);
+}
+
+Dispatcher::Dispatcher() : is_closed_(false) {
+}
+
+Dispatcher::~Dispatcher() {
+ // Make sure that |Close()| was called.
+ DCHECK(is_closed_);
+}
+
+void Dispatcher::CancelAllAwakablesNoLock() {
+ lock_.AssertAcquired();
+ DCHECK(is_closed_);
+ // By default, waiting isn't supported. Only dispatchers that can be waited on
+ // will do something nontrivial.
+}
+
+void Dispatcher::CloseImplNoLock() {
+ lock_.AssertAcquired();
+ DCHECK(is_closed_);
+ // This may not need to do anything. Dispatchers should override this to do
+ // any actual close-time cleanup necessary.
+}
+
+MojoResult Dispatcher::WriteMessageImplNoLock(
+ const void* /*bytes*/,
+ uint32_t /*num_bytes*/,
+ std::vector<DispatcherTransport>* /*transports*/,
+ MojoWriteMessageFlags /*flags*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for message pipe dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::ReadMessageImplNoLock(
+ void* /*bytes*/,
+ uint32_t* /*num_bytes*/,
+ DispatcherVector* /*dispatchers*/,
+ uint32_t* /*num_dispatchers*/,
+ MojoReadMessageFlags /*flags*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for message pipe dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::WriteDataImplNoLock(const void* /*elements*/,
+ uint32_t* /*num_bytes*/,
+ MojoWriteDataFlags /*flags*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for data pipe dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::BeginWriteDataImplNoLock(
+ void** /*buffer*/,
+ uint32_t* /*buffer_num_bytes*/,
+ MojoWriteDataFlags /*flags*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for data pipe dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::EndWriteDataImplNoLock(uint32_t /*num_bytes_written*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for data pipe dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::ReadDataImplNoLock(void* /*elements*/,
+ uint32_t* /*num_bytes*/,
+ MojoReadDataFlags /*flags*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for data pipe dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::BeginReadDataImplNoLock(
+ const void** /*buffer*/,
+ uint32_t* /*buffer_num_bytes*/,
+ MojoReadDataFlags /*flags*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for data pipe dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::EndReadDataImplNoLock(uint32_t /*num_bytes_read*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for data pipe dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::DuplicateBufferHandleImplNoLock(
+ const MojoDuplicateBufferHandleOptions* /*options*/,
+ scoped_refptr<Dispatcher>* /*new_dispatcher*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for buffer dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::MapBufferImplNoLock(
+ uint64_t /*offset*/,
+ uint64_t /*num_bytes*/,
+ MojoMapBufferFlags /*flags*/,
+ scoped_ptr<PlatformSharedBufferMapping>* /*mapping*/) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, not supported. Only needed for buffer dispatchers.
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+HandleSignalsState Dispatcher::GetHandleSignalsStateImplNoLock() const {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, waiting isn't supported. Only dispatchers that can be waited on
+ // will do something nontrivial.
+ return HandleSignalsState();
+}
+
+MojoResult Dispatcher::AddAwakableImplNoLock(
+ Awakable* /*awakable*/,
+ MojoHandleSignals /*signals*/,
+ uint32_t /*context*/,
+ HandleSignalsState* signals_state) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, waiting isn't supported. Only dispatchers that can be waited on
+ // will do something nontrivial.
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+ return MOJO_RESULT_FAILED_PRECONDITION;
+}
+
+void Dispatcher::RemoveAwakableImplNoLock(Awakable* /*awakable*/,
+ HandleSignalsState* signals_state) {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // By default, waiting isn't supported. Only dispatchers that can be waited on
+ // will do something nontrivial.
+ if (signals_state)
+ *signals_state = HandleSignalsState();
+}
+
+void Dispatcher::StartSerializeImplNoLock(size_t* max_size,
+ size_t* max_platform_handles) {
+ DCHECK(HasOneRef()); // Only one ref => no need to take the lock.
+ DCHECK(!is_closed_);
+ *max_size = 0;
+ *max_platform_handles = 0;
+}
+
+bool Dispatcher::EndSerializeAndCloseImplNoLock(
+ void* /*destination*/,
+ size_t* /*actual_size*/,
+ PlatformHandleVector* /*platform_handles*/) {
+ DCHECK(HasOneRef()); // Only one ref => no need to take the lock.
+ DCHECK(is_closed_);
+ // By default, serializing isn't supported, so just close.
+ CloseImplNoLock();
+ return false;
+}
+
+bool Dispatcher::IsBusyNoLock() const {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+ // Most dispatchers support only "atomic" operations, so they are never busy
+ // (in this sense).
+ return false;
+}
+
+void Dispatcher::CloseNoLock() {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+
+ is_closed_ = true;
+ CancelAllAwakablesNoLock();
+ CloseImplNoLock();
+}
+
+scoped_refptr<Dispatcher>
+Dispatcher::CreateEquivalentDispatcherAndCloseNoLock() {
+ lock_.AssertAcquired();
+ DCHECK(!is_closed_);
+
+ is_closed_ = true;
+ CancelAllAwakablesNoLock();
+ return CreateEquivalentDispatcherAndCloseImplNoLock();
+}
+
+void Dispatcher::StartSerialize(size_t* max_size,
+ size_t* max_platform_handles) {
+ DCHECK(max_size);
+ DCHECK(max_platform_handles);
+ DCHECK(!is_closed_);
+ StartSerializeImplNoLock(max_size, max_platform_handles);
+}
+
+bool Dispatcher::EndSerializeAndClose(void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) {
+ DCHECK(actual_size);
+ DCHECK(!is_closed_);
+
+ // Like other |...Close()| methods, we mark ourselves as closed before calling
+ // the impl. But there's no need to cancel waiters: we shouldn't have any (and
+ // shouldn't be in |Core|'s handle table.
+ is_closed_ = true;
+
+#if !defined(NDEBUG)
+ // See the comment above |EndSerializeAndCloseImplNoLock()|. In brief: Locking
+ // isn't actually needed, but we need to satisfy assertions (which we don't
+ // want to remove or weaken).
+ base::AutoLock locker(lock_);
+#endif
+
+ return EndSerializeAndCloseImplNoLock(destination, actual_size,
+ platform_handles);
+}
+
+// DispatcherTransport ---------------------------------------------------------
+
+void DispatcherTransport::End() {
+ DCHECK(dispatcher_);
+ dispatcher_->lock_.Release();
+
+ dispatcher_->TransportEnded();
+
+ dispatcher_ = nullptr;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/dispatcher.h b/mojo/edk/system/dispatcher.h
new file mode 100644
index 0000000..514c329
--- /dev/null
+++ b/mojo/edk/system/dispatcher.h
@@ -0,0 +1,409 @@
+// 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 MOJO_EDK_SYSTEM_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_DISPATCHER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <ostream>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/buffer.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+class Core;
+class Dispatcher;
+class DispatcherTransport;
+class HandleTable;
+class LocalMessagePipeEndpoint;
+class PlatformSharedBufferMapping;
+class ProxyMessagePipeEndpoint;
+class TransportData;
+class Awakable;
+
+using DispatcherVector = std::vector<scoped_refptr<Dispatcher>>;
+
+namespace test {
+
+// Test helper. We need to declare it here so we can friend it.
+MOJO_SYSTEM_IMPL_EXPORT DispatcherTransport
+DispatcherTryStartTransport(Dispatcher* dispatcher);
+
+} // namespace test
+
+// A |Dispatcher| implements Mojo primitives that are "attached" to a particular
+// handle. This includes most (all?) primitives except for |MojoWait...()|. This
+// object is thread-safe, with its state being protected by a single lock
+// |lock_|, which is also made available to implementation subclasses (via the
+// |lock()| method).
+class MOJO_SYSTEM_IMPL_EXPORT Dispatcher
+ : public base::RefCountedThreadSafe<Dispatcher> {
+ public:
+ enum class Type {
+ UNKNOWN = 0,
+ MESSAGE_PIPE,
+ DATA_PIPE_PRODUCER,
+ DATA_PIPE_CONSUMER,
+ SHARED_BUFFER,
+
+ // "Private" types (not exposed via the public interface):
+ PLATFORM_HANDLE = -1
+ };
+ virtual Type GetType() const = 0;
+
+ // These methods implement the various primitives named |Mojo...()|. These
+ // take |lock_| and handle races with |Close()|. Then they call out to
+ // subclasses' |...ImplNoLock()| methods (still under |lock_|), which actually
+ // implement the primitives.
+ // NOTE(vtl): This puts a big lock around each dispatcher (i.e., handle), and
+ // prevents the various |...ImplNoLock()|s from releasing the lock as soon as
+ // possible. If this becomes an issue, we can rethink this.
+ MojoResult Close();
+
+ // |transports| may be non-null if and only if there are handles to be
+ // written; not that |this| must not be in |transports|. On success, all the
+ // dispatchers in |transports| must have been moved to a closed state; on
+ // failure, they should remain in their original state.
+ MojoResult WriteMessage(const void* bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags flags);
+ // |dispatchers| must be non-null but empty, if |num_dispatchers| is non-null
+ // and nonzero. On success, it will be set to the dispatchers to be received
+ // (and assigned handles) as part of the message.
+ MojoResult ReadMessage(void* bytes,
+ uint32_t* num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags);
+ MojoResult WriteData(const void* elements,
+ uint32_t* elements_num_bytes,
+ MojoWriteDataFlags flags);
+ MojoResult BeginWriteData(void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoWriteDataFlags flags);
+ MojoResult EndWriteData(uint32_t num_bytes_written);
+ MojoResult ReadData(void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags);
+ MojoResult BeginReadData(const void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoReadDataFlags flags);
+ MojoResult EndReadData(uint32_t num_bytes_read);
+ // |options| may be null. |new_dispatcher| must not be null, but
+ // |*new_dispatcher| should be null (and will contain the dispatcher for the
+ // new handle on success).
+ MojoResult DuplicateBufferHandle(
+ const MojoDuplicateBufferHandleOptions* options,
+ scoped_refptr<Dispatcher>* new_dispatcher);
+ MojoResult MapBuffer(
+ uint64_t offset,
+ uint64_t num_bytes,
+ MojoMapBufferFlags flags,
+ scoped_ptr<PlatformSharedBufferMapping>* mapping);
+
+ // Gets the current handle signals state. (The default implementation simply
+ // returns a default-constructed |HandleSignalsState|, i.e., no signals
+ // satisfied or satisfiable.) Note: The state is subject to change from other
+ // threads.
+ HandleSignalsState GetHandleSignalsState() const;
+
+ // Adds an awakable to this dispatcher, which will be woken up when this
+ // object changes state to satisfy |signals| with context |context|. It will
+ // also be woken up when it becomes impossible for the object to ever satisfy
+ // |signals| with a suitable error status.
+ //
+ // If |signals_state| is non-null, on *failure* |*signals_state| will be set
+ // to the current handle signals state (on success, it is left untouched).
+ //
+ // Returns:
+ // - |MOJO_RESULT_OK| if the awakable was added;
+ // - |MOJO_RESULT_ALREADY_EXISTS| if |signals| is already satisfied;
+ // - |MOJO_RESULT_INVALID_ARGUMENT| if the dispatcher has been closed; and
+ // - |MOJO_RESULT_FAILED_PRECONDITION| if it is not (or no longer) possible
+ // that |signals| will ever be satisfied.
+ MojoResult AddAwakable(Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state);
+ // Removes an awakable from this dispatcher. (It is valid to call this
+ // multiple times for the same |awakable| on the same object, so long as
+ // |AddAwakable()| was called at most once.) If |signals_state| is non-null,
+ // |*signals_state| will be set to the current handle signals state.
+ void RemoveAwakable(Awakable* awakable, HandleSignalsState* signals_state);
+
+ // A dispatcher must be put into a special state in order to be sent across a
+ // message pipe. Outside of tests, only |HandleTableAccess| is allowed to do
+ // this, since there are requirements on the handle table (see below).
+ //
+ // In this special state, only a restricted set of operations is allowed.
+ // These are the ones available as |DispatcherTransport| methods. Other
+ // |Dispatcher| methods must not be called until |DispatcherTransport::End()|
+ // has been called.
+ class HandleTableAccess {
+ private:
+ friend class Core;
+ friend class HandleTable;
+ // Tests also need this, to avoid needing |Core|.
+ friend DispatcherTransport test::DispatcherTryStartTransport(Dispatcher*);
+
+ // This must be called under the handle table lock and only if the handle
+ // table entry is not marked busy. The caller must maintain a reference to
+ // |dispatcher| until |DispatcherTransport::End()| is called.
+ static DispatcherTransport TryStartTransport(Dispatcher* dispatcher);
+ };
+
+ // A |TransportData| may serialize dispatchers that are given to it (and which
+ // were previously attached to the |MessageInTransit| that is creating it) and
+ // and then (probably in a different process) deserialize.
+ // Note that the |MessageInTransit| "owns" (i.e., has the only ref to) these
+ // dispatchers, so there are no locking issues. (There's no lock ordering
+ // issue, and in fact no need to take dispatcher locks at all.)
+ // TODO(vtl): Consider making another wrapper similar to |DispatcherTransport|
+ // (but with an owning, unique reference), and having
+ // |CreateEquivalentDispatcherAndCloseImplNoLock()| return that wrapper (and
+ // |MessageInTransit|, etc. only holding on to such wrappers).
+ class TransportDataAccess {
+ private:
+ friend class TransportData;
+
+ // Serialization API. These functions may only be called on such
+ // dispatchers. See the |Dispatcher| methods of the same names for more
+ // details.
+ static void StartSerialize(Dispatcher* dispatcher,
+ size_t* max_size,
+ size_t* max_platform_handles);
+ static bool EndSerializeAndClose(
+ Dispatcher* dispatcher,
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles);
+
+ // Deserialization API.
+ // Note: This "clears" (i.e., reset to the invalid handle) any platform
+ // handles that it takes ownership of.
+ static scoped_refptr<Dispatcher> Deserialize(
+ int32_t type,
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles);
+ };
+
+ protected:
+ friend class base::RefCountedThreadSafe<Dispatcher>;
+ friend class MessagePipeDispatcher; // For TransportStarted/TransportEnded.
+
+ Dispatcher();
+ virtual ~Dispatcher();
+
+ // These are to be overridden by subclasses (if necessary). They are called
+ // exactly once -- first |CancelAllAwakablesNoLock()|, then
+ // |CloseImplNoLock()|,
+ // when the dispatcher is being closed. They are called under |lock_|.
+ virtual void CancelAllAwakablesNoLock();
+ virtual void CloseImplNoLock();
+ virtual scoped_refptr<Dispatcher>
+ CreateEquivalentDispatcherAndCloseImplNoLock() = 0;
+
+ // These are to be overridden by subclasses (if necessary). They are never
+ // called after the dispatcher has been closed. They are called under |lock_|.
+ // See the descriptions of the methods without the "ImplNoLock" for more
+ // information.
+ virtual MojoResult WriteMessageImplNoLock(
+ const void* bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags flags);
+ virtual MojoResult ReadMessageImplNoLock(void* bytes,
+ uint32_t* num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags);
+ virtual MojoResult WriteDataImplNoLock(const void* elements,
+ uint32_t* num_bytes,
+ MojoWriteDataFlags flags);
+ virtual MojoResult BeginWriteDataImplNoLock(
+ void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoWriteDataFlags flags);
+ virtual MojoResult EndWriteDataImplNoLock(uint32_t num_bytes_written);
+ virtual MojoResult ReadDataImplNoLock(void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags);
+ virtual MojoResult BeginReadDataImplNoLock(
+ const void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoReadDataFlags flags);
+ virtual MojoResult EndReadDataImplNoLock(uint32_t num_bytes_read);
+ virtual MojoResult DuplicateBufferHandleImplNoLock(
+ const MojoDuplicateBufferHandleOptions* options,
+ scoped_refptr<Dispatcher>* new_dispatcher);
+ virtual MojoResult MapBufferImplNoLock(
+ uint64_t offset,
+ uint64_t num_bytes,
+ MojoMapBufferFlags flags,
+ scoped_ptr<PlatformSharedBufferMapping>* mapping);
+ virtual HandleSignalsState GetHandleSignalsStateImplNoLock() const;
+ virtual MojoResult AddAwakableImplNoLock(Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state);
+ virtual void RemoveAwakableImplNoLock(Awakable* awakable,
+ HandleSignalsState* signals_state);
+
+ // These implement the API used to serialize dispatchers (described below).
+ // They will only be called on a dispatcher that's attached to and "owned" by
+ // a |MessageInTransit|. See the non-"impl" versions for more information.
+ //
+ // Note: |StartSerializeImplNoLock()| is actually called with |lock_| NOT
+ // held, since the dispatcher should only be accessible to the calling thread.
+ // On Debug builds, |EndSerializeAndCloseImplNoLock()| is called with |lock_|
+ // held, to satisfy any |lock_.AssertAcquired()| (e.g., in |CloseImplNoLock()|
+ // -- and anything it calls); disentangling those assertions is
+ // difficult/fragile, and would weaken our general checking of invariants.
+ //
+ // TODO(vtl): Consider making these pure virtual once most things support
+ // being passed over a message pipe.
+ virtual void StartSerializeImplNoLock(size_t* max_size,
+ size_t* max_platform_handles);
+ virtual bool EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles);
+
+ // These are called before and after a dispatcher is being transported. i.e.
+ // |TransportStarted| is called |StartSerializeImplNoLock| and
+ // |TransportEnded| is called after |EndSerializeAndCloseImplNoLock|. They are
+ // needed to avoid deadlocks when transporting a dispatcher. The reason is
+ // that |StartSerializeImplNoLock| is called on the main thread and will lead
+ // to calling RawChannel::ReleaseHandle. The dispatcher is locked and it will
+ // acquire RawChannel's locks as well. however the RawChannel could have its
+ // locks acquired on the IO thread and be waiting to acquire the dispatcher's
+ // lock. To solve this deadlock, |TransportStarted| is called before the
+ // dispatcher's lock is acquired.
+ virtual void TransportStarted() {}
+ virtual void TransportEnded() {}
+
+ // This should be overridden to return true if/when there's an ongoing
+ // operation (e.g., two-phase read/writes on data pipes) that should prevent a
+ // handle from being sent over a message pipe (with status "busy").
+ virtual bool IsBusyNoLock() const;
+
+ // Available to subclasses. (Note: Returns a non-const reference, just like
+ // |base::AutoLock|'s constructor takes a non-const reference.)
+ base::Lock& lock() const { return lock_; }
+
+ private:
+ friend class DispatcherTransport;
+
+ // Closes the dispatcher. This must be done under lock, and unlike |Close()|,
+ // the dispatcher must not be closed already. (This is the "equivalent" of
+ // |CreateEquivalentDispatcherAndCloseNoLock()|, for situations where the
+ // dispatcher must be disposed of instead of "transferred".)
+ void CloseNoLock();
+
+ // Creates an equivalent dispatcher -- representing the same resource as this
+ // dispatcher -- and close (i.e., disable) this dispatcher. I.e., this
+ // dispatcher will look as though it was closed, but the resource it
+ // represents will be assigned to the new dispatcher. This must be called
+ // under the dispatcher's lock.
+ scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndCloseNoLock();
+
+ // API to serialize dispatchers, exposed to only |TransportData| (via
+ // |TransportData|). They may only be called on a dispatcher attached to a
+ // |MessageInTransit| (and in particular not in |CoreImpl|'s handle table).
+ //
+ // Starts the serialization. Returns (via the two "out" parameters) the
+ // maximum amount of space that may be needed to serialize this dispatcher (no
+ // more than |TransportData::kMaxSerializedDispatcherSize|) and the maximum
+ // number of |PlatformHandle|s that may need to be attached (no more than
+ // |TransportData::kMaxSerializedDispatcherPlatformHandles|). If this
+ // dispatcher cannot be serialized, |*max_size| and |*max_platform_handles|
+ // should be set to zero. A call to this method will ALWAYS be followed by a
+ // call to |EndSerializeAndClose()| (even if this dispatcher cannot be
+ // serialized).
+ void StartSerialize(size_t* max_size,
+ size_t* max_platform_handles);
+ // Completes the serialization of this dispatcher and closes it. (This call
+ // will always follow an earlier call to |StartSerialize()|. This does so by
+ // writing to |destination| and appending any |PlatformHandle|s needed to
+ // |platform_handles| (which may be null if no platform handles were indicated
+ // to be required to |StartSerialize()|). This may write no more than the
+ // amount indicated by |StartSerialize()|. (WARNING: Beware of races, e.g., if
+ // something can be mutated between the two calls!) Returns true on success,
+ // in which case |*actual_size| is set to the amount it actually wrote to
+ // |destination|. On failure, |*actual_size| should not be modified; however,
+ // the dispatcher will still be closed.
+ bool EndSerializeAndClose(void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles);
+
+ // This protects the following members as well as any state added by
+ // subclasses.
+ mutable base::Lock lock_;
+ bool is_closed_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Dispatcher);
+};
+
+// Wrapper around a |Dispatcher| pointer, while it's being processed to be
+// passed in a message pipe. See the comment about
+// |Dispatcher::HandleTableAccess| for more details.
+//
+// Note: This class is deliberately "thin" -- no more expensive than a
+// |Dispatcher*|.
+class MOJO_SYSTEM_IMPL_EXPORT DispatcherTransport {
+ public:
+ DispatcherTransport() : dispatcher_(nullptr) {}
+
+ void End();
+
+ Dispatcher::Type GetType() const { return dispatcher_->GetType(); }
+ bool IsBusy() const { return dispatcher_->IsBusyNoLock(); }
+ void Close() { dispatcher_->CloseNoLock(); }
+ scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndClose() {
+ return dispatcher_->CreateEquivalentDispatcherAndCloseNoLock();
+ }
+
+ bool is_valid() const { return !!dispatcher_; }
+
+ protected:
+ Dispatcher* dispatcher() { return dispatcher_; }
+
+ private:
+ friend class Dispatcher::HandleTableAccess;
+ friend class MessagePipeDispatcher; // For AttachTransportsNoLock.
+
+ explicit DispatcherTransport(Dispatcher* dispatcher)
+ : dispatcher_(dispatcher) {}
+
+ Dispatcher* dispatcher_;
+
+ // Copy and assign allowed.
+};
+
+// So logging macros and |DCHECK_EQ()|, etc. work.
+MOJO_SYSTEM_IMPL_EXPORT inline std::ostream& operator<<(std::ostream& out,
+ Dispatcher::Type type) {
+ return out << static_cast<int>(type);
+}
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_DISPATCHER_H_
diff --git a/mojo/edk/system/dispatcher_unittest.cc b/mojo/edk/system/dispatcher_unittest.cc
new file mode 100644
index 0000000..cf4e6cf
--- /dev/null
+++ b/mojo/edk/system/dispatcher_unittest.cc
@@ -0,0 +1,295 @@
+// 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 "mojo/edk/system/dispatcher.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+// Trivial subclass that makes the constructor public.
+class TrivialDispatcher final : public Dispatcher {
+ public:
+ TrivialDispatcher() {}
+
+ Type GetType() const override { return Type::UNKNOWN; }
+
+ private:
+ friend class base::RefCountedThreadSafe<TrivialDispatcher>;
+ ~TrivialDispatcher() override {}
+
+ scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndCloseImplNoLock()
+ override {
+ lock().AssertAcquired();
+ return scoped_refptr<Dispatcher>(new TrivialDispatcher());
+ }
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TrivialDispatcher);
+};
+
+TEST(DispatcherTest, Basic) {
+ scoped_refptr<Dispatcher> d(new TrivialDispatcher());
+
+ EXPECT_EQ(Dispatcher::Type::UNKNOWN, d->GetType());
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->WriteMessage(nullptr, 0, nullptr, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->ReadMessage(nullptr, nullptr, nullptr, nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->WriteData(nullptr, nullptr, MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->BeginWriteData(nullptr, nullptr, MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndWriteData(0));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->ReadData(nullptr, nullptr, MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->BeginReadData(nullptr, nullptr, MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndReadData(0));
+ Waiter w;
+ w.Init();
+ HandleSignalsState hss;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ d->AddAwakable(&w, ~MOJO_HANDLE_SIGNAL_NONE, 0, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ // Okay to remove even if it wasn't added (or was already removed).
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->WriteMessage(nullptr, 0, nullptr, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->ReadMessage(nullptr, nullptr, nullptr, nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->WriteData(nullptr, nullptr, MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->BeginWriteData(nullptr, nullptr, MOJO_WRITE_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndWriteData(0));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->ReadData(nullptr, nullptr, MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->BeginReadData(nullptr, nullptr, MOJO_READ_DATA_FLAG_NONE));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->EndReadData(0));
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->AddAwakable(&w, ~MOJO_HANDLE_SIGNAL_NONE, 0, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+}
+
+class ThreadSafetyStressThread : public base::SimpleThread {
+ public:
+ enum DispatcherOp {
+ CLOSE = 0,
+ WRITE_MESSAGE,
+ READ_MESSAGE,
+ WRITE_DATA,
+ BEGIN_WRITE_DATA,
+ END_WRITE_DATA,
+ READ_DATA,
+ BEGIN_READ_DATA,
+ END_READ_DATA,
+ DUPLICATE_BUFFER_HANDLE,
+ MAP_BUFFER,
+ ADD_WAITER,
+ REMOVE_WAITER,
+ DISPATCHER_OP_COUNT
+ };
+
+ ThreadSafetyStressThread(base::WaitableEvent* event,
+ scoped_refptr<Dispatcher> dispatcher,
+ DispatcherOp op)
+ : base::SimpleThread("thread_safety_stress_thread"),
+ event_(event),
+ dispatcher_(dispatcher),
+ op_(op) {
+ CHECK_LE(0, op_);
+ CHECK_LT(op_, DISPATCHER_OP_COUNT);
+ }
+
+ ~ThreadSafetyStressThread() override { Join(); }
+
+ private:
+ void Run() override {
+ event_->Wait();
+
+ waiter_.Init();
+ switch (op_) {
+ case CLOSE: {
+ MojoResult r = dispatcher_->Close();
+ EXPECT_TRUE(r == MOJO_RESULT_OK || r == MOJO_RESULT_INVALID_ARGUMENT)
+ << "Result: " << r;
+ break;
+ }
+ case WRITE_MESSAGE:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->WriteMessage(nullptr, 0, nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ break;
+ case READ_MESSAGE:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->ReadMessage(nullptr, nullptr, nullptr, nullptr,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ break;
+ case WRITE_DATA:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->WriteData(nullptr, nullptr,
+ MOJO_WRITE_DATA_FLAG_NONE));
+ break;
+ case BEGIN_WRITE_DATA:
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->BeginWriteData(nullptr, nullptr,
+ MOJO_WRITE_DATA_FLAG_NONE));
+ break;
+ case END_WRITE_DATA:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, dispatcher_->EndWriteData(0));
+ break;
+ case READ_DATA:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->ReadData(nullptr, nullptr,
+ MOJO_READ_DATA_FLAG_NONE));
+ break;
+ case BEGIN_READ_DATA:
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->BeginReadData(nullptr, nullptr,
+ MOJO_READ_DATA_FLAG_NONE));
+ break;
+ case END_READ_DATA:
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, dispatcher_->EndReadData(0));
+ break;
+ case DUPLICATE_BUFFER_HANDLE: {
+ scoped_refptr<Dispatcher> unused;
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->DuplicateBufferHandle(nullptr, &unused));
+ break;
+ }
+ case MAP_BUFFER: {
+ scoped_ptr<PlatformSharedBufferMapping> unused;
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher_->MapBuffer(0u, 0u, MOJO_MAP_BUFFER_FLAG_NONE, &unused));
+ break;
+ }
+ case ADD_WAITER: {
+ HandleSignalsState hss;
+ MojoResult r = dispatcher_->AddAwakable(
+ &waiter_, ~MOJO_HANDLE_SIGNAL_NONE, 0, &hss);
+ EXPECT_TRUE(r == MOJO_RESULT_FAILED_PRECONDITION ||
+ r == MOJO_RESULT_INVALID_ARGUMENT);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ break;
+ }
+ case REMOVE_WAITER: {
+ HandleSignalsState hss;
+ dispatcher_->RemoveAwakable(&waiter_, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ // Always try to remove the waiter, in case we added it.
+ HandleSignalsState hss;
+ dispatcher_->RemoveAwakable(&waiter_, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ }
+
+ base::WaitableEvent* const event_;
+ const scoped_refptr<Dispatcher> dispatcher_;
+ const DispatcherOp op_;
+
+ Waiter waiter_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ThreadSafetyStressThread);
+};
+
+TEST(DispatcherTest, ThreadSafetyStress) {
+ static const size_t kRepeatCount = 20;
+ static const size_t kNumThreads = 100;
+
+ for (size_t i = 0; i < kRepeatCount; i++) {
+ // Manual reset, not initially signalled.
+ base::WaitableEvent event(true, false);
+ scoped_refptr<Dispatcher> d(new TrivialDispatcher());
+
+ {
+ ScopedVector<ThreadSafetyStressThread> threads;
+ for (size_t j = 0; j < kNumThreads; j++) {
+ ThreadSafetyStressThread::DispatcherOp op =
+ static_cast<ThreadSafetyStressThread::DispatcherOp>(
+ (i + j) % ThreadSafetyStressThread::DISPATCHER_OP_COUNT);
+ threads.push_back(new ThreadSafetyStressThread(&event, d, op));
+ threads.back()->Start();
+ }
+ // Kicks off real work on the threads:
+ event.Signal();
+ } // Joins all the threads.
+
+ // One of the threads should already have closed the dispatcher.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, d->Close());
+ }
+}
+
+TEST(DispatcherTest, ThreadSafetyStressNoClose) {
+ static const size_t kRepeatCount = 20;
+ static const size_t kNumThreads = 100;
+
+ for (size_t i = 0; i < kRepeatCount; i++) {
+ // Manual reset, not initially signalled.
+ base::WaitableEvent event(true, false);
+ scoped_refptr<Dispatcher> d(new TrivialDispatcher());
+
+ {
+ ScopedVector<ThreadSafetyStressThread> threads;
+ for (size_t j = 0; j < kNumThreads; j++) {
+ ThreadSafetyStressThread::DispatcherOp op =
+ static_cast<ThreadSafetyStressThread::DispatcherOp>(
+ (i + j) % (ThreadSafetyStressThread::DISPATCHER_OP_COUNT - 1) +
+ 1);
+ threads.push_back(new ThreadSafetyStressThread(&event, d, op));
+ threads.back()->Start();
+ }
+ // Kicks off real work on the threads:
+ event.Signal();
+ } // Joins all the threads.
+
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ }
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/handle_signals_state.h b/mojo/edk/system/handle_signals_state.h
new file mode 100644
index 0000000..1c47a28
--- /dev/null
+++ b/mojo/edk/system/handle_signals_state.h
@@ -0,0 +1,49 @@
+// Copyright 2014 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 MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_
+#define MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_
+
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace edk {
+
+// Just "add" some constructors and methods to the C struct
+// |MojoHandleSignalsState| (for convenience). This should add no overhead.
+struct MOJO_SYSTEM_IMPL_EXPORT HandleSignalsState final
+ : public MojoHandleSignalsState {
+ HandleSignalsState() {
+ satisfied_signals = MOJO_HANDLE_SIGNAL_NONE;
+ satisfiable_signals = MOJO_HANDLE_SIGNAL_NONE;
+ }
+ HandleSignalsState(MojoHandleSignals satisfied,
+ MojoHandleSignals satisfiable) {
+ satisfied_signals = satisfied;
+ satisfiable_signals = satisfiable;
+ }
+
+ bool equals(const HandleSignalsState& other) const {
+ return satisfied_signals == other.satisfied_signals &&
+ satisfiable_signals == other.satisfiable_signals;
+ }
+
+ bool satisfies(MojoHandleSignals signals) const {
+ return !!(satisfied_signals & signals);
+ }
+
+ bool can_satisfy(MojoHandleSignals signals) const {
+ return !!(satisfiable_signals & signals);
+ }
+
+ // (Copy and assignment allowed.)
+};
+static_assert(sizeof(HandleSignalsState) == sizeof(MojoHandleSignalsState),
+ "HandleSignalsState should add no overhead");
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_
diff --git a/mojo/edk/system/handle_table.cc b/mojo/edk/system/handle_table.cc
new file mode 100644
index 0000000..a9774c8
--- /dev/null
+++ b/mojo/edk/system/handle_table.cc
@@ -0,0 +1,243 @@
+// Copyright 2014 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 "mojo/edk/system/handle_table.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/dispatcher.h"
+
+namespace mojo {
+namespace edk {
+
+HandleTable::Entry::Entry() : busy(false) {
+}
+
+HandleTable::Entry::Entry(const scoped_refptr<Dispatcher>& dispatcher)
+ : dispatcher(dispatcher), busy(false) {
+}
+
+HandleTable::Entry::~Entry() {
+ DCHECK(!busy);
+}
+
+HandleTable::HandleTable() : next_handle_(MOJO_HANDLE_INVALID + 1) {
+}
+
+HandleTable::~HandleTable() {
+ // This should usually not be reached (the only instance should be owned by
+ // the singleton |Core|, which lives forever), except in tests.
+}
+
+Dispatcher* HandleTable::GetDispatcher(MojoHandle handle) {
+ DCHECK_NE(handle, MOJO_HANDLE_INVALID);
+
+ HandleToEntryMap::iterator it = handle_to_entry_map_.find(handle);
+ if (it == handle_to_entry_map_.end())
+ return nullptr;
+ return it->second.dispatcher.get();
+}
+
+MojoResult HandleTable::GetAndRemoveDispatcher(
+ MojoHandle handle,
+ scoped_refptr<Dispatcher>* dispatcher) {
+ DCHECK_NE(handle, MOJO_HANDLE_INVALID);
+ DCHECK(dispatcher);
+
+ HandleToEntryMap::iterator it = handle_to_entry_map_.find(handle);
+ if (it == handle_to_entry_map_.end())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (it->second.busy)
+ return MOJO_RESULT_BUSY;
+ *dispatcher = it->second.dispatcher;
+ handle_to_entry_map_.erase(it);
+
+ return MOJO_RESULT_OK;
+}
+
+MojoHandle HandleTable::AddDispatcher(
+ const scoped_refptr<Dispatcher>& dispatcher) {
+ if (handle_to_entry_map_.size() >= GetConfiguration().max_handle_table_size)
+ return MOJO_HANDLE_INVALID;
+ return AddDispatcherNoSizeCheck(dispatcher);
+}
+
+std::pair<MojoHandle, MojoHandle> HandleTable::AddDispatcherPair(
+ const scoped_refptr<Dispatcher>& dispatcher0,
+ const scoped_refptr<Dispatcher>& dispatcher1) {
+ if (handle_to_entry_map_.size() + 1 >=
+ GetConfiguration().max_handle_table_size)
+ return std::make_pair(MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID);
+ return std::make_pair(AddDispatcherNoSizeCheck(dispatcher0),
+ AddDispatcherNoSizeCheck(dispatcher1));
+}
+
+bool HandleTable::AddDispatcherVector(const DispatcherVector& dispatchers,
+ MojoHandle* handles) {
+ size_t max_message_num_handles = GetConfiguration().max_message_num_handles;
+ size_t max_handle_table_size = GetConfiguration().max_handle_table_size;
+
+ DCHECK_LE(dispatchers.size(), max_message_num_handles);
+ DCHECK(handles);
+ DCHECK_LT(
+ static_cast<uint64_t>(max_handle_table_size) + max_message_num_handles,
+ std::numeric_limits<size_t>::max())
+ << "Addition may overflow";
+
+ if (handle_to_entry_map_.size() + dispatchers.size() > max_handle_table_size)
+ return false;
+
+ for (size_t i = 0; i < dispatchers.size(); i++) {
+ if (dispatchers[i]) {
+ handles[i] = AddDispatcherNoSizeCheck(dispatchers[i]);
+ } else {
+ LOG(WARNING) << "Invalid dispatcher at index " << i;
+ handles[i] = MOJO_HANDLE_INVALID;
+ }
+ }
+ return true;
+}
+
+MojoResult HandleTable::MarkBusyAndStartTransport(
+ MojoHandle disallowed_handle,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ std::vector<DispatcherTransport>* transports) {
+ DCHECK_NE(disallowed_handle, MOJO_HANDLE_INVALID);
+ DCHECK(handles);
+ DCHECK_LE(num_handles, GetConfiguration().max_message_num_handles);
+ DCHECK(transports);
+ DCHECK_EQ(transports->size(), num_handles);
+
+ std::vector<Entry*> entries(num_handles);
+
+ // First verify all the handles and get their dispatchers.
+ uint32_t i;
+ MojoResult error_result = MOJO_RESULT_INTERNAL;
+ for (i = 0; i < num_handles; i++) {
+ // Sending your own handle is not allowed (and, for consistency, returns
+ // "busy").
+ if (handles[i] == disallowed_handle) {
+ error_result = MOJO_RESULT_BUSY;
+ break;
+ }
+
+ HandleToEntryMap::iterator it = handle_to_entry_map_.find(handles[i]);
+ if (it == handle_to_entry_map_.end()) {
+ error_result = MOJO_RESULT_INVALID_ARGUMENT;
+ break;
+ }
+
+ entries[i] = &it->second;
+ if (entries[i]->busy) {
+ error_result = MOJO_RESULT_BUSY;
+ break;
+ }
+ // Note: By marking the handle as busy here, we're also preventing the
+ // same handle from being sent multiple times in the same message.
+ entries[i]->busy = true;
+
+ // Try to start the transport.
+ DispatcherTransport transport =
+ Dispatcher::HandleTableAccess::TryStartTransport(
+ entries[i]->dispatcher.get());
+ if (!transport.is_valid()) {
+ // Only log for Debug builds, since this is not a problem with the system
+ // code, but with user code.
+ DLOG(WARNING) << "Likely race condition in user code detected: attempt "
+ "to transfer handle " << handles[i]
+ << " while it is in use on a different thread";
+
+ // Unset the busy flag (since it won't be unset below).
+ entries[i]->busy = false;
+ error_result = MOJO_RESULT_BUSY;
+ break;
+ }
+
+ // Check if the dispatcher is busy (e.g., in a two-phase read/write).
+ // (Note that this must be done after the dispatcher's lock is acquired.)
+ if (transport.IsBusy()) {
+ // Unset the busy flag and end the transport (since it won't be done
+ // below).
+ entries[i]->busy = false;
+ transport.End();
+ error_result = MOJO_RESULT_BUSY;
+ break;
+ }
+
+ // Hang on to the transport (which we'll need to end the transport).
+ (*transports)[i] = transport;
+ }
+ if (i < num_handles) {
+ DCHECK_NE(error_result, MOJO_RESULT_INTERNAL);
+
+ // Unset the busy flags and release the locks.
+ for (uint32_t j = 0; j < i; j++) {
+ DCHECK(entries[j]->busy);
+ entries[j]->busy = false;
+ (*transports)[j].End();
+ }
+ return error_result;
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoHandle HandleTable::AddDispatcherNoSizeCheck(
+ const scoped_refptr<Dispatcher>& dispatcher) {
+ DCHECK(dispatcher);
+ DCHECK_LT(handle_to_entry_map_.size(),
+ GetConfiguration().max_handle_table_size);
+ DCHECK_NE(next_handle_, MOJO_HANDLE_INVALID);
+
+ // TODO(vtl): Maybe we want to do something different/smarter. (Or maybe try
+ // assigning randomly?)
+ while (handle_to_entry_map_.find(next_handle_) !=
+ handle_to_entry_map_.end()) {
+ next_handle_++;
+ if (next_handle_ == MOJO_HANDLE_INVALID)
+ next_handle_++;
+ }
+
+ MojoHandle new_handle = next_handle_;
+ handle_to_entry_map_[new_handle] = Entry(dispatcher);
+
+ next_handle_++;
+ if (next_handle_ == MOJO_HANDLE_INVALID)
+ next_handle_++;
+
+ return new_handle;
+}
+
+void HandleTable::RemoveBusyHandles(const MojoHandle* handles,
+ uint32_t num_handles) {
+ DCHECK(handles);
+ DCHECK_LE(num_handles, GetConfiguration().max_message_num_handles);
+
+ for (uint32_t i = 0; i < num_handles; i++) {
+ HandleToEntryMap::iterator it = handle_to_entry_map_.find(handles[i]);
+ DCHECK(it != handle_to_entry_map_.end());
+ DCHECK(it->second.busy);
+ it->second.busy = false; // For the sake of a |DCHECK()|.
+ handle_to_entry_map_.erase(it);
+ }
+}
+
+void HandleTable::RestoreBusyHandles(const MojoHandle* handles,
+ uint32_t num_handles) {
+ DCHECK(handles);
+ DCHECK_LE(num_handles, GetConfiguration().max_message_num_handles);
+
+ for (uint32_t i = 0; i < num_handles; i++) {
+ HandleToEntryMap::iterator it = handle_to_entry_map_.find(handles[i]);
+ DCHECK(it != handle_to_entry_map_.end());
+ DCHECK(it->second.busy);
+ it->second.busy = false;
+ }
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/handle_table.h b/mojo/edk/system/handle_table.h
new file mode 100644
index 0000000..de5ac05
--- /dev/null
+++ b/mojo/edk/system/handle_table.h
@@ -0,0 +1,144 @@
+// Copyright 2014 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 MOJO_EDK_SYSTEM_HANDLE_TABLE_H_
+#define MOJO_EDK_SYSTEM_HANDLE_TABLE_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+class Core;
+class Dispatcher;
+class DispatcherTransport;
+
+using DispatcherVector = std::vector<scoped_refptr<Dispatcher>>;
+
+// Test-only function (defined/used in embedder/test_embedder.cc). Declared here
+// so it can be friended.
+namespace internal {
+bool ShutdownCheckNoLeaks(Core*);
+}
+
+// This class provides the (global) handle table (owned by |Core|), which maps
+// (valid) |MojoHandle|s to |Dispatcher|s. This is abstracted so that, e.g.,
+// caching may be added.
+//
+// This class is NOT thread-safe; locking is left to |Core| (since it may need
+// to make several changes -- "atomically" or in rapid successsion, in which
+// case the extra locking/unlocking would be unnecessary overhead).
+
+class MOJO_SYSTEM_IMPL_EXPORT HandleTable {
+ public:
+ HandleTable();
+ ~HandleTable();
+
+ // Gets the dispatcher for a given handle (which should not be
+ // |MOJO_HANDLE_INVALID|). Returns null if there's no dispatcher for the given
+ // handle.
+ // WARNING: For efficiency, this returns a dumb pointer. If you're going to
+ // use the result outside |Core|'s lock, you MUST take a reference (e.g., by
+ // storing the result inside a |scoped_refptr|).
+ Dispatcher* GetDispatcher(MojoHandle handle);
+
+ // On success, gets the dispatcher for a given handle (which should not be
+ // |MOJO_HANDLE_INVALID|) and removes it. (On failure, returns an appropriate
+ // result (and leaves |dispatcher| alone), namely
+ // |MOJO_RESULT_INVALID_ARGUMENT| if there's no dispatcher for the given
+ // handle or |MOJO_RESULT_BUSY| if the handle is marked as busy.)
+ MojoResult GetAndRemoveDispatcher(MojoHandle handle,
+ scoped_refptr<Dispatcher>* dispatcher);
+
+ // Adds a dispatcher (which must be valid), returning the handle for it.
+ // Returns |MOJO_HANDLE_INVALID| on failure (if the handle table is full).
+ MojoHandle AddDispatcher(const scoped_refptr<Dispatcher>& dispatcher);
+
+ // Adds a pair of dispatchers (which must be valid), return a pair of handles
+ // for them. On failure (if the handle table is full), the first (and second)
+ // handles will be |MOJO_HANDLE_INVALID|, and neither dispatcher will be
+ // added.
+ std::pair<MojoHandle, MojoHandle> AddDispatcherPair(
+ const scoped_refptr<Dispatcher>& dispatcher0,
+ const scoped_refptr<Dispatcher>& dispatcher1);
+
+ // Adds the given vector of dispatchers (of size at most
+ // |kMaxMessageNumHandles|). |handles| must point to an array of size at least
+ // |dispatchers.size()|. Unlike the other |AddDispatcher...()| functions, some
+ // of the dispatchers may be invalid (null). Returns true on success and false
+ // on failure (if the handle table is full), in which case it leaves
+ // |handles[...]| untouched (and all dispatchers unadded).
+ bool AddDispatcherVector(const DispatcherVector& dispatchers,
+ MojoHandle* handles);
+
+ // Tries to mark the given handles as busy and start transport on them (i.e.,
+ // take their dispatcher locks); |transports| must be sized to contain
+ // |num_handles| elements. On failure, returns them to their original
+ // (non-busy, unlocked state).
+ MojoResult MarkBusyAndStartTransport(
+ MojoHandle disallowed_handle,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ std::vector<DispatcherTransport>* transports);
+
+ // Remove the given handles, which must all be present and which should have
+ // previously been marked busy by |MarkBusyAndStartTransport()|.
+ void RemoveBusyHandles(const MojoHandle* handles, uint32_t num_handles);
+
+ // Restores the given handles, which must all be present and which should have
+ // previously been marked busy by |MarkBusyAndStartTransport()|, to a non-busy
+ // state.
+ void RestoreBusyHandles(const MojoHandle* handles, uint32_t num_handles);
+
+ private:
+ friend bool internal::ShutdownCheckNoLeaks(Core*);
+
+ // The |busy| member is used only to deal with functions (in particular
+ // |Core::WriteMessage()|) that want to hold on to a dispatcher and later
+ // remove it from the handle table, without holding on to the handle table
+ // lock.
+ //
+ // For example, if |Core::WriteMessage()| is called with a handle to be sent,
+ // (under the handle table lock) it must first check that that handle is not
+ // busy (if it is busy, then it fails with |MOJO_RESULT_BUSY|) and then marks
+ // it as busy. To avoid deadlock, it should also try to acquire the locks for
+ // all the dispatchers for the handles that it is sending (and fail with
+ // |MOJO_RESULT_BUSY| if the attempt fails). At this point, it can release the
+ // handle table lock.
+ //
+ // If |Core::Close()| is simultaneously called on that handle, it too checks
+ // if the handle is marked busy. If it is, it fails (with |MOJO_RESULT_BUSY|).
+ // This prevents |Core::WriteMessage()| from sending a handle that has been
+ // closed (or learning about this too late).
+ struct Entry {
+ Entry();
+ explicit Entry(const scoped_refptr<Dispatcher>& dispatcher);
+ ~Entry();
+
+ scoped_refptr<Dispatcher> dispatcher;
+ bool busy;
+ };
+ using HandleToEntryMap = base::hash_map<MojoHandle, Entry>;
+
+ // Adds the given dispatcher to the handle table, not doing any size checks.
+ MojoHandle AddDispatcherNoSizeCheck(
+ const scoped_refptr<Dispatcher>& dispatcher);
+
+ HandleToEntryMap handle_to_entry_map_;
+ MojoHandle next_handle_; // Invariant: never |MOJO_HANDLE_INVALID|.
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(HandleTable);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_HANDLE_TABLE_H_
diff --git a/mojo/edk/system/mapping_table.cc b/mojo/edk/system/mapping_table.cc
new file mode 100644
index 0000000..46afe5a
--- /dev/null
+++ b/mojo/edk/system/mapping_table.cc
@@ -0,0 +1,48 @@
+// Copyright 2014 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 "mojo/edk/system/mapping_table.h"
+
+#include "base/logging.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/system/configuration.h"
+
+namespace mojo {
+namespace edk {
+
+MappingTable::MappingTable() {
+}
+
+MappingTable::~MappingTable() {
+ // This should usually not be reached (the only instance should be owned by
+ // the singleton |Core|, which lives forever), except in tests.
+}
+
+MojoResult MappingTable::AddMapping(
+ scoped_ptr<PlatformSharedBufferMapping> mapping) {
+ DCHECK(mapping);
+
+ if (address_to_mapping_map_.size() >=
+ GetConfiguration().max_mapping_table_sze)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ void* address = mapping->GetBase();
+ DCHECK(address_to_mapping_map_.find(address) ==
+ address_to_mapping_map_.end());
+ address_to_mapping_map_[address] = mapping.release();
+ return MOJO_RESULT_OK;
+}
+
+MojoResult MappingTable::RemoveMapping(void* address) {
+ AddressToMappingMap::iterator it = address_to_mapping_map_.find(address);
+ if (it == address_to_mapping_map_.end())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ PlatformSharedBufferMapping* mapping_to_delete = it->second;
+ address_to_mapping_map_.erase(it);
+ delete mapping_to_delete;
+ return MOJO_RESULT_OK;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/mapping_table.h b/mojo/edk/system/mapping_table.h
new file mode 100644
index 0000000..fb2acf3
--- /dev/null
+++ b/mojo/edk/system/mapping_table.h
@@ -0,0 +1,57 @@
+// Copyright 2014 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 MOJO_EDK_SYSTEM_MAPPING_TABLE_H_
+#define MOJO_EDK_SYSTEM_MAPPING_TABLE_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+namespace edk {
+class Core;
+class PlatformSharedBufferMapping;
+
+// Test-only function (defined/used in embedder/test_embedder.cc). Declared here
+// so it can be friended.
+namespace internal {
+bool ShutdownCheckNoLeaks(Core*);
+}
+
+// This class provides the (global) table of memory mappings (owned by |Core|),
+// which maps mapping base addresses to |PlatformSharedBufferMapping|s.
+//
+// This class is NOT thread-safe; locking is left to |Core|.
+class MOJO_SYSTEM_IMPL_EXPORT MappingTable {
+ public:
+ MappingTable();
+ ~MappingTable();
+
+ // Tries to add a mapping. (Takes ownership of the mapping in all cases; on
+ // failure, it will be destroyed.)
+ MojoResult AddMapping(scoped_ptr<PlatformSharedBufferMapping> mapping);
+ MojoResult RemoveMapping(void* address);
+
+ private:
+ friend bool internal::ShutdownCheckNoLeaks(Core*);
+
+ using AddressToMappingMap =
+ base::hash_map<void*, PlatformSharedBufferMapping*>;
+ AddressToMappingMap address_to_mapping_map_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MappingTable);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MAPPING_TABLE_H_
diff --git a/mojo/edk/system/master.mojom b/mojo/edk/system/master.mojom
new file mode 100644
index 0000000..0757a8d
--- /dev/null
+++ b/mojo/edk/system/master.mojom
@@ -0,0 +1,11 @@
+// Copyright 2015 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.
+
+module mojo;
+
+interface Master {
+ // These methods are only called on Windows.
+ HandleToToken(handle platform_handle) => (int32 result, uint64 token);
+ TokenToHandle(uint64 token) => (int32 result, handle platform_handle);
+};
diff --git a/mojo/edk/system/master_impl.cc b/mojo/edk/system/master_impl.cc
new file mode 100644
index 0000000..13b76f2
--- /dev/null
+++ b/mojo/edk/system/master_impl.cc
@@ -0,0 +1,107 @@
+// Copyright 2015 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 "mojo/edk/system/master_impl.h"
+
+#include "base/containers/hash_tables.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "mojo/edk/embedder/embedder.h"
+
+#if defined(OS_WIN)
+base::LazyInstance<base::hash_map<uint64_t, HANDLE>>::Leaky
+ g_token_map = LAZY_INSTANCE_INITIALIZER;
+#endif
+
+namespace mojo {
+namespace edk {
+
+MasterImpl::MasterImpl(base::ProcessId slave_pid)
+#if defined(OS_WIN)
+ : slave_process_(
+ base::Process::OpenWithAccess(slave_pid, PROCESS_DUP_HANDLE))
+#endif
+ {
+#if defined(OS_WIN)
+ DCHECK(slave_process_.IsValid());
+#endif
+}
+
+MasterImpl::~MasterImpl() {
+}
+
+void MasterImpl::HandleToToken(ScopedHandle platform_handle,
+ const HandleToTokenCallback& callback) {
+#if defined(OS_WIN)
+ ScopedPlatformHandle sender_handle;
+ MojoResult unwrap_result = PassWrappedPlatformHandle(
+ platform_handle.get().value(), &sender_handle);
+ if (unwrap_result != MOJO_RESULT_OK) {
+ DLOG(WARNING) << "HandleToToken couldn't unwrap platform handle.";
+ callback.Run(unwrap_result, 0);
+ return;
+ }
+
+ HANDLE master_handle = NULL;
+ BOOL dup_result =
+ DuplicateHandle(slave_process_.Handle(), sender_handle.release().handle,
+ base::GetCurrentProcessHandle(), &master_handle, 0,
+ FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
+ if (!dup_result) {
+ DLOG(WARNING) << "HandleToToken couldn't duplicate slave handle.";
+ callback.Run(MOJO_RESULT_INVALID_ARGUMENT, 0);
+ return;
+ }
+
+ uint64_t token = base::RandUint64();
+ g_token_map.Get()[token] = master_handle;
+ callback.Run(MOJO_RESULT_OK, token);
+#else
+ NOTREACHED();
+#endif
+}
+
+void MasterImpl::TokenToHandle(uint64_t token,
+ const TokenToHandleCallback& callback) {
+#if defined(OS_WIN)
+ auto it = g_token_map.Get().find(token);
+ if (it == g_token_map.Get().end()) {
+ DLOG(WARNING) << "TokenToHandle didn't find token.";
+ callback.Run(MOJO_RESULT_INVALID_ARGUMENT, ScopedHandle());
+ return;
+ }
+
+ HANDLE slave_handle = NULL;
+ BOOL dup_result =
+ DuplicateHandle(base::GetCurrentProcessHandle(), it->second,
+ slave_process_.Handle(), &slave_handle, 0,
+ FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
+ if (!dup_result) {
+ DLOG(WARNING) << "TokenToHandle couldn't duplicate slave handle.";
+ callback.Run(MOJO_RESULT_INTERNAL, ScopedHandle());
+ return;
+ }
+
+ ScopedPlatformHandle platform_handle;
+ platform_handle.reset(PlatformHandle(slave_handle));
+
+ MojoHandle handle;
+ MojoResult wrap_result = CreatePlatformHandleWrapper(
+ platform_handle.Pass(), &handle);
+ if (wrap_result != MOJO_RESULT_OK) {
+ DLOG(WARNING) << "TokenToHandle couldn't unwrap platform handle.";
+ callback.Run(wrap_result, ScopedHandle());
+ return;
+ }
+
+ callback.Run(MOJO_RESULT_OK, ScopedHandle(Handle(handle)));
+ g_token_map.Get().erase(it);
+#else
+ NOTREACHED();
+#endif
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/master_impl.h b/mojo/edk/system/master_impl.h
new file mode 100644
index 0000000..7d5bd8c
--- /dev/null
+++ b/mojo/edk/system/master_impl.h
@@ -0,0 +1,43 @@
+// Copyright 2015 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 MOJO_EDK_SYSTEM_MASTER_IMPL_H_
+#define MOJO_EDK_SYSTEM_MASTER_IMPL_H_
+
+#include "base/process/process.h"
+#include "mojo/edk/system/master.mojom.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace mojo {
+namespace edk {
+
+// An instance of this class exists in the maste process for each slave process.
+class MOJO_SYSTEM_IMPL_EXPORT MasterImpl : public Master {
+ public:
+ explicit MasterImpl(base::ProcessId slave_pid);
+ ~MasterImpl() override;
+
+ // Master implementation:
+ void HandleToToken(ScopedHandle platform_handle,
+ const HandleToTokenCallback& callback) override;
+ void TokenToHandle(uint64_t token,
+ const TokenToHandleCallback& callback) override;
+
+ private:
+#if defined(OS_WIN)
+ base::Process slave_process_;
+#endif
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MasterImpl);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MASTER_IMPL_H_
diff --git a/mojo/edk/system/master_impl_unittest.cc b/mojo/edk/system/master_impl_unittest.cc
new file mode 100644
index 0000000..a0a731e
--- /dev/null
+++ b/mojo/edk/system/master_impl_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright 2015 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 "mojo/edk/system/master_impl.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/test/test_utils.h"
+#include "mojo/public/c/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+static const char kHelloWorld[] = "hello world";
+
+#if defined(OS_WIN)
+
+class MasterImplTest : public ::testing::Test {
+ public:
+ MasterImplTest() {}
+
+ // Returns a ScopedHandle to a file with the magic string.
+ ScopedHandle GetScopedHandle() {
+ if (!temp_dir_.IsValid())
+ CHECK(temp_dir_.CreateUniqueTempDir());
+ base::FilePath unused;
+ base::ScopedFILE fp(
+ CreateAndOpenTemporaryFileInDir(temp_dir_.path(), &unused));
+ CHECK(fp);
+ EXPECT_EQ(sizeof(kHelloWorld),
+ fwrite(kHelloWorld, 1, sizeof(kHelloWorld), fp.get()));
+ ScopedPlatformHandle platform_handle(
+ test::PlatformHandleFromFILE(fp.Pass()));
+ CHECK(platform_handle.is_valid());
+
+ MojoHandle handle;
+ MojoResult wrap_result = CreatePlatformHandleWrapper(
+ platform_handle.Pass(), &handle);
+ CHECK(wrap_result == MOJO_RESULT_OK);
+ return ScopedHandle(Handle(handle));
+ }
+
+ // Check that the given ScopedHandle has a file with the magic string.
+ bool CheckScopedHandle(ScopedHandle handle) {
+ ScopedPlatformHandle platform_handle;
+ MojoResult unwrap_result = PassWrappedPlatformHandle(
+ handle.get().value(), &platform_handle);
+ if (unwrap_result != MOJO_RESULT_OK)
+ return false;
+ base::ScopedFILE fp =
+ test::FILEFromPlatformHandle(platform_handle.Pass(), "rb").Pass();
+ if (!fp)
+ return false;
+ rewind(fp.get());
+ char read_buffer[1000] = {};
+ if (fread(read_buffer, 1, sizeof(read_buffer), fp.get()) !=
+ sizeof(kHelloWorld)) {
+ return false;
+ }
+ return std::string(read_buffer) == kHelloWorld;
+ }
+
+ private:
+ base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(MasterImplTest, Basic) {
+ MasterImpl master(base::GetCurrentProcId());
+ uint64_t token;
+ master.HandleToToken(GetScopedHandle(),
+ [&token](int32_t result, uint64_t t) {
+ ASSERT_EQ(result, MOJO_RESULT_OK);
+ token = t;
+ });
+
+ ScopedHandle handle;
+ master.TokenToHandle(token,
+ [&handle](int32_t result, ScopedHandle h) {
+ ASSERT_EQ(result, MOJO_RESULT_OK);
+ handle = h.Pass();
+ });
+
+ ASSERT_TRUE(CheckScopedHandle(handle.Pass()));
+}
+
+TEST_F(MasterImplTest, TokenIsRemoved) {
+ MasterImpl master(base::GetCurrentProcId());
+ uint64_t token;
+ master.HandleToToken(GetScopedHandle(),
+ [&token](int32_t result, uint64_t t) {
+ ASSERT_EQ(result, MOJO_RESULT_OK);
+ token = t;
+ });
+
+ ScopedHandle handle;
+ master.TokenToHandle(token,
+ [&handle](int32_t result, ScopedHandle h) {
+ ASSERT_EQ(result, MOJO_RESULT_OK);
+ handle = h.Pass();
+ });
+
+ ASSERT_TRUE(CheckScopedHandle(handle.Pass()));
+
+ master.TokenToHandle(token,
+ [&handle](int32_t result, ScopedHandle h) {
+ ASSERT_EQ(result, MOJO_RESULT_INVALID_ARGUMENT);
+ });
+}
+
+#endif // OS_WIN
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/message_in_transit.cc b/mojo/edk/system/message_in_transit.cc
new file mode 100644
index 0000000..b85efc7
--- /dev/null
+++ b/mojo/edk/system/message_in_transit.cc
@@ -0,0 +1,184 @@
+// 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 "mojo/edk/system/message_in_transit.h"
+
+#include <string.h>
+
+#include <ostream>
+
+#include "base/logging.h"
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/transport_data.h"
+
+namespace mojo {
+namespace edk {
+
+MOJO_STATIC_CONST_MEMBER_DEFINITION const size_t
+ MessageInTransit::kMessageAlignment;
+
+struct MessageInTransit::PrivateStructForCompileAsserts {
+ // The size of |Header| must be a multiple of the alignment.
+ static_assert(sizeof(Header) % kMessageAlignment == 0,
+ "sizeof(MessageInTransit::Header) invalid");
+};
+
+MessageInTransit::View::View(size_t message_size, const void* buffer)
+ : buffer_(buffer) {
+ size_t next_message_size = 0;
+ DCHECK(MessageInTransit::GetNextMessageSize(buffer_, message_size,
+ &next_message_size));
+ DCHECK_EQ(message_size, next_message_size);
+ // This should be equivalent.
+ DCHECK_EQ(message_size, total_size());
+}
+
+bool MessageInTransit::View::IsValid(size_t serialized_platform_handle_size,
+ const char** error_message) const {
+ size_t max_message_num_bytes = GetConfiguration().max_message_num_bytes;
+ // Avoid dangerous situations, but making sure that the size of the "header" +
+ // the size of the data fits into a 31-bit number.
+ DCHECK_LE(static_cast<uint64_t>(sizeof(Header)) + max_message_num_bytes,
+ 0x7fffffffULL)
+ << "GetConfiguration().max_message_num_bytes too big";
+
+ // We assume (to avoid extra rounding code) that the maximum message (data)
+ // size is a multiple of the alignment.
+ DCHECK_EQ(max_message_num_bytes % kMessageAlignment, 0U)
+ << "GetConfiguration().max_message_num_bytes not a multiple of alignment";
+
+ // Note: This also implies a check on the |main_buffer_size()|, which is just
+ // |RoundUpMessageAlignment(sizeof(Header) + num_bytes())|.
+ if (num_bytes() > max_message_num_bytes) {
+ *error_message = "Message data payload too large";
+ return false;
+ }
+
+ if (transport_data_buffer_size() > 0) {
+ const char* e = TransportData::ValidateBuffer(
+ serialized_platform_handle_size, transport_data_buffer(),
+ transport_data_buffer_size());
+ if (e) {
+ *error_message = e;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+MessageInTransit::MessageInTransit(Type type,
+ uint32_t num_bytes,
+ const void* bytes)
+ : main_buffer_size_(RoundUpMessageAlignment(sizeof(Header) + num_bytes)),
+ main_buffer_(static_cast<char*>(
+ base::AlignedAlloc(main_buffer_size_, kMessageAlignment))) {
+ ConstructorHelper(type, num_bytes);
+ if (bytes) {
+ memcpy(MessageInTransit::bytes(), bytes, num_bytes);
+ memset(static_cast<char*>(MessageInTransit::bytes()) + num_bytes, 0,
+ main_buffer_size_ - sizeof(Header) - num_bytes);
+ } else {
+ memset(MessageInTransit::bytes(), 0, main_buffer_size_ - sizeof(Header));
+ }
+}
+
+MessageInTransit::MessageInTransit(const View& message_view)
+ : main_buffer_size_(message_view.main_buffer_size()),
+ main_buffer_(static_cast<char*>(
+ base::AlignedAlloc(main_buffer_size_, kMessageAlignment))) {
+ DCHECK_GE(main_buffer_size_, sizeof(Header));
+ DCHECK_EQ(main_buffer_size_ % kMessageAlignment, 0u);
+
+ memcpy(main_buffer_.get(), message_view.main_buffer(), main_buffer_size_);
+ DCHECK_EQ(main_buffer_size_,
+ RoundUpMessageAlignment(sizeof(Header) + num_bytes()));
+}
+
+MessageInTransit::~MessageInTransit() {
+ if (dispatchers_) {
+ for (size_t i = 0; i < dispatchers_->size(); i++) {
+ if (!(*dispatchers_)[i])
+ continue;
+ (*dispatchers_)[i]->Close();
+ }
+ }
+}
+
+// static
+bool MessageInTransit::GetNextMessageSize(const void* buffer,
+ size_t buffer_size,
+ size_t* next_message_size) {
+ DCHECK(next_message_size);
+ if (!buffer_size)
+ return false;
+ DCHECK(buffer);
+ DCHECK_EQ(
+ reinterpret_cast<uintptr_t>(buffer) % MessageInTransit::kMessageAlignment,
+ 0u);
+
+ if (buffer_size < sizeof(Header))
+ return false;
+
+ const Header* header = static_cast<const Header*>(buffer);
+ *next_message_size = header->total_size;
+ DCHECK_EQ(*next_message_size % kMessageAlignment, 0u);
+ return true;
+}
+
+void MessageInTransit::SetDispatchers(
+ scoped_ptr<DispatcherVector> dispatchers) {
+ DCHECK(dispatchers);
+ DCHECK(!dispatchers_);
+ DCHECK(!transport_data_);
+
+ dispatchers_ = dispatchers.Pass();
+}
+
+void MessageInTransit::SetTransportData(
+ scoped_ptr<TransportData> transport_data) {
+ DCHECK(transport_data);
+ DCHECK(!transport_data_);
+ DCHECK(!dispatchers_);
+
+ transport_data_ = transport_data.Pass();
+ UpdateTotalSize();
+}
+
+void MessageInTransit::SerializeAndCloseDispatchers() {
+ DCHECK(!transport_data_);
+
+ if (!dispatchers_ || !dispatchers_->size())
+ return;
+
+ transport_data_.reset(new TransportData(dispatchers_.Pass()));
+
+ // Update the sizes in the message header.
+ UpdateTotalSize();
+}
+
+void MessageInTransit::ConstructorHelper(Type type,
+ uint32_t num_bytes) {
+ DCHECK_LE(num_bytes, GetConfiguration().max_message_num_bytes);
+
+ // |total_size| is updated below, from the other values.
+ header()->type = type;
+ header()->num_bytes = num_bytes;
+ header()->unused = 0;
+ // Note: If dispatchers are subsequently attached, then |total_size| will have
+ // to be adjusted.
+ UpdateTotalSize();
+}
+
+void MessageInTransit::UpdateTotalSize() {
+ DCHECK_EQ(main_buffer_size_ % kMessageAlignment, 0u);
+ header()->total_size = static_cast<uint32_t>(main_buffer_size_);
+ if (transport_data_) {
+ header()->total_size +=
+ static_cast<uint32_t>(transport_data_->buffer_size());
+ }
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/message_in_transit.h b/mojo/edk/system/message_in_transit.h
new file mode 100644
index 0000000..f0cd7a7
--- /dev/null
+++ b/mojo/edk/system/message_in_transit.h
@@ -0,0 +1,234 @@
+// 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 MOJO_EDK_SYSTEM_MESSAGE_IN_TRANSIT_H_
+#define MOJO_EDK_SYSTEM_MESSAGE_IN_TRANSIT_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <ostream>
+#include <vector>
+
+#include "base/memory/aligned_memory.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+class RawChannel;
+class TransportData;
+
+// This class is used to represent data in transit. It is thread-unsafe.
+//
+// |MessageInTransit| buffers:
+//
+// A |MessageInTransit| can be serialized by writing the main buffer and then,
+// if it has one, the transport data buffer. Both buffers are
+// |kMessageAlignment|-byte aligned and a multiple of |kMessageAlignment| bytes
+// in size.
+//
+// The main buffer consists of the header (of type |Header|, which is an
+// internal detail of this class) followed immediately by the message data
+// (accessed by |bytes()| and of size |num_bytes()|, and also
+// |kMessageAlignment|-byte aligned), and then any padding needed to make the
+// main buffer a multiple of |kMessageAlignment| bytes in size.
+//
+// See |TransportData| for a description of the (serialized) transport data
+// buffer.
+class MOJO_SYSTEM_IMPL_EXPORT MessageInTransit {
+ public:
+ enum class Type : uint16_t {
+ MESSAGE = 0,
+ RAW_CHANNEL_POSIX_EXTRA_PLATFORM_HANDLES = 1,
+ RAW_CHANNEL_QUIT = 2,
+ };
+
+ // Messages (the header and data) must always be aligned to a multiple of this
+ // quantity (which must be a power of 2).
+ static const size_t kMessageAlignment = 8;
+
+ // Forward-declare |Header| so that |View| can use it:
+ private:
+ struct Header;
+
+ public:
+ // This represents a view of serialized message data in a raw buffer.
+ class MOJO_SYSTEM_IMPL_EXPORT View {
+ public:
+ // Constructs a view from the given buffer of the given size. (The size must
+ // be as provided by |MessageInTransit::GetNextMessageSize()|.) The buffer
+ // must remain alive/unmodified through the lifetime of this object.
+ // |buffer| should be |kMessageAlignment|-byte aligned.
+ View(size_t message_size, const void* buffer);
+
+ // Checks that the given |View| appears to be for a valid message, within
+ // predetermined limits (e.g., |num_bytes()| and |main_buffer_size()|, that
+ // |transport_data_buffer()|/|transport_data_buffer_size()| is for valid
+ // transport data -- see |TransportData::ValidateBuffer()|).
+ //
+ // It returns true (and leaves |error_message| alone) if this object appears
+ // to be a valid message (according to the above) and false, pointing
+ // |*error_message| to a suitable error message, if not.
+ bool IsValid(size_t serialized_platform_handle_size,
+ const char** error_message) const;
+
+ // API parallel to that for |MessageInTransit| itself (mostly getters for
+ // header data).
+ const void* main_buffer() const { return buffer_; }
+ size_t main_buffer_size() const {
+ return RoundUpMessageAlignment(sizeof(Header) + header()->num_bytes);
+ }
+ const void* transport_data_buffer() const {
+ return (total_size() > main_buffer_size())
+ ? static_cast<const char*>(buffer_) + main_buffer_size()
+ : nullptr;
+ }
+ size_t transport_data_buffer_size() const {
+ return total_size() - main_buffer_size();
+ }
+ size_t total_size() const { return header()->total_size; }
+ uint32_t num_bytes() const { return header()->num_bytes; }
+ const void* bytes() const {
+ return static_cast<const char*>(buffer_) + sizeof(Header);
+ }
+ Type type() const { return header()->type; }
+
+ private:
+ const Header* header() const { return static_cast<const Header*>(buffer_); }
+
+ const void* const buffer_;
+
+ // Though this struct is trivial, disallow copy and assign, since it doesn't
+ // own its data. (If you're copying/assigning this, you're probably doing
+ // something wrong.)
+ MOJO_DISALLOW_COPY_AND_ASSIGN(View);
+ };
+
+ // |bytes| is optional; if null, the message data will be zero-initialized.
+ MessageInTransit(Type type,
+ uint32_t num_bytes,
+ const void* bytes);
+ // Constructs a |MessageInTransit| from a |View|.
+ explicit MessageInTransit(const View& message_view);
+
+ ~MessageInTransit();
+
+ // Gets the size of the next message from |buffer|, which has |buffer_size|
+ // bytes currently available, returning true and setting |*next_message_size|
+ // on success. |buffer| should be aligned on a |kMessageAlignment| boundary
+ // (and on success, |*next_message_size| will be a multiple of
+ // |kMessageAlignment|).
+ // TODO(vtl): In |RawChannelPosix|, the alignment requirements are currently
+ // satisified on a faith-based basis.
+ static bool GetNextMessageSize(const void* buffer,
+ size_t buffer_size,
+ size_t* next_message_size);
+
+ // Makes this message "own" the given set of dispatchers. The dispatchers must
+ // not be referenced from anywhere else (in particular, not from the handle
+ // table), i.e., each dispatcher must have a reference count of 1. This
+ // message must not already have dispatchers.
+ void SetDispatchers(scoped_ptr<DispatcherVector> dispatchers);
+
+ // Sets the |TransportData| for this message. This should only be done when
+ // there are no dispatchers and no existing |TransportData|.
+ void SetTransportData(scoped_ptr<TransportData> transport_data);
+
+ // Serializes any dispatchers to the secondary buffer. This message must not
+ // already have a secondary buffer (so this must only be called once). The
+ // caller must ensure (e.g., by holding on to a reference) that |channel|
+ // stays alive through the call.
+ void SerializeAndCloseDispatchers();
+
+ // Gets the main buffer and its size (in number of bytes), respectively.
+ const void* main_buffer() const { return main_buffer_.get(); }
+ size_t main_buffer_size() const { return main_buffer_size_; }
+
+ // Gets the transport data buffer (if any).
+ const TransportData* transport_data() const { return transport_data_.get(); }
+ TransportData* transport_data() { return transport_data_.get(); }
+
+ // Gets the total size of the message (see comment in |Header|, below).
+ size_t total_size() const { return header()->total_size; }
+
+ // Gets the size of the message data.
+ uint32_t num_bytes() const { return header()->num_bytes; }
+
+ // Gets the message data (of size |num_bytes()| bytes).
+ const void* bytes() const { return main_buffer_.get() + sizeof(Header); }
+ void* bytes() { return main_buffer_.get() + sizeof(Header); }
+
+ Type type() const { return header()->type; }
+
+ // Gets the dispatchers attached to this message; this may return null if
+ // there are none. Note that the caller may mutate the set of dispatchers
+ // (e.g., take ownership of all the dispatchers, leaving the vector empty).
+ DispatcherVector* dispatchers() { return dispatchers_.get(); }
+
+ // Returns true if this message has dispatchers attached.
+ bool has_dispatchers() const {
+ return dispatchers_ && !dispatchers_->empty();
+ }
+
+ // Rounds |n| up to a multiple of |kMessageAlignment|.
+ static inline size_t RoundUpMessageAlignment(size_t n) {
+ return (n + kMessageAlignment - 1) & ~(kMessageAlignment - 1);
+ }
+
+ private:
+ // To allow us to make compile-assertions about |Header| in the .cc file.
+ struct PrivateStructForCompileAsserts;
+
+ // Header for the data (main buffer). Must be a multiple of
+ // |kMessageAlignment| bytes in size. Must be POD.
+ struct Header {
+ // Total size of the message, including the header, the message data
+ // ("bytes") including padding (to make it a multiple of |kMessageAlignment|
+ // bytes), and serialized handle information. Note that this may not be the
+ // correct value if dispatchers are attached but
+ // |SerializeAndCloseDispatchers()| has not been called.
+ uint32_t total_size;
+ Type type; // 2 bytes.
+ Type unusedforalignment; // 2 bytes.
+ uint32_t num_bytes;
+ uint32_t unused;
+ };
+
+ const Header* header() const {
+ return reinterpret_cast<const Header*>(main_buffer_.get());
+ }
+ Header* header() { return reinterpret_cast<Header*>(main_buffer_.get()); }
+
+ void ConstructorHelper(Type type, uint32_t num_bytes);
+ void UpdateTotalSize();
+
+ const size_t main_buffer_size_;
+ const scoped_ptr<char, base::AlignedFreeDeleter> main_buffer_; // Never null.
+
+ scoped_ptr<TransportData> transport_data_; // May be null.
+
+ // Any dispatchers that may be attached to this message. These dispatchers
+ // should be "owned" by this message, i.e., have a ref count of exactly 1. (We
+ // allow a dispatcher entry to be null, in case it couldn't be duplicated for
+ // some reason.)
+ scoped_ptr<DispatcherVector> dispatchers_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MessageInTransit);
+};
+
+// So logging macros and |DCHECK_EQ()|, etc. work.
+MOJO_SYSTEM_IMPL_EXPORT inline std::ostream& operator<<(
+ std::ostream& out,
+ MessageInTransit::Type type) {
+ return out << static_cast<uint16_t>(type);
+}
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MESSAGE_IN_TRANSIT_H_
diff --git a/mojo/edk/system/message_in_transit_queue.cc b/mojo/edk/system/message_in_transit_queue.cc
new file mode 100644
index 0000000..f95bf72
--- /dev/null
+++ b/mojo/edk/system/message_in_transit_queue.cc
@@ -0,0 +1,33 @@
+// Copyright 2014 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 "mojo/edk/system/message_in_transit_queue.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace edk {
+
+MessageInTransitQueue::MessageInTransitQueue() {
+}
+
+MessageInTransitQueue::~MessageInTransitQueue() {
+ if (!IsEmpty()) {
+ LOG(WARNING) << "Destroying nonempty message queue";
+ Clear();
+ }
+}
+
+void MessageInTransitQueue::Clear() {
+ for (auto* message : queue_)
+ delete message;
+ queue_.clear();
+}
+
+void MessageInTransitQueue::Swap(MessageInTransitQueue* other) {
+ queue_.swap(other->queue_);
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/message_in_transit_queue.h b/mojo/edk/system/message_in_transit_queue.h
new file mode 100644
index 0000000..4a05490
--- /dev/null
+++ b/mojo/edk/system/message_in_transit_queue.h
@@ -0,0 +1,62 @@
+// Copyright 2014 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 MOJO_EDK_SYSTEM_MESSAGE_IN_TRANSIT_QUEUE_H_
+#define MOJO_EDK_SYSTEM_MESSAGE_IN_TRANSIT_QUEUE_H_
+
+#include <deque>
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// A simple queue for |MessageInTransit|s (that owns its messages).
+// This class is not thread-safe.
+class MOJO_SYSTEM_IMPL_EXPORT MessageInTransitQueue {
+ public:
+ MessageInTransitQueue();
+ ~MessageInTransitQueue();
+
+ bool IsEmpty() const { return queue_.empty(); }
+ size_t Size() const { return queue_.size(); }
+
+ void AddMessage(scoped_ptr<MessageInTransit> message) {
+ queue_.push_back(message.release());
+ }
+
+ scoped_ptr<MessageInTransit> GetMessage() {
+ MessageInTransit* rv = queue_.front();
+ queue_.pop_front();
+ return make_scoped_ptr(rv);
+ }
+
+ const MessageInTransit* PeekMessage() const { return queue_.front(); }
+ MessageInTransit* PeekMessage() { return queue_.front(); }
+
+ void DiscardMessage() {
+ delete queue_.front();
+ queue_.pop_front();
+ }
+
+ void Clear();
+
+ // Efficiently swaps contents with |*other|.
+ void Swap(MessageInTransitQueue* other);
+
+ private:
+ // TODO(vtl): When C++11 is available, switch this to a deque of
+ // |scoped_ptr|/|unique_ptr|s.
+ std::deque<MessageInTransit*> queue_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MessageInTransitQueue);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MESSAGE_IN_TRANSIT_QUEUE_H_
diff --git a/mojo/edk/system/message_in_transit_queue_unittest.cc b/mojo/edk/system/message_in_transit_queue_unittest.cc
new file mode 100644
index 0000000..7e715d2
--- /dev/null
+++ b/mojo/edk/system/message_in_transit_queue_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2015 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 "mojo/edk/system/message_in_transit_queue.h"
+
+#include "mojo/edk/system/message_in_transit_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+TEST(MessageInTransitQueueTest, Basic) {
+ MessageInTransitQueue queue;
+ EXPECT_TRUE(queue.IsEmpty());
+
+ queue.AddMessage(test::MakeTestMessage(1));
+ ASSERT_FALSE(queue.IsEmpty());
+ EXPECT_EQ(1u, queue.Size());
+
+ test::VerifyTestMessage(queue.PeekMessage(), 1);
+ ASSERT_FALSE(queue.IsEmpty());
+ EXPECT_EQ(1u, queue.Size());
+
+ queue.AddMessage(test::MakeTestMessage(2));
+ queue.AddMessage(test::MakeTestMessage(3));
+ ASSERT_FALSE(queue.IsEmpty());
+ EXPECT_EQ(3u, queue.Size());
+
+ test::VerifyTestMessage(queue.GetMessage().get(), 1);
+ ASSERT_FALSE(queue.IsEmpty());
+ EXPECT_EQ(2u, queue.Size());
+
+ test::VerifyTestMessage(queue.PeekMessage(), 2);
+ ASSERT_FALSE(queue.IsEmpty());
+ EXPECT_EQ(2u, queue.Size());
+
+ queue.DiscardMessage();
+ ASSERT_FALSE(queue.IsEmpty());
+ EXPECT_EQ(1u, queue.Size());
+
+ test::VerifyTestMessage(queue.GetMessage().get(), 3);
+ EXPECT_TRUE(queue.IsEmpty());
+ EXPECT_EQ(0u, queue.Size());
+
+ queue.AddMessage(test::MakeTestMessage(4));
+ ASSERT_FALSE(queue.IsEmpty());
+ EXPECT_EQ(1u, queue.Size());
+
+ test::VerifyTestMessage(queue.PeekMessage(), 4);
+ ASSERT_FALSE(queue.IsEmpty());
+ EXPECT_EQ(1u, queue.Size());
+
+ queue.Clear();
+ EXPECT_TRUE(queue.IsEmpty());
+ EXPECT_EQ(0u, queue.Size());
+}
+
+TEST(MessageInTransitQueueTest, Swap) {
+ MessageInTransitQueue queue1;
+ MessageInTransitQueue queue2;
+
+ queue1.AddMessage(test::MakeTestMessage(1));
+ queue1.AddMessage(test::MakeTestMessage(2));
+ queue1.AddMessage(test::MakeTestMessage(3));
+ EXPECT_EQ(3u, queue1.Size());
+
+ queue2.AddMessage(test::MakeTestMessage(4));
+ queue2.AddMessage(test::MakeTestMessage(5));
+ EXPECT_EQ(2u, queue2.Size());
+
+ queue1.Swap(&queue2);
+ EXPECT_EQ(2u, queue1.Size());
+ EXPECT_EQ(3u, queue2.Size());
+ test::VerifyTestMessage(queue1.GetMessage().get(), 4);
+ test::VerifyTestMessage(queue1.GetMessage().get(), 5);
+ EXPECT_TRUE(queue1.IsEmpty());
+
+ queue1.Swap(&queue2);
+ EXPECT_TRUE(queue2.IsEmpty());
+
+ test::VerifyTestMessage(queue1.GetMessage().get(), 1);
+ test::VerifyTestMessage(queue1.GetMessage().get(), 2);
+ test::VerifyTestMessage(queue1.GetMessage().get(), 3);
+ EXPECT_TRUE(queue1.IsEmpty());
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/message_in_transit_test_utils.cc b/mojo/edk/system/message_in_transit_test_utils.cc
new file mode 100644
index 0000000..8013626
--- /dev/null
+++ b/mojo/edk/system/message_in_transit_test_utils.cc
@@ -0,0 +1,37 @@
+// Copyright 2015 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 "mojo/edk/system/message_in_transit_test_utils.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+scoped_ptr<MessageInTransit> MakeTestMessage(unsigned id) {
+ return make_scoped_ptr(
+ new MessageInTransit(MessageInTransit::Type::MESSAGE,
+ static_cast<uint32_t>(sizeof(id)), &id));
+}
+
+void VerifyTestMessage(const MessageInTransit* message, unsigned id) {
+ ASSERT_TRUE(message);
+ EXPECT_EQ(MessageInTransit::Type::MESSAGE, message->type());
+ EXPECT_EQ(sizeof(id), message->num_bytes());
+ EXPECT_EQ(id, *static_cast<const unsigned*>(message->bytes()));
+}
+
+bool IsTestMessage(MessageInTransit* message, unsigned* id) {
+ if (message->type() != MessageInTransit::Type::MESSAGE ||
+ message->num_bytes() != sizeof(*id))
+ return false;
+
+ *id = *static_cast<const unsigned*>(message->bytes());
+ return true;
+}
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/message_in_transit_test_utils.h b/mojo/edk/system/message_in_transit_test_utils.h
new file mode 100644
index 0000000..079d27b
--- /dev/null
+++ b/mojo/edk/system/message_in_transit_test_utils.h
@@ -0,0 +1,35 @@
+// Copyright 2015 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 MOJO_EDK_SYSTEM_MESSAGE_IN_TRANSIT_TEST_UTILS_H_
+#define MOJO_EDK_SYSTEM_MESSAGE_IN_TRANSIT_TEST_UTILS_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/system/message_in_transit.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+// Makes a test message. It will be of type
+// |MessageInTransit::Type::ENDPOINT_CLIENT| and subtype
+// |MessageInTransit::Subtype::ENDPOINT_CLIENT_DATA|, and contain data
+// associated with |id| (so that test messages with different |id|s are
+// distinguishable).
+scoped_ptr<MessageInTransit> MakeTestMessage(unsigned id);
+
+// Verifies a test message: ASSERTs that |message| is non-null, and EXPECTs that
+// it looks like a message created using |MakeTestMessage(id)| (see above).
+void VerifyTestMessage(const MessageInTransit* message, unsigned id);
+
+// Checks if |message| looks like a test message created using
+// |MakeTestMessage()|, in which case it returns true and sets |*id|. (Otherwise
+// it returns false and leaves |*id| alone.)
+bool IsTestMessage(MessageInTransit* message, unsigned* id);
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MESSAGE_IN_TRANSIT_TEST_UTILS_H_
diff --git a/mojo/edk/system/message_pipe_dispatcher.cc b/mojo/edk/system/message_pipe_dispatcher.cc
new file mode 100644
index 0000000..ebd07a3c
--- /dev/null
+++ b/mojo/edk/system/message_pipe_dispatcher.cc
@@ -0,0 +1,720 @@
+// Copyright 2015 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 "mojo/edk/system/message_pipe_dispatcher.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/options_validation.h"
+#include "mojo/edk/system/transport_data.h"
+
+namespace mojo {
+namespace edk {
+
+// TODO(jam): do more tests on using channel on same thread if it supports it (
+// i.e. with USE_CHROME_EDK and Windows). Also see ipc_channel_mojo.cc
+bool g_use_channel_on_io_thread_only = true;
+
+const size_t kInvalidMessagePipeHandleIndex = static_cast<size_t>(-1);
+
+struct MOJO_ALIGNAS(8) SerializedMessagePipeHandleDispatcher {
+ size_t platform_handle_index; // (Or |kInvalidMessagePipeHandleIndex|.)
+ size_t read_buffer_size; // any bytes after this are serialized messages
+};
+
+// MessagePipeDispatcher -------------------------------------------------------
+
+const MojoCreateMessagePipeOptions
+ MessagePipeDispatcher::kDefaultCreateOptions = {
+ static_cast<uint32_t>(sizeof(MojoCreateMessagePipeOptions)),
+ MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE};
+
+MojoResult MessagePipeDispatcher::ValidateCreateOptions(
+ const MojoCreateMessagePipeOptions* in_options,
+ MojoCreateMessagePipeOptions* out_options) {
+ const MojoCreateMessagePipeOptionsFlags kKnownFlags =
+ MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE;
+
+ *out_options = kDefaultCreateOptions;
+ if (!in_options)
+ return MOJO_RESULT_OK;
+
+ UserOptionsReader<MojoCreateMessagePipeOptions> reader(in_options);
+ if (!reader.is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(MojoCreateMessagePipeOptions, flags, reader))
+ return MOJO_RESULT_OK;
+ if ((reader.options().flags & ~kKnownFlags))
+ return MOJO_RESULT_UNIMPLEMENTED;
+ out_options->flags = reader.options().flags;
+
+ // Checks for fields beyond |flags|:
+
+ // (Nothing here yet.)
+
+ return MOJO_RESULT_OK;
+}
+
+void MessagePipeDispatcher::Init(ScopedPlatformHandle message_pipe) {
+ InitWithReadBuffer(message_pipe.Pass(), nullptr, 0);
+}
+
+void MessagePipeDispatcher::InitWithReadBuffer(
+ ScopedPlatformHandle message_pipe,
+ char* data,
+ size_t size) {
+ if (message_pipe.get().is_valid()) {
+ channel_ = RawChannel::Create(message_pipe.Pass());
+
+ // TODO(jam): It's probably cleaner to pass this in Init call.
+ if (size)
+ channel_->SetInitialReadBufferData(data, size);
+ if (g_use_channel_on_io_thread_only) {
+ internal::g_io_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(&MessagePipeDispatcher::InitOnIO, this));
+ } else {
+ InitOnIO();
+ }
+ // TODO(jam): optimize for when running on IO thread?
+ }
+}
+
+void MessagePipeDispatcher::InitOnIO() {
+ base::AutoLock locker(lock());
+ calling_init_ = true;
+ if (channel_)
+ channel_->Init(this);
+ calling_init_ = false;
+}
+
+void MessagePipeDispatcher::CloseOnIO() {
+ base::AutoLock locker(lock());
+
+ if (channel_) {
+ channel_->Shutdown();
+ channel_ = nullptr;
+ }
+}
+
+Dispatcher::Type MessagePipeDispatcher::GetType() const {
+ return Type::MESSAGE_PIPE;
+}
+
+// TODO(jam): this is copied from RawChannelWin till I figure out what's the
+// best way we want to share this. Need to also consider posix which does
+// require access to the RawChannel.
+// Since this is used for serialization of messages read/written to a MP that
+// aren't consumed by Mojo primitives yet, there could be an unbounded number of
+// them when a MP is being sent. As a result, even for POSIX we will probably
+// want to send the handles to the shell process and exchange them for tokens
+// (since we can be sure that the shell will respond to our IPCs, compared to
+// the other end where we're sending the MP to, which may not be reading...).
+ScopedPlatformHandleVectorPtr GetReadPlatformHandles(
+ size_t num_platform_handles,
+ const void* platform_handle_table) {
+ // TODO(jam): this code will have to be updated once it's used in a sandbox
+ // and the receiving process doesn't have duplicate permission for the
+ // receiver. Once there's a broker and we have a connection to it (possibly
+ // through ConnectionManager), then we can make a sync IPC to it here to get a
+ // token for this handle, and it will duplicate the handle to is process. Then
+ // we pass the token to the receiver, which will then make a sync call to the
+ // broker to get a duplicated handle. This will also allow us to avoid leaks
+ // of the handle if the receiver dies, since the broker can notice that.
+ DCHECK_GT(num_platform_handles, 0u);
+ ScopedPlatformHandleVectorPtr rv(new PlatformHandleVector());
+
+#if defined(OS_WIN)
+ const char* serialization_data =
+ static_cast<const char*>(platform_handle_table);
+ for (size_t i = 0; i < num_platform_handles; i++) {
+ DWORD pid = *reinterpret_cast<const DWORD*>(serialization_data);
+ serialization_data += sizeof(DWORD);
+ HANDLE source_handle = *reinterpret_cast<const HANDLE*>(serialization_data);
+ serialization_data += sizeof(HANDLE);
+ base::Process sender =
+ base::Process::OpenWithAccess(pid, PROCESS_DUP_HANDLE);
+ DCHECK(sender.IsValid());
+ HANDLE target_handle = NULL;
+ BOOL dup_result =
+ DuplicateHandle(sender.Handle(), source_handle,
+ base::GetCurrentProcessHandle(), &target_handle, 0,
+ FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
+ DCHECK(dup_result);
+ rv->push_back(PlatformHandle(target_handle));
+ }
+#else
+ NOTREACHED() << "TODO(jam): implement";
+#endif
+ return rv.Pass();
+}
+
+scoped_refptr<MessagePipeDispatcher> MessagePipeDispatcher::Deserialize(
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles) {
+ const SerializedMessagePipeHandleDispatcher* serialization =
+ static_cast<const SerializedMessagePipeHandleDispatcher*>(source);
+ size_t platform_handle_index = serialization->platform_handle_index;
+
+
+ // Starts off invalid, which is what we want.
+ PlatformHandle platform_handle;
+
+ if (platform_handle_index != kInvalidMessagePipeHandleIndex) {
+ if (!platform_handles ||
+ platform_handle_index >= platform_handles->size()) {
+ LOG(ERROR)
+ << "Invalid serialized platform handle dispatcher (missing handles)";
+ return nullptr;
+ }
+
+ // We take ownership of the handle, so we have to invalidate the one in
+ // |platform_handles|.
+ std::swap(platform_handle, (*platform_handles)[platform_handle_index]);
+ }
+
+ // TODO(jam): temporary until we send message_queue_ via shared memory.
+ size -= sizeof(SerializedMessagePipeHandleDispatcher);
+ const char* messages = static_cast<const char*>(source);
+ messages += sizeof(SerializedMessagePipeHandleDispatcher);
+
+ char* initial_read_data = nullptr;
+ size_t initial_read_size = 0;
+
+ if (serialization->read_buffer_size) {
+ initial_read_data = const_cast<char*>(messages);
+ initial_read_size = serialization->read_buffer_size;
+
+ messages += initial_read_size;
+ size -= initial_read_size;
+ }
+
+ scoped_refptr<MessagePipeDispatcher> rv(
+ Create(MessagePipeDispatcher::kDefaultCreateOptions));
+ rv->InitWithReadBuffer(
+ ScopedPlatformHandle(platform_handle),
+ initial_read_data, initial_read_size);
+
+ while (size) {
+ size_t message_size;
+ CHECK(MessageInTransit::GetNextMessageSize(
+ messages, size, &message_size));
+ MessageInTransit::View message_view(message_size, messages);
+ size -= message_size;
+ messages += message_size;
+
+ // TODO(jam): Copied below from RawChannelWin. See commment above
+ // GetReadPlatformHandles.
+ ScopedPlatformHandleVectorPtr platform_handles;
+ if (message_view.transport_data_buffer()) {
+ size_t num_platform_handles;
+ const void* platform_handle_table;
+ TransportData::GetPlatformHandleTable(
+ message_view.transport_data_buffer(), &num_platform_handles,
+ &platform_handle_table);
+
+ if (num_platform_handles > 0) {
+ platform_handles =
+ GetReadPlatformHandles(num_platform_handles,
+ platform_handle_table).Pass();
+ if (!platform_handles) {
+ LOG(ERROR) << "Invalid number of platform handles received";
+ return nullptr;
+ }
+ }
+ }
+
+ // TODO(jam): Copied below from RawChannelWin. See commment above
+ // GetReadPlatformHandles.
+ scoped_ptr<MessageInTransit> message(new MessageInTransit(message_view));
+ if (message_view.transport_data_buffer_size() > 0) {
+ DCHECK(message_view.transport_data_buffer());
+ message->SetDispatchers(TransportData::DeserializeDispatchers(
+ message_view.transport_data_buffer(),
+ message_view.transport_data_buffer_size(), platform_handles.Pass()));
+ }
+
+ rv->message_queue_.AddMessage(message.Pass());
+ }
+
+ return rv;
+}
+
+MessagePipeDispatcher::MessagePipeDispatcher()
+ : channel_(nullptr),
+ serialized_(false),
+ calling_init_(false),
+ error_(false) {
+}
+
+MessagePipeDispatcher::~MessagePipeDispatcher() {
+ // |Close()|/|CloseImplNoLock()| should have taken care of the channel.
+ DCHECK(!channel_);
+}
+
+void MessagePipeDispatcher::CancelAllAwakablesNoLock() {
+ lock().AssertAcquired();
+ awakable_list_.CancelAll();
+}
+
+void MessagePipeDispatcher::CloseImplNoLock() {
+ lock().AssertAcquired();
+ if (g_use_channel_on_io_thread_only) {
+ internal::g_io_thread_task_runner->PostTask(
+ FROM_HERE, base::Bind(&MessagePipeDispatcher::CloseOnIO, this));
+ } else {
+ CloseOnIO();
+ }
+}
+
+void MessagePipeDispatcher::SerializeInternal() {
+ // We need to stop watching handle immediately, even tho not on IO thread, so
+ // that other messages aren't read after this.
+ {
+ if (channel_) {
+ serialized_platform_handle_ =
+ channel_->ReleaseHandle(&serialized_read_buffer_).release();
+ channel_ = nullptr;
+ } else {
+ // It's valid that the other side wrote some data and closed its end.
+ }
+ }
+
+ DCHECK(serialized_message_queue_.empty());
+ // see comment in method below, this is only temporary till we implement a
+ // solution with shared buffer
+ while (!message_queue_.IsEmpty()) {
+ scoped_ptr<MessageInTransit> message = message_queue_.GetMessage();
+ size_t cur_size = serialized_message_queue_.size();
+
+
+ // When MojoWriteMessage is called, the MessageInTransit doesn't have
+ // dispatchers set and CreateEquivaent... is called since the dispatchers
+ // can be referenced by others. here dispatchers aren't referenced by
+ // others, but rawchannel can still call to them. so since we dont call
+ // createequiv, manually call TransportStarted and TransportEnd.
+ DispatcherVector dispatchers;
+ if (message->has_dispatchers())
+ dispatchers = *message->dispatchers();
+ for (size_t i = 0; i < dispatchers.size(); ++i)
+ dispatchers[i]->TransportStarted();
+
+ // TODO(jam): this handling for dispatchers only works on windows where we
+ // send transportdata as bytes instead of as parameters to sendmsg.
+ message->SerializeAndCloseDispatchers();
+ // cont'd below
+
+
+ size_t main_buffer_size = message->main_buffer_size();
+ size_t transport_data_buffer_size = message->transport_data() ?
+ message->transport_data()->buffer_size() : 0;
+ size_t total_size = message->total_size();
+
+ serialized_message_queue_.resize(cur_size + total_size);
+ memcpy(&serialized_message_queue_[cur_size], message->main_buffer(),
+ main_buffer_size);
+
+ // cont'd
+ if (transport_data_buffer_size != 0) {
+#if defined(OS_WIN)
+ // TODO(jam): copied from RawChannelWin::WriteNoLock(
+ if (RawChannel::GetSerializedPlatformHandleSize()) {
+ char* serialization_data =
+ static_cast<char*>(message->transport_data()->buffer()) +
+ message->transport_data()->platform_handle_table_offset();
+ PlatformHandleVector* all_platform_handles =
+ message->transport_data()->platform_handles();
+ if (all_platform_handles) {
+ DWORD current_process_id = base::GetCurrentProcId();
+ for (size_t i = 0; i < all_platform_handles->size(); i++) {
+ *reinterpret_cast<DWORD*>(serialization_data) = current_process_id;
+ serialization_data += sizeof(DWORD);
+ *reinterpret_cast<HANDLE*>(serialization_data) =
+ all_platform_handles->at(i).handle;
+ serialization_data += sizeof(HANDLE);
+ all_platform_handles->at(i) = PlatformHandle();
+ }
+ }
+ }
+
+ memcpy(&serialized_message_queue_[
+ cur_size + total_size - transport_data_buffer_size],
+ message->transport_data()->buffer(), transport_data_buffer_size);
+#else
+ NOTREACHED() << "TODO(jam) implement";
+#endif
+ }
+
+ for (size_t i = 0; i < dispatchers.size(); ++i)
+ dispatchers[i]->TransportEnded();
+ }
+
+ serialized_ = true;
+}
+
+scoped_refptr<Dispatcher>
+MessagePipeDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() {
+ lock().AssertAcquired();
+
+ SerializeInternal();
+
+ // TODO(vtl): Currently, there are no options, so we just use
+ // |kDefaultCreateOptions|. Eventually, we'll have to duplicate the options
+ // too.
+ scoped_refptr<MessagePipeDispatcher> rv = Create(kDefaultCreateOptions);
+ rv->serialized_platform_handle_ = serialized_platform_handle_;
+ serialized_platform_handle_ = PlatformHandle();
+ serialized_message_queue_.swap(rv->serialized_message_queue_);
+ serialized_read_buffer_.swap(rv->serialized_read_buffer_);
+ rv->serialized_ = true;
+ return scoped_refptr<Dispatcher>(rv.get());
+}
+
+MojoResult MessagePipeDispatcher::WriteMessageImplNoLock(
+ const void* bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags flags) {
+
+ DCHECK(!transports ||
+ (transports->size() > 0 &&
+ transports->size() <= GetConfiguration().max_message_num_handles));
+
+ lock().AssertAcquired();
+
+ if (!channel_) {
+ DCHECK(error_);
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ if (num_bytes > GetConfiguration().max_message_num_bytes)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ scoped_ptr<MessageInTransit> message(new MessageInTransit(
+ MessageInTransit::Type::MESSAGE, num_bytes, bytes));
+ if (transports) {
+ MojoResult result = AttachTransportsNoLock(message.get(), transports);
+ if (result != MOJO_RESULT_OK)
+ return result;
+ }
+
+ message->SerializeAndCloseDispatchers();
+ channel_->WriteMessage(message.Pass());
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult MessagePipeDispatcher::ReadMessageImplNoLock(
+ void* bytes,
+ uint32_t* num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags) {
+ lock().AssertAcquired();
+ DCHECK(!dispatchers || dispatchers->empty());
+
+ const uint32_t max_bytes = !num_bytes ? 0 : *num_bytes;
+ const uint32_t max_num_dispatchers = num_dispatchers ? *num_dispatchers : 0;
+
+ if (message_queue_.IsEmpty()) {
+ return error_ ? MOJO_RESULT_FAILED_PRECONDITION
+ : MOJO_RESULT_SHOULD_WAIT;
+ }
+
+ // TODO(vtl): If |flags & MOJO_READ_MESSAGE_FLAG_MAY_DISCARD|, we could pop
+ // and release the lock immediately.
+ bool enough_space = true;
+ MessageInTransit* message = message_queue_.PeekMessage();
+ if (num_bytes)
+ *num_bytes = message->num_bytes();
+ if (message->num_bytes() <= max_bytes)
+ memcpy(bytes, message->bytes(), message->num_bytes());
+ else
+ enough_space = false;
+
+ if (DispatcherVector* queued_dispatchers = message->dispatchers()) {
+ if (num_dispatchers)
+ *num_dispatchers = static_cast<uint32_t>(queued_dispatchers->size());
+ if (enough_space) {
+ if (queued_dispatchers->empty()) {
+ // Nothing to do.
+ } else if (queued_dispatchers->size() <= max_num_dispatchers) {
+ DCHECK(dispatchers);
+ dispatchers->swap(*queued_dispatchers);
+ } else {
+ enough_space = false;
+ }
+ }
+ } else {
+ if (num_dispatchers)
+ *num_dispatchers = 0;
+ }
+
+ message = nullptr;
+
+ if (enough_space || (flags & MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)) {
+ message_queue_.DiscardMessage();
+
+ // Now it's empty, thus no longer readable.
+ if (message_queue_.IsEmpty()) {
+ // It's currently not possible to wait for non-readability, but we should
+ // do the state change anyway.
+ awakable_list_.AwakeForStateChange(GetHandleSignalsStateImplNoLock());
+ }
+ }
+
+ if (!enough_space)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ return MOJO_RESULT_OK;
+}
+
+HandleSignalsState MessagePipeDispatcher::GetHandleSignalsStateImplNoLock()
+ const {
+ lock().AssertAcquired();
+
+ HandleSignalsState rv;
+ if (!message_queue_.IsEmpty()) {
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_READABLE;
+ rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE;
+ }
+ if (!error_) {
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE;
+ rv.satisfiable_signals |=
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE;
+ } else {
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+ }
+ rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+ return rv;
+}
+
+MojoResult MessagePipeDispatcher::AddAwakableImplNoLock(
+ Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ HandleSignalsState state = GetHandleSignalsStateImplNoLock();
+ if (state.satisfies(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_ALREADY_EXISTS;
+ }
+ if (!state.can_satisfy(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ awakable_list_.Add(awakable, signals, context);
+ return MOJO_RESULT_OK;
+}
+
+void MessagePipeDispatcher::RemoveAwakableImplNoLock(
+ Awakable* awakable,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+
+ awakable_list_.Remove(awakable);
+ if (signals_state)
+ *signals_state = GetHandleSignalsStateImplNoLock();
+}
+
+void MessagePipeDispatcher::StartSerializeImplNoLock(
+ size_t* max_size,
+ size_t* max_platform_handles) {
+ if (!serialized_)
+ SerializeInternal();
+
+ *max_platform_handles = serialized_platform_handle_.is_valid() ? 1 : 0;
+
+ DCHECK_EQ(serialized_message_queue_.size() %
+ MessageInTransit::kMessageAlignment, 0U);
+ *max_size = sizeof(SerializedMessagePipeHandleDispatcher) +
+ serialized_message_queue_.size() +
+ serialized_read_buffer_.size();
+
+ DCHECK_LE(*max_size, TransportData::kMaxSerializedDispatcherSize);
+}
+
+bool MessagePipeDispatcher::EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) {
+ CloseImplNoLock();
+ SerializedMessagePipeHandleDispatcher* serialization =
+ static_cast<SerializedMessagePipeHandleDispatcher*>(destination);
+ if (serialized_platform_handle_.is_valid()) {
+ serialization->platform_handle_index = platform_handles->size();
+ platform_handles->push_back(serialized_platform_handle_);
+ } else {
+ serialization->platform_handle_index = kInvalidMessagePipeHandleIndex;
+ }
+ serialization->read_buffer_size = serialized_read_buffer_.size();
+
+ char* destination_char = static_cast<char*>(destination);
+ destination_char += sizeof(SerializedMessagePipeHandleDispatcher);
+
+ if (!serialized_read_buffer_.empty()) {
+ memcpy(destination_char, &serialized_read_buffer_[0],
+ serialized_read_buffer_.size());
+ destination_char += serialized_read_buffer_.size();
+ }
+
+
+ if (!serialized_message_queue_.empty()) {
+ memcpy(destination_char,
+ &serialized_message_queue_[0],
+ serialized_message_queue_.size());
+ }
+
+ *actual_size =
+ sizeof(SerializedMessagePipeHandleDispatcher) +
+ serialized_message_queue_.size() +
+ serialized_read_buffer_.size();
+
+ return true;
+}
+
+void MessagePipeDispatcher::TransportStarted() {
+ started_transport_.Acquire();
+}
+
+void MessagePipeDispatcher::TransportEnded() {
+ started_transport_.Release();
+
+ base::AutoLock locker(lock());
+
+ // If transporting of MPD failed, we might have got more data and didn't
+ // awake for.
+ // TODO(jam): should we care about only alerting if it was empty before
+ // TransportStarted?
+ if (!message_queue_.IsEmpty())
+ awakable_list_.AwakeForStateChange(GetHandleSignalsStateImplNoLock());
+}
+
+void MessagePipeDispatcher::OnReadMessage(
+ const MessageInTransit::View& message_view,
+ ScopedPlatformHandleVectorPtr platform_handles) {
+ scoped_ptr<MessageInTransit> message(new MessageInTransit(message_view));
+ if (message_view.transport_data_buffer_size() > 0) {
+ DCHECK(message_view.transport_data_buffer());
+ message->SetDispatchers(TransportData::DeserializeDispatchers(
+ message_view.transport_data_buffer(),
+ message_view.transport_data_buffer_size(), platform_handles.Pass()));
+ }
+
+ if (started_transport_.Try()) {
+ // we're not in the middle of being sent
+
+ // Can get synchronously called back in Init if there was initial data.
+ scoped_ptr<base::AutoLock> locker;
+ if (!calling_init_) {
+ locker.reset(new base::AutoLock(lock()));
+ }
+
+ bool was_empty = message_queue_.IsEmpty();
+ message_queue_.AddMessage(message.Pass());
+ if (was_empty)
+ awakable_list_.AwakeForStateChange(GetHandleSignalsStateImplNoLock());
+
+ started_transport_.Release();
+ } else {
+
+ // if RawChannel is calling OnRead, that means it has its read_lock_
+ // acquired. that means StartSerialize can't be accessing message queue as
+ // it waits on releasehandle first which acquires readlock_!
+ message_queue_.AddMessage(message.Pass());
+ }
+}
+
+void MessagePipeDispatcher::OnError(Error error) {
+ switch (error) {
+ case ERROR_READ_SHUTDOWN:
+ // The other side was cleanly closed, so this isn't actually an error.
+ DVLOG(1) << "MessagePipeDispatcher read error (shutdown)";
+ break;
+ case ERROR_READ_BROKEN:
+ LOG(ERROR) << "MessagePipeDispatcher read error (connection broken)";
+ break;
+ case ERROR_READ_BAD_MESSAGE:
+ // Receiving a bad message means either a bug, data corruption, or
+ // malicious attack (probably due to some other bug).
+ LOG(ERROR) << "MessagePipeDispatcher read error (received bad message)";
+ break;
+ case ERROR_READ_UNKNOWN:
+ LOG(ERROR) << "MessagePipeDispatcher read error (unknown)";
+ break;
+ case ERROR_WRITE:
+ // Write errors are slightly notable: they probably shouldn't happen under
+ // normal operation (but maybe the other side crashed).
+ LOG(WARNING) << "MessagePipeDispatcher write error";
+ break;
+ }
+
+ error_ = true;
+ if (started_transport_.Try()) {
+ base::AutoLock locker(lock());
+ awakable_list_.AwakeForStateChange(GetHandleSignalsStateImplNoLock());
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&RawChannel::Shutdown, base::Unretained(channel_)));
+ channel_ = nullptr;
+ started_transport_.Release();
+ } else {
+ // We must be waiting to call ReleaseHandle. It will call Shutdown.
+ }
+}
+
+MojoResult MessagePipeDispatcher::AttachTransportsNoLock(
+ MessageInTransit* message,
+ std::vector<DispatcherTransport>* transports) {
+ DCHECK(!message->has_dispatchers());
+
+ // You're not allowed to send either handle to a message pipe over the message
+ // pipe, so check for this. (The case of trying to write a handle to itself is
+ // taken care of by |Core|. That case kind of makes sense, but leads to
+ // complications if, e.g., both sides try to do the same thing with their
+ // respective handles simultaneously. The other case, of trying to write the
+ // peer handle to a handle, doesn't make sense -- since no handle will be
+ // available to read the message from.)
+ for (size_t i = 0; i < transports->size(); i++) {
+ if (!(*transports)[i].is_valid())
+ continue;
+ if ((*transports)[i].GetType() == Dispatcher::Type::MESSAGE_PIPE) {
+ MessagePipeDispatcher* mp =
+ static_cast<MessagePipeDispatcher*>(((*transports)[i]).dispatcher());
+ if (channel_ && mp->channel_ && channel_->IsOtherEndOf(mp->channel_)) {
+ // The other case should have been disallowed by |Core|. (Note: |port|
+ // is the peer port of the handle given to |WriteMessage()|.)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+ }
+ }
+
+ // Clone the dispatchers and attach them to the message. (This must be done as
+ // a separate loop, since we want to leave the dispatchers alone on failure.)
+ scoped_ptr<DispatcherVector> dispatchers(new DispatcherVector());
+ dispatchers->reserve(transports->size());
+ for (size_t i = 0; i < transports->size(); i++) {
+ if ((*transports)[i].is_valid()) {
+ dispatchers->push_back(
+ (*transports)[i].CreateEquivalentDispatcherAndClose());
+ } else {
+ LOG(WARNING) << "Enqueueing null dispatcher";
+ dispatchers->push_back(nullptr);
+ }
+ }
+ message->SetDispatchers(dispatchers.Pass());
+ return MOJO_RESULT_OK;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/message_pipe_dispatcher.h b/mojo/edk/system/message_pipe_dispatcher.h
new file mode 100644
index 0000000..c47b550
--- /dev/null
+++ b/mojo/edk/system/message_pipe_dispatcher.h
@@ -0,0 +1,145 @@
+// Copyright 2015 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 MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_
+
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/system/awakable_list.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/raw_channel.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// This is the |Dispatcher| implementation for message pipes (created by the
+// Mojo primitive |MojoCreateMessagePipe()|). This class is thread-safe.
+class MOJO_SYSTEM_IMPL_EXPORT MessagePipeDispatcher final
+ : public Dispatcher, public RawChannel::Delegate {
+ public:
+ // The default options to use for |MojoCreateMessagePipe()|. (Real uses
+ // should obtain this via |ValidateCreateOptions()| with a null |in_options|;
+ // this is exposed directly for testing convenience.)
+ static const MojoCreateMessagePipeOptions kDefaultCreateOptions;
+
+ static scoped_refptr<MessagePipeDispatcher> Create(
+ const MojoCreateMessagePipeOptions& /*validated_options*/) {
+ return make_scoped_refptr(new MessagePipeDispatcher());
+ }
+
+ // Validates and/or sets default options for |MojoCreateMessagePipeOptions|.
+ // If non-null, |in_options| must point to a struct of at least
+ // |in_options->struct_size| bytes. |out_options| must point to a (current)
+ // |MojoCreateMessagePipeOptions| and will be entirely overwritten on success
+ // (it may be partly overwritten on failure).
+ static MojoResult ValidateCreateOptions(
+ const MojoCreateMessagePipeOptions* in_options,
+ MojoCreateMessagePipeOptions* out_options);
+
+ // Must be called before any other methods. (This method is not thread-safe.)
+ void Init(ScopedPlatformHandle message_pipe);
+
+ // |Dispatcher| public methods:
+ Type GetType() const override;
+
+ // The "opposite" of |SerializeAndClose()|. (Typically this is called by
+ // |Dispatcher::Deserialize()|.)
+ static scoped_refptr<MessagePipeDispatcher> Deserialize(
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles);
+
+ private:
+ MessagePipeDispatcher();
+ ~MessagePipeDispatcher() override;
+
+ void InitWithReadBuffer(ScopedPlatformHandle message_pipe,
+ char* data,
+ size_t size);
+
+ void InitOnIO();
+ void CloseOnIO();
+
+ // |Dispatcher| protected methods:
+ void CancelAllAwakablesNoLock() override;
+ void CloseImplNoLock() override;
+ scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndCloseImplNoLock()
+ override;
+ MojoResult WriteMessageImplNoLock(
+ const void* bytes,
+ uint32_t num_bytes,
+ std::vector<DispatcherTransport>* transports,
+ MojoWriteMessageFlags flags) override;
+ MojoResult ReadMessageImplNoLock(void* bytes,
+ uint32_t* num_bytes,
+ DispatcherVector* dispatchers,
+ uint32_t* num_dispatchers,
+ MojoReadMessageFlags flags) override;
+ HandleSignalsState GetHandleSignalsStateImplNoLock() const override;
+ MojoResult AddAwakableImplNoLock(Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) override;
+ void RemoveAwakableImplNoLock(Awakable* awakable,
+ HandleSignalsState* signals_state) override;
+ void StartSerializeImplNoLock(size_t* max_size,
+ size_t* max_platform_handles) override;
+ bool EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) override;
+ void TransportStarted() override;
+ void TransportEnded() override;
+
+ // |RawChannel::Delegate methods:
+ void OnReadMessage(
+ const MessageInTransit::View& message_view,
+ ScopedPlatformHandleVectorPtr platform_handles) override;
+ void OnError(Error error) override;
+
+ // Calls ReleaseHandle and serializes the raw channel. This is split into a
+ // function because it's called in two different ways:
+ // 1) When serializing "live" dispatchers that are passed to MojoWriteMessage,
+ // CreateEquivalentDispatcherAndCloseImplNoLock calls this.
+ // 2) When serializing dispatchers that are attached to deserialized messages
+ // which haven't been consumed by MojoReadMessage, StartSerializeImplNoLock
+ // calls this.
+ void SerializeInternal();
+
+ MojoResult AttachTransportsNoLock(
+ MessageInTransit* message,
+ std::vector<DispatcherTransport>* transports);
+
+ // Protected by |lock()|:
+ RawChannel* channel_;
+
+ // Queue of incoming messages that we read from RawChannel but haven't been
+ // consumed through MojoReadMessage yet.
+ MessageInTransitQueue message_queue_;
+ // When sending MP, contains serialized message_queue_.
+ bool serialized_;
+ // TODO(jam): stop using this and use shared memory instead since we are
+ // limited to 10K.
+ std::vector<char> serialized_message_queue_;
+ std::vector<char> serialized_read_buffer_;
+ PlatformHandle serialized_platform_handle_;
+ AwakableList awakable_list_;
+
+ // If DispatcherTransport is created. Must be set before lock() is called to
+ // avoid deadlocks with RawChannel calling us.
+ base::Lock started_transport_;
+
+ bool calling_init_;
+ bool error_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MessagePipeDispatcher);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_
diff --git a/mojo/edk/system/message_pipe_perftest.cc b/mojo/edk/system/message_pipe_perftest.cc
new file mode 100644
index 0000000..5d59e65
--- /dev/null
+++ b/mojo/edk/system/message_pipe_perftest.cc
@@ -0,0 +1,162 @@
+// Copyright 2014 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/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/perf_time_logger.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/message_pipe_test_utils.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/test/test_utils.h"
+#include "mojo/public/c/system/functions.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+class MultiprocessMessagePipePerfTest
+ : public test::MultiprocessMessagePipeTestBase {
+ public:
+ MultiprocessMessagePipePerfTest()
+ : test::MultiprocessMessagePipeTestBase(base::MessageLoop::TYPE_IO),
+ message_count_(0),
+ message_size_(0) {}
+
+ void SetUpMeasurement(int message_count, size_t message_size) {
+ message_count_ = message_count;
+ message_size_ = message_size;
+ payload_ = std::string(message_size, '*');
+ read_buffer_.resize(message_size * 2);
+ }
+
+ protected:
+ void WriteWaitThenRead(MojoHandle mp) {
+ CHECK_EQ(MojoWriteMessage(mp, payload_.data(),
+ static_cast<uint32_t>(payload_.size()), nullptr,
+ 0, MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ HandleSignalsState hss;
+ CHECK_EQ(MojoWait(mp, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE,
+ &hss),
+ MOJO_RESULT_OK);
+ uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer_.size());
+ CHECK_EQ(MojoReadMessage(mp, &read_buffer_[0], &read_buffer_size, nullptr,
+ nullptr, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ CHECK_EQ(read_buffer_size, static_cast<uint32_t>(payload_.size()));
+ }
+
+ void SendQuitMessage(MojoHandle mp) {
+ CHECK_EQ(MojoWriteMessage(mp, "", 0, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ }
+
+ void Measure(MojoHandle mp) {
+ // Have one ping-pong to ensure channel being established.
+ WriteWaitThenRead(mp);
+
+ std::string test_name =
+ base::StringPrintf("IPC_Perf_%dx_%u", message_count_,
+ static_cast<unsigned>(message_size_));
+ base::PerfTimeLogger logger(test_name.c_str());
+
+ for (int i = 0; i < message_count_; ++i)
+ WriteWaitThenRead(mp);
+
+ logger.Done();
+ }
+
+ private:
+ int message_count_;
+ size_t message_size_;
+ std::string payload_;
+ std::string read_buffer_;
+ scoped_ptr<base::PerfTimeLogger> perf_logger_;
+};
+
+// For each message received, sends a reply message with the same contents
+// repeated twice, until the other end is closed or it receives "quitquitquit"
+// (which it doesn't reply to). It'll return the number of messages received,
+// not including any "quitquitquit" message, modulo 100.
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(PingPongClient) {
+ SimplePlatformSupport platform_support;
+ base::MessageLoop message_loop(base::MessageLoop::TYPE_IO);
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ test::ScopedIPCSupport ipc_support(test_io_thread.task_runner());
+
+ ScopedPlatformHandle client_platform_handle =
+ test::MultiprocessTestHelper::client_platform_handle.Pass();
+ CHECK(client_platform_handle.is_valid());
+ ScopedMessagePipeHandle mp =
+ CreateMessagePipe(client_platform_handle.Pass());
+
+ std::string buffer(1000000, '\0');
+ int rv = 0;
+ while (true) {
+ // Wait for our end of the message pipe to be readable.
+ HandleSignalsState hss;
+ MojoResult result =
+ MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss);
+ if (result != MOJO_RESULT_OK) {
+ rv = result;
+ break;
+ }
+
+ uint32_t read_size = static_cast<uint32_t>(buffer.size());
+ CHECK_EQ(MojoReadMessage(mp.get().value(), &buffer[0],
+ &read_size, nullptr,
+ 0, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+
+ // Empty message indicates quit.
+ if (read_size == 0)
+ break;
+
+ CHECK_EQ(MojoWriteMessage(mp.get().value(), &buffer[0],
+ read_size,
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ }
+
+ return rv;
+}
+
+// Repeatedly sends messages as previous one got replied by the child.
+// Waits for the child to close its end before quitting once specified
+// number of messages has been sent.
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_PingPong DISABLED_PingPong
+#else
+#define MAYBE_PingPong PingPong
+#endif // defined(OS_ANDROID)
+TEST_F(MultiprocessMessagePipePerfTest, MAYBE_PingPong) {
+ helper()->StartChild("PingPongClient");
+
+ ScopedMessagePipeHandle mp = CreateMessagePipe(
+ helper()->server_platform_handle.Pass());
+
+ // This values are set to align with one at ipc_pertests.cc for comparison.
+ const size_t kMsgSize[5] = {12, 144, 1728, 20736, 248832};
+ const int kMessageCount[5] = {50000, 50000, 50000, 12000, 1000};
+
+ for (size_t i = 0; i < 5; i++) {
+ SetUpMeasurement(kMessageCount[i], kMsgSize[i]);
+ Measure(mp.get().value());
+ }
+
+ SendQuitMessage(mp.get().value());
+ EXPECT_EQ(0, helper()->WaitForChildShutdown());
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/message_pipe_test_utils.cc b/mojo/edk/system/message_pipe_test_utils.cc
new file mode 100644
index 0000000..1e337fb
--- /dev/null
+++ b/mojo/edk/system/message_pipe_test_utils.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 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 "mojo/edk/system/message_pipe_test_utils.h"
+
+#include "mojo/edk/system/test_utils.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+#if !defined(OS_IOS)
+MultiprocessMessagePipeTestBase::MultiprocessMessagePipeTestBase()
+ : test_io_thread_(base::TestIOThread::kAutoStart),
+ ipc_support_(test_io_thread_.task_runner()) {
+}
+
+MultiprocessMessagePipeTestBase::MultiprocessMessagePipeTestBase(
+ base::MessageLoop::Type main_message_loop_type)
+ : message_loop_(main_message_loop_type),
+ test_io_thread_(base::TestIOThread::kAutoStart),
+ ipc_support_(test_io_thread_.task_runner()) {
+}
+
+MultiprocessMessagePipeTestBase::~MultiprocessMessagePipeTestBase() {
+}
+#endif
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/message_pipe_test_utils.h b/mojo/edk/system/message_pipe_test_utils.h
new file mode 100644
index 0000000..e067329
--- /dev/null
+++ b/mojo/edk/system/message_pipe_test_utils.h
@@ -0,0 +1,51 @@
+// Copyright 2014 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 MOJO_EDK_SYSTEM_MESSAGE_PIPE_TEST_UTILS_H_
+#define MOJO_EDK_SYSTEM_MESSAGE_PIPE_TEST_UTILS_H_
+
+#include "base/message_loop/message_loop.h"
+#include "base/test/test_io_thread.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/test/multiprocess_test_helper.h"
+#include "mojo/edk/test/scoped_ipc_support.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+class ChannelEndpoint;
+class MessagePipe;
+
+namespace test {
+
+#if !defined(OS_IOS)
+class MultiprocessMessagePipeTestBase : public testing::Test {
+ public:
+ MultiprocessMessagePipeTestBase();
+ MultiprocessMessagePipeTestBase(
+ base::MessageLoop::Type main_message_loop_type);
+ ~MultiprocessMessagePipeTestBase() override;
+
+ protected:
+ PlatformSupport* platform_support() { return &platform_support_; }
+ test::MultiprocessTestHelper* helper() { return &helper_; }
+
+ private:
+ SimplePlatformSupport platform_support_;
+ base::MessageLoop message_loop_;
+ base::TestIOThread test_io_thread_;
+ test::ScopedIPCSupport ipc_support_;
+ test::MultiprocessTestHelper helper_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MultiprocessMessagePipeTestBase);
+};
+#endif
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_MESSAGE_PIPE_TEST_UTILS_H_
diff --git a/mojo/edk/system/message_pipe_unittest.cc b/mojo/edk/system/message_pipe_unittest.cc
new file mode 100644
index 0000000..9283acc
--- /dev/null
+++ b/mojo/edk/system/message_pipe_unittest.cc
@@ -0,0 +1,409 @@
+// 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 "base/memory/ref_counted.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/public/c/system/core.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+const MojoHandleSignals kAllSignals = MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+static const char kHelloWorld[] = "hello world";
+
+class MessagePipeTest : public test::MojoSystemTest {
+ public:
+ MessagePipeTest() {
+ CHECK_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &pipe0_, &pipe1_));
+ }
+
+ ~MessagePipeTest() override {
+ if (pipe0_ != MOJO_HANDLE_INVALID)
+ CHECK_EQ(MOJO_RESULT_OK, MojoClose(pipe0_));
+ if (pipe1_ != MOJO_HANDLE_INVALID)
+ CHECK_EQ(MOJO_RESULT_OK, MojoClose(pipe1_));
+ }
+
+ MojoResult WriteMessage(MojoHandle message_pipe_handle,
+ const void* bytes,
+ uint32_t num_bytes) {
+ return MojoWriteMessage(message_pipe_handle, bytes, num_bytes, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ }
+
+ MojoResult ReadMessage(MojoHandle message_pipe_handle,
+ void* bytes,
+ uint32_t* num_bytes,
+ bool may_discard = false) {
+ return MojoReadMessage(message_pipe_handle, bytes, num_bytes, nullptr, 0,
+ may_discard ? MOJO_READ_MESSAGE_FLAG_MAY_DISCARD :
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ }
+
+ MojoHandle pipe0_, pipe1_;
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MessagePipeTest);
+};
+
+TEST_F(MessagePipeTest, WriteData) {
+ ASSERT_EQ(MOJO_RESULT_OK,
+ WriteMessage(pipe0_, kHelloWorld, sizeof(kHelloWorld)));
+}
+
+// Tests:
+// - only default flags
+// - reading messages from a port
+// - when there are no/one/two messages available for that port
+// - with buffer size 0 (and null buffer) -- should get size
+// - with too-small buffer -- should get size
+// - also verify that buffers aren't modified when/where they shouldn't be
+// - writing messages to a port
+// - in the obvious scenarios (as above)
+// - to a port that's been closed
+// - writing a message to a port, closing the other (would be the source) port,
+// and reading it
+TEST_F(MessagePipeTest, Basic) {
+ int32_t buffer[2];
+ const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer));
+ uint32_t buffer_size;
+
+ // Nothing to read yet on port 0.
+ buffer[0] = 123;
+ buffer[1] = 456;
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe0_, buffer, &buffer_size));
+ ASSERT_EQ(kBufferSize, buffer_size);
+ ASSERT_EQ(123, buffer[0]);
+ ASSERT_EQ(456, buffer[1]);
+
+ // Ditto for port 1.
+ buffer[0] = 123;
+ buffer[1] = 456;
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe1_, buffer, &buffer_size));
+
+ // Write from port 1 (to port 0).
+ buffer[0] = 789012345;
+ buffer[1] = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
+
+ MojoHandleSignalsState state;
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+
+ // Read from port 0.
+ buffer[0] = 123;
+ buffer[1] = 456;
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe0_, buffer, &buffer_size));
+ ASSERT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size);
+ ASSERT_EQ(789012345, buffer[0]);
+ ASSERT_EQ(456, buffer[1]);
+
+ // Read again from port 0 -- it should be empty.
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe0_, buffer, &buffer_size));
+
+ // Write two messages from port 0 (to port 1).
+ buffer[0] = 123456789;
+ buffer[1] = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0])));
+ buffer[0] = 234567890;
+ buffer[1] = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0])));
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+
+ // Read from port 1 with buffer size 0 (should get the size of next message).
+ // Also test that giving a null buffer is okay when the buffer size is 0.
+ buffer_size = 0;
+ ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ ReadMessage(pipe1_, nullptr, &buffer_size));
+ ASSERT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size);
+
+ // Read from port 1 with buffer size 1 (too small; should get the size of next
+ // message).
+ buffer[0] = 123;
+ buffer[1] = 456;
+ buffer_size = 1;
+ ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ ReadMessage(pipe1_, buffer, &buffer_size));
+ ASSERT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size);
+ ASSERT_EQ(123, buffer[0]);
+ ASSERT_EQ(456, buffer[1]);
+
+ // Read from port 1.
+ buffer[0] = 123;
+ buffer[1] = 456;
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size));
+ ASSERT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size);
+ ASSERT_EQ(123456789, buffer[0]);
+ ASSERT_EQ(456, buffer[1]);
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+
+ // Read again from port 1.
+ buffer[0] = 123;
+ buffer[1] = 456;
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size));
+ ASSERT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size);
+ ASSERT_EQ(234567890, buffer[0]);
+ ASSERT_EQ(456, buffer[1]);
+
+ // Read again from port 1 -- it should be empty.
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe1_, buffer, &buffer_size));
+
+ // Write from port 0 (to port 1).
+ buffer[0] = 345678901;
+ buffer[1] = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0])));
+
+ // Close port 0.
+ MojoClose(pipe0_);
+ pipe0_ = MOJO_HANDLE_INVALID;
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &state));
+
+ // Try to write from port 1 (to port 0).
+ buffer[0] = 456789012;
+ buffer[1] = 0;
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
+
+ // Read from port 1; should still get message (even though port 0 was closed).
+ buffer[0] = 123;
+ buffer[1] = 456;
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size));
+ ASSERT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size);
+ ASSERT_EQ(345678901, buffer[0]);
+ ASSERT_EQ(456, buffer[1]);
+
+ // Read again from port 1 -- it should be empty (and port 0 is closed).
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ ReadMessage(pipe1_, buffer, &buffer_size));
+}
+
+TEST_F(MessagePipeTest, CloseWithQueuedIncomingMessages) {
+ int32_t buffer[1];
+ const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer));
+ uint32_t buffer_size;
+
+ // Write some messages from port 1 (to port 0).
+ for (int32_t i = 0; i < 5; i++) {
+ buffer[0] = i;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, kBufferSize));
+ }
+
+ MojoHandleSignalsState state;
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+
+ // Port 0 shouldn't be empty.
+ buffer_size = 0;
+ ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ ReadMessage(pipe0_, nullptr, &buffer_size));
+ ASSERT_EQ(kBufferSize, buffer_size);
+
+ // Close port 0 first, which should have outstanding (incoming) messages.
+ MojoClose(pipe0_);
+ MojoClose(pipe1_);
+ pipe0_ = pipe1_ = MOJO_HANDLE_INVALID;
+}
+
+TEST_F(MessagePipeTest, DiscardMode) {
+ int32_t buffer[2];
+ const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer));
+ uint32_t buffer_size;
+
+ // Write from port 1 (to port 0).
+ buffer[0] = 789012345;
+ buffer[1] = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
+
+ MojoHandleSignalsState state;
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+
+ // Read/discard from port 0 (no buffer); get size.
+ buffer_size = 0;
+ ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ ReadMessage(pipe0_, nullptr, &buffer_size, true));
+ ASSERT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size);
+
+ // Read again from port 0 -- it should be empty.
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ ReadMessage(pipe0_, buffer, &buffer_size, true));
+
+ // Write from port 1 (to port 0).
+ buffer[0] = 890123456;
+ buffer[1] = 0;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+
+ // Read from port 0 (buffer big enough).
+ buffer[0] = 123;
+ buffer[1] = 456;
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe0_, buffer, &buffer_size, true));
+ ASSERT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size);
+ ASSERT_EQ(890123456, buffer[0]);
+ ASSERT_EQ(456, buffer[1]);
+
+ // Read again from port 0 -- it should be empty.
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ ReadMessage(pipe0_, buffer, &buffer_size, true));
+
+ // Write from port 1 (to port 0).
+ buffer[0] = 901234567;
+ buffer[1] = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+
+ // Read/discard from port 0 (buffer too small); get size.
+ buffer_size = 1;
+ ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ ReadMessage(pipe0_, buffer, &buffer_size, true));
+ ASSERT_EQ(static_cast<uint32_t>(sizeof(buffer[0])), buffer_size);
+
+ // Read again from port 0 -- it should be empty.
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ ReadMessage(pipe0_, buffer, &buffer_size, true));
+
+ // Write from port 1 (to port 0).
+ buffer[0] = 123456789;
+ buffer[1] = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0])));
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &state));
+
+ // Discard from port 0.
+ buffer_size = 1;
+ ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ ReadMessage(pipe0_, nullptr, 0, true));
+
+ // Read again from port 0 -- it should be empty.
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ ReadMessage(pipe0_, buffer, &buffer_size, true));
+}
+
+TEST_F(MessagePipeTest, BasicWaiting) {
+ MojoHandleSignalsState hss;
+
+ int32_t buffer[1];
+ const uint32_t kBufferSize = static_cast<uint32_t>(sizeof(buffer));
+ uint32_t buffer_size;
+
+ // Always writable (until the other port is closed).
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_WRITABLE, 0,
+ &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
+ hss = MojoHandleSignalsState();
+
+ // Not yet readable.
+ ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
+
+ // The peer is not closed.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ MojoWait(pipe0_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, 0, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
+
+ // Write from port 0 (to port 1), to make port 1 readable.
+ buffer[0] = 123456789;
+ ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, kBufferSize));
+
+ // Port 1 should already be readable now.
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
+ // ... and still writable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfied_signals);
+ ASSERT_EQ(kAllSignals, hss.satisfiable_signals);
+
+ // Close port 0.
+ MojoClose(pipe0_);
+ pipe0_ = MOJO_HANDLE_INVALID;
+
+ // Port 1 should be signaled with peer closed.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Port 1 should not be writable.
+ hss = MojoHandleSignalsState();
+
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // But it should still be readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK, MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ hss.satisfiable_signals);
+
+ // Read from port 1.
+ buffer[0] = 0;
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size));
+ ASSERT_EQ(123456789, buffer[0]);
+
+ // Now port 1 should no longer be readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(pipe1_, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/multiprocess_message_pipe_unittest.cc b/mojo/edk/system/multiprocess_message_pipe_unittest.cc
new file mode 100644
index 0000000..4cfeef5
--- /dev/null
+++ b/mojo/edk/system/multiprocess_message_pipe_unittest.cc
@@ -0,0 +1,782 @@
+// 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 <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/test/test_io_thread.h"
+#include "build/build_config.h" // TODO(vtl): Remove this.
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/message_pipe_test_utils.h"
+#include "mojo/edk/system/platform_handle_dispatcher.h"
+#include "mojo/edk/system/raw_channel.h"
+#include "mojo/edk/system/shared_buffer_dispatcher.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+
+namespace mojo {
+namespace edk {
+namespace {
+
+class MultiprocessMessagePipeTest
+ : public test::MultiprocessMessagePipeTestBase {};
+
+// For each message received, sends a reply message with the same contents
+// repeated twice, until the other end is closed or it receives "quitquitquit"
+// (which it doesn't reply to). It'll return the number of messages received,
+// not including any "quitquitquit" message, modulo 100.
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(EchoEcho) {
+ SimplePlatformSupport platform_support;
+ base::MessageLoop message_loop;
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ test::ScopedIPCSupport ipc_support(test_io_thread.task_runner());
+
+ ScopedPlatformHandle client_platform_handle =
+ test::MultiprocessTestHelper::client_platform_handle.Pass();
+ CHECK(client_platform_handle.is_valid());
+ ScopedMessagePipeHandle mp =
+ CreateMessagePipe(client_platform_handle.Pass());
+
+ const std::string quitquitquit("quitquitquit");
+ int rv = 0;
+ for (;; rv = (rv + 1) % 100) {
+ // Wait for our end of the message pipe to be readable.
+ HandleSignalsState hss;
+ MojoResult result =
+ MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss);
+ if (result != MOJO_RESULT_OK) {
+ // It was closed, probably.
+ CHECK_EQ(result, MOJO_RESULT_FAILED_PRECONDITION);
+ CHECK_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ break;
+ } else {
+ CHECK((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ CHECK((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ }
+
+ std::string read_buffer(1000, '\0');
+ uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size());
+ CHECK_EQ(MojoReadMessage(mp.get().value(), &read_buffer[0],
+ &read_buffer_size, nullptr,
+ 0, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ read_buffer.resize(read_buffer_size);
+ VLOG(2) << "Child got: " << read_buffer;
+
+ if (read_buffer == quitquitquit) {
+ VLOG(2) << "Child quitting.";
+ break;
+ }
+
+ std::string write_buffer = read_buffer + read_buffer;
+ CHECK_EQ(MojoWriteMessage(mp.get().value(), write_buffer.data(),
+ write_buffer.size(),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ }
+
+ return rv;
+}
+
+// Sends "hello" to child, and expects "hellohello" back.
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_Basic DISABLED_Basic
+#else
+#define MAYBE_Basic Basic
+#endif // defined(OS_ANDROID)
+TEST_F(MultiprocessMessagePipeTest, MAYBE_Basic) {
+ helper()->StartChild("EchoEcho");
+
+ ScopedMessagePipeHandle mp = CreateMessagePipe(
+ helper()->server_platform_handle.Pass());
+
+ std::string hello("hello");
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp.get().value(), hello.data(),
+ hello.size(), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ HandleSignalsState hss;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ // The child may or may not have closed its end of the message pipe and died
+ // (and we may or may not know it yet), so our end may or may not appear as
+ // writable.
+ EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
+
+ std::string read_buffer(1000, '\0');
+ uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size());
+ CHECK_EQ(MojoReadMessage(mp.get().value(), &read_buffer[0],
+ &read_buffer_size, nullptr, 0,
+ MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ read_buffer.resize(read_buffer_size);
+ VLOG(2) << "Parent got: " << read_buffer;
+ ASSERT_EQ(hello + hello, read_buffer);
+
+ MojoClose(mp.release().value());
+
+ // We sent one message.
+ ASSERT_EQ(1 % 100, helper()->WaitForChildShutdown());
+}
+
+// Sends a bunch of messages to the child. Expects them "repeated" back. Waits
+// for the child to close its end before quitting.
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_QueueMessages DISABLED_QueueMessages
+#else
+#define MAYBE_QueueMessages QueueMessages
+#endif // defined(OS_ANDROID)
+TEST_F(MultiprocessMessagePipeTest, MAYBE_QueueMessages) {
+ helper()->StartChild("EchoEcho");
+
+ ScopedMessagePipeHandle mp = CreateMessagePipe(
+ helper()->server_platform_handle.Pass());
+
+ static const size_t kNumMessages = 1001;
+ for (size_t i = 0; i < kNumMessages; i++) {
+ std::string write_buffer(i, 'A' + (i % 26));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp.get().value(), write_buffer.data(),
+ write_buffer.size(), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ }
+
+ const std::string quitquitquit("quitquitquit");
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp.get().value(), quitquitquit.data(),
+ quitquitquit.size(), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ for (size_t i = 0; i < kNumMessages; i++) {
+ HandleSignalsState hss;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ // The child may or may not have closed its end of the message pipe and died
+ // (and we may or may not know it yet), so our end may or may not appear as
+ // writable.
+ ASSERT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ ASSERT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
+
+ std::string read_buffer(kNumMessages * 2, '\0');
+ uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size());
+ ASSERT_EQ(MojoReadMessage(mp.get().value(), &read_buffer[0],
+ &read_buffer_size, nullptr, 0,
+ MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ read_buffer.resize(read_buffer_size);
+
+ ASSERT_EQ(std::string(i * 2, 'A' + (i % 26)), read_buffer);
+ }
+
+ // Wait for it to become readable, which should fail (since we sent
+ // "quitquitquit").
+ HandleSignalsState hss;
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+
+ ASSERT_EQ(static_cast<int>(kNumMessages % 100),
+ helper()->WaitForChildShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(CheckSharedBuffer) {
+ SimplePlatformSupport platform_support;
+ base::MessageLoop message_loop;
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ test::ScopedIPCSupport ipc_support(test_io_thread.task_runner());
+
+ ScopedPlatformHandle client_platform_handle =
+ test::MultiprocessTestHelper::client_platform_handle.Pass();
+ CHECK(client_platform_handle.is_valid());
+ ScopedMessagePipeHandle mp =
+ CreateMessagePipe(client_platform_handle.Pass());
+
+ // Wait for the first message from our parent.
+ HandleSignalsState hss;
+ CHECK_EQ(MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss),
+ MOJO_RESULT_OK);
+ // In this test, the parent definitely doesn't close its end of the message
+ // pipe before we do.
+ CHECK_EQ(hss.satisfied_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ // It should have a shared buffer.
+ std::string read_buffer(100, '\0');
+ uint32_t num_bytes = static_cast<uint32_t>(read_buffer.size());
+ MojoHandle handles[10];
+ uint32_t num_handlers = MOJO_ARRAYSIZE(handles); // Maximum number to receive
+ CHECK_EQ(MojoReadMessage(mp.get().value(), &read_buffer[0],
+ &num_bytes, &handles[0],
+ &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ read_buffer.resize(num_bytes);
+ CHECK_EQ(read_buffer, std::string("go 1"));
+ CHECK_EQ(num_handlers, 1u);
+
+ // Make a mapping.
+ void* buffer;
+ CHECK_EQ(MojoMapBuffer(handles[0], 0, 100, &buffer,
+ MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE),
+ MOJO_RESULT_OK);
+
+ // Write some stuff to the shared buffer.
+ static const char kHello[] = "hello";
+ memcpy(buffer, kHello, sizeof(kHello));
+
+ // We should be able to close the dispatcher now.
+ MojoClose(handles[0]);
+
+ // And send a message to signal that we've written stuff.
+ const std::string go2("go 2");
+ CHECK_EQ(MojoWriteMessage(mp.get().value(), go2.data(),
+ go2.size(), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+
+ // Now wait for our parent to send us a message.
+ hss = HandleSignalsState();
+ CHECK_EQ(MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss),
+ MOJO_RESULT_OK);
+ CHECK_EQ(hss.satisfied_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ read_buffer = std::string(100, '\0');
+ num_bytes = static_cast<uint32_t>(read_buffer.size());
+ CHECK_EQ(MojoReadMessage(mp.get().value(), &read_buffer[0], &num_bytes,
+ nullptr, 0, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ read_buffer.resize(num_bytes);
+ CHECK_EQ(read_buffer, std::string("go 3"));
+
+ // It should have written something to the shared buffer.
+ static const char kWorld[] = "world!!!";
+ CHECK_EQ(memcmp(buffer, kWorld, sizeof(kWorld)), 0);
+
+ // And we're done.
+
+ return 0;
+}
+
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_SharedBufferPassing DISABLED_SharedBufferPassing
+#else
+#define MAYBE_SharedBufferPassing SharedBufferPassing
+#endif
+TEST_F(MultiprocessMessagePipeTest, MAYBE_SharedBufferPassing) {
+ helper()->StartChild("CheckSharedBuffer");
+
+ ScopedMessagePipeHandle mp = CreateMessagePipe(
+ helper()->server_platform_handle.Pass());
+
+ // Make a shared buffer.
+ MojoCreateSharedBufferOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE;
+
+ MojoHandle shared_buffer;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateSharedBuffer(&options, 100, &shared_buffer));
+
+ // Send the shared buffer.
+ const std::string go1("go 1");
+
+ MojoHandle duplicated_shared_buffer;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoDuplicateBufferHandle(
+ shared_buffer,
+ nullptr,
+ &duplicated_shared_buffer));
+ MojoHandle handles[1];
+ handles[0] = duplicated_shared_buffer;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp.get().value(), &go1[0], go1.size(), &handles[0],
+ MOJO_ARRAYSIZE(handles),
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait for a message from the child.
+ HandleSignalsState hss;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
+
+ std::string read_buffer(100, '\0');
+ uint32_t num_bytes = static_cast<uint32_t>(read_buffer.size());
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(mp.get().value(), &read_buffer[0],
+ &num_bytes, nullptr, 0,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ read_buffer.resize(num_bytes);
+ ASSERT_EQ(std::string("go 2"), read_buffer);
+
+ // After we get it, the child should have written something to the shared
+ // buffer.
+ static const char kHello[] = "hello";
+ void* buffer;
+ CHECK_EQ(MojoMapBuffer(shared_buffer, 0, 100, &buffer,
+ MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE),
+ MOJO_RESULT_OK);
+ ASSERT_EQ(0, memcmp(buffer, kHello, sizeof(kHello)));
+
+ // Now we'll write some stuff to the shared buffer.
+ static const char kWorld[] = "world!!!";
+ memcpy(buffer, kWorld, sizeof(kWorld));
+
+ // And send a message to signal that we've written stuff.
+ const std::string go3("go 3");
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp.get().value(), &go3[0],
+ go3.size(), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait for |mp| to become readable, which should fail.
+ hss = HandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+
+ MojoClose(mp.release().value());
+
+ ASSERT_EQ(0, helper()->WaitForChildShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(CheckPlatformHandleFile) {
+ SimplePlatformSupport platform_support;
+ base::MessageLoop message_loop;
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ test::ScopedIPCSupport ipc_support(test_io_thread.task_runner());
+
+ ScopedPlatformHandle client_platform_handle =
+ test::MultiprocessTestHelper::client_platform_handle.Pass();
+ CHECK(client_platform_handle.is_valid());
+ ScopedMessagePipeHandle mp =
+ CreateMessagePipe(client_platform_handle.Pass());
+
+ HandleSignalsState hss;
+ CHECK_EQ(MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss),
+ MOJO_RESULT_OK);
+ CHECK_EQ(hss.satisfied_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ std::string read_buffer(100, '\0');
+ uint32_t num_bytes = static_cast<uint32_t>(read_buffer.size());
+ MojoHandle handles[255]; // Maximum number to receive.
+ uint32_t num_handlers = MOJO_ARRAYSIZE(handles);
+
+ CHECK_EQ(MojoReadMessage(mp.get().value(), &read_buffer[0],
+ &num_bytes, &handles[0],
+ &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ MojoClose(mp.release().value());
+
+ read_buffer.resize(num_bytes);
+ char hello[32];
+ int num_handles = 0;
+ sscanf(read_buffer.c_str(), "%s %d", hello, &num_handles);
+ CHECK_EQ(std::string("hello"), std::string(hello));
+ CHECK_GT(num_handles, 0);
+
+ for (int i = 0; i < num_handles; ++i) {
+ ScopedPlatformHandle h;
+ CHECK_EQ(PassWrappedPlatformHandle(
+ handles[i], &h),
+ MOJO_RESULT_OK);
+ CHECK(h.is_valid());
+ MojoClose(handles[i]);
+
+ base::ScopedFILE fp(test::FILEFromPlatformHandle(h.Pass(), "r"));
+ CHECK(fp);
+ std::string fread_buffer(100, '\0');
+ size_t bytes_read =
+ fread(&fread_buffer[0], 1, fread_buffer.size(), fp.get());
+ fread_buffer.resize(bytes_read);
+ CHECK_EQ(fread_buffer, "world");
+ }
+
+ return 0;
+}
+
+class MultiprocessMessagePipeTestWithPipeCount
+ : public test::MultiprocessMessagePipeTestBase,
+ public testing::WithParamInterface<size_t> {};
+
+TEST_P(MultiprocessMessagePipeTestWithPipeCount, PlatformHandlePassing) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ helper()->StartChild("CheckPlatformHandleFile");
+ ScopedMessagePipeHandle mp = CreateMessagePipe(
+ helper()->server_platform_handle.Pass());
+
+ std::vector<MojoHandle> handles;
+
+ size_t pipe_count = GetParam();
+ for (size_t i = 0; i < pipe_count; ++i) {
+ base::FilePath unused;
+ base::ScopedFILE fp(
+ CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused));
+ const std::string world("world");
+ CHECK_EQ(fwrite(&world[0], 1, world.size(), fp.get()), world.size());
+ fflush(fp.get());
+ rewind(fp.get());
+ MojoHandle handle;
+ ASSERT_EQ(CreatePlatformHandleWrapper(
+ ScopedPlatformHandle(test::PlatformHandleFromFILE(fp.Pass())),
+ &handle),
+ MOJO_RESULT_OK);
+ handles.push_back(handle);
+ }
+
+ char message[128];
+ sprintf(message, "hello %d", static_cast<int>(pipe_count));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp.get().value(), message,
+ strlen(message),
+ &handles[0], handles.size(),
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait for it to become readable, which should fail.
+ HandleSignalsState hss;
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals);
+ ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals);
+
+ MojoClose(mp.release().value());
+
+ ASSERT_EQ(0, helper()->WaitForChildShutdown());
+}
+
+// Android multi-process tests are not executing the new process. This is flaky.
+#if !defined(OS_ANDROID)
+INSTANTIATE_TEST_CASE_P(PipeCount,
+ MultiprocessMessagePipeTestWithPipeCount,
+ testing::Values(1u, 128u, 140u));
+#endif
+
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(CheckMessagePipe) {
+ SimplePlatformSupport platform_support;
+ base::MessageLoop message_loop;
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ test::ScopedIPCSupport ipc_support(test_io_thread.task_runner());
+
+ ScopedPlatformHandle client_platform_handle =
+ test::MultiprocessTestHelper::client_platform_handle.Pass();
+ CHECK(client_platform_handle.is_valid());
+
+ ScopedMessagePipeHandle mp =
+ CreateMessagePipe(client_platform_handle.Pass());
+
+ // Wait for the first message from our parent.
+ HandleSignalsState hss;
+ CHECK_EQ(MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss),
+ MOJO_RESULT_OK);
+ // In this test, the parent definitely doesn't close its end of the message
+ // pipe before we do.
+ CHECK_EQ(hss.satisfied_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ // It should have a message pipe.
+ MojoHandle handles[10];
+ uint32_t num_handlers = MOJO_ARRAYSIZE(handles);
+ CHECK_EQ(MojoReadMessage(mp.get().value(), nullptr,
+ nullptr, &handles[0],
+ &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ CHECK_EQ(num_handlers, 1u);
+
+ // Read data from the received message pipe.
+ CHECK_EQ(MojoWait(handles[0], MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss),
+ MOJO_RESULT_OK);
+ CHECK_EQ(hss.satisfied_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ std::string read_buffer(100, '\0');
+ uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size());
+ CHECK_EQ(MojoReadMessage(handles[0], &read_buffer[0],
+ &read_buffer_size, nullptr,
+ 0, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ read_buffer.resize(read_buffer_size);
+ CHECK_EQ(read_buffer, std::string("hello"));
+
+ // Now write some data into the message pipe.
+ std::string write_buffer = "world";
+ CHECK_EQ(MojoWriteMessage(handles[0], write_buffer.data(),
+ write_buffer.size(),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ MojoClose(handles[0]);
+ return 0;
+}
+
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_MessagePipePassing DISABLED_MessagePipePassing
+#else
+#define MAYBE_MessagePipePassing MessagePipePassing
+#endif
+TEST_F(MultiprocessMessagePipeTest, MAYBE_MessagePipePassing) {
+ helper()->StartChild("CheckMessagePipe");
+
+ ScopedMessagePipeHandle mp =
+ CreateMessagePipe(helper()->server_platform_handle.Pass());
+ MojoCreateSharedBufferOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE;
+
+ MojoHandle mp1, mp2;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateMessagePipe(nullptr, &mp1, &mp2));
+
+ // Write a string into one end of the new message pipe and send the other end.
+ const std::string hello("hello");
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp1, &hello[0], hello.size(), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp.get().value(), nullptr, 0, &mp2, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait for a message from the child.
+ HandleSignalsState hss;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
+
+ std::string read_buffer(100, '\0');
+ uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size());
+ CHECK_EQ(MojoReadMessage(mp1, &read_buffer[0],
+ &read_buffer_size, nullptr,
+ 0, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ read_buffer.resize(read_buffer_size);
+ CHECK_EQ(read_buffer, std::string("world"));
+
+ MojoClose(mp1);
+ MojoClose(mp.release().value());
+
+ ASSERT_EQ(0, helper()->WaitForChildShutdown());
+}
+
+// Like above test, but verifies passing the other MP handle works as well.
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_MessagePipeTwoPassing DISABLED_MessagePipeTwoPassing
+#else
+#define MAYBE_MessagePipeTwoPassing MessagePipeTwoPassing
+#endif
+TEST_F(MultiprocessMessagePipeTest, MAYBE_MessagePipeTwoPassing) {
+ helper()->StartChild("CheckMessagePipe");
+
+ ScopedMessagePipeHandle mp =
+ CreateMessagePipe(helper()->server_platform_handle.Pass());
+
+ MojoHandle mp1, mp2;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateMessagePipe(nullptr, &mp2, &mp1));
+
+ // Write a string into one end of the new message pipe and send the other end.
+ const std::string hello("hello");
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp1, &hello[0], hello.size(), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp.get().value(), nullptr, 0, &mp2, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait for a message from the child.
+ HandleSignalsState hss;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
+
+ std::string read_buffer(100, '\0');
+ uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size());
+ CHECK_EQ(MojoReadMessage(mp1, &read_buffer[0],
+ &read_buffer_size, nullptr,
+ 0, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ read_buffer.resize(read_buffer_size);
+ CHECK_EQ(read_buffer, std::string("world"));
+
+ MojoClose(mp.release().value());
+
+ ASSERT_EQ(0, helper()->WaitForChildShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(DataPipeConsumer) {
+ SimplePlatformSupport platform_support;
+ base::MessageLoop message_loop;
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ test::ScopedIPCSupport ipc_support(test_io_thread.task_runner());
+
+ ScopedPlatformHandle client_platform_handle =
+ test::MultiprocessTestHelper::client_platform_handle.Pass();
+ CHECK(client_platform_handle.is_valid());
+
+ ScopedMessagePipeHandle mp =
+ CreateMessagePipe(client_platform_handle.Pass());
+
+ // Wait for the first message from our parent.
+ HandleSignalsState hss;
+ CHECK_EQ(MojoWait(mp.get().value(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss),
+ MOJO_RESULT_OK);
+ // In this test, the parent definitely doesn't close its end of the message
+ // pipe before we do.
+ CHECK_EQ(hss.satisfied_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ // It should have a message pipe.
+ MojoHandle handles[10];
+ uint32_t num_handlers = MOJO_ARRAYSIZE(handles);
+ CHECK_EQ(MojoReadMessage(mp.get().value(), nullptr,
+ nullptr, &handles[0],
+ &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ CHECK_EQ(num_handlers, 1u);
+
+ // Read data from the received message pipe.
+ CHECK_EQ(MojoWait(handles[0], MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss),
+ MOJO_RESULT_OK);
+ CHECK_EQ(hss.satisfied_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ std::string read_buffer(100, '\0');
+ uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size());
+ CHECK_EQ(MojoReadMessage(handles[0], &read_buffer[0],
+ &read_buffer_size, nullptr,
+ 0, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ read_buffer.resize(read_buffer_size);
+ CHECK_EQ(read_buffer, std::string("hello"));
+
+ // Now write some data into the message pipe.
+ std::string write_buffer = "world";
+ CHECK_EQ(MojoWriteMessage(handles[0], write_buffer.data(),
+ write_buffer.size(),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ MojoClose(handles[0]);
+ return 0;
+}
+
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_DataPipeConsumer DISABLED_DataPipeConsumer
+#else
+#define MAYBE_DataPipeConsumer DataPipeConsumer
+#endif
+TEST_F(MultiprocessMessagePipeTest, MAYBE_DataPipeConsumer) {
+ helper()->StartChild("DataPipeConsumer");
+
+ ScopedMessagePipeHandle mp =
+ CreateMessagePipe(helper()->server_platform_handle.Pass());
+ MojoCreateSharedBufferOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE;
+
+ MojoHandle mp1, mp2;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateMessagePipe(nullptr, &mp2, &mp1));
+
+ // Write a string into one end of the new message pipe and send the other end.
+ const std::string hello("hello");
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp1, &hello[0], hello.size(), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(mp.get().value(), nullptr, 0, &mp2, 1,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
+
+ // Wait for a message from the child.
+ HandleSignalsState hss;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWait(mp1, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, &hss));
+ EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE));
+
+ std::string read_buffer(100, '\0');
+ uint32_t read_buffer_size = static_cast<uint32_t>(read_buffer.size());
+ CHECK_EQ(MojoReadMessage(mp1, &read_buffer[0],
+ &read_buffer_size, nullptr,
+ 0, MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ read_buffer.resize(read_buffer_size);
+ CHECK_EQ(read_buffer, std::string("world"));
+
+ MojoClose(mp1);
+ MojoClose(mp.release().value());
+
+ ASSERT_EQ(0, helper()->WaitForChildShutdown());
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/options_validation.h b/mojo/edk/system/options_validation.h
new file mode 100644
index 0000000..6925075
--- /dev/null
+++ b/mojo/edk/system/options_validation.h
@@ -0,0 +1,97 @@
+// Copyright 2014 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.
+
+// Functions to help with verifying various |Mojo...Options| structs from the
+// (public, C) API. These are "extensible" structs, which all have |struct_size|
+// as their first member. All fields (other than |struct_size|) are optional,
+// but any |flags| specified must be known to the system (otherwise, an error of
+// |MOJO_RESULT_UNIMPLEMENTED| should be returned).
+
+#ifndef MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_
+#define MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+template <class Options>
+class UserOptionsReader {
+ public:
+ // Constructor from a |const* Options| (which it checks -- this constructor
+ // has side effects!).
+ // Note: We initialize |options_reader_| without checking, since we do a check
+ // in |GetSizeForReader()|.
+ explicit UserOptionsReader(const Options* options) {
+ CHECK(options && IsAligned<MOJO_ALIGNOF(Options)>(options));
+ options_ = GetSizeForReader(options) == 0 ? nullptr : options;
+ static_assert(offsetof(Options, struct_size) == 0,
+ "struct_size not first member of Options");
+ // TODO(vtl): Enable when MSVC supports this (C++11 extended sizeof):
+ // static_assert(sizeof(Options::struct_size) == sizeof(uint32_t),
+ // "Options::struct_size not a uint32_t");
+ // (Or maybe assert that its type is uint32_t?)
+ }
+
+ bool is_valid() const { return !!options_; }
+
+ const Options& options() const {
+ DCHECK(is_valid());
+ return *options_;
+ }
+
+ // Checks that the given (variable-size) |options| passed to the constructor
+ // (plausibly) has a member at the given offset with the given size. You
+ // probably want to use |OPTIONS_STRUCT_HAS_MEMBER()| instead.
+ bool HasMember(size_t offset, size_t size) const {
+ DCHECK(is_valid());
+ // We assume that |offset| and |size| are reasonable, since they should come
+ // from |offsetof(Options, some_member)| and |sizeof(Options::some_member)|,
+ // respectively.
+ return options().struct_size >= offset + size;
+ }
+
+ private:
+ static inline size_t GetSizeForReader(const Options* options) {
+ uint32_t struct_size = *reinterpret_cast<const uint32_t*>(options);
+ if (struct_size < sizeof(uint32_t))
+ return 0;
+
+ return std::min(static_cast<size_t>(struct_size), sizeof(Options));
+ }
+
+ template <size_t alignment>
+ static bool IsAligned(const void* pointer) {
+ return reinterpret_cast<uintptr_t>(pointer) % alignment == 0;
+ }
+
+ const Options* options_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(UserOptionsReader);
+};
+
+// Macro to invoke |UserOptionsReader<Options>::HasMember()| parametrized by
+// member name instead of offset and size.
+//
+// (We can't just give |HasMember()| a member pointer template argument instead,
+// since there's no good/strictly-correct way to get an offset from that.)
+//
+// TODO(vtl): With C++11, use |sizeof(Options::member)| instead of (the
+// contortion below). We might also be able to pull out the type |Options| from
+// |reader| (using |decltype|) instead of requiring a parameter.
+#define OPTIONS_STRUCT_HAS_MEMBER(Options, member, reader) \
+ reader.HasMember(offsetof(Options, member), sizeof(reader.options().member))
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_
diff --git a/mojo/edk/system/options_validation_unittest.cc b/mojo/edk/system/options_validation_unittest.cc
new file mode 100644
index 0000000..d2c8180
--- /dev/null
+++ b/mojo/edk/system/options_validation_unittest.cc
@@ -0,0 +1,130 @@
+// Copyright 2014 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 "mojo/edk/system/options_validation.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "mojo/public/c/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+// Declare a test options struct just as we do in actual public headers.
+
+using TestOptionsFlags = uint32_t;
+
+static_assert(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
+struct MOJO_ALIGNAS(8) TestOptions {
+ uint32_t struct_size;
+ TestOptionsFlags flags;
+ uint32_t member1;
+ uint32_t member2;
+};
+static_assert(sizeof(TestOptions) == 16, "TestOptions has wrong size");
+
+const uint32_t kSizeOfTestOptions = static_cast<uint32_t>(sizeof(TestOptions));
+
+TEST(OptionsValidationTest, Valid) {
+ {
+ const TestOptions kOptions = {kSizeOfTestOptions};
+ UserOptionsReader<TestOptions> reader(&kOptions);
+ EXPECT_TRUE(reader.is_valid());
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
+ }
+ {
+ const TestOptions kOptions = {static_cast<uint32_t>(
+ offsetof(TestOptions, struct_size) + sizeof(uint32_t))};
+ UserOptionsReader<TestOptions> reader(&kOptions);
+ EXPECT_TRUE(reader.is_valid());
+ EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
+ EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
+ EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
+ }
+
+ {
+ const TestOptions kOptions = {
+ static_cast<uint32_t>(offsetof(TestOptions, flags) + sizeof(uint32_t))};
+ UserOptionsReader<TestOptions> reader(&kOptions);
+ EXPECT_TRUE(reader.is_valid());
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
+ EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
+ EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
+ }
+ {
+ MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {};
+ TestOptions* options = reinterpret_cast<TestOptions*>(buf);
+ options->struct_size = kSizeOfTestOptions + 1;
+ UserOptionsReader<TestOptions> reader(options);
+ EXPECT_TRUE(reader.is_valid());
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
+ }
+ {
+ MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {};
+ TestOptions* options = reinterpret_cast<TestOptions*>(buf);
+ options->struct_size = kSizeOfTestOptions + 4;
+ UserOptionsReader<TestOptions> reader(options);
+ EXPECT_TRUE(reader.is_valid());
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader));
+ EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader));
+ }
+}
+
+TEST(OptionsValidationTest, Invalid) {
+ // Size too small:
+ for (size_t i = 0; i < sizeof(uint32_t); i++) {
+ TestOptions options = {static_cast<uint32_t>(i)};
+ UserOptionsReader<TestOptions> reader(&options);
+ EXPECT_FALSE(reader.is_valid()) << i;
+ }
+}
+
+// These test invalid arguments that should cause death if we're being paranoid
+// about checking arguments (which we would want to do if, e.g., we were in a
+// true "kernel" situation, but we might not want to do otherwise for
+// performance reasons). Probably blatant errors like passing in null pointers
+// (for required pointer arguments) will still cause death, but perhaps not
+// predictably.
+TEST(OptionsValidationTest, InvalidDeath) {
+ const char kMemoryCheckFailedRegex[] = "Check failed";
+
+ // Null:
+ EXPECT_DEATH_IF_SUPPORTED(
+ { UserOptionsReader<TestOptions> reader((nullptr)); },
+ kMemoryCheckFailedRegex);
+
+ // Unaligned:
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ UserOptionsReader<TestOptions> reader(
+ reinterpret_cast<const TestOptions*>(1));
+ },
+ kMemoryCheckFailedRegex);
+ // Note: The current implementation checks the size only after checking the
+ // alignment versus that required for the |uint32_t| size, so it won't die in
+ // the expected way if you pass, e.g., 4. So we have to manufacture a valid
+ // pointer at an offset of alignment 4.
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ uint32_t buffer[100] = {};
+ TestOptions* options = (reinterpret_cast<uintptr_t>(buffer) % 8 == 0)
+ ? reinterpret_cast<TestOptions*>(&buffer[1])
+ : reinterpret_cast<TestOptions*>(&buffer[0]);
+ options->struct_size = static_cast<uint32_t>(sizeof(TestOptions));
+ UserOptionsReader<TestOptions> reader(options);
+ },
+ kMemoryCheckFailedRegex);
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/platform_handle_dispatcher.cc b/mojo/edk/system/platform_handle_dispatcher.cc
new file mode 100644
index 0000000..f788626
--- /dev/null
+++ b/mojo/edk/system/platform_handle_dispatcher.cc
@@ -0,0 +1,113 @@
+// Copyright 2014 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 "mojo/edk/system/platform_handle_dispatcher.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+const size_t kInvalidPlatformHandleIndex = static_cast<size_t>(-1);
+
+struct MOJO_ALIGNAS(8) SerializedPlatformHandleDispatcher {
+ size_t platform_handle_index; // (Or |kInvalidPlatformHandleIndex|.)
+};
+
+} // namespace
+
+ScopedPlatformHandle PlatformHandleDispatcher::PassPlatformHandle() {
+ base::AutoLock locker(lock());
+ return platform_handle_.Pass();
+}
+
+Dispatcher::Type PlatformHandleDispatcher::GetType() const {
+ return Type::PLATFORM_HANDLE;
+}
+
+// static
+scoped_refptr<PlatformHandleDispatcher> PlatformHandleDispatcher::Deserialize(
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles) {
+ if (size != sizeof(SerializedPlatformHandleDispatcher)) {
+ LOG(ERROR) << "Invalid serialized platform handle dispatcher (bad size)";
+ return nullptr;
+ }
+
+ const SerializedPlatformHandleDispatcher* serialization =
+ static_cast<const SerializedPlatformHandleDispatcher*>(source);
+ size_t platform_handle_index = serialization->platform_handle_index;
+
+ // Starts off invalid, which is what we want.
+ PlatformHandle platform_handle;
+
+ if (platform_handle_index != kInvalidPlatformHandleIndex) {
+ if (!platform_handles ||
+ platform_handle_index >= platform_handles->size()) {
+ LOG(ERROR)
+ << "Invalid serialized platform handle dispatcher (missing handles)";
+ return nullptr;
+ }
+
+ // We take ownership of the handle, so we have to invalidate the one in
+ // |platform_handles|.
+ std::swap(platform_handle, (*platform_handles)[platform_handle_index]);
+ }
+
+ return Create(ScopedPlatformHandle(platform_handle));
+}
+
+PlatformHandleDispatcher::PlatformHandleDispatcher(
+ ScopedPlatformHandle platform_handle)
+ : platform_handle_(platform_handle.Pass()) {
+}
+
+PlatformHandleDispatcher::~PlatformHandleDispatcher() {
+}
+
+void PlatformHandleDispatcher::CloseImplNoLock() {
+ lock().AssertAcquired();
+ platform_handle_.reset();
+}
+
+scoped_refptr<Dispatcher>
+PlatformHandleDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() {
+ lock().AssertAcquired();
+ return Create(platform_handle_.Pass());
+}
+
+void PlatformHandleDispatcher::StartSerializeImplNoLock(
+ size_t* max_size,
+ size_t* max_platform_handles) {
+ DCHECK(HasOneRef()); // Only one ref => no need to take the lock.
+ *max_size = sizeof(SerializedPlatformHandleDispatcher);
+ *max_platform_handles = 1;
+}
+
+bool PlatformHandleDispatcher::EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) {
+ DCHECK(HasOneRef()); // Only one ref => no need to take the lock.
+
+ SerializedPlatformHandleDispatcher* serialization =
+ static_cast<SerializedPlatformHandleDispatcher*>(destination);
+ if (platform_handle_.is_valid()) {
+ serialization->platform_handle_index = platform_handles->size();
+ platform_handles->push_back(platform_handle_.release());
+ } else {
+ serialization->platform_handle_index = kInvalidPlatformHandleIndex;
+ }
+
+ *actual_size = sizeof(SerializedPlatformHandleDispatcher);
+ return true;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/platform_handle_dispatcher.h b/mojo/edk/system/platform_handle_dispatcher.h
new file mode 100644
index 0000000..99ffcac
--- /dev/null
+++ b/mojo/edk/system/platform_handle_dispatcher.h
@@ -0,0 +1,63 @@
+// Copyright 2014 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 MOJO_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_
+
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/simple_dispatcher.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// A dispatcher that simply wraps/transports a |PlatformHandle| (only for use by
+// the embedder).
+class MOJO_SYSTEM_IMPL_EXPORT PlatformHandleDispatcher final
+ : public SimpleDispatcher {
+ public:
+ static scoped_refptr<PlatformHandleDispatcher> Create(
+ ScopedPlatformHandle platform_handle) {
+ return make_scoped_refptr(
+ new PlatformHandleDispatcher(platform_handle.Pass()));
+ }
+
+ ScopedPlatformHandle PassPlatformHandle();
+
+ // |Dispatcher| public methods:
+ Type GetType() const override;
+
+ // The "opposite" of |SerializeAndClose()|. (Typically this is called by
+ // |Dispatcher::Deserialize()|.)
+ static scoped_refptr<PlatformHandleDispatcher> Deserialize(
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles);
+
+ private:
+ explicit PlatformHandleDispatcher(
+ ScopedPlatformHandle platform_handle);
+ ~PlatformHandleDispatcher() override;
+
+ // |Dispatcher| protected methods:
+ void CloseImplNoLock() override;
+ scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndCloseImplNoLock()
+ override;
+ void StartSerializeImplNoLock(size_t* max_size,
+ size_t* max_platform_handles) override;
+ bool EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) override;
+
+ ScopedPlatformHandle platform_handle_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(PlatformHandleDispatcher);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_
diff --git a/mojo/edk/system/platform_handle_dispatcher_unittest.cc b/mojo/edk/system/platform_handle_dispatcher_unittest.cc
new file mode 100644
index 0000000..77f4761
--- /dev/null
+++ b/mojo/edk/system/platform_handle_dispatcher_unittest.cc
@@ -0,0 +1,110 @@
+// Copyright 2014 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 "mojo/edk/system/platform_handle_dispatcher.h"
+
+#include <stdio.h>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+TEST(PlatformHandleDispatcherTest, Basic) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ static const char kHelloWorld[] = "hello world";
+
+ base::FilePath unused;
+ base::ScopedFILE fp(
+ CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused));
+ ASSERT_TRUE(fp);
+ EXPECT_EQ(sizeof(kHelloWorld),
+ fwrite(kHelloWorld, 1, sizeof(kHelloWorld), fp.get()));
+
+ ScopedPlatformHandle h(test::PlatformHandleFromFILE(fp.Pass()));
+ EXPECT_FALSE(fp);
+ ASSERT_TRUE(h.is_valid());
+
+ scoped_refptr<PlatformHandleDispatcher> dispatcher =
+ PlatformHandleDispatcher::Create(h.Pass());
+ EXPECT_FALSE(h.is_valid());
+ EXPECT_EQ(Dispatcher::Type::PLATFORM_HANDLE, dispatcher->GetType());
+
+ h = dispatcher->PassPlatformHandle().Pass();
+ EXPECT_TRUE(h.is_valid());
+
+ fp = test::FILEFromPlatformHandle(h.Pass(), "rb").Pass();
+ EXPECT_FALSE(h.is_valid());
+ EXPECT_TRUE(fp);
+
+ rewind(fp.get());
+ char read_buffer[1000] = {};
+ EXPECT_EQ(sizeof(kHelloWorld),
+ fread(read_buffer, 1, sizeof(read_buffer), fp.get()));
+ EXPECT_STREQ(kHelloWorld, read_buffer);
+
+ // Try getting the handle again. (It should fail cleanly.)
+ h = dispatcher->PassPlatformHandle().Pass();
+ EXPECT_FALSE(h.is_valid());
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close());
+}
+
+TEST(PlatformHandleDispatcherTest, CreateEquivalentDispatcherAndClose) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ static const char kFooBar[] = "foo bar";
+
+ base::FilePath unused;
+ base::ScopedFILE fp(
+ CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused));
+ EXPECT_EQ(sizeof(kFooBar), fwrite(kFooBar, 1, sizeof(kFooBar), fp.get()));
+
+ scoped_refptr<PlatformHandleDispatcher> dispatcher =
+ PlatformHandleDispatcher::Create(
+ test::PlatformHandleFromFILE(fp.Pass()));
+
+ DispatcherTransport transport(
+ test::DispatcherTryStartTransport(dispatcher.get()));
+ EXPECT_TRUE(transport.is_valid());
+ EXPECT_EQ(Dispatcher::Type::PLATFORM_HANDLE, transport.GetType());
+ EXPECT_FALSE(transport.IsBusy());
+
+ scoped_refptr<Dispatcher> generic_dispatcher =
+ transport.CreateEquivalentDispatcherAndClose();
+ ASSERT_TRUE(generic_dispatcher);
+
+ transport.End();
+ EXPECT_TRUE(dispatcher->HasOneRef());
+ dispatcher = nullptr;
+
+ ASSERT_EQ(Dispatcher::Type::PLATFORM_HANDLE, generic_dispatcher->GetType());
+ dispatcher = static_cast<PlatformHandleDispatcher*>(generic_dispatcher.get());
+
+ fp = test::FILEFromPlatformHandle(dispatcher->PassPlatformHandle(),
+ "rb").Pass();
+ EXPECT_TRUE(fp);
+
+ rewind(fp.get());
+ char read_buffer[1000] = {};
+ EXPECT_EQ(sizeof(kFooBar),
+ fread(read_buffer, 1, sizeof(read_buffer), fp.get()));
+ EXPECT_STREQ(kFooBar, read_buffer);
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close());
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/raw_channel.cc b/mojo/edk/system/raw_channel.cc
new file mode 100644
index 0000000..acada52
--- /dev/null
+++ b/mojo/edk/system/raw_channel.cc
@@ -0,0 +1,631 @@
+// Copyright 2014 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 "mojo/edk/system/raw_channel.h"
+
+#include <string.h>
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/transport_data.h"
+
+namespace mojo {
+namespace edk {
+
+const size_t kReadSize = 4096;
+
+// RawChannel::ReadBuffer ------------------------------------------------------
+
+RawChannel::ReadBuffer::ReadBuffer() : buffer_(kReadSize), num_valid_bytes_(0) {
+}
+
+RawChannel::ReadBuffer::~ReadBuffer() {
+}
+
+void RawChannel::ReadBuffer::GetBuffer(char** addr, size_t* size) {
+ DCHECK_GE(buffer_.size(), num_valid_bytes_ + kReadSize);
+ *addr = &buffer_[0] + num_valid_bytes_;
+ *size = kReadSize;
+}
+
+// RawChannel::WriteBuffer -----------------------------------------------------
+
+RawChannel::WriteBuffer::WriteBuffer()
+ : serialized_platform_handle_size_(0),
+ platform_handles_offset_(0),
+ data_offset_(0) {
+}
+
+RawChannel::WriteBuffer::~WriteBuffer() {
+ message_queue_.Clear();
+}
+
+bool RawChannel::WriteBuffer::HavePlatformHandlesToSend() const {
+ if (message_queue_.IsEmpty())
+ return false;
+
+ const TransportData* transport_data =
+ message_queue_.PeekMessage()->transport_data();
+ if (!transport_data)
+ return false;
+
+ const PlatformHandleVector* all_platform_handles =
+ transport_data->platform_handles();
+ if (!all_platform_handles) {
+ DCHECK_EQ(platform_handles_offset_, 0u);
+ return false;
+ }
+ if (platform_handles_offset_ >= all_platform_handles->size()) {
+ DCHECK_EQ(platform_handles_offset_, all_platform_handles->size());
+ return false;
+ }
+
+ return true;
+}
+
+void RawChannel::WriteBuffer::GetPlatformHandlesToSend(
+ size_t* num_platform_handles,
+ PlatformHandle** platform_handles,
+ void** serialization_data) {
+ DCHECK(HavePlatformHandlesToSend());
+
+ MessageInTransit* message = message_queue_.PeekMessage();
+ TransportData* transport_data = message->transport_data();
+ PlatformHandleVector* all_platform_handles =
+ transport_data->platform_handles();
+ *num_platform_handles =
+ all_platform_handles->size() - platform_handles_offset_;
+ *platform_handles = &(*all_platform_handles)[platform_handles_offset_];
+
+ if (serialized_platform_handle_size_ > 0) {
+ size_t serialization_data_offset =
+ transport_data->platform_handle_table_offset();
+ serialization_data_offset +=
+ platform_handles_offset_ * serialized_platform_handle_size_;
+ *serialization_data = static_cast<char*>(transport_data->buffer()) +
+ serialization_data_offset;
+ } else {
+ *serialization_data = nullptr;
+ }
+}
+
+void RawChannel::WriteBuffer::GetBuffers(std::vector<Buffer>* buffers) const {
+ buffers->clear();
+
+ if (message_queue_.IsEmpty())
+ return;
+
+ const MessageInTransit* message = message_queue_.PeekMessage();
+ DCHECK_LT(data_offset_, message->total_size());
+ size_t bytes_to_write = message->total_size() - data_offset_;
+
+ size_t transport_data_buffer_size =
+ message->transport_data() ? message->transport_data()->buffer_size() : 0;
+
+ if (!transport_data_buffer_size) {
+ // Only write from the main buffer.
+ DCHECK_LT(data_offset_, message->main_buffer_size());
+ DCHECK_LE(bytes_to_write, message->main_buffer_size());
+ Buffer buffer = {
+ static_cast<const char*>(message->main_buffer()) + data_offset_,
+ bytes_to_write};
+
+ buffers->push_back(buffer);
+ return;
+ }
+
+ if (data_offset_ >= message->main_buffer_size()) {
+ // Only write from the transport data buffer.
+ DCHECK_LT(data_offset_ - message->main_buffer_size(),
+ transport_data_buffer_size);
+ DCHECK_LE(bytes_to_write, transport_data_buffer_size);
+ Buffer buffer = {
+ static_cast<const char*>(message->transport_data()->buffer()) +
+ (data_offset_ - message->main_buffer_size()),
+ bytes_to_write};
+
+ buffers->push_back(buffer);
+ return;
+ }
+
+ // TODO(vtl): We could actually send out buffers from multiple messages, with
+ // the "stopping" condition being reaching a message with platform handles
+ // attached.
+
+ // Write from both buffers.
+ DCHECK_EQ(bytes_to_write, message->main_buffer_size() - data_offset_ +
+ transport_data_buffer_size);
+ Buffer buffer1 = {
+ static_cast<const char*>(message->main_buffer()) + data_offset_,
+ message->main_buffer_size() - data_offset_};
+ buffers->push_back(buffer1);
+ Buffer buffer2 = {
+ static_cast<const char*>(message->transport_data()->buffer()),
+ transport_data_buffer_size};
+ buffers->push_back(buffer2);
+}
+
+// RawChannel ------------------------------------------------------------------
+
+RawChannel::RawChannel()
+ : message_loop_for_io_(nullptr),
+ delegate_(nullptr),
+ write_ready_(false),
+ write_stopped_(false),
+ error_occurred_(false),
+ weak_ptr_factory_(this) {
+ read_buffer_.reset(new ReadBuffer);
+ write_buffer_.reset(new WriteBuffer());
+}
+
+RawChannel::~RawChannel() {
+ DCHECK(!read_buffer_);
+ DCHECK(!write_buffer_);
+
+ // Only want to decrement counter if Init was called.
+ if (message_loop_for_io_) {
+ // No need to take the |write_lock_| here -- if there are still weak
+ // pointers outstanding, then we're hosed anyway (since we wouldn't be able
+ // to invalidate them cleanly, since we might not be on the I/O thread).
+ // DCHECK(!weak_ptr_factory_.HasWeakPtrs());
+ internal::ChannelShutdown();
+ }
+}
+
+void RawChannel::Init(Delegate* delegate) {
+ internal::ChannelStarted();
+ DCHECK(delegate);
+
+ base::AutoLock read_locker(read_lock_);
+ // solves race where initialiing on io thread while main thread is serializing
+ // this channel and releases handle.
+ base::AutoLock locker(write_lock_);
+
+ DCHECK(!delegate_);
+ delegate_ = delegate;
+
+ DCHECK(!message_loop_for_io_);
+ message_loop_for_io_ =
+ static_cast<base::MessageLoopForIO*>(base::MessageLoop::current());
+
+ OnInit();
+
+ if (read_buffer_->num_valid_bytes()) {
+ // We had serialized read buffer data through SetInitialReadBufferData call.
+ // Make sure we read messages out of it now, otherwise the delegate won't
+ // get notified if no other data gets written to the pipe.
+ // Although this means that we can call back synchronously into the caller,
+ // that's easier than posting a task to do this. That is because if we post
+ // a task, a pending read could have started and we wouldn't be able to move
+ // the read buffer since it can be in use by the OS in an async operation.
+ bool did_dispatch_message = false;
+ bool stop_dispatching = false;
+ DispatchMessages(&did_dispatch_message, &stop_dispatching);
+ }
+
+ IOResult io_result = ScheduleRead();
+ if (io_result != IO_PENDING) {
+ // This will notify the delegate about the read failure. Although we're on
+ // the I/O thread, don't call it in the nested context.
+ message_loop_for_io_->PostTask(
+ FROM_HERE, base::Bind(&RawChannel::OnReadCompleted,
+ weak_ptr_factory_.GetWeakPtr(), io_result, 0));
+ }
+ // Note: |ScheduleRead()| failure is treated as a read failure (by notifying
+ // the delegate), not an initialization failure.
+
+ write_ready_ = true;
+ write_buffer_->serialized_platform_handle_size_ =
+ GetSerializedPlatformHandleSize();
+ if (!write_buffer_->message_queue_.IsEmpty())
+ SendQueuedMessagesNoLock();
+}
+
+void RawChannel::Shutdown() {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+
+ // Normally, we want to flush any pending writes before shutting down. This
+ // doesn't apply when 1) we don't have a handle (for obvious reasons) or
+ // 2) when the other side already quit and asked us to close the handle to
+ // ensure that we read everything out of the pipe first.
+ if (!HandleForDebuggingNoLock().is_valid() || error_occurred_) {
+ {
+ base::AutoLock read_locker(read_lock_);
+ base::AutoLock locker(write_lock_);
+ OnShutdownNoLock(read_buffer_.Pass(), write_buffer_.Pass());
+ }
+ delete this;
+ return;
+ }
+
+ base::AutoLock read_locker(read_lock_);
+ base::AutoLock locker(write_lock_);
+ DCHECK(read_buffer_->num_valid_bytes() == 0) <<
+ "RawChannel::Shutdown called but there is pending data to be read";
+
+ // happens on shutdown if didn't call init when doing createduplicate
+ if (message_loop_for_io()) {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_);
+ }
+
+ // Reset the delegate so that it won't receive further calls.
+ delegate_ = nullptr;
+
+ bool empty = write_buffer_->message_queue_.IsEmpty();
+
+ // We may have no messages to write. However just because our end of the pipe
+ // wrote everything doesn't mean that the other end read it. We don't want to
+ // call FlushFileBuffers since a) that only works for server end of the pipe,
+ // and b) it pauses this thread (which can block a process on another, or
+ // worse hang if both pipes are in the same process).
+ scoped_ptr<MessageInTransit> quit_message(new MessageInTransit(
+ MessageInTransit::Type::RAW_CHANNEL_QUIT, 0, nullptr));
+ EnqueueMessageNoLock(quit_message.Pass());
+ write_stopped_ = true;
+
+ if (empty)
+ SendQueuedMessagesNoLock();
+}
+
+ScopedPlatformHandle RawChannel::ReleaseHandle(
+ std::vector<char>* read_buffer) {
+ ScopedPlatformHandle rv;
+ {
+ base::AutoLock read_locker(read_lock_);
+ base::AutoLock locker(write_lock_);
+ rv = ReleaseHandleNoLock(read_buffer);
+
+ // TODO(jam); if we use these, use nolock versions of these methods that are
+ // copied.
+ if (!write_buffer_->message_queue_.IsEmpty()) {
+ NOTREACHED() << "TODO(JAM)";
+ }
+
+ delegate_ = nullptr;
+
+ // The Unretained is safe because above cancelled IO so we shouldn't get any
+ // channel errors.
+ // |message_loop_for_io_| might not be set yet
+ internal::g_io_thread_task_runner->PostTask(
+ FROM_HERE,
+ base::Bind(&RawChannel::Shutdown, base::Unretained(this)));
+ }
+
+ return rv;
+}
+
+// Reminder: This must be thread-safe.
+bool RawChannel::WriteMessage(scoped_ptr<MessageInTransit> message) {
+ DCHECK(message);
+ base::AutoLock locker(write_lock_);
+ if (write_stopped_)
+ return false;
+
+ bool queue_was_empty = write_buffer_->message_queue_.IsEmpty();
+ EnqueueMessageNoLock(message.Pass());
+ if (queue_was_empty && write_ready_)
+ return SendQueuedMessagesNoLock();
+
+ return true;
+}
+
+bool RawChannel::SendQueuedMessagesNoLock() {
+ DCHECK_EQ(write_buffer_->data_offset_, 0u);
+
+ size_t platform_handles_written = 0;
+ size_t bytes_written = 0;
+ IOResult io_result = WriteNoLock(&platform_handles_written, &bytes_written);
+ if (io_result == IO_PENDING)
+ return true;
+
+ bool result = OnWriteCompletedNoLock(io_result, platform_handles_written,
+ bytes_written);
+ if (!result) {
+ // Even if we're on the I/O thread, don't call |OnError()| in the nested
+ // context.
+ message_loop_for_io_->PostTask(
+ FROM_HERE,
+ base::Bind(&RawChannel::LockAndCallOnError,
+ weak_ptr_factory_.GetWeakPtr(),
+ Delegate::ERROR_WRITE));
+ }
+
+ return result;
+}
+
+// Reminder: This must be thread-safe.
+bool RawChannel::IsWriteBufferEmpty() {
+ base::AutoLock locker(write_lock_);
+ return write_buffer_->message_queue_.IsEmpty();
+}
+
+bool RawChannel::IsReadBufferEmpty() {
+ base::AutoLock locker(read_lock_);
+ return read_buffer_->num_valid_bytes_ != 0;
+}
+
+void RawChannel::SetInitialReadBufferData(char* data, size_t size) {
+ base::AutoLock locker(read_lock_);
+ // TODO(jam): copy power of 2 algorithm below? or share.
+ read_buffer_->buffer_.resize(size+kReadSize);
+ memcpy(&read_buffer_->buffer_[0], data, size);
+ read_buffer_->num_valid_bytes_ = size;
+}
+
+void RawChannel::OnReadCompleted(IOResult io_result, size_t bytes_read) {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_);
+
+ base::AutoLock locker(read_lock_);
+
+ // Keep reading data in a loop, and dispatch messages if enough data is
+ // received. Exit the loop if any of the following happens:
+ // - one or more messages were dispatched;
+ // - the last read failed, was a partial read or would block;
+ // - |Shutdown()| was called.
+ do {
+ switch (io_result) {
+ case IO_SUCCEEDED:
+ break;
+ case IO_FAILED_SHUTDOWN:
+ case IO_FAILED_BROKEN:
+ case IO_FAILED_UNKNOWN:
+ CallOnError(ReadIOResultToError(io_result));
+ return; // |this| may have been destroyed in |CallOnError()|.
+ case IO_PENDING:
+ NOTREACHED();
+ return;
+ }
+
+ read_buffer_->num_valid_bytes_ += bytes_read;
+
+ // Dispatch all the messages that we can.
+ bool did_dispatch_message = false;
+ bool stop_dispatching = false;
+ DispatchMessages(&did_dispatch_message, &stop_dispatching);
+ if (stop_dispatching)
+ return;
+
+ if (read_buffer_->buffer_.size() - read_buffer_->num_valid_bytes_ <
+ kReadSize) {
+ // Use power-of-2 buffer sizes.
+ // TODO(vtl): Make sure the buffer doesn't get too large (and enforce the
+ // maximum message size to whatever extent necessary).
+ // TODO(vtl): We may often be able to peek at the header and get the real
+ // required extra space (which may be much bigger than |kReadSize|).
+ size_t new_size = std::max(read_buffer_->buffer_.size(), kReadSize);
+ while (new_size < read_buffer_->num_valid_bytes_ + kReadSize)
+ new_size *= 2;
+
+ // TODO(vtl): It's suboptimal to zero out the fresh memory.
+ read_buffer_->buffer_.resize(new_size, 0);
+ }
+
+ // (1) If we dispatched any messages, stop reading for now (and let the
+ // message loop do its thing for another round).
+ // TODO(vtl): Is this the behavior we want? (Alternatives: i. Dispatch only
+ // a single message. Risks: slower, more complex if we want to avoid lots of
+ // copying. ii. Keep reading until there's no more data and dispatch all the
+ // messages we can. Risks: starvation of other users of the message loop.)
+ // (2) If we didn't max out |kReadSize|, stop reading for now.
+ bool schedule_for_later = did_dispatch_message || bytes_read < kReadSize;
+ bytes_read = 0;
+ io_result = schedule_for_later ? ScheduleRead() : Read(&bytes_read);
+ } while (io_result != IO_PENDING);
+}
+
+void RawChannel::OnWriteCompleted(IOResult io_result,
+ size_t platform_handles_written,
+ size_t bytes_written) {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_);
+ DCHECK_NE(io_result, IO_PENDING);
+
+ bool did_fail = false;
+ {
+ base::AutoLock locker(write_lock_);
+ did_fail = !OnWriteCompletedNoLock(io_result, platform_handles_written,
+ bytes_written);
+ }
+
+ if (did_fail) {
+ LockAndCallOnError(Delegate::ERROR_WRITE);
+ return; // |this| may have been destroyed in |CallOnError()|.
+ }
+}
+
+void RawChannel::EnqueueMessageNoLock(scoped_ptr<MessageInTransit> message) {
+ write_lock_.AssertAcquired();
+ DCHECK(HandleForDebuggingNoLock().is_valid());
+ write_buffer_->message_queue_.AddMessage(message.Pass());
+}
+
+bool RawChannel::OnReadMessageForRawChannel(
+ const MessageInTransit::View& message_view) {
+ if (message_view.type() == MessageInTransit::Type::RAW_CHANNEL_QUIT) {
+ message_loop_for_io_->PostTask(
+ FROM_HERE, base::Bind(&RawChannel::LockAndCallOnError,
+ weak_ptr_factory_.GetWeakPtr(),
+ Delegate::ERROR_READ_SHUTDOWN));
+ return true;
+ }
+
+ // No non-implementation specific |RawChannel| control messages.
+ LOG(ERROR) << "Invalid control message (type " << message_view.type()
+ << ")";
+ return false;
+}
+
+RawChannel::Delegate::Error RawChannel::ReadIOResultToError(
+ IOResult io_result) {
+ switch (io_result) {
+ case IO_FAILED_SHUTDOWN:
+ return Delegate::ERROR_READ_SHUTDOWN;
+ case IO_FAILED_BROKEN:
+ return Delegate::ERROR_READ_BROKEN;
+ case IO_FAILED_UNKNOWN:
+ return Delegate::ERROR_READ_UNKNOWN;
+ case IO_SUCCEEDED:
+ case IO_PENDING:
+ NOTREACHED();
+ break;
+ }
+ return Delegate::ERROR_READ_UNKNOWN;
+}
+
+void RawChannel::CallOnError(Delegate::Error error) {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io_);
+ read_lock_.AssertAcquired();
+ error_occurred_ = true;
+ if (delegate_) {
+ delegate_->OnError(error);
+ } else {
+ // We depend on delegate to delete since it could be waiting to call
+ // ReleaseHandle.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&RawChannel::Shutdown, weak_ptr_factory_.GetWeakPtr()));
+ }
+}
+
+void RawChannel::LockAndCallOnError(Delegate::Error error) {
+ base::AutoLock locker(read_lock_);
+ CallOnError(error);
+}
+
+bool RawChannel::OnWriteCompletedNoLock(IOResult io_result,
+ size_t platform_handles_written,
+ size_t bytes_written) {
+ write_lock_.AssertAcquired();
+
+ DCHECK(!write_buffer_->message_queue_.IsEmpty());
+
+ if (io_result == IO_SUCCEEDED) {
+ write_buffer_->platform_handles_offset_ += platform_handles_written;
+ write_buffer_->data_offset_ += bytes_written;
+
+ MessageInTransit* message = write_buffer_->message_queue_.PeekMessage();
+ if (write_buffer_->data_offset_ >= message->total_size()) {
+ // Complete write.
+ CHECK_EQ(write_buffer_->data_offset_, message->total_size());
+ write_buffer_->message_queue_.DiscardMessage();
+ write_buffer_->platform_handles_offset_ = 0;
+ write_buffer_->data_offset_ = 0;
+
+ if (write_buffer_->message_queue_.IsEmpty())
+ return true;
+ }
+
+ // Schedule the next write.
+ io_result = ScheduleWriteNoLock();
+ if (io_result == IO_PENDING)
+ return true;
+ DCHECK_NE(io_result, IO_SUCCEEDED);
+ }
+
+ write_stopped_ = true;
+ write_buffer_->message_queue_.Clear();
+ write_buffer_->platform_handles_offset_ = 0;
+ write_buffer_->data_offset_ = 0;
+ return false;
+}
+
+void RawChannel::DispatchMessages(bool* did_dispatch_message,
+ bool* stop_dispatching) {
+ *did_dispatch_message = false;
+ *stop_dispatching = false;
+ // Tracks the offset of the first undispatched message in |read_buffer_|.
+ // Currently, we copy data to ensure that this is zero at the beginning.
+ size_t read_buffer_start = 0;
+ size_t remaining_bytes = read_buffer_->num_valid_bytes_;
+ size_t message_size;
+ // Note that we rely on short-circuit evaluation here:
+ // - |read_buffer_start| may be an invalid index into
+ // |read_buffer_->buffer_| if |remaining_bytes| is zero.
+ // - |message_size| is only valid if |GetNextMessageSize()| returns true.
+ // TODO(vtl): Use |message_size| more intelligently (e.g., to request the
+ // next read).
+ // TODO(vtl): Validate that |message_size| is sane.
+ while (remaining_bytes > 0 && MessageInTransit::GetNextMessageSize(
+ &read_buffer_->buffer_[read_buffer_start],
+ remaining_bytes, &message_size) &&
+ remaining_bytes >= message_size) {
+ MessageInTransit::View message_view(
+ message_size, &read_buffer_->buffer_[read_buffer_start]);
+ DCHECK_EQ(message_view.total_size(), message_size);
+
+ const char* error_message = nullptr;
+ if (!message_view.IsValid(GetSerializedPlatformHandleSize(),
+ &error_message)) {
+ DCHECK(error_message);
+ LOG(ERROR) << "Received invalid message: " << error_message;
+ CallOnError(Delegate::ERROR_READ_BAD_MESSAGE);
+ *stop_dispatching = true;
+ return; // |this| may have been destroyed in |CallOnError()|.
+ }
+
+ if (message_view.type() != MessageInTransit::Type::MESSAGE) {
+ if (!OnReadMessageForRawChannel(message_view)) {
+ CallOnError(Delegate::ERROR_READ_BAD_MESSAGE);
+ *stop_dispatching = true;
+ return; // |this| may have been destroyed in |CallOnError()|.
+ }
+ } else {
+ ScopedPlatformHandleVectorPtr platform_handles;
+ if (message_view.transport_data_buffer()) {
+ size_t num_platform_handles;
+ const void* platform_handle_table;
+ TransportData::GetPlatformHandleTable(
+ message_view.transport_data_buffer(), &num_platform_handles,
+ &platform_handle_table);
+
+ if (num_platform_handles > 0) {
+ platform_handles =
+ GetReadPlatformHandles(num_platform_handles,
+ platform_handle_table).Pass();
+ if (!platform_handles) {
+ LOG(ERROR) << "Invalid number of platform handles received";
+ CallOnError(Delegate::ERROR_READ_BAD_MESSAGE);
+ *stop_dispatching = true;
+ return; // |this| may have been destroyed in |CallOnError()|.
+ }
+ }
+ }
+
+ // TODO(vtl): In the case that we aren't expecting any platform handles,
+ // for the POSIX implementation, we should confirm that none are stored.
+
+ // Dispatch the message.
+ // Note: it's valid to get here without a delegate. i.e. after Shutdown
+ // is called, if this object still has a valid handle we keep it alive
+ // until the other side closes it in response to the RAW_CHANNEL_QUIT
+ // message. In the meantime the sender could have sent us a message.
+ if (delegate_)
+ delegate_->OnReadMessage(message_view, platform_handles.Pass());
+ }
+
+ *did_dispatch_message = true;
+
+ // Update our state.
+ read_buffer_start += message_size;
+ remaining_bytes -= message_size;
+ }
+
+ if (read_buffer_start > 0) {
+ // Move data back to start.
+ read_buffer_->num_valid_bytes_ = remaining_bytes;
+ if (read_buffer_->num_valid_bytes_ > 0) {
+ memmove(&read_buffer_->buffer_[0],
+ &read_buffer_->buffer_[read_buffer_start], remaining_bytes);
+ }
+ read_buffer_start = 0;
+ }
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/raw_channel.h b/mojo/edk/system/raw_channel.h
new file mode 100644
index 0000000..ccc9f13
--- /dev/null
+++ b/mojo/edk/system/raw_channel.h
@@ -0,0 +1,409 @@
+// 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 MOJO_EDK_SYSTEM_RAW_CHANNEL_H_
+#define MOJO_EDK_SYSTEM_RAW_CHANNEL_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/message_in_transit_queue.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace base {
+class MessageLoopForIO;
+}
+
+namespace mojo {
+namespace edk {
+
+// |RawChannel| is an interface and base class for objects that wrap an OS
+// "pipe". It presents the following interface to users:
+// - Receives and dispatches messages on an I/O thread (running a
+// |MessageLoopForIO|.
+// - Provides a thread-safe way of writing messages (|WriteMessage()|);
+// writing/queueing messages will not block and is atomic from the point of
+// view of the caller. If necessary, messages are queued (to be written on
+// the aforementioned thread).
+//
+// OS-specific implementation subclasses are to be instantiated using the
+// |Create()| static factory method.
+//
+// With the exception of |WriteMessage()|, this class is thread-unsafe (and in
+// general its methods should only be used on the I/O thread, i.e., the thread
+// on which |Init()| is called).
+class MOJO_SYSTEM_IMPL_EXPORT RawChannel {
+ public:
+
+ // The |Delegate| is only accessed on the same thread as the message loop
+ // (passed in on creation).
+ class MOJO_SYSTEM_IMPL_EXPORT Delegate {
+ public:
+ enum Error {
+ // Failed read due to raw channel shutdown (e.g., on the other side).
+ ERROR_READ_SHUTDOWN,
+ // Failed read due to raw channel being broken (e.g., if the other side
+ // died without shutting down).
+ ERROR_READ_BROKEN,
+ // Received a bad message.
+ ERROR_READ_BAD_MESSAGE,
+ // Unknown read error.
+ ERROR_READ_UNKNOWN,
+ // Generic write error.
+ ERROR_WRITE
+ };
+
+ // Called when a message is read. The delegate may not call back into this
+ // object synchronously.
+ virtual void OnReadMessage(
+ const MessageInTransit::View& message_view,
+ ScopedPlatformHandleVectorPtr platform_handles) = 0;
+
+ // Called when there's a (fatal) error. The delegate may not call back into
+ // this object synchronously.
+ //
+ // For each raw channel, there'll be at most one |ERROR_READ_...| and at
+ // most one |ERROR_WRITE| notification. After |OnError(ERROR_READ_...)|,
+ // |OnReadMessage()| won't be called again.
+ virtual void OnError(Error error) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // Static factory method. |handle| should be a handle to a
+ // (platform-appropriate) bidirectional communication channel (e.g., a socket
+ // on POSIX, a named pipe on Windows).
+ static RawChannel* Create(ScopedPlatformHandle handle);
+
+ // Returns the amount of space needed in the |MessageInTransit|'s
+ // |TransportData|'s "platform handle table" per platform handle (to be
+ // attached to a message). (This amount may be zero.)
+ static size_t GetSerializedPlatformHandleSize();
+
+ // This must be called (on an I/O thread) before this object is used. Does
+ // *not* take ownership of |delegate|. Both the I/O thread and |delegate| must
+ // remain alive until |Shutdown()| is called (unless this fails); |delegate|
+ // will no longer be used after |Shutdown()|.
+ void Init(Delegate* delegate);
+
+ // This must be called (on the I/O thread) before this object is destroyed.
+ void Shutdown();
+
+ // Returns the platform handle for the pipe synchronously.
+ // |read_buffer| contains partially read data, if any.
+ // NOTE: After calling this, consider the channel shutdown and don't call into
+ // it anymore
+ ScopedPlatformHandle ReleaseHandle(std::vector<char>* read_buffer);
+
+ // Writes the given message (or schedules it to be written). |message| must
+ // have no |Dispatcher|s still attached (i.e.,
+ // |SerializeAndCloseDispatchers()| should have been called). This method is
+ // thread-safe and may be called from any thread. Returns true on success.
+ bool WriteMessage(scoped_ptr<MessageInTransit> message);
+
+ // Returns true if the write buffer is empty (i.e., all messages written using
+ // |WriteMessage()| have actually been sent.
+ // TODO(vtl): We should really also notify our delegate when the write buffer
+ // becomes empty (or something like that).
+ bool IsWriteBufferEmpty();
+
+ bool IsReadBufferEmpty();
+
+ void SetInitialReadBufferData(char* data, size_t size);
+
+ // Checks if this RawChannel is the other endpoint to |other|.
+ bool IsOtherEndOf(RawChannel* other);
+
+ protected:
+ // Result of I/O operations.
+ enum IOResult {
+ IO_SUCCEEDED,
+ // Failed due to a (probably) clean shutdown (e.g., of the other end).
+ IO_FAILED_SHUTDOWN,
+ // Failed due to the connection being broken (e.g., the other end dying).
+ IO_FAILED_BROKEN,
+ // Failed due to some other (unexpected) reason.
+ IO_FAILED_UNKNOWN,
+ IO_PENDING
+ };
+
+ class MOJO_SYSTEM_IMPL_EXPORT ReadBuffer {
+ public:
+ ReadBuffer();
+ ~ReadBuffer();
+
+ void GetBuffer(char** addr, size_t* size);
+
+ void Reset() {num_valid_bytes_ = 0; }
+
+ // temp for debugging
+ // TODO(jam): pass in a cleaner way to ReleaseHandle, just like shutdown
+ // case.
+ char* buffer() { return &buffer_[0]; }
+ size_t num_valid_bytes() {return num_valid_bytes_;}
+
+ private:
+ friend class RawChannel;
+
+ // We store data from |[Schedule]Read()|s in |buffer_|. The start of
+ // |buffer_| is always aligned with a message boundary (we will copy memory
+ // to ensure this), but |buffer_| may be larger than the actual number of
+ // bytes we have.
+ std::vector<char> buffer_;
+ size_t num_valid_bytes_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ReadBuffer);
+ };
+
+ class MOJO_SYSTEM_IMPL_EXPORT WriteBuffer {
+ public:
+ struct Buffer {
+ const char* addr;
+ size_t size;
+ };
+
+ WriteBuffer();
+ ~WriteBuffer();
+
+ // Returns true if there are (more) platform handles to be sent (from the
+ // front of |message_queue_|).
+ bool HavePlatformHandlesToSend() const;
+ // Gets platform handles to be sent (from the front of |message_queue_|).
+ // This should only be called if |HavePlatformHandlesToSend()| returned
+ // true. There are two components to this: the actual |PlatformHandle|s
+ // (which should be closed once sent) and any additional serialization
+ // information (which will be embedded in the message's data; there are
+ // |GetSerializedPlatformHandleSize()| bytes per handle). Once all platform
+ // handles have been sent, the message data should be written next (see
+ // |GetBuffers()|).
+ // TODO(vtl): Maybe this method should be const, but
+ // |PlatformHandle::CloseIfNecessary()| isn't const (and actually modifies
+ // state).
+ void GetPlatformHandlesToSend(size_t* num_platform_handles,
+ PlatformHandle** platform_handles,
+ void** serialization_data);
+
+ // Gets buffers to be written. These buffers will always come from the front
+ // of |message_queue_|. Once they are completely written, the front
+ // |MessageInTransit| should be popped (and destroyed); this is done in
+ // |OnWriteCompletedNoLock()|.
+ void GetBuffers(std::vector<Buffer>* buffers) const;
+
+
+
+
+
+ // temp for testing
+ size_t queue_size() {return message_queue_.Size();}
+
+ // TODO(jam): better way of giving buffer on release handle
+ MessageInTransitQueue* message_queue() { return &message_queue_; }
+
+
+
+ // TODO JAM REMOVE AND ADD METHODS
+// private:
+ friend class RawChannel;
+
+ size_t serialized_platform_handle_size_;
+
+ MessageInTransitQueue message_queue_;
+ // Platform handles are sent before the message data, but doing so may
+ // require several passes. |platform_handles_offset_| indicates the position
+ // in the first message's vector of platform handles to send next.
+ size_t platform_handles_offset_;
+ // The first message's data may have been partially sent. |data_offset_|
+ // indicates the position in the first message's data to start the next
+ // write.
+ size_t data_offset_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(WriteBuffer);
+ };
+
+ RawChannel();
+
+ // Shutdown must be called on the IO thread. This object deletes itself once
+ // it's flushed all pending writes and insured that the other side of the pipe
+ // read them.
+ virtual ~RawChannel();
+
+ // |result| must not be |IO_PENDING|. Must be called on the I/O thread WITHOUT
+ // |write_lock_| held. This object may be destroyed by this call.
+ void OnReadCompleted(IOResult io_result, size_t bytes_read);
+ // |result| must not be |IO_PENDING|. Must be called on the I/O thread WITHOUT
+ // |write_lock_| held. This object may be destroyed by this call.
+ void OnWriteCompleted(IOResult io_result,
+ size_t platform_handles_written,
+ size_t bytes_written);
+
+ base::MessageLoopForIO* message_loop_for_io() { return message_loop_for_io_; }
+ base::Lock& write_lock() { return write_lock_; }
+ base::Lock& read_lock() { return read_lock_; }
+
+ // Should only be called on the I/O thread.
+ ReadBuffer* read_buffer() { return read_buffer_.get(); }
+
+ // Only called under |write_lock_|.
+ WriteBuffer* write_buffer_no_lock() {
+ write_lock_.AssertAcquired();
+ return write_buffer_.get();
+ }
+
+ // Adds |message| to the write message queue. Implementation subclasses may
+ // override this to add any additional "control" messages needed. This is
+ // called (on any thread) with |write_lock_| held.
+ virtual void EnqueueMessageNoLock(scoped_ptr<MessageInTransit> message);
+
+ // Handles any control messages targeted to the |RawChannel| (or
+ // implementation subclass). Implementation subclasses may override this to
+ // handle any implementation-specific control messages, but should call
+ // |RawChannel::OnReadMessageForRawChannel()| for any remaining messages.
+ // Returns true on success and false on error (e.g., invalid control message).
+ // This is only called on the I/O thread.
+ virtual bool OnReadMessageForRawChannel(
+ const MessageInTransit::View& message_view);
+
+ virtual PlatformHandle HandleForDebuggingNoLock() = 0;
+
+ // Implementation must write any pending messages synchronously.
+ // TODO(jam): change to return shared memory with pending serialized msgs.
+ virtual ScopedPlatformHandle ReleaseHandleNoLock(
+ std::vector<char>* read_buffer) = 0;
+
+ // Reads into |read_buffer()|.
+ // This class guarantees that:
+ // - the area indicated by |GetBuffer()| will stay valid until read completion
+ // (but please also see the comments for |OnShutdownNoLock()|);
+ // - a second read is not started if there is a pending read;
+ // - the method is called on the I/O thread WITHOUT |write_lock_| held.
+ //
+ // The implementing subclass must guarantee that:
+ // - |bytes_read| is untouched unless |Read()| returns |IO_SUCCEEDED|;
+ // - if the method returns |IO_PENDING|, |OnReadCompleted()| will be called on
+ // the I/O thread to report the result, unless |Shutdown()| is called.
+ virtual IOResult Read(size_t* bytes_read) = 0;
+ // Similar to |Read()|, except that the implementing subclass must also
+ // guarantee that the method doesn't succeed synchronously, i.e., it only
+ // returns |IO_FAILED_...| or |IO_PENDING|.
+ virtual IOResult ScheduleRead() = 0;
+
+ // Called by |OnReadCompleted()| to get the platform handles associated with
+ // the given platform handle table (from a message). This should only be
+ // called when |num_platform_handles| is nonzero. Returns null if the
+ // |num_platform_handles| handles are not available. Only called on the I/O
+ // thread (without |write_lock_| held).
+ virtual ScopedPlatformHandleVectorPtr GetReadPlatformHandles(
+ size_t num_platform_handles,
+ const void* platform_handle_table) = 0;
+
+ // Writes contents in |write_buffer_no_lock()|.
+ // This class guarantees that:
+ // - the |PlatformHandle|s given by |GetPlatformHandlesToSend()| and the
+ // buffer(s) given by |GetBuffers()| will remain valid until write
+ // completion (see also the comments for |OnShutdownNoLock()|);
+ // - a second write is not started if there is a pending write;
+ // - the method is called under |write_lock_|.
+ //
+ // The implementing subclass must guarantee that:
+ // - |platform_handles_written| and |bytes_written| are untouched unless
+ // |WriteNoLock()| returns |IO_SUCCEEDED|;
+ // - if the method returns |IO_PENDING|, |OnWriteCompleted()| will be called
+ // on the I/O thread to report the result, unless |Shutdown()| is called.
+ virtual IOResult WriteNoLock(size_t* platform_handles_written,
+ size_t* bytes_written) = 0;
+ // Similar to |WriteNoLock()|, except that the implementing subclass must also
+ // guarantee that the method doesn't succeed synchronously, i.e., it only
+ // returns |IO_FAILED_...| or |IO_PENDING|.
+ virtual IOResult ScheduleWriteNoLock() = 0;
+
+ // Must be called on the I/O thread WITHOUT |write_lock_| held.
+ virtual void OnInit() = 0;
+ // On shutdown, passes the ownership of the buffers to subclasses, which may
+ // want to preserve them if there are pending read/writes. After this is
+ // called, |OnReadCompleted()| must no longer be called. Must be called on the
+ // I/O thread under |write_lock_|.
+ virtual void OnShutdownNoLock(scoped_ptr<ReadBuffer> read_buffer,
+ scoped_ptr<WriteBuffer> write_buffer) = 0;
+
+ bool SendQueuedMessagesNoLock();
+
+ private:
+ friend class base::DeleteHelper<RawChannel>;
+
+ // Converts an |IO_FAILED_...| for a read to a |Delegate::Error|.
+ static Delegate::Error ReadIOResultToError(IOResult io_result);
+
+ // Calls |delegate_->OnError(error)|. Must be called on the I/O thread WITHOUT
+ // |write_lock_| held. This object may be destroyed by this call.
+ void CallOnError(Delegate::Error error);
+
+ void LockAndCallOnError(Delegate::Error error);
+
+ // If |io_result| is |IO_SUCCESS|, updates the write buffer and schedules a
+ // write operation to run later if there is more to write. If |io_result| is
+ // failure or any other error occurs, cancels pending writes and returns
+ // false. Must be called under |write_lock_| and only if |write_stopped_| is
+ // false.
+ bool OnWriteCompletedNoLock(IOResult io_result,
+ size_t platform_handles_written,
+ size_t bytes_written);
+
+ // Helper method to dispatch messages from the read buffer.
+ // |did_dispatch_message| is true iff it dispatched any messages.
+ // |stop_dispatching| is set to true if the code calling this should stop
+ // dispatching, either because we hit an erorr or the delegate shutdown the
+ // channel.
+ void DispatchMessages(bool* did_dispatch_message, bool* stop_dispatching);
+
+ // Set in |Init()| and never changed (hence usable on any thread without
+ // locking):
+ base::MessageLoopForIO* message_loop_for_io_;
+
+
+
+
+
+ // TODO(jam): one lock only... but profile first to ensure it doesn't slow
+ // things down compared to fine grained locks.
+
+
+
+
+ // Only used on the I/O thread:
+
+ base::Lock read_lock_; // Protects read_buffer_.
+ // This is usually only accessed on IO thread, except when ReleaseHandle is
+ // called.
+ scoped_ptr<ReadBuffer> read_buffer_;
+ // ditto: usually used on io thread except ReleaseHandle
+ Delegate* delegate_;
+
+ // If grabbing both locks, grab read first.
+
+ base::Lock write_lock_; // Protects the following members.
+ bool write_ready_;
+ bool write_stopped_;
+ scoped_ptr<WriteBuffer> write_buffer_;
+
+ bool error_occurred_;
+
+ // This is used for posting tasks from write threads to the I/O thread. It
+ // must only be accessed under |write_lock_|. The weak pointers it produces
+ // are only used/invalidated on the I/O thread.
+ base::WeakPtrFactory<RawChannel> weak_ptr_factory_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RawChannel);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_RAW_CHANNEL_H_
diff --git a/mojo/edk/system/raw_channel_posix.cc b/mojo/edk/system/raw_channel_posix.cc
new file mode 100644
index 0000000..91922b0
--- /dev/null
+++ b/mojo/edk/system/raw_channel_posix.cc
@@ -0,0 +1,520 @@
+// 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 "mojo/edk/system/raw_channel.h"
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <deque>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/embedder/platform_channel_utils_posix.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/system/transport_data.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+class RawChannelPosix final : public RawChannel,
+ public base::MessageLoopForIO::Watcher {
+ public:
+ explicit RawChannelPosix(ScopedPlatformHandle handle);
+ ~RawChannelPosix() override;
+
+ private:
+ // |RawChannel| protected methods:
+ // Actually override this so that we can send multiple messages with (only)
+ // FDs if necessary.
+ void EnqueueMessageNoLock(scoped_ptr<MessageInTransit> message) override;
+ // Override this to handle those extra FD-only messages.
+ bool OnReadMessageForRawChannel(
+ const MessageInTransit::View& message_view) override;
+
+ ScopedPlatformHandle ReleaseHandleNoLock(
+ std::vector<char>* read_buffer_out) override;
+ PlatformHandle HandleForDebuggingNoLock() override;
+ IOResult Read(size_t* bytes_read) override;
+ IOResult ScheduleRead() override;
+ ScopedPlatformHandleVectorPtr GetReadPlatformHandles(
+ size_t num_platform_handles,
+ const void* platform_handle_table) override;
+ IOResult WriteNoLock(size_t* platform_handles_written,
+ size_t* bytes_written) override;
+ IOResult ScheduleWriteNoLock() override;
+ void OnInit() override;
+ void OnShutdownNoLock(scoped_ptr<ReadBuffer> read_buffer,
+ scoped_ptr<WriteBuffer> write_buffer) override;
+
+ // |base::MessageLoopForIO::Watcher| implementation:
+ void OnFileCanReadWithoutBlocking(int fd) override;
+ void OnFileCanWriteWithoutBlocking(int fd) override;
+
+ // Implements most of |Read()| (except for a bit of clean-up):
+ IOResult ReadImpl(size_t* bytes_read);
+
+ // Watches for |fd_| to become writable. Must be called on the I/O thread.
+ void WaitToWrite();
+
+ ScopedPlatformHandle fd_;
+
+ // The following members are only used on the I/O thread:
+ scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> read_watcher_;
+ scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> write_watcher_;
+
+ bool pending_read_;
+
+ std::deque<PlatformHandle> read_platform_handles_;
+
+ // The following members are used on multiple threads and protected by
+ // |write_lock()|:
+ bool pending_write_;
+
+ // This is used for posting tasks from write threads to the I/O thread. It
+ // must only be accessed under |write_lock_|. The weak pointers it produces
+ // are only used/invalidated on the I/O thread.
+ base::WeakPtrFactory<RawChannelPosix> weak_ptr_factory_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RawChannelPosix);
+};
+
+RawChannelPosix::RawChannelPosix(ScopedPlatformHandle handle)
+ : fd_(handle.Pass()),
+ pending_read_(false),
+ pending_write_(false),
+ weak_ptr_factory_(this) {
+ DCHECK(fd_.is_valid());
+}
+
+RawChannelPosix::~RawChannelPosix() {
+ DCHECK(!pending_read_);
+ DCHECK(!pending_write_);
+
+ // No need to take the |write_lock()| here -- if there are still weak pointers
+ // outstanding, then we're hosed anyway (since we wouldn't be able to
+ // invalidate them cleanly, since we might not be on the I/O thread).
+ DCHECK(!weak_ptr_factory_.HasWeakPtrs());
+
+ // These must have been shut down/destroyed on the I/O thread.
+ DCHECK(!read_watcher_);
+ DCHECK(!write_watcher_);
+
+ CloseAllPlatformHandles(&read_platform_handles_);
+}
+
+void RawChannelPosix::EnqueueMessageNoLock(
+ scoped_ptr<MessageInTransit> message) {
+ if (message->transport_data()) {
+ PlatformHandleVector* const platform_handles =
+ message->transport_data()->platform_handles();
+ if (platform_handles &&
+ platform_handles->size() > kPlatformChannelMaxNumHandles) {
+ // We can't attach all the FDs to a single message, so we have to "split"
+ // the message. Send as many control messages as needed first with FDs
+ // attached (and no data).
+ size_t i = 0;
+ for (; platform_handles->size() - i > kPlatformChannelMaxNumHandles;
+ i += kPlatformChannelMaxNumHandles) {
+ scoped_ptr<MessageInTransit> fd_message(new MessageInTransit(
+ MessageInTransit::Type::RAW_CHANNEL_POSIX_EXTRA_PLATFORM_HANDLES, 0,
+ nullptr));
+ ScopedPlatformHandleVectorPtr fds(
+ new PlatformHandleVector(
+ platform_handles->begin() + i,
+ platform_handles->begin() + i + kPlatformChannelMaxNumHandles));
+ fd_message->SetTransportData(make_scoped_ptr(
+ new TransportData(fds.Pass(), GetSerializedPlatformHandleSize())));
+ RawChannel::EnqueueMessageNoLock(fd_message.Pass());
+ }
+
+ // Remove the handles that we "moved" into the other messages.
+ platform_handles->erase(platform_handles->begin(),
+ platform_handles->begin() + i);
+ }
+ }
+
+ RawChannel::EnqueueMessageNoLock(message.Pass());
+}
+
+bool RawChannelPosix::OnReadMessageForRawChannel(
+ const MessageInTransit::View& message_view) {
+ if (message_view.type() ==
+ MessageInTransit::Type::RAW_CHANNEL_POSIX_EXTRA_PLATFORM_HANDLES) {
+ // We don't need to do anything. |RawChannel| won't extract the platform
+ // handles, and they'll be accumulated in |Read()|.
+ return true;
+ }
+
+ return RawChannel::OnReadMessageForRawChannel(message_view);
+}
+
+
+ScopedPlatformHandle RawChannelPosix::ReleaseHandleNoLock(
+ std::vector<char>* read_buffer_out) {
+ std::vector<WriteBuffer::Buffer> buffers;
+ write_buffer_no_lock()->GetBuffers(&buffers);
+ if (!buffers.empty()) {
+ // TODO(jam): copy code in OnShutdownNoLock
+ NOTREACHED() << "releasing handle with pending write buffer";
+ }
+
+ NOTREACHED() << "TODO(jam) IMPLEMENT";/*
+ if (handle_.is_valid()) {
+ // SetInitialBuffer could have been called on main thread before OnInit
+ // is called on Io thread. and in meantime releasehandle called.
+ //DCHECK(read_buffer()->num_valid_bytes() == 0);
+ if (read_buffer()->num_valid_bytes()) {
+ read_buffer_out->resize(read_buffer()->num_valid_bytes());
+ memcpy(&(*read_buffer_out)[0], read_buffer()->buffer(),
+ read_buffer()->num_valid_bytes());
+ read_buffer()->Reset();
+ }
+ DCHECK(write_buffer_no_lock()->queue_size() == 0);
+ return ScopedPlatformHandle(PlatformHandle(handle_.release().handle));
+ }
+
+ return io_handler_->ReleaseHandle(read_buffer_out);
+ */
+ return fd_.Pass();
+}
+
+PlatformHandle RawChannelPosix::HandleForDebuggingNoLock() {
+ return fd_.get();
+}
+
+RawChannel::IOResult RawChannelPosix::Read(size_t* bytes_read) {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+ DCHECK(!pending_read_);
+
+ IOResult rv = ReadImpl(bytes_read);
+ if (rv != IO_SUCCEEDED && rv != IO_PENDING) {
+ // Make sure that |OnFileCanReadWithoutBlocking()| won't be called again.
+ read_watcher_.reset();
+ }
+ return rv;
+}
+
+RawChannel::IOResult RawChannelPosix::ScheduleRead() {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+ DCHECK(!pending_read_);
+
+ pending_read_ = true;
+
+ return IO_PENDING;
+}
+
+ScopedPlatformHandleVectorPtr RawChannelPosix::GetReadPlatformHandles(
+ size_t num_platform_handles,
+ const void* /*platform_handle_table*/) {
+ DCHECK_GT(num_platform_handles, 0u);
+
+ if (read_platform_handles_.size() < num_platform_handles) {
+ CloseAllPlatformHandles(&read_platform_handles_);
+ read_platform_handles_.clear();
+ return ScopedPlatformHandleVectorPtr();
+ }
+
+ ScopedPlatformHandleVectorPtr rv(
+ new PlatformHandleVector(num_platform_handles));
+ rv->assign(read_platform_handles_.begin(),
+ read_platform_handles_.begin() + num_platform_handles);
+ read_platform_handles_.erase(
+ read_platform_handles_.begin(),
+ read_platform_handles_.begin() + num_platform_handles);
+ return rv.Pass();
+}
+
+RawChannel::IOResult RawChannelPosix::WriteNoLock(
+ size_t* platform_handles_written,
+ size_t* bytes_written) {
+ write_lock().AssertAcquired();
+
+ DCHECK(!pending_write_);
+
+ size_t num_platform_handles = 0;
+ ssize_t write_result;
+ if (write_buffer_no_lock()->HavePlatformHandlesToSend()) {
+ PlatformHandle* platform_handles;
+ void* serialization_data; // Actually unused.
+ write_buffer_no_lock()->GetPlatformHandlesToSend(
+ &num_platform_handles, &platform_handles, &serialization_data);
+ DCHECK_GT(num_platform_handles, 0u);
+ DCHECK_LE(num_platform_handles, kPlatformChannelMaxNumHandles);
+ DCHECK(platform_handles);
+
+ // TODO(vtl): Reduce code duplication. (This is duplicated from below.)
+ std::vector<WriteBuffer::Buffer> buffers;
+ write_buffer_no_lock()->GetBuffers(&buffers);
+ DCHECK(!buffers.empty());
+ const size_t kMaxBufferCount = 10;
+ iovec iov[kMaxBufferCount];
+ size_t buffer_count = std::min(buffers.size(), kMaxBufferCount);
+ for (size_t i = 0; i < buffer_count; ++i) {
+ iov[i].iov_base = const_cast<char*>(buffers[i].addr);
+ iov[i].iov_len = buffers[i].size;
+ }
+
+ write_result = PlatformChannelSendmsgWithHandles(
+ fd_.get(), iov, buffer_count, platform_handles, num_platform_handles);
+ if (write_result >= 0) {
+ for (size_t i = 0; i < num_platform_handles; i++)
+ platform_handles[i].CloseIfNecessary();
+ }
+ } else {
+ std::vector<WriteBuffer::Buffer> buffers;
+ write_buffer_no_lock()->GetBuffers(&buffers);
+ DCHECK(!buffers.empty());
+
+ if (buffers.size() == 1) {
+ write_result = PlatformChannelWrite(fd_.get(), buffers[0].addr,
+ buffers[0].size);
+ } else {
+ const size_t kMaxBufferCount = 10;
+ iovec iov[kMaxBufferCount];
+ size_t buffer_count = std::min(buffers.size(), kMaxBufferCount);
+ for (size_t i = 0; i < buffer_count; ++i) {
+ iov[i].iov_base = const_cast<char*>(buffers[i].addr);
+ iov[i].iov_len = buffers[i].size;
+ }
+
+ write_result = PlatformChannelWritev(fd_.get(), iov, buffer_count);
+ }
+ }
+
+ if (write_result >= 0) {
+ *platform_handles_written = num_platform_handles;
+ *bytes_written = static_cast<size_t>(write_result);
+ return IO_SUCCEEDED;
+ }
+
+ if (errno == EPIPE)
+ return IO_FAILED_SHUTDOWN;
+
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ PLOG(WARNING) << "sendmsg/write/writev";
+ return IO_FAILED_UNKNOWN;
+ }
+
+ return ScheduleWriteNoLock();
+}
+
+RawChannel::IOResult RawChannelPosix::ScheduleWriteNoLock() {
+ write_lock().AssertAcquired();
+
+ DCHECK(!pending_write_);
+
+ // Set up to wait for the FD to become writable.
+ // If we're not on the I/O thread, we have to post a task to do this.
+ if (base::MessageLoop::current() != message_loop_for_io()) {
+ message_loop_for_io()->PostTask(FROM_HERE,
+ base::Bind(&RawChannelPosix::WaitToWrite,
+ weak_ptr_factory_.GetWeakPtr()));
+ pending_write_ = true;
+ return IO_PENDING;
+ }
+
+ if (message_loop_for_io()->WatchFileDescriptor(
+ fd_.get().fd, false, base::MessageLoopForIO::WATCH_WRITE,
+ write_watcher_.get(), this)) {
+ pending_write_ = true;
+ return IO_PENDING;
+ }
+
+ return IO_FAILED_UNKNOWN;
+}
+
+void RawChannelPosix::OnInit() {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+
+ DCHECK(!read_watcher_);
+ read_watcher_.reset(new base::MessageLoopForIO::FileDescriptorWatcher());
+ DCHECK(!write_watcher_);
+ write_watcher_.reset(new base::MessageLoopForIO::FileDescriptorWatcher());
+
+ // I don't know how this can fail (unless |fd_| is bad, in which case it's a
+ // bug in our code). I also don't know if |WatchFileDescriptor()| actually
+ // fails cleanly.
+ CHECK(message_loop_for_io()->WatchFileDescriptor(
+ fd_.get().fd, true, base::MessageLoopForIO::WATCH_READ,
+ read_watcher_.get(), this));
+}
+
+void RawChannelPosix::OnShutdownNoLock(
+ scoped_ptr<ReadBuffer> /*read_buffer*/,
+ scoped_ptr<WriteBuffer> /*write_buffer*/) {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+ write_lock().AssertAcquired();
+
+ read_watcher_.reset(); // This will stop watching (if necessary).
+ write_watcher_.reset(); // This will stop watching (if necessary).
+
+ pending_read_ = false;
+ pending_write_ = false;
+
+ DCHECK(fd_.is_valid());
+ fd_.reset();
+
+ weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+void RawChannelPosix::OnFileCanReadWithoutBlocking(int fd) {
+ DCHECK_EQ(fd, fd_.get().fd);
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+
+ if (!pending_read_) {
+ NOTREACHED();
+ return;
+ }
+
+ pending_read_ = false;
+ size_t bytes_read = 0;
+ IOResult io_result = Read(&bytes_read);
+ if (io_result != IO_PENDING) {
+ OnReadCompleted(io_result, bytes_read);
+ // TODO(vtl): If we weren't destroyed, we'd like to do
+ //
+ // DCHECK(!read_watcher_ || pending_read_);
+ //
+ // On failure, |read_watcher_| must have been reset; on success, we assume
+ // that |OnReadCompleted()| always schedules another read. Otherwise, we
+ // could end up spinning -- getting |OnFileCanReadWithoutBlocking()| again
+ // and again but not doing any actual read.
+ // TODO(yzshen): An alternative is to stop watching if RawChannel doesn't
+ // schedule a new read. But that code won't be reached under the current
+ // RawChannel implementation.
+ return; // |this| may have been destroyed in |OnReadCompleted()|.
+ }
+
+ DCHECK(pending_read_);
+}
+
+void RawChannelPosix::OnFileCanWriteWithoutBlocking(int fd) {
+ DCHECK_EQ(fd, fd_.get().fd);
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+
+ IOResult io_result;
+ size_t platform_handles_written = 0;
+ size_t bytes_written = 0;
+ {
+ base::AutoLock locker(write_lock());
+
+ DCHECK(pending_write_);
+
+ pending_write_ = false;
+ io_result = WriteNoLock(&platform_handles_written, &bytes_written);
+ }
+
+ if (io_result != IO_PENDING) {
+ OnWriteCompleted(io_result, platform_handles_written, bytes_written);
+ return; // |this| may have been destroyed in |OnWriteCompleted()|.
+ }
+}
+
+RawChannel::IOResult RawChannelPosix::ReadImpl(size_t* bytes_read) {
+ char* buffer = nullptr;
+ size_t bytes_to_read = 0;
+ read_buffer()->GetBuffer(&buffer, &bytes_to_read);
+
+ size_t old_num_platform_handles = read_platform_handles_.size();
+ ssize_t read_result = PlatformChannelRecvmsg(
+ fd_.get(), buffer, bytes_to_read, &read_platform_handles_);
+ if (read_platform_handles_.size() > old_num_platform_handles) {
+ DCHECK_LE(read_platform_handles_.size() - old_num_platform_handles,
+ kPlatformChannelMaxNumHandles);
+
+ // We should never accumulate more than |TransportData::kMaxPlatformHandles
+ // + kPlatformChannelMaxNumHandles| handles. (The latter part is
+ // possible because we could have accumulated all the handles for a message,
+ // then received the message data plus the first set of handles for the next
+ // message in the subsequent |recvmsg()|.)
+ if (read_platform_handles_.size() >
+ (TransportData::GetMaxPlatformHandles() +
+ kPlatformChannelMaxNumHandles)) {
+ LOG(ERROR) << "Received too many platform handles";
+ CloseAllPlatformHandles(&read_platform_handles_);
+ read_platform_handles_.clear();
+ return IO_FAILED_UNKNOWN;
+ }
+ }
+
+ if (read_result > 0) {
+ *bytes_read = static_cast<size_t>(read_result);
+ return IO_SUCCEEDED;
+ }
+
+ // |read_result == 0| means "end of file".
+ if (read_result == 0)
+ return IO_FAILED_SHUTDOWN;
+
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ return ScheduleRead();
+
+ if (errno == ECONNRESET)
+ return IO_FAILED_BROKEN;
+
+ PLOG(WARNING) << "recvmsg";
+ return IO_FAILED_UNKNOWN;
+}
+
+void RawChannelPosix::WaitToWrite() {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+
+ DCHECK(write_watcher_);
+
+ if (!message_loop_for_io()->WatchFileDescriptor(
+ fd_.get().fd, false, base::MessageLoopForIO::WATCH_WRITE,
+ write_watcher_.get(), this)) {
+ {
+ base::AutoLock locker(write_lock());
+
+ DCHECK(pending_write_);
+ pending_write_ = false;
+ }
+ OnWriteCompleted(IO_FAILED_UNKNOWN, 0, 0);
+ return; // |this| may have been destroyed in |OnWriteCompleted()|.
+ }
+}
+
+} // namespace
+
+// -----------------------------------------------------------------------------
+
+// Static factory method declared in raw_channel.h.
+// static
+RawChannel* RawChannel::Create(ScopedPlatformHandle handle) {
+ return new RawChannelPosix(handle.Pass());
+}
+
+size_t RawChannel::GetSerializedPlatformHandleSize() {
+ // We don't actually need any space on POSIX (since we just send FDs).
+ return 0;
+}
+
+bool RawChannel::IsOtherEndOf(RawChannel* other) {
+ PlatformHandle this_handle = HandleForDebuggingNoLock();
+ PlatformHandle other_handle = other->HandleForDebuggingNoLock();
+
+ struct stat stat1, stat2;
+ if (fstat(this_handle.fd, &stat1) < 0)
+ return false;
+ if (fstat(other_handle.fd, &stat2) < 0)
+ return false;
+ return (stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino);
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/raw_channel_unittest.cc b/mojo/edk/system/raw_channel_unittest.cc
new file mode 100644
index 0000000..af75543
--- /dev/null
+++ b/mojo/edk/system/raw_channel_unittest.cc
@@ -0,0 +1,661 @@
+// Copyright 2014 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 "mojo/edk/system/raw_channel.h"
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/rand_util.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_io_thread.h"
+#include "base/threading/simple_thread.h"
+#include "build/build_config.h" // TODO(vtl): Remove this.
+#include "mojo/edk/embedder/platform_channel_pair.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/system/transport_data.h"
+#include "mojo/edk/test/test_utils.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+scoped_ptr<MessageInTransit> MakeTestMessage(uint32_t num_bytes) {
+ std::vector<unsigned char> bytes(num_bytes, 0);
+ for (size_t i = 0; i < num_bytes; i++)
+ bytes[i] = static_cast<unsigned char>(i + num_bytes);
+ return make_scoped_ptr(
+ new MessageInTransit(MessageInTransit::Type::MESSAGE,
+ num_bytes, bytes.empty() ? nullptr : &bytes[0]));
+}
+
+bool CheckMessageData(const void* bytes, uint32_t num_bytes) {
+ const unsigned char* b = static_cast<const unsigned char*>(bytes);
+ for (uint32_t i = 0; i < num_bytes; i++) {
+ if (b[i] != static_cast<unsigned char>(i + num_bytes))
+ return false;
+ }
+ return true;
+}
+
+void InitOnIOThread(RawChannel* raw_channel, RawChannel::Delegate* delegate) {
+ raw_channel->Init(delegate);
+}
+
+bool WriteTestMessageToHandle(const PlatformHandle& handle,
+ uint32_t num_bytes) {
+ scoped_ptr<MessageInTransit> message(MakeTestMessage(num_bytes));
+
+ size_t write_size = 0;
+ test::BlockingWrite(handle, message->main_buffer(),
+ message->main_buffer_size(), &write_size);
+ return write_size == message->main_buffer_size();
+}
+
+// -----------------------------------------------------------------------------
+
+class RawChannelTest : public test::MojoSystemTest {
+ public:
+ RawChannelTest() {}
+ ~RawChannelTest() override {}
+
+ void SetUp() override {
+ PlatformChannelPair channel_pair;
+ handles[0] = channel_pair.PassServerHandle();
+ handles[1] = channel_pair.PassClientHandle();\
+ }
+
+ void TearDown() override {
+ handles[0].reset();
+ handles[1].reset();
+ }
+
+ protected:
+ ScopedPlatformHandle handles[2];
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RawChannelTest);
+};
+
+// RawChannelTest.WriteMessage -------------------------------------------------
+
+class WriteOnlyRawChannelDelegate : public RawChannel::Delegate {
+ public:
+ WriteOnlyRawChannelDelegate() {}
+ ~WriteOnlyRawChannelDelegate() override {}
+
+ // |RawChannel::Delegate| implementation:
+ void OnReadMessage(
+ const MessageInTransit::View& /*message_view*/,
+ ScopedPlatformHandleVectorPtr /*platform_handles*/) override {
+ CHECK(false); // Should not get called.
+ }
+ void OnError(Error error) override {
+ // We'll get a read (shutdown) error when the connection is closed.
+ CHECK_EQ(error, ERROR_READ_SHUTDOWN);
+ }
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(WriteOnlyRawChannelDelegate);
+};
+
+static const unsigned kMessageReaderSleepMs = 1;
+static const size_t kMessageReaderMaxPollIterations = 3000;
+
+class TestMessageReaderAndChecker {
+ public:
+ explicit TestMessageReaderAndChecker(PlatformHandle handle)
+ : handle_(handle) {}
+ ~TestMessageReaderAndChecker() { CHECK(bytes_.empty()); }
+
+ bool ReadAndCheckNextMessage(uint32_t expected_size) {
+ unsigned char buffer[4096];
+
+ for (size_t i = 0; i < kMessageReaderMaxPollIterations;) {
+ size_t read_size = 0;
+ CHECK(test::NonBlockingRead(handle_, buffer, sizeof(buffer), &read_size));
+
+ // Append newly-read data to |bytes_|.
+ bytes_.insert(bytes_.end(), buffer, buffer + read_size);
+
+ // If we have the header....
+ size_t message_size;
+ if (MessageInTransit::GetNextMessageSize(
+ bytes_.empty() ? nullptr : &bytes_[0], bytes_.size(),
+ &message_size)) {
+ // If we've read the whole message....
+ if (bytes_.size() >= message_size) {
+ bool rv = true;
+ MessageInTransit::View message_view(message_size, &bytes_[0]);
+ CHECK_EQ(message_view.main_buffer_size(), message_size);
+
+ if (message_view.num_bytes() != expected_size) {
+ LOG(ERROR) << "Wrong size: " << message_size << " instead of "
+ << expected_size << " bytes.";
+ rv = false;
+ } else if (!CheckMessageData(message_view.bytes(),
+ message_view.num_bytes())) {
+ LOG(ERROR) << "Incorrect message bytes.";
+ rv = false;
+ }
+
+ // Erase message data.
+ bytes_.erase(bytes_.begin(),
+ bytes_.begin() + message_view.main_buffer_size());
+ return rv;
+ }
+ }
+
+ if (static_cast<size_t>(read_size) < sizeof(buffer)) {
+ i++;
+ test::Sleep(test::DeadlineFromMilliseconds(kMessageReaderSleepMs));
+ }
+ }
+
+ LOG(ERROR) << "Too many iterations.";
+ return false;
+ }
+
+ private:
+ const PlatformHandle handle_;
+
+ // The start of the received data should always be on a message boundary.
+ std::vector<unsigned char> bytes_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestMessageReaderAndChecker);
+};
+
+// Tests writing (and verifies reading using our own custom reader).
+TEST_F(RawChannelTest, WriteMessage) {
+ WriteOnlyRawChannelDelegate delegate;
+ RawChannel* rc = RawChannel::Create(handles[0].Pass());
+ TestMessageReaderAndChecker checker(handles[1].get());
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&InitOnIOThread, rc, base::Unretained(&delegate)));
+
+ // Write and read, for a variety of sizes.
+ for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1) {
+ EXPECT_TRUE(rc->WriteMessage(MakeTestMessage(size)));
+ EXPECT_TRUE(checker.ReadAndCheckNextMessage(size)) << size;
+ }
+
+ // Write/queue and read afterwards, for a variety of sizes.
+ for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1)
+ EXPECT_TRUE(rc->WriteMessage(MakeTestMessage(size)));
+ for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1)
+ EXPECT_TRUE(checker.ReadAndCheckNextMessage(size)) << size;
+
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE, base::Bind(&RawChannel::Shutdown, base::Unretained(rc)));
+}
+
+// RawChannelTest.OnReadMessage ------------------------------------------------
+
+class ReadCheckerRawChannelDelegate : public RawChannel::Delegate {
+ public:
+ ReadCheckerRawChannelDelegate() : done_event_(false, false), position_(0) {}
+ ~ReadCheckerRawChannelDelegate() override {}
+
+ // |RawChannel::Delegate| implementation (called on the I/O thread):
+ void OnReadMessage(
+ const MessageInTransit::View& message_view,
+ ScopedPlatformHandleVectorPtr platform_handles) override {
+ EXPECT_FALSE(platform_handles);
+
+ size_t position;
+ size_t expected_size;
+ bool should_signal = false;
+ {
+ base::AutoLock locker(lock_);
+ CHECK_LT(position_, expected_sizes_.size());
+ position = position_;
+ expected_size = expected_sizes_[position];
+ position_++;
+ if (position_ >= expected_sizes_.size())
+ should_signal = true;
+ }
+
+ EXPECT_EQ(expected_size, message_view.num_bytes()) << position;
+ if (message_view.num_bytes() == expected_size) {
+ EXPECT_TRUE(
+ CheckMessageData(message_view.bytes(), message_view.num_bytes()))
+ << position;
+ }
+
+ if (should_signal)
+ done_event_.Signal();
+ }
+ void OnError(Error error) override {
+ // We'll get a read (shutdown) error when the connection is closed.
+ CHECK_EQ(error, ERROR_READ_SHUTDOWN);
+ }
+
+ // Waits for all the messages (of sizes |expected_sizes_|) to be seen.
+ void Wait() { done_event_.Wait(); }
+
+ void SetExpectedSizes(const std::vector<uint32_t>& expected_sizes) {
+ base::AutoLock locker(lock_);
+ CHECK_EQ(position_, expected_sizes_.size());
+ expected_sizes_ = expected_sizes;
+ position_ = 0;
+ }
+
+ private:
+ base::WaitableEvent done_event_;
+
+ base::Lock lock_; // Protects the following members.
+ std::vector<uint32_t> expected_sizes_;
+ size_t position_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ReadCheckerRawChannelDelegate);
+};
+
+// Tests reading (writing using our own custom writer).
+TEST_F(RawChannelTest, OnReadMessage) {
+ ReadCheckerRawChannelDelegate delegate;
+ RawChannel* rc = RawChannel::Create(handles[0].Pass());
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&InitOnIOThread, rc, base::Unretained(&delegate)));
+
+ // Write and read, for a variety of sizes.
+ for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1) {
+ delegate.SetExpectedSizes(std::vector<uint32_t>(1, size));
+
+ EXPECT_TRUE(WriteTestMessageToHandle(handles[1].get(), size));
+
+ delegate.Wait();
+ }
+
+ // Set up reader and write as fast as we can.
+ // Write/queue and read afterwards, for a variety of sizes.
+ std::vector<uint32_t> expected_sizes;
+ for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1)
+ expected_sizes.push_back(size);
+ delegate.SetExpectedSizes(expected_sizes);
+ for (uint32_t size = 1; size < 5 * 1000 * 1000; size += size / 2 + 1)
+ EXPECT_TRUE(WriteTestMessageToHandle(handles[1].get(), size));
+ delegate.Wait();
+
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE, base::Bind(&RawChannel::Shutdown, base::Unretained(rc)));
+}
+
+// RawChannelTest.WriteMessageAndOnReadMessage ---------------------------------
+
+class RawChannelWriterThread : public base::SimpleThread {
+ public:
+ RawChannelWriterThread(RawChannel* raw_channel, size_t write_count)
+ : base::SimpleThread("raw_channel_writer_thread"),
+ raw_channel_(raw_channel),
+ left_to_write_(write_count) {}
+
+ ~RawChannelWriterThread() override { Join(); }
+
+ private:
+ void Run() override {
+ static const int kMaxRandomMessageSize = 25000;
+
+ while (left_to_write_-- > 0) {
+ EXPECT_TRUE(raw_channel_->WriteMessage(MakeTestMessage(
+ static_cast<uint32_t>(base::RandInt(1, kMaxRandomMessageSize)))));
+ }
+ }
+
+ RawChannel* const raw_channel_;
+ size_t left_to_write_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RawChannelWriterThread);
+};
+
+class ReadCountdownRawChannelDelegate : public RawChannel::Delegate {
+ public:
+ explicit ReadCountdownRawChannelDelegate(size_t expected_count)
+ : done_event_(false, false), expected_count_(expected_count), count_(0) {}
+ ~ReadCountdownRawChannelDelegate() override {}
+
+ // |RawChannel::Delegate| implementation (called on the I/O thread):
+ void OnReadMessage(
+ const MessageInTransit::View& message_view,
+ ScopedPlatformHandleVectorPtr platform_handles) override {
+ EXPECT_FALSE(platform_handles);
+
+ EXPECT_LT(count_, expected_count_);
+ count_++;
+
+ EXPECT_TRUE(
+ CheckMessageData(message_view.bytes(), message_view.num_bytes()));
+
+ if (count_ >= expected_count_)
+ done_event_.Signal();
+ }
+ void OnError(Error error) override {
+ // We'll get a read (shutdown) error when the connection is closed.
+ CHECK_EQ(error, ERROR_READ_SHUTDOWN);
+ }
+
+ // Waits for all the messages to have been seen.
+ void Wait() { done_event_.Wait(); }
+
+ private:
+ base::WaitableEvent done_event_;
+ size_t expected_count_;
+ size_t count_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ReadCountdownRawChannelDelegate);
+};
+
+TEST_F(RawChannelTest, WriteMessageAndOnReadMessage) {
+ static const size_t kNumWriterThreads = 10;
+ static const size_t kNumWriteMessagesPerThread = 4000;
+
+ WriteOnlyRawChannelDelegate writer_delegate;
+ RawChannel* writer_rc = RawChannel::Create(handles[0].Pass());
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&InitOnIOThread, writer_rc,
+ base::Unretained(&writer_delegate)));
+
+ ReadCountdownRawChannelDelegate reader_delegate(kNumWriterThreads *
+ kNumWriteMessagesPerThread);
+ RawChannel* reader_rc = RawChannel::Create(handles[1].Pass());
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&InitOnIOThread, reader_rc,
+ base::Unretained(&reader_delegate)));
+
+ {
+ ScopedVector<RawChannelWriterThread> writer_threads;
+ for (size_t i = 0; i < kNumWriterThreads; i++) {
+ writer_threads.push_back(new RawChannelWriterThread(
+ writer_rc, kNumWriteMessagesPerThread));
+ }
+ for (size_t i = 0; i < writer_threads.size(); i++)
+ writer_threads[i]->Start();
+ } // Joins all the writer threads.
+
+ // Sleep a bit, to let any extraneous reads be processed. (There shouldn't be
+ // any, but we want to know about them.)
+ test::Sleep(test::DeadlineFromMilliseconds(100));
+
+ // Wait for reading to finish.
+ reader_delegate.Wait();
+
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&RawChannel::Shutdown, base::Unretained(reader_rc)));
+
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&RawChannel::Shutdown, base::Unretained(writer_rc)));
+}
+
+// RawChannelTest.OnError ------------------------------------------------------
+
+class ErrorRecordingRawChannelDelegate
+ : public ReadCountdownRawChannelDelegate {
+ public:
+ ErrorRecordingRawChannelDelegate(size_t expected_read_count,
+ bool expect_read_error,
+ bool expect_write_error)
+ : ReadCountdownRawChannelDelegate(expected_read_count),
+ got_read_error_event_(false, false),
+ got_write_error_event_(false, false),
+ expecting_read_error_(expect_read_error),
+ expecting_write_error_(expect_write_error) {}
+
+ ~ErrorRecordingRawChannelDelegate() override {}
+
+ void OnError(Error error) override {
+ switch (error) {
+ case ERROR_READ_SHUTDOWN:
+ ASSERT_TRUE(expecting_read_error_);
+ expecting_read_error_ = false;
+ got_read_error_event_.Signal();
+ break;
+ case ERROR_READ_BROKEN:
+ // TODO(vtl): Test broken connections.
+ CHECK(false);
+ break;
+ case ERROR_READ_BAD_MESSAGE:
+ // TODO(vtl): Test reception/detection of bad messages.
+ CHECK(false);
+ break;
+ case ERROR_READ_UNKNOWN:
+ // TODO(vtl): Test however it is we might get here.
+ CHECK(false);
+ break;
+ case ERROR_WRITE:
+ ASSERT_TRUE(expecting_write_error_);
+ expecting_write_error_ = false;
+ got_write_error_event_.Signal();
+ break;
+ }
+ }
+
+ void WaitForReadError() { got_read_error_event_.Wait(); }
+ void WaitForWriteError() { got_write_error_event_.Wait(); }
+
+ private:
+ base::WaitableEvent got_read_error_event_;
+ base::WaitableEvent got_write_error_event_;
+
+ bool expecting_read_error_;
+ bool expecting_write_error_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ErrorRecordingRawChannelDelegate);
+};
+
+// Tests (fatal) errors.
+TEST_F(RawChannelTest, OnError) {
+ ErrorRecordingRawChannelDelegate delegate(0, true, true);
+ RawChannel* rc = RawChannel::Create(handles[0].Pass());
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&InitOnIOThread, rc, base::Unretained(&delegate)));
+
+ // Close the handle of the other end, which should make writing fail.
+ handles[1].reset();
+
+ EXPECT_FALSE(rc->WriteMessage(MakeTestMessage(1)));
+
+ // We should get a write error.
+ delegate.WaitForWriteError();
+
+ // We should also get a read error.
+ delegate.WaitForReadError();
+
+ EXPECT_FALSE(rc->WriteMessage(MakeTestMessage(2)));
+
+ // Sleep a bit, to make sure we don't get another |OnError()|
+ // notification. (If we actually get another one, |OnError()| crashes.)
+ test::Sleep(test::DeadlineFromMilliseconds(20));
+
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE, base::Bind(&RawChannel::Shutdown, base::Unretained(rc)));
+}
+
+// RawChannelTest.ReadUnaffectedByWriteError -----------------------------------
+
+TEST_F(RawChannelTest, ReadUnaffectedByWriteError) {
+ const size_t kMessageCount = 5;
+
+ // Write a few messages into the other end.
+ uint32_t message_size = 1;
+ for (size_t i = 0; i < kMessageCount;
+ i++, message_size += message_size / 2 + 1)
+ EXPECT_TRUE(WriteTestMessageToHandle(handles[1].get(), message_size));
+
+ // Close the other end, which should make writing fail.
+ handles[1].reset();
+
+ // Only start up reading here. The system buffer should still contain the
+ // messages that were written.
+ ErrorRecordingRawChannelDelegate delegate(kMessageCount, true, true);
+ RawChannel* rc = RawChannel::Create(handles[0].Pass());
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&InitOnIOThread, rc, base::Unretained(&delegate)));
+
+ EXPECT_FALSE(rc->WriteMessage(MakeTestMessage(1)));
+
+ // We should definitely get a write error.
+ delegate.WaitForWriteError();
+
+ // Wait for reading to finish. A writing failure shouldn't affect reading.
+ delegate.Wait();
+
+ // And then we should get a read error.
+ delegate.WaitForReadError();
+
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE, base::Bind(&RawChannel::Shutdown, base::Unretained(rc)));
+}
+
+// RawChannelTest.WriteMessageAfterShutdown ------------------------------------
+
+// Makes sure that calling |WriteMessage()| after |Shutdown()| behaves
+// correctly.
+TEST_F(RawChannelTest, WriteMessageAfterShutdown) {
+ WriteOnlyRawChannelDelegate delegate;
+ RawChannel* rc = RawChannel::Create(handles[0].Pass());
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&InitOnIOThread, rc, base::Unretained(&delegate)));
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE, base::Bind(&RawChannel::Shutdown, base::Unretained(rc)));
+
+ EXPECT_FALSE(rc->WriteMessage(MakeTestMessage(1)));
+}
+
+// RawChannelTest.ReadWritePlatformHandles -------------------------------------
+
+class ReadPlatformHandlesCheckerRawChannelDelegate
+ : public RawChannel::Delegate {
+ public:
+ ReadPlatformHandlesCheckerRawChannelDelegate() : done_event_(false, false) {}
+ ~ReadPlatformHandlesCheckerRawChannelDelegate() override {}
+
+ // |RawChannel::Delegate| implementation (called on the I/O thread):
+ void OnReadMessage(
+ const MessageInTransit::View& message_view,
+ ScopedPlatformHandleVectorPtr platform_handles) override {
+ const char kHello[] = "hello";
+
+ EXPECT_EQ(sizeof(kHello), message_view.num_bytes());
+ EXPECT_STREQ(kHello, static_cast<const char*>(message_view.bytes()));
+
+ ASSERT_TRUE(platform_handles);
+ ASSERT_EQ(2u, platform_handles->size());
+ ScopedPlatformHandle h1(platform_handles->at(0));
+ EXPECT_TRUE(h1.is_valid());
+ ScopedPlatformHandle h2(platform_handles->at(1));
+ EXPECT_TRUE(h2.is_valid());
+ platform_handles->clear();
+
+ {
+ char buffer[100] = {};
+
+ base::ScopedFILE fp(test::FILEFromPlatformHandle(h1.Pass(), "rb"));
+ EXPECT_TRUE(fp);
+ rewind(fp.get());
+ EXPECT_EQ(1u, fread(buffer, 1, sizeof(buffer), fp.get()));
+ EXPECT_EQ('1', buffer[0]);
+ }
+
+ {
+ char buffer[100] = {};
+ base::ScopedFILE fp(test::FILEFromPlatformHandle(h2.Pass(), "rb"));
+ EXPECT_TRUE(fp);
+ rewind(fp.get());
+ EXPECT_EQ(1u, fread(buffer, 1, sizeof(buffer), fp.get()));
+ EXPECT_EQ('2', buffer[0]);
+ }
+
+ done_event_.Signal();
+ }
+ void OnError(Error error) override {
+ // We'll get a read (shutdown) error when the connection is closed.
+ CHECK_EQ(error, ERROR_READ_SHUTDOWN);
+ }
+
+ void Wait() { done_event_.Wait(); }
+
+ private:
+ base::WaitableEvent done_event_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ReadPlatformHandlesCheckerRawChannelDelegate);
+};
+
+TEST_F(RawChannelTest, ReadWritePlatformHandles) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ WriteOnlyRawChannelDelegate write_delegate;
+ RawChannel* rc_write = RawChannel::Create(handles[0].Pass());
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&InitOnIOThread, rc_write, base::Unretained(&write_delegate)));
+
+ ReadPlatformHandlesCheckerRawChannelDelegate read_delegate;
+ RawChannel* rc_read = RawChannel::Create(handles[1].Pass());
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&InitOnIOThread, rc_read, base::Unretained(&read_delegate)));
+
+ base::FilePath unused;
+ base::ScopedFILE fp1(
+ base::CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused));
+ EXPECT_EQ(1u, fwrite("1", 1, 1, fp1.get()));
+ base::ScopedFILE fp2(
+ base::CreateAndOpenTemporaryFileInDir(temp_dir.path(), &unused));
+ EXPECT_EQ(1u, fwrite("2", 1, 1, fp2.get()));
+
+ {
+ const char kHello[] = "hello";
+ ScopedPlatformHandleVectorPtr platform_handles(new PlatformHandleVector());
+ platform_handles->push_back(
+ test::PlatformHandleFromFILE(fp1.Pass()).release());
+ platform_handles->push_back(
+ test::PlatformHandleFromFILE(fp2.Pass()).release());
+
+ scoped_ptr<MessageInTransit> message(
+ new MessageInTransit(MessageInTransit::Type::MESSAGE,
+ sizeof(kHello), kHello));
+ message->SetTransportData(make_scoped_ptr(new TransportData(
+ platform_handles.Pass(), rc_write->GetSerializedPlatformHandleSize())));
+ EXPECT_TRUE(rc_write->WriteMessage(message.Pass()));
+ }
+
+ read_delegate.Wait();
+
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&RawChannel::Shutdown, base::Unretained(rc_read)));
+ test_io_thread()->PostTaskAndWait(
+ FROM_HERE,
+ base::Bind(&RawChannel::Shutdown, base::Unretained(rc_write)));
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/raw_channel_win.cc b/mojo/edk/system/raw_channel_win.cc
new file mode 100644
index 0000000..b4fdb72
--- /dev/null
+++ b/mojo/edk/system/raw_channel_win.cc
@@ -0,0 +1,866 @@
+// 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 "mojo/edk/system/raw_channel.h"
+
+#include <windows.h>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/process/process.h"
+#include "base/synchronization/lock.h"
+#include "base/win/object_watcher.h"
+#include "base/win/windows_version.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/public/cpp/system/macros.h"
+
+#define STATUS_CANCELLED 0xC0000120
+#define STATUS_PIPE_BROKEN 0xC000014B
+
+// We can't use IO completion ports if we send a message pipe. The reason is
+// that the only way to stop an existing IOCP is by closing the pipe handle.
+// See https://msdn.microsoft.com/en-us/library/windows/hardware/ff545834(v=vs.85).aspx
+bool g_use_iocp = false;
+
+// Manual reset per
+// Doc for overlapped I/O says use manual per
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ms684342(v=vs.85).aspx
+// However using an auto-reset event makes the perf test 5x faster and also
+// works since we don't wait on the event elsewhere or call GetOverlappedResult
+// before it fires.
+bool g_use_autoreset_event = true;
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+struct MOJO_ALIGNAS(8) SerializedHandle {
+ DWORD handle_pid;
+ HANDLE handle;
+};
+
+class VistaOrHigherFunctions {
+ public:
+ VistaOrHigherFunctions()
+ : is_vista_or_higher_(
+ base::win::GetVersion() >= base::win::VERSION_VISTA),
+ set_file_completion_notification_modes_(nullptr),
+ cancel_io_ex_(nullptr) {
+ if (!is_vista_or_higher_)
+ return;
+
+ HMODULE module = GetModuleHandleW(L"kernel32.dll");
+ set_file_completion_notification_modes_ =
+ reinterpret_cast<SetFileCompletionNotificationModesFunc>(
+ GetProcAddress(module, "SetFileCompletionNotificationModes"));
+ DCHECK(set_file_completion_notification_modes_);
+
+ cancel_io_ex_ =
+ reinterpret_cast<CancelIoExFunc>(GetProcAddress(module, "CancelIoEx"));
+ DCHECK(cancel_io_ex_);
+ }
+
+ bool is_vista_or_higher() const { return is_vista_or_higher_; }
+
+ BOOL SetFileCompletionNotificationModes(HANDLE handle, UCHAR flags) {
+ return set_file_completion_notification_modes_(handle, flags);
+ }
+
+ BOOL CancelIoEx(HANDLE handle, LPOVERLAPPED overlapped) {
+ return cancel_io_ex_(handle, overlapped);
+ }
+
+ private:
+ using SetFileCompletionNotificationModesFunc = BOOL(WINAPI*)(HANDLE, UCHAR);
+ using CancelIoExFunc = BOOL(WINAPI*)(HANDLE, LPOVERLAPPED);
+
+ bool is_vista_or_higher_;
+ SetFileCompletionNotificationModesFunc
+ set_file_completion_notification_modes_;
+ CancelIoExFunc cancel_io_ex_;
+};
+
+base::LazyInstance<VistaOrHigherFunctions> g_vista_or_higher_functions =
+ LAZY_INSTANCE_INITIALIZER;
+
+class RawChannelWin final : public RawChannel {
+ public:
+ RawChannelWin(ScopedPlatformHandle handle)
+ : handle_(handle.Pass()),
+ io_handler_(nullptr),
+ skip_completion_port_on_success_(
+ g_use_iocp &&
+ g_vista_or_higher_functions.Get().is_vista_or_higher()) {
+ DCHECK(handle_.is_valid());
+ }
+ ~RawChannelWin() override {
+ DCHECK(!io_handler_);
+ }
+
+ private:
+ // RawChannelIOHandler receives OS notifications for I/O completion. It must
+ // be created on the I/O thread.
+ //
+ // It manages its own destruction. Destruction happens on the I/O thread when
+ // all the following conditions are satisfied:
+ // - |DetachFromOwnerNoLock()| has been called;
+ // - there is no pending read;
+ // - there is no pending write.
+ class RawChannelIOHandler : public base::MessageLoopForIO::IOHandler,
+ public base::win::ObjectWatcher::Delegate {
+ public:
+ RawChannelIOHandler(RawChannelWin* owner,
+ ScopedPlatformHandle handle)
+ : handle_(handle.Pass()),
+ owner_(owner),
+ suppress_self_destruct_(false),
+ pending_read_(false),
+ pending_write_(false),
+ platform_handles_written_(0) {
+ memset(&read_context_.overlapped, 0, sizeof(read_context_.overlapped));
+ memset(&write_context_.overlapped, 0, sizeof(write_context_.overlapped));
+ if (g_use_iocp) {
+ owner_->message_loop_for_io()->RegisterIOHandler(
+ handle_.get().handle, this);
+ read_context_.handler = this;
+ write_context_.handler = this;
+ } else {
+ read_event = CreateEvent(
+ NULL, g_use_autoreset_event ? FALSE : TRUE, FALSE, NULL);
+ write_event = CreateEvent(
+ NULL, g_use_autoreset_event ? FALSE : TRUE, FALSE, NULL);
+ read_context_.overlapped.hEvent = read_event;
+ write_context_.overlapped.hEvent = write_event;
+
+
+ if (g_use_autoreset_event) {
+ read_watcher_.StartWatchingMultipleTimes(read_event, this);
+ write_watcher_.StartWatchingMultipleTimes(write_event, this);
+ }
+ }
+ }
+
+ ~RawChannelIOHandler() override {
+ DCHECK(ShouldSelfDestruct());
+ }
+
+ HANDLE handle() const { return handle_.get().handle; }
+
+ // The following methods are only called by the owner on the I/O thread.
+ bool pending_read() const {
+ DCHECK(owner_);
+ DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
+ return pending_read_;
+ }
+
+ base::MessageLoopForIO::IOContext* read_context() {
+ DCHECK(owner_);
+ DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
+ return &read_context_;
+ }
+
+ // Instructs the object to wait for an |OnIOCompleted()| notification.
+ void OnPendingReadStarted() {
+ DCHECK(owner_);
+ DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
+ DCHECK(!pending_read_);
+ pending_read_ = true;
+ }
+
+ // The following methods are only called by the owner under
+ // |owner_->write_lock()|.
+ bool pending_write_no_lock() const {
+ DCHECK(owner_);
+ owner_->write_lock().AssertAcquired();
+ return pending_write_;
+ }
+
+ base::MessageLoopForIO::IOContext* write_context_no_lock() {
+ DCHECK(owner_);
+ owner_->write_lock().AssertAcquired();
+ return &write_context_;
+ }
+ // Instructs the object to wait for an |OnIOCompleted()| notification.
+ void OnPendingWriteStartedNoLock(size_t platform_handles_written) {
+ DCHECK(owner_);
+ owner_->write_lock().AssertAcquired();
+ DCHECK(!pending_write_);
+ pending_write_ = true;
+ platform_handles_written_ = platform_handles_written;
+ }
+
+ // |base::MessageLoopForIO::IOHandler| implementation:
+ // Must be called on the I/O thread. It could be called before or after
+ // detached from the owner.
+ void OnIOCompleted(base::MessageLoopForIO::IOContext* context,
+ DWORD bytes_transferred,
+ DWORD error) override {
+ DCHECK(!owner_ ||
+ base::MessageLoop::current() == owner_->message_loop_for_io());
+
+ // Suppress self-destruction inside |OnReadCompleted()|, etc. (in case
+ // they result in a call to |Shutdown()|).
+ bool old_suppress_self_destruct = suppress_self_destruct_;
+ suppress_self_destruct_ = true;
+
+ if (context == &read_context_)
+ OnReadCompleted(bytes_transferred, error);
+ else if (context == &write_context_)
+ OnWriteCompleted(bytes_transferred, error);
+ else
+ NOTREACHED();
+
+ // Maybe allow self-destruction again.
+ suppress_self_destruct_ = old_suppress_self_destruct;
+
+ if (ShouldSelfDestruct())
+ delete this;
+ }
+
+ // Must be called on the I/O thread under |owner_->write_lock()|.
+ // After this call, the owner must not make any further calls on this
+ // object, and therefore the object is used on the I/O thread exclusively
+ // (if it stays alive).
+ void DetachFromOwnerNoLock(scoped_ptr<ReadBuffer> read_buffer,
+ scoped_ptr<WriteBuffer> write_buffer) {
+ DCHECK(owner_);
+ DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
+ //owner_->write_lock().AssertAcquired();
+
+ // If read/write is pending, we have to retain the corresponding buffer.
+ if (pending_read_)
+ preserved_read_buffer_after_detach_ = read_buffer.Pass();
+ if (pending_write_)
+ preserved_write_buffer_after_detach_ = write_buffer.Pass();
+
+ owner_ = nullptr;
+ if (ShouldSelfDestruct())
+ delete this;
+ }
+
+ ScopedPlatformHandle ReleaseHandle(std::vector<char>* read_buffer) {
+ // TODO(jam): handle XP
+ CancelIoEx(handle(), NULL);
+ // NOTE: The above call will cancel pending IO calls.
+ size_t read_buffer_byte_size = owner_->read_buffer()->num_valid_bytes();
+
+ if (pending_read_) {
+ DWORD bytes_read_dword = 0;
+
+ DWORD old_bytes = read_context_.overlapped.InternalHigh;
+
+ // Since we cancelled pending IO calls above, we need to know if the
+ // read did succeed (i.e. it completed and there's a pending task posted
+ // to alert us) or if it was cancelled. This important because if the
+ // read completed, we don't want to serialize those bytes again.
+ //TODO(jam): for XP, can return TRUE here to wait. also below.
+ BOOL rv = GetOverlappedResult(
+ handle(), &read_context_.overlapped, &bytes_read_dword, FALSE);
+ DCHECK_EQ(old_bytes, bytes_read_dword);
+ if (rv) {
+ if (read_context_.overlapped.Internal != STATUS_CANCELLED) {
+ read_buffer_byte_size += read_context_.overlapped.InternalHigh;
+ }
+ }
+ pending_read_ = false;
+ }
+
+ RawChannel::WriteBuffer* write_buffer = owner_->write_buffer_no_lock();
+
+ if (pending_write_) {
+ DWORD bytes_written_dword = 0;
+ DWORD old_bytes = write_context_.overlapped.InternalHigh;
+
+ // See comment above.
+ BOOL rv = GetOverlappedResult(
+ handle(), &write_context_.overlapped, &bytes_written_dword, FALSE);
+
+ if (old_bytes != bytes_written_dword) {
+ NOTREACHED() << "TODO(jam): this shouldn't be reached";
+ }
+
+ if (rv) {
+ if (write_context_.overlapped.Internal != STATUS_CANCELLED) {
+ CHECK(write_buffer->queue_size() != 0);
+
+ // TODO(jam)
+ DCHECK(!write_buffer->HavePlatformHandlesToSend());
+
+ write_buffer->data_offset_ += bytes_written_dword;
+
+ // TODO(jam): copied from OnWriteCompletedNoLock
+ MessageInTransit* message =
+ write_buffer->message_queue()->PeekMessage();
+ if (write_buffer->data_offset_ >= message->total_size()) {
+ // Complete write.
+ CHECK_EQ(write_buffer->data_offset_, message->total_size());
+ write_buffer->message_queue_.DiscardMessage();
+ write_buffer->platform_handles_offset_ = 0;
+ write_buffer->data_offset_ = 0;
+ }
+
+
+ //TODO(jam): handle more write msgs
+ DCHECK(write_buffer->message_queue_.IsEmpty());
+ }
+ }
+ pending_write_ = false;
+ }
+
+ if (read_buffer_byte_size) {
+ read_buffer->resize(read_buffer_byte_size);
+ memcpy(&(*read_buffer)[0], owner_->read_buffer()->buffer(),
+ read_buffer_byte_size);
+ owner_->read_buffer()->Reset();
+ }
+
+ return ScopedPlatformHandle(handle_.release());
+ }
+
+ void OnObjectSignaled(HANDLE object) override {
+ // Since this is called on the IO thread, no locks needed for owner_.
+ bool handle_is_valid = false;
+ if (owner_)
+ owner_->read_lock().Acquire();
+ handle_is_valid = handle_.is_valid();
+ if (owner_)
+ owner_->read_lock().Release();
+ if (!handle_is_valid) {
+ if (object == read_event)
+ pending_read_ = false;
+ else
+ pending_write_ = false;
+ if (ShouldSelfDestruct())
+ delete this;
+ return;
+ }
+
+ if (object == read_event) {
+ OnIOCompleted(&read_context_, read_context_.overlapped.InternalHigh,
+ read_context_.overlapped.Internal);
+
+ } else {
+ CHECK(object == write_event);
+ OnIOCompleted(&write_context_, write_context_.overlapped.InternalHigh,
+ write_context_.overlapped.Internal);
+ }
+ }
+ HANDLE read_event, write_event;
+ base::win::ObjectWatcher read_watcher_, write_watcher_;
+
+ private:
+ // Returns true if |owner_| has been reset and there is not pending read or
+ // write.
+ // Must be called on the I/O thread.
+ bool ShouldSelfDestruct() const {
+ if (owner_ || suppress_self_destruct_)
+ return false;
+
+ // Note: Detached, hence no lock needed for |pending_write_|.
+ return !pending_read_ && !pending_write_;
+ }
+
+ // Must be called on the I/O thread. It may be called before or after
+ // detaching from the owner.
+ void OnReadCompleted(DWORD bytes_read, DWORD error) {
+ DCHECK(!owner_ ||
+ base::MessageLoop::current() == owner_->message_loop_for_io());
+ DCHECK(suppress_self_destruct_);
+
+ if (g_use_autoreset_event && !pending_read_)
+ return;
+
+ CHECK(pending_read_);
+ pending_read_ = false;
+ if (!owner_)
+ return;
+
+ // Note: |OnReadCompleted()| may detach us from |owner_|.
+ if (error == ERROR_SUCCESS) {
+ DCHECK_GT(bytes_read, 0u);
+ owner_->OnReadCompleted(IO_SUCCEEDED, bytes_read);
+ } else if (error == ERROR_BROKEN_PIPE ||
+ (g_use_autoreset_event && error == STATUS_PIPE_BROKEN)) {
+ DCHECK_EQ(bytes_read, 0u);
+ owner_->OnReadCompleted(IO_FAILED_SHUTDOWN, 0);
+ } else if (error == ERROR_NO_MORE_ITEMS && g_use_autoreset_event) {
+ return owner_->OnReadCompleted(IO_SUCCEEDED, bytes_read);
+ } else {
+ DCHECK_EQ(bytes_read, 0u);
+ LOG(WARNING) << "ReadFile: " << logging::SystemErrorCodeToString(error);
+ owner_->OnReadCompleted(IO_FAILED_UNKNOWN, 0);
+ }
+ }
+
+ // Must be called on the I/O thread. It may be called before or after
+ // detaching from the owner.
+ void OnWriteCompleted(DWORD bytes_written, DWORD error) {
+ DCHECK(!owner_ ||
+ base::MessageLoop::current() == owner_->message_loop_for_io());
+ DCHECK(suppress_self_destruct_);
+
+ if (!owner_) {
+ // No lock needed.
+ CHECK(pending_write_);
+ pending_write_ = false;
+ return;
+ }
+
+ {
+ base::AutoLock locker(owner_->write_lock());
+ if (g_use_autoreset_event && !pending_write_)
+ return;
+
+ CHECK(pending_write_);
+ pending_write_ = false;
+ }
+
+ // Note: |OnWriteCompleted()| may detach us from |owner_|.
+ if (error == ERROR_SUCCESS) {
+ // Reset |platform_handles_written_| before calling |OnWriteCompleted()|
+ // since that function may call back to this class and set it again.
+ size_t local_platform_handles_written_ = platform_handles_written_;
+ platform_handles_written_ = 0;
+ owner_->OnWriteCompleted(IO_SUCCEEDED, local_platform_handles_written_,
+ bytes_written);
+ } else if (error == ERROR_BROKEN_PIPE ||
+ (g_use_autoreset_event && error == STATUS_PIPE_BROKEN)) {
+ owner_->OnWriteCompleted(IO_FAILED_SHUTDOWN, 0, 0);
+ } else if (error == ERROR_NO_MORE_ITEMS && g_use_autoreset_event) {
+ size_t local_platform_handles_written_ = platform_handles_written_;
+ platform_handles_written_ = 0;
+ owner_->OnWriteCompleted(IO_SUCCEEDED, local_platform_handles_written_,
+ bytes_written);
+ } else {
+ LOG(WARNING) << "WriteFile: "
+ << logging::SystemErrorCodeToString(error);
+ owner_->OnWriteCompleted(IO_FAILED_UNKNOWN, 0, 0);
+ }
+ }
+
+ ScopedPlatformHandle handle_;
+
+ // |owner_| is reset on the I/O thread under |owner_->write_lock()|.
+ // Therefore, it may be used on any thread under lock; or on the I/O thread
+ // without locking.
+ RawChannelWin* owner_;
+
+ // The following members must be used on the I/O thread.
+ scoped_ptr<ReadBuffer> preserved_read_buffer_after_detach_;
+ scoped_ptr<WriteBuffer> preserved_write_buffer_after_detach_;
+ bool suppress_self_destruct_;
+
+ bool pending_read_;
+ base::MessageLoopForIO::IOContext read_context_;
+
+ // The following members must be used under |owner_->write_lock()| while the
+ // object is still attached to the owner, and only on the I/O thread
+ // afterwards.
+ bool pending_write_;
+ size_t platform_handles_written_;
+ base::MessageLoopForIO::IOContext write_context_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RawChannelIOHandler);
+ };
+
+ ScopedPlatformHandle ReleaseHandleNoLock(
+ std::vector<char>* read_buffer_out) override {
+ std::vector<WriteBuffer::Buffer> buffers;
+ write_buffer_no_lock()->GetBuffers(&buffers);
+ if (!buffers.empty()) {
+ // TODO(jam): copy code in OnShutdownNoLock
+ NOTREACHED() << "releasing handle with pending write buffer";
+ }
+
+
+ if (handle_.is_valid()) {
+ // SetInitialBuffer could have been called on main thread before OnInit
+ // is called on Io thread. and in meantime releasehandle called.
+ //DCHECK(read_buffer()->num_valid_bytes() == 0);
+ if (read_buffer()->num_valid_bytes()) {
+ read_buffer_out->resize(read_buffer()->num_valid_bytes());
+ memcpy(&(*read_buffer_out)[0], read_buffer()->buffer(),
+ read_buffer()->num_valid_bytes());
+ read_buffer()->Reset();
+ }
+ DCHECK(write_buffer_no_lock()->queue_size() == 0);
+ return ScopedPlatformHandle(PlatformHandle(handle_.release().handle));
+ }
+
+ return io_handler_->ReleaseHandle(read_buffer_out);
+ }
+ PlatformHandle HandleForDebuggingNoLock() override {
+ if (handle_.is_valid())
+ return handle_.get();
+
+ if (!io_handler_)
+ return PlatformHandle();
+
+ return PlatformHandle(io_handler_->handle());
+ }
+
+ IOResult Read(size_t* bytes_read) override {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+
+ char* buffer = nullptr;
+ size_t bytes_to_read = 0;
+ read_buffer()->GetBuffer(&buffer, &bytes_to_read);
+
+ DCHECK(io_handler_);
+ DCHECK(!io_handler_->pending_read());
+ BOOL result = ReadFile(
+ io_handler_->handle(), buffer, static_cast<DWORD>(bytes_to_read),
+ nullptr, &io_handler_->read_context()->overlapped);
+ if (!result) {
+ DWORD error = GetLastError();
+ if (error == ERROR_BROKEN_PIPE)
+ return IO_FAILED_SHUTDOWN;
+ if (error != ERROR_IO_PENDING) {
+ LOG(WARNING) << "ReadFile: " << logging::SystemErrorCodeToString(error);
+ return IO_FAILED_UNKNOWN;
+ }
+ }
+
+ if (result && skip_completion_port_on_success_) {
+ DWORD bytes_read_dword = 0;
+ BOOL get_size_result = GetOverlappedResult(
+ io_handler_->handle(), &io_handler_->read_context()->overlapped,
+ &bytes_read_dword, FALSE);
+ DPCHECK(get_size_result);
+ *bytes_read = bytes_read_dword;
+ return IO_SUCCEEDED;
+ }
+
+ if (!g_use_autoreset_event) {
+ if (!g_use_iocp) {
+ io_handler_->read_watcher_.StartWatchingOnce(
+ io_handler_->read_event, io_handler_);
+ }
+ }
+ // If the read is pending or the read has succeeded but we don't skip
+ // completion port on success, instruct |io_handler_| to wait for the
+ // completion packet.
+ //
+ // TODO(yzshen): It seems there isn't document saying that all error cases
+ // (other than ERROR_IO_PENDING) are guaranteed to *not* queue a completion
+ // packet. If we do get one for errors,
+ // |RawChannelIOHandler::OnIOCompleted()| will crash so we will learn about
+ // it.
+
+ io_handler_->OnPendingReadStarted();
+ return IO_PENDING;
+ }
+
+ IOResult ScheduleRead() override {
+ if (!io_handler_)
+ return IO_PENDING; // OnInit could have earlied out.
+
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+ DCHECK(io_handler_);
+ DCHECK(!io_handler_->pending_read());
+
+ size_t bytes_read = 0;
+ IOResult io_result = Read(&bytes_read);
+ if (io_result == IO_SUCCEEDED) {
+ DCHECK(skip_completion_port_on_success_);
+
+ // We have finished reading successfully. Queue a notification manually.
+ io_handler_->OnPendingReadStarted();
+ // |io_handler_| won't go away before the task is run, so it is safe to
+ // use |base::Unretained()|.
+ message_loop_for_io()->PostTask(
+ FROM_HERE, base::Bind(&RawChannelIOHandler::OnIOCompleted,
+ base::Unretained(io_handler_),
+ base::Unretained(io_handler_->read_context()),
+ static_cast<DWORD>(bytes_read), ERROR_SUCCESS));
+ return IO_PENDING;
+ }
+
+ return io_result;
+ }
+ ScopedPlatformHandleVectorPtr GetReadPlatformHandles(
+ size_t num_platform_handles,
+ const void* platform_handle_table) override {
+ // TODO(jam): this code will have to be updated once it's used in a sandbox
+ // and the receiving process doesn't have duplicate permission for the
+ // receiver. Once there's a broker and we have a connection to it (possibly
+ // through ConnectionManager), then we can make a sync IPC to it here to get
+ // a token for this handle, and it will duplicate the handle to is process.
+ // Then we pass the token to the receiver, which will then make a sync call
+ // to the broker to get a duplicated handle. This will also allow us to
+ // avoid leaks of the handle if the receiver dies, since the broker can
+ // notice that.
+ DCHECK_GT(num_platform_handles, 0u);
+ ScopedPlatformHandleVectorPtr rv(new PlatformHandleVector());
+
+ const SerializedHandle* serialization_data =
+ static_cast<const SerializedHandle*>(platform_handle_table);
+ for (size_t i = 0; i < num_platform_handles; i++) {
+ DWORD pid = serialization_data->handle_pid;
+ HANDLE source_handle = serialization_data->handle;
+ serialization_data ++;
+ base::Process sender =
+ base::Process::OpenWithAccess(pid, PROCESS_DUP_HANDLE);
+ DCHECK(sender.IsValid());
+ HANDLE target_handle = NULL;
+ BOOL dup_result = DuplicateHandle(
+ sender.Handle(), source_handle,
+ base::GetCurrentProcessHandle(), &target_handle, 0,
+ FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
+ DCHECK(dup_result);
+ rv->push_back(PlatformHandle(target_handle));
+ }
+ return rv.Pass();
+ }
+
+ IOResult WriteNoLock(size_t* platform_handles_written,
+ size_t* bytes_written) override {
+ write_lock().AssertAcquired();
+
+ DCHECK(io_handler_);
+ DCHECK(!io_handler_->pending_write_no_lock());
+
+ size_t num_platform_handles = 0;
+ if (write_buffer_no_lock()->HavePlatformHandlesToSend()) {
+ // Since we're not sure which process might ultimately deserialize this
+ // message, we can't duplicate the handle now. Instead, write the process
+ // ID and handle now and let the receiver duplicate it.
+ PlatformHandle* platform_handles;
+ void* serialization_data_temp;
+ write_buffer_no_lock()->GetPlatformHandlesToSend(
+ &num_platform_handles, &platform_handles, &serialization_data_temp);
+ SerializedHandle* serialization_data =
+ static_cast<SerializedHandle*>(serialization_data_temp);
+ DCHECK_GT(num_platform_handles, 0u);
+ DCHECK(platform_handles);
+
+ DWORD current_process_id = base::GetCurrentProcId();
+ for (size_t i = 0; i < num_platform_handles; i++) {
+ serialization_data->handle_pid = current_process_id;
+ serialization_data->handle = platform_handles[i].handle;
+ serialization_data++;
+ platform_handles[i] = PlatformHandle();
+ }
+ }
+
+ std::vector<WriteBuffer::Buffer> buffers;
+ write_buffer_no_lock()->GetBuffers(&buffers);
+ DCHECK(!buffers.empty());
+
+ // TODO(yzshen): Handle multi-segment writes more efficiently.
+ DWORD bytes_written_dword = 0;
+
+
+ /* jam: comment below is commented out because with the latest code I don't
+ see this. Note that this code is incorrect for two reasons:
+ 1) the buffer would need to be saved until the write is finished
+ 2) this still doesn't fix the problem of there being more data or messages
+ to be sent. the proper fix is to serialize pending messages to shared
+ memory.
+
+ // TODO(jam): right now we get in bad situation where we might first write
+ // the main buffer and then the MP gets sent before we write the transport
+ // buffer. We can fix this by sending information about partially written
+ // messages, or by teaching transport buffer how to grow the main buffer and
+ // write its data there.
+ // Until that's done, for now make another copy.
+
+ size_t total_size = buffers[0].size;
+ if (buffers.size() > 1)
+ total_size+=buffers[1].size;
+ char* buf = new char[total_size];
+ memcpy(buf, buffers[0].addr, buffers[0].size);
+ if (buffers.size() > 1)
+ memcpy(buf + buffers[0].size, buffers[1].addr, buffers[1].size);
+
+ BOOL result = WriteFile(
+ io_handler_->handle(), buf,
+ static_cast<DWORD>(total_size),
+ &bytes_written_dword,
+ &io_handler_->write_context_no_lock()->overlapped);
+ delete [] buf;
+
+ if (!result) {
+ DWORD error = GetLastError();
+ if (error == ERROR_BROKEN_PIPE)
+ return IO_FAILED_SHUTDOWN;
+ if (error != ERROR_IO_PENDING) {
+ LOG(WARNING) << "WriteFile: "
+ << logging::SystemErrorCodeToString(error);
+ return IO_FAILED_UNKNOWN;
+ }
+ }
+ */
+
+ BOOL result =
+ WriteFile(io_handler_->handle(), buffers[0].addr,
+ static_cast<DWORD>(buffers[0].size), &bytes_written_dword,
+ &io_handler_->write_context_no_lock()->overlapped);
+ if (!result) {
+ DWORD error = GetLastError();
+ if (error == ERROR_BROKEN_PIPE)
+ return IO_FAILED_SHUTDOWN;
+ if (error != ERROR_IO_PENDING) {
+ LOG(WARNING) << "WriteFile: " << logging::SystemErrorCodeToString(error);
+ return IO_FAILED_UNKNOWN;
+ }
+ }
+
+ if (result && skip_completion_port_on_success_) {
+ *platform_handles_written = num_platform_handles;
+ *bytes_written = bytes_written_dword;
+ return IO_SUCCEEDED;
+ }
+
+ if (!g_use_autoreset_event) {
+ if (!g_use_iocp) {
+ io_handler_->write_watcher_.StartWatchingOnce(
+ io_handler_->write_event, io_handler_);
+ }
+ }
+ // If the write is pending or the write has succeeded but we don't skip
+ // completion port on success, instruct |io_handler_| to wait for the
+ // completion packet.
+ //
+ // TODO(yzshen): it seems there isn't document saying that all error cases
+ // (other than ERROR_IO_PENDING) are guaranteed to *not* queue a completion
+ // packet. If we do get one for errors,
+ // |RawChannelIOHandler::OnIOCompleted()| will crash so we will learn about
+ // it.
+
+ io_handler_->OnPendingWriteStartedNoLock(num_platform_handles);
+ return IO_PENDING;
+ }
+
+ IOResult ScheduleWriteNoLock() override {
+ write_lock().AssertAcquired();
+
+ DCHECK(io_handler_);
+ DCHECK(!io_handler_->pending_write_no_lock());
+
+ size_t platform_handles_written = 0;
+ size_t bytes_written = 0;
+ IOResult io_result = WriteNoLock(&platform_handles_written, &bytes_written);
+ if (io_result == IO_SUCCEEDED) {
+ DCHECK(skip_completion_port_on_success_);
+
+ // We have finished writing successfully. Queue a notification manually.
+ io_handler_->OnPendingWriteStartedNoLock(platform_handles_written);
+ // |io_handler_| won't go away before that task is run, so it is safe to
+ // use |base::Unretained()|.
+ message_loop_for_io()->PostTask(
+ FROM_HERE,
+ base::Bind(&RawChannelIOHandler::OnIOCompleted,
+ base::Unretained(io_handler_),
+ base::Unretained(io_handler_->write_context_no_lock()),
+ static_cast<DWORD>(bytes_written), ERROR_SUCCESS));
+ return IO_PENDING;
+ }
+
+ return io_result;
+ }
+
+ void OnInit() override {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+
+ if (!handle_.is_valid()) {
+ LOG(ERROR) << "Note: RawChannelWin " << this
+ << " early exiting in OnInit because no handle";
+ return;
+ }
+
+ DCHECK(handle_.is_valid());
+ if (skip_completion_port_on_success_) {
+ // I don't know how this can fail (unless |handle_| is bad, in which case
+ // it's a bug in our code).
+ CHECK(g_vista_or_higher_functions.Get().
+ SetFileCompletionNotificationModes(
+ handle_.get().handle, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS));
+ }
+
+ DCHECK(!io_handler_);
+ io_handler_ = new RawChannelIOHandler(this, handle_.Pass());
+ }
+
+ void OnShutdownNoLock(scoped_ptr<ReadBuffer> read_buffer,
+ scoped_ptr<WriteBuffer> write_buffer) override {
+ // happens on shutdown if didn't call init when doing createduplicate
+ if (message_loop_for_io()) {
+ DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
+ }
+
+ if (!io_handler_) {
+ // This is hit when creating a duplicate dispatcher since we don't call
+ // Init on it.
+ DCHECK_EQ(read_buffer->num_valid_bytes(), 0U);
+ DCHECK_EQ(write_buffer->queue_size(), 0U);
+ return;
+ }
+
+ if (io_handler_->pending_read() || io_handler_->pending_write_no_lock()) {
+ // |io_handler_| will be alive until pending read/write (if any)
+ // completes. Call |CancelIoEx()| or |CancelIo()| so that resources can be
+ // freed up as soon as possible.
+ // Note: |CancelIo()| only cancels read/write requests started from this
+ // thread.
+ if (g_vista_or_higher_functions.Get().is_vista_or_higher()) {
+ g_vista_or_higher_functions.Get().CancelIoEx(io_handler_->handle(),
+ nullptr);
+ } else {
+ CancelIo(io_handler_->handle());
+ }
+ }
+
+ io_handler_->DetachFromOwnerNoLock(read_buffer.Pass(), write_buffer.Pass());
+ io_handler_ = nullptr;
+ }
+
+ // Passed to |io_handler_| during initialization.
+ ScopedPlatformHandle handle_;
+
+ RawChannelIOHandler* io_handler_;
+
+ const bool skip_completion_port_on_success_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(RawChannelWin);
+};
+
+
+} // namespace
+
+// -----------------------------------------------------------------------------
+
+RawChannel* RawChannel::Create(ScopedPlatformHandle handle) {
+ return new RawChannelWin(handle.Pass());
+}
+
+size_t RawChannel::GetSerializedPlatformHandleSize() {
+ return sizeof(SerializedHandle);
+}
+
+bool RawChannel::IsOtherEndOf(RawChannel* other) {
+ PlatformHandle this_handle = HandleForDebuggingNoLock();
+ PlatformHandle other_handle = other->HandleForDebuggingNoLock();
+
+ // TODO: XP: see http://stackoverflow.com/questions/65170/how-to-get-name-associated-with-open-handle/5286888#5286888
+ WCHAR data1[_MAX_PATH + sizeof(FILE_NAME_INFO)];
+ WCHAR data2[_MAX_PATH + sizeof(FILE_NAME_INFO)];
+ FILE_NAME_INFO* fileinfo1 = reinterpret_cast<FILE_NAME_INFO *>(data1);
+ FILE_NAME_INFO* fileinfo2 = reinterpret_cast<FILE_NAME_INFO *>(data2);
+ CHECK(GetFileInformationByHandleEx(
+ this_handle.handle, FileNameInfo, fileinfo1, arraysize(data1)));
+ CHECK(GetFileInformationByHandleEx(
+ other_handle.handle, FileNameInfo, fileinfo2, arraysize(data2)));
+ std::wstring filepath1(fileinfo1->FileName, fileinfo1->FileNameLength / 2);
+ std::wstring filepath2(fileinfo2->FileName, fileinfo2->FileNameLength / 2);
+ return filepath1 == filepath2;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/run_all_unittests.cc b/mojo/edk/system/run_all_unittests.cc
new file mode 100644
index 0000000..b68f6e2
--- /dev/null
+++ b/mojo/edk/system/run_all_unittests.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 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/command_line.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+int main(int argc, char** argv) {
+// Silence death test thread warnings on Linux. We can afford to run our death
+// tests a little more slowly (< 10 ms per death test on a Z620).
+// On android, we need to run in the default mode, as the threadsafe mode
+// relies on execve which is not available.
+#if !defined(OS_ANDROID)
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
+#endif
+ mojo::edk::Init();
+ base::TestSuite test_suite(argc, argv);
+ // TODO(use_chrome_edk): temporary to force new EDK.
+ base::CommandLine::ForCurrentProcess()->AppendSwitch("--use-new-edk");
+
+ return base::LaunchUnitTests(
+ argc, argv,
+ base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/mojo/edk/system/shared_buffer_dispatcher.cc b/mojo/edk/system/shared_buffer_dispatcher.cc
new file mode 100644
index 0000000..bf68eb3
--- /dev/null
+++ b/mojo/edk/system/shared_buffer_dispatcher.cc
@@ -0,0 +1,270 @@
+// Copyright 2014 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 "mojo/edk/system/shared_buffer_dispatcher.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/edk/embedder/embedder_internal.h"
+#include "mojo/edk/embedder/platform_support.h"
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/options_validation.h"
+#include "mojo/public/c/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+namespace {
+
+struct MOJO_ALIGNAS(8) SerializedSharedBufferDispatcher {
+ size_t num_bytes;
+ size_t platform_handle_index;
+};
+
+} // namespace
+
+// static
+const MojoCreateSharedBufferOptions
+ SharedBufferDispatcher::kDefaultCreateOptions = {
+ static_cast<uint32_t>(sizeof(MojoCreateSharedBufferOptions)),
+ MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE};
+
+// static
+MojoResult SharedBufferDispatcher::ValidateCreateOptions(
+ const MojoCreateSharedBufferOptions* in_options,
+ MojoCreateSharedBufferOptions* out_options) {
+ const MojoCreateSharedBufferOptionsFlags kKnownFlags =
+ MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE;
+
+ *out_options = kDefaultCreateOptions;
+ if (!in_options)
+ return MOJO_RESULT_OK;
+
+ UserOptionsReader<MojoCreateSharedBufferOptions> reader(in_options);
+ if (!reader.is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(MojoCreateSharedBufferOptions, flags, reader))
+ return MOJO_RESULT_OK;
+ if ((reader.options().flags & ~kKnownFlags))
+ return MOJO_RESULT_UNIMPLEMENTED;
+ out_options->flags = reader.options().flags;
+
+ // Checks for fields beyond |flags|:
+
+ // (Nothing here yet.)
+
+ return MOJO_RESULT_OK;
+}
+
+// static
+MojoResult SharedBufferDispatcher::Create(
+ PlatformSupport* platform_support,
+ const MojoCreateSharedBufferOptions& /*validated_options*/,
+ uint64_t num_bytes,
+ scoped_refptr<SharedBufferDispatcher>* result) {
+ if (!num_bytes)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (num_bytes > GetConfiguration().max_shared_memory_num_bytes)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ scoped_refptr<PlatformSharedBuffer> shared_buffer(
+ platform_support->CreateSharedBuffer(static_cast<size_t>(num_bytes)));
+ if (!shared_buffer)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ *result = CreateInternal(shared_buffer.Pass());
+ return MOJO_RESULT_OK;
+}
+
+Dispatcher::Type SharedBufferDispatcher::GetType() const {
+ return Type::SHARED_BUFFER;
+}
+
+// static
+scoped_refptr<SharedBufferDispatcher> SharedBufferDispatcher::Deserialize(
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles) {
+
+ if (size != sizeof(SerializedSharedBufferDispatcher)) {
+ LOG(ERROR) << "Invalid serialized shared buffer dispatcher (bad size)";
+ return nullptr;
+ }
+
+ const SerializedSharedBufferDispatcher* serialization =
+ static_cast<const SerializedSharedBufferDispatcher*>(source);
+ size_t num_bytes = serialization->num_bytes;
+ size_t platform_handle_index = serialization->platform_handle_index;
+
+ if (!num_bytes) {
+ LOG(ERROR)
+ << "Invalid serialized shared buffer dispatcher (invalid num_bytes)";
+ return nullptr;
+ }
+
+ if (!platform_handles || platform_handle_index >= platform_handles->size()) {
+ LOG(ERROR)
+ << "Invalid serialized shared buffer dispatcher (missing handles)";
+ return nullptr;
+ }
+
+ // Starts off invalid, which is what we want.
+ PlatformHandle platform_handle;
+ // We take ownership of the handle, so we have to invalidate the one in
+ // |platform_handles|.
+ std::swap(platform_handle, (*platform_handles)[platform_handle_index]);
+
+ // Wrapping |platform_handle| in a |ScopedPlatformHandle| means that it'll be
+ // closed even if creation fails.
+ scoped_refptr<PlatformSharedBuffer> shared_buffer(
+ internal::g_platform_support->CreateSharedBufferFromHandle(
+ num_bytes, ScopedPlatformHandle(platform_handle)));
+ if (!shared_buffer) {
+ LOG(ERROR)
+ << "Invalid serialized shared buffer dispatcher (invalid num_bytes?)";
+ return nullptr;
+ }
+
+ return CreateInternal(shared_buffer.Pass());
+}
+
+SharedBufferDispatcher::SharedBufferDispatcher(
+ scoped_refptr<PlatformSharedBuffer> shared_buffer)
+ : shared_buffer_(shared_buffer) {
+ DCHECK(shared_buffer_);
+}
+
+SharedBufferDispatcher::~SharedBufferDispatcher() {
+}
+
+// static
+MojoResult SharedBufferDispatcher::ValidateDuplicateOptions(
+ const MojoDuplicateBufferHandleOptions* in_options,
+ MojoDuplicateBufferHandleOptions* out_options) {
+ const MojoDuplicateBufferHandleOptionsFlags kKnownFlags =
+ MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE;
+ static const MojoDuplicateBufferHandleOptions kDefaultOptions = {
+ static_cast<uint32_t>(sizeof(MojoDuplicateBufferHandleOptions)),
+ MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE};
+
+ *out_options = kDefaultOptions;
+ if (!in_options)
+ return MOJO_RESULT_OK;
+
+ UserOptionsReader<MojoDuplicateBufferHandleOptions> reader(in_options);
+ if (!reader.is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (!OPTIONS_STRUCT_HAS_MEMBER(MojoDuplicateBufferHandleOptions, flags,
+ reader))
+ return MOJO_RESULT_OK;
+ if ((reader.options().flags & ~kKnownFlags))
+ return MOJO_RESULT_UNIMPLEMENTED;
+ out_options->flags = reader.options().flags;
+
+ // Checks for fields beyond |flags|:
+
+ // (Nothing here yet.)
+
+ return MOJO_RESULT_OK;
+}
+
+void SharedBufferDispatcher::CloseImplNoLock() {
+ lock().AssertAcquired();
+ DCHECK(shared_buffer_);
+ shared_buffer_ = nullptr;
+}
+
+scoped_refptr<Dispatcher>
+SharedBufferDispatcher::CreateEquivalentDispatcherAndCloseImplNoLock() {
+ lock().AssertAcquired();
+ DCHECK(shared_buffer_);
+ return CreateInternal(shared_buffer_.Pass());
+}
+
+MojoResult SharedBufferDispatcher::DuplicateBufferHandleImplNoLock(
+ const MojoDuplicateBufferHandleOptions* options,
+ scoped_refptr<Dispatcher>* new_dispatcher) {
+ lock().AssertAcquired();
+
+ MojoDuplicateBufferHandleOptions validated_options;
+ MojoResult result = ValidateDuplicateOptions(options, &validated_options);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ // Note: Since this is "duplicate", we keep our ref to |shared_buffer_|.
+ *new_dispatcher = CreateInternal(shared_buffer_);
+ return MOJO_RESULT_OK;
+}
+
+MojoResult SharedBufferDispatcher::MapBufferImplNoLock(
+ uint64_t offset,
+ uint64_t num_bytes,
+ MojoMapBufferFlags flags,
+ scoped_ptr<PlatformSharedBufferMapping>* mapping) {
+ lock().AssertAcquired();
+ DCHECK(shared_buffer_);
+
+ if (offset > static_cast<uint64_t>(std::numeric_limits<size_t>::max()))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (num_bytes > static_cast<uint64_t>(std::numeric_limits<size_t>::max()))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (!shared_buffer_->IsValidMap(static_cast<size_t>(offset),
+ static_cast<size_t>(num_bytes)))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ DCHECK(mapping);
+ *mapping = shared_buffer_->MapNoCheck(static_cast<size_t>(offset),
+ static_cast<size_t>(num_bytes));
+ if (!*mapping)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ return MOJO_RESULT_OK;
+}
+
+void SharedBufferDispatcher::StartSerializeImplNoLock(
+ size_t* max_size,
+ size_t* max_platform_handles) {
+ DCHECK(HasOneRef()); // Only one ref => no need to take the lock.
+ *max_size = sizeof(SerializedSharedBufferDispatcher);
+ *max_platform_handles = 1;
+}
+
+bool SharedBufferDispatcher::EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) {
+ DCHECK(HasOneRef()); // Only one ref => no need to take the lock.
+ DCHECK(shared_buffer_);
+
+ SerializedSharedBufferDispatcher* serialization =
+ static_cast<SerializedSharedBufferDispatcher*>(destination);
+ // If there's only one reference to |shared_buffer_|, then it's ours (and no
+ // one else can make any more references to it), so we can just take its
+ // handle.
+ ScopedPlatformHandle platform_handle(
+ shared_buffer_->HasOneRef() ? shared_buffer_->PassPlatformHandle()
+ : shared_buffer_->DuplicatePlatformHandle());
+ if (!platform_handle.is_valid()) {
+ shared_buffer_ = nullptr;
+ return false;
+ }
+
+ serialization->num_bytes = shared_buffer_->GetNumBytes();
+ serialization->platform_handle_index = platform_handles->size();
+ platform_handles->push_back(platform_handle.release());
+ *actual_size = sizeof(SerializedSharedBufferDispatcher);
+
+ shared_buffer_ = nullptr;
+
+ return true;
+}
+
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/shared_buffer_dispatcher.h b/mojo/edk/system/shared_buffer_dispatcher.h
new file mode 100644
index 0000000..f3fc9e8
--- /dev/null
+++ b/mojo/edk/system/shared_buffer_dispatcher.h
@@ -0,0 +1,104 @@
+// Copyright 2014 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 MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_
+
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/system/simple_dispatcher.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+
+namespace edk {
+class PlatformSupport;
+
+// TODO(vtl): We derive from SimpleDispatcher, even though we don't currently
+// have anything that's waitable. I want to add a "transferrable" wait flag
+// (which would entail overriding |GetHandleSignalsStateImplNoLock()|, etc.).
+class MOJO_SYSTEM_IMPL_EXPORT SharedBufferDispatcher final
+ : public SimpleDispatcher {
+ public:
+ // The default options to use for |MojoCreateSharedBuffer()|. (Real uses
+ // should obtain this via |ValidateCreateOptions()| with a null |in_options|;
+ // this is exposed directly for testing convenience.)
+ static const MojoCreateSharedBufferOptions kDefaultCreateOptions;
+
+ // Validates and/or sets default options for |MojoCreateSharedBufferOptions|.
+ // If non-null, |in_options| must point to a struct of at least
+ // |in_options->struct_size| bytes. |out_options| must point to a (current)
+ // |MojoCreateSharedBufferOptions| and will be entirely overwritten on success
+ // (it may be partly overwritten on failure).
+ static MojoResult ValidateCreateOptions(
+ const MojoCreateSharedBufferOptions* in_options,
+ MojoCreateSharedBufferOptions* out_options);
+
+ // Static factory method: |validated_options| must be validated (obviously).
+ // On failure, |*result| will be left as-is.
+ // TODO(vtl): This should probably be made to return a scoped_refptr and have
+ // a MojoResult out parameter instead.
+ static MojoResult Create(
+ PlatformSupport* platform_support,
+ const MojoCreateSharedBufferOptions& validated_options,
+ uint64_t num_bytes,
+ scoped_refptr<SharedBufferDispatcher>* result);
+
+ // |Dispatcher| public methods:
+ Type GetType() const override;
+
+ // The "opposite" of |SerializeAndClose()|. (Typically this is called by
+ // |Dispatcher::Deserialize()|.)
+ static scoped_refptr<SharedBufferDispatcher> Deserialize(
+ const void* source,
+ size_t size,
+ PlatformHandleVector* platform_handles);
+
+ private:
+ static scoped_refptr<SharedBufferDispatcher> CreateInternal(
+ scoped_refptr<PlatformSharedBuffer> shared_buffer) {
+ return make_scoped_refptr(new SharedBufferDispatcher(shared_buffer.Pass()));
+ }
+
+ explicit SharedBufferDispatcher(
+ scoped_refptr<PlatformSharedBuffer> shared_buffer);
+ ~SharedBufferDispatcher() override;
+
+ // Validates and/or sets default options for
+ // |MojoDuplicateBufferHandleOptions|. If non-null, |in_options| must point to
+ // a struct of at least |in_options->struct_size| bytes. |out_options| must
+ // point to a (current) |MojoDuplicateBufferHandleOptions| and will be
+ // entirely overwritten on success (it may be partly overwritten on failure).
+ static MojoResult ValidateDuplicateOptions(
+ const MojoDuplicateBufferHandleOptions* in_options,
+ MojoDuplicateBufferHandleOptions* out_options);
+
+ // |Dispatcher| protected methods:
+ void CloseImplNoLock() override;
+ scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndCloseImplNoLock()
+ override;
+ MojoResult DuplicateBufferHandleImplNoLock(
+ const MojoDuplicateBufferHandleOptions* options,
+ scoped_refptr<Dispatcher>* new_dispatcher) override;
+ MojoResult MapBufferImplNoLock(
+ uint64_t offset,
+ uint64_t num_bytes,
+ MojoMapBufferFlags flags,
+ scoped_ptr<PlatformSharedBufferMapping>* mapping) override;
+ void StartSerializeImplNoLock(size_t* max_size,
+ size_t* max_platform_handles) override;
+ bool EndSerializeAndCloseImplNoLock(
+ void* destination,
+ size_t* actual_size,
+ PlatformHandleVector* platform_handles) override;
+
+ scoped_refptr<PlatformSharedBuffer> shared_buffer_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcher);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_
diff --git a/mojo/edk/system/shared_buffer_dispatcher_unittest.cc b/mojo/edk/system/shared_buffer_dispatcher_unittest.cc
new file mode 100644
index 0000000..ceb936b
--- /dev/null
+++ b/mojo/edk/system/shared_buffer_dispatcher_unittest.cc
@@ -0,0 +1,277 @@
+// Copyright 2014 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 "mojo/edk/system/shared_buffer_dispatcher.h"
+
+#include <limits>
+
+#include "base/memory/ref_counted.h"
+#include "mojo/edk/embedder/platform_shared_buffer.h"
+#include "mojo/edk/embedder/simple_platform_support.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+// NOTE(vtl): There's currently not much to test for in
+// |SharedBufferDispatcher::ValidateCreateOptions()|, but the tests should be
+// expanded if/when options are added, so I've kept the general form of the
+// tests from data_pipe_unittest.cc.
+
+const uint32_t kSizeOfCreateOptions = sizeof(MojoCreateSharedBufferOptions);
+
+// Does a cursory sanity check of |validated_options|. Calls
+// |ValidateCreateOptions()| on already-validated options. The validated options
+// should be valid, and the revalidated copy should be the same.
+void RevalidateCreateOptions(
+ const MojoCreateSharedBufferOptions& validated_options) {
+ EXPECT_EQ(kSizeOfCreateOptions, validated_options.struct_size);
+ // Nothing to check for flags.
+
+ MojoCreateSharedBufferOptions revalidated_options = {};
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::ValidateCreateOptions(
+ &validated_options, &revalidated_options));
+ EXPECT_EQ(validated_options.struct_size, revalidated_options.struct_size);
+ EXPECT_EQ(validated_options.flags, revalidated_options.flags);
+}
+
+class SharedBufferDispatcherTest : public testing::Test {
+ public:
+ SharedBufferDispatcherTest() {}
+ ~SharedBufferDispatcherTest() override {}
+
+ PlatformSupport* platform_support() { return &platform_support_; }
+
+ private:
+ SimplePlatformSupport platform_support_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcherTest);
+};
+
+// Tests valid inputs to |ValidateCreateOptions()|.
+TEST_F(SharedBufferDispatcherTest, ValidateCreateOptionsValid) {
+ // Default options.
+ {
+ MojoCreateSharedBufferOptions validated_options = {};
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::ValidateCreateOptions(
+ nullptr, &validated_options));
+ RevalidateCreateOptions(validated_options);
+ }
+
+ // Different flags.
+ MojoCreateSharedBufferOptionsFlags flags_values[] = {
+ MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE};
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(flags_values); i++) {
+ const MojoCreateSharedBufferOptionsFlags flags = flags_values[i];
+
+ // Different capacities (size 1).
+ for (uint32_t capacity = 1; capacity <= 100 * 1000 * 1000; capacity *= 10) {
+ MojoCreateSharedBufferOptions options = {
+ kSizeOfCreateOptions, // |struct_size|.
+ flags // |flags|.
+ };
+ MojoCreateSharedBufferOptions validated_options = {};
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::ValidateCreateOptions(
+ &options, &validated_options))
+ << capacity;
+ RevalidateCreateOptions(validated_options);
+ EXPECT_EQ(options.flags, validated_options.flags);
+ }
+ }
+}
+
+TEST_F(SharedBufferDispatcherTest, ValidateCreateOptionsInvalid) {
+ // Invalid |struct_size|.
+ {
+ MojoCreateSharedBufferOptions options = {
+ 1, // |struct_size|.
+ MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE // |flags|.
+ };
+ MojoCreateSharedBufferOptions unused;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ SharedBufferDispatcher::ValidateCreateOptions(
+ &options, &unused));
+ }
+
+ // Unknown |flags|.
+ {
+ MojoCreateSharedBufferOptions options = {
+ kSizeOfCreateOptions, // |struct_size|.
+ ~0u // |flags|.
+ };
+ MojoCreateSharedBufferOptions unused;
+ EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ SharedBufferDispatcher::ValidateCreateOptions(
+ &options, &unused));
+ }
+}
+
+TEST_F(SharedBufferDispatcherTest, CreateAndMapBuffer) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 100, &dispatcher));
+ ASSERT_TRUE(dispatcher);
+ EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher->GetType());
+
+ // Make a couple of mappings.
+ scoped_ptr<PlatformSharedBufferMapping> mapping1;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer(
+ 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping1));
+ ASSERT_TRUE(mapping1);
+ ASSERT_TRUE(mapping1->GetBase());
+ EXPECT_EQ(100u, mapping1->GetLength());
+ // Write something.
+ static_cast<char*>(mapping1->GetBase())[50] = 'x';
+
+ scoped_ptr<PlatformSharedBufferMapping> mapping2;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer(
+ 50, 50, MOJO_MAP_BUFFER_FLAG_NONE, &mapping2));
+ ASSERT_TRUE(mapping2);
+ ASSERT_TRUE(mapping2->GetBase());
+ EXPECT_EQ(50u, mapping2->GetLength());
+ EXPECT_EQ('x', static_cast<char*>(mapping2->GetBase())[0]);
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close());
+
+ // Check that we can still read/write to mappings after the dispatcher has
+ // gone away.
+ static_cast<char*>(mapping2->GetBase())[1] = 'y';
+ EXPECT_EQ('y', static_cast<char*>(mapping1->GetBase())[51]);
+}
+
+TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandle) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher1;
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 100, &dispatcher1));
+
+ // Map and write something.
+ scoped_ptr<PlatformSharedBufferMapping> mapping;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->MapBuffer(
+ 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping));
+ static_cast<char*>(mapping->GetBase())[0] = 'x';
+ mapping.reset();
+
+ // Duplicate |dispatcher1| and then close it.
+ scoped_refptr<Dispatcher> dispatcher2;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->DuplicateBufferHandle(
+ nullptr, &dispatcher2));
+ ASSERT_TRUE(dispatcher2);
+ EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher2->GetType());
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close());
+
+ // Map |dispatcher2| and read something.
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->MapBuffer(
+ 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping));
+ EXPECT_EQ('x', static_cast<char*>(mapping->GetBase())[0]);
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close());
+}
+
+TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsValid) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher1;
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 100, &dispatcher1));
+
+ MojoDuplicateBufferHandleOptions options[] = {
+ {sizeof(MojoDuplicateBufferHandleOptions),
+ MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE},
+ {sizeof(MojoDuplicateBufferHandleOptionsFlags), ~0u}};
+ for (size_t i = 0; i < MOJO_ARRAYSIZE(options); i++) {
+ scoped_refptr<Dispatcher> dispatcher2;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->DuplicateBufferHandle(
+ &options[i], &dispatcher2));
+ ASSERT_TRUE(dispatcher2);
+ EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher2->GetType());
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close());
+ }
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close());
+}
+
+TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsInvalid) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher1;
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 100, &dispatcher1));
+
+ // Invalid |struct_size|.
+ {
+ MojoDuplicateBufferHandleOptions options = {
+ 1u, MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE};
+ scoped_refptr<Dispatcher> dispatcher2;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher1->DuplicateBufferHandle(&options, &dispatcher2));
+ EXPECT_FALSE(dispatcher2);
+ }
+
+ // Unknown |flags|.
+ {
+ MojoDuplicateBufferHandleOptions options = {
+ sizeof(MojoDuplicateBufferHandleOptions), ~0u};
+ scoped_refptr<Dispatcher> dispatcher2;
+ EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ dispatcher1->DuplicateBufferHandle(&options, &dispatcher2));
+ EXPECT_FALSE(dispatcher2);
+ }
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close());
+}
+
+TEST_F(SharedBufferDispatcherTest, CreateInvalidNumBytes) {
+ // Size too big.
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(
+ MOJO_RESULT_RESOURCE_EXHAUSTED,
+ SharedBufferDispatcher::Create(
+ platform_support(), SharedBufferDispatcher::kDefaultCreateOptions,
+ std::numeric_limits<uint64_t>::max(), &dispatcher));
+ EXPECT_FALSE(dispatcher);
+
+ // Zero size.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions, 0, &dispatcher));
+ EXPECT_FALSE(dispatcher);
+}
+
+TEST_F(SharedBufferDispatcherTest, MapBufferInvalidArguments) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
+ platform_support(),
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ 100, &dispatcher));
+
+ scoped_ptr<PlatformSharedBufferMapping> mapping;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher->MapBuffer(0, 101, MOJO_MAP_BUFFER_FLAG_NONE, &mapping));
+ EXPECT_FALSE(mapping);
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher->MapBuffer(1, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping));
+ EXPECT_FALSE(mapping);
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher->MapBuffer(0, 0, MOJO_MAP_BUFFER_FLAG_NONE, &mapping));
+ EXPECT_FALSE(mapping);
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close());
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/simple_dispatcher.cc b/mojo/edk/system/simple_dispatcher.cc
new file mode 100644
index 0000000..447fe2e
--- /dev/null
+++ b/mojo/edk/system/simple_dispatcher.cc
@@ -0,0 +1,61 @@
+// 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 "mojo/edk/system/simple_dispatcher.h"
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace edk {
+
+SimpleDispatcher::SimpleDispatcher() {
+}
+
+SimpleDispatcher::~SimpleDispatcher() {
+}
+
+void SimpleDispatcher::HandleSignalsStateChangedNoLock() {
+ lock().AssertAcquired();
+ awakable_list_.AwakeForStateChange(GetHandleSignalsStateImplNoLock());
+}
+
+void SimpleDispatcher::CancelAllAwakablesNoLock() {
+ lock().AssertAcquired();
+ awakable_list_.CancelAll();
+}
+
+MojoResult SimpleDispatcher::AddAwakableImplNoLock(
+ Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+
+ HandleSignalsState state(GetHandleSignalsStateImplNoLock());
+ if (state.satisfies(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_ALREADY_EXISTS;
+ }
+ if (!state.can_satisfy(signals)) {
+ if (signals_state)
+ *signals_state = state;
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ }
+
+ awakable_list_.Add(awakable, signals, context);
+ return MOJO_RESULT_OK;
+}
+
+void SimpleDispatcher::RemoveAwakableImplNoLock(
+ Awakable* awakable,
+ HandleSignalsState* signals_state) {
+ lock().AssertAcquired();
+ awakable_list_.Remove(awakable);
+ if (signals_state)
+ *signals_state = GetHandleSignalsStateImplNoLock();
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/simple_dispatcher.h b/mojo/edk/system/simple_dispatcher.h
new file mode 100644
index 0000000..ccef564
--- /dev/null
+++ b/mojo/edk/system/simple_dispatcher.h
@@ -0,0 +1,51 @@
+// 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 MOJO_EDK_SYSTEM_SIMPLE_DISPATCHER_H_
+#define MOJO_EDK_SYSTEM_SIMPLE_DISPATCHER_H_
+
+#include <list>
+
+#include "mojo/edk/system/awakable_list.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// A base class for simple dispatchers. "Simple" means that there's a one-to-one
+// correspondence between handles and dispatchers (see the explanatory comment
+// in core.cc). This class implements the standard waiter-signalling mechanism
+// in that case.
+class MOJO_SYSTEM_IMPL_EXPORT SimpleDispatcher : public Dispatcher {
+ protected:
+ SimpleDispatcher();
+ ~SimpleDispatcher() override;
+
+ // To be called by subclasses when the state changes (so
+ // |GetHandleSignalsStateImplNoLock()| should be checked again). Must be
+ // called under lock.
+ void HandleSignalsStateChangedNoLock();
+
+ // |Dispatcher| protected methods:
+ void CancelAllAwakablesNoLock() override;
+ MojoResult AddAwakableImplNoLock(Awakable* awakable,
+ MojoHandleSignals signals,
+ uint32_t context,
+ HandleSignalsState* signals_state) override;
+ void RemoveAwakableImplNoLock(Awakable* awakable,
+ HandleSignalsState* signals_state) override;
+
+ private:
+ // Protected by |lock()|:
+ AwakableList awakable_list_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(SimpleDispatcher);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_SIMPLE_DISPATCHER_H_
diff --git a/mojo/edk/system/simple_dispatcher_unittest.cc b/mojo/edk/system/simple_dispatcher_unittest.cc
new file mode 100644
index 0000000..7d7b9c2
--- /dev/null
+++ b/mojo/edk/system/simple_dispatcher_unittest.cc
@@ -0,0 +1,604 @@
+// 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.
+
+// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a
+// heavily-loaded system). Sorry. |test::EpsilonDeadline()| may be increased to
+// increase tolerance and reduce observed flakiness (though doing so reduces the
+// meaningfulness of the test).
+
+#include "mojo/edk/system/simple_dispatcher.h"
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/edk/system/waiter_test_utils.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+class MockSimpleDispatcher final : public SimpleDispatcher {
+ public:
+ MockSimpleDispatcher()
+ : state_(MOJO_HANDLE_SIGNAL_NONE,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE) {}
+
+ void SetSatisfiedSignals(MojoHandleSignals new_satisfied_signals) {
+ base::AutoLock locker(lock());
+
+ // Any new signals that are set should be satisfiable.
+ CHECK_EQ(new_satisfied_signals & ~state_.satisfied_signals,
+ new_satisfied_signals & ~state_.satisfied_signals &
+ state_.satisfiable_signals);
+
+ if (new_satisfied_signals == state_.satisfied_signals)
+ return;
+
+ state_.satisfied_signals = new_satisfied_signals;
+ HandleSignalsStateChangedNoLock();
+ }
+
+ void SetSatisfiableSignals(MojoHandleSignals new_satisfiable_signals) {
+ base::AutoLock locker(lock());
+
+ // Satisfied implies satisfiable.
+ CHECK_EQ(new_satisfiable_signals & state_.satisfied_signals,
+ state_.satisfied_signals);
+
+ if (new_satisfiable_signals == state_.satisfiable_signals)
+ return;
+
+ state_.satisfiable_signals = new_satisfiable_signals;
+ HandleSignalsStateChangedNoLock();
+ }
+
+ Type GetType() const override { return Type::UNKNOWN; }
+
+ private:
+ friend class base::RefCountedThreadSafe<MockSimpleDispatcher>;
+ ~MockSimpleDispatcher() override {}
+
+ scoped_refptr<Dispatcher> CreateEquivalentDispatcherAndCloseImplNoLock()
+ override {
+ scoped_refptr<MockSimpleDispatcher> rv(new MockSimpleDispatcher());
+ rv->state_ = state_;
+ return scoped_refptr<Dispatcher>(rv.get());
+ }
+
+ // |Dispatcher| override:
+ HandleSignalsState GetHandleSignalsStateImplNoLock() const override {
+ lock().AssertAcquired();
+ return state_;
+ }
+
+ // Protected by |lock()|:
+ HandleSignalsState state_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MockSimpleDispatcher);
+};
+
+#if defined(OS_WIN)
+// http://crbug.com/396404
+#define MAYBE_Basic DISABLED_Basic
+#else
+#define MAYBE_Basic Basic
+#endif
+TEST(SimpleDispatcherTest, MAYBE_Basic) {
+ test::Stopwatch stopwatch;
+
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ Waiter w;
+ uint32_t context = 0;
+ HandleSignalsState hss;
+
+ // Try adding a readable waiter when already readable.
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+ // Shouldn't need to remove the waiter (it was not added).
+
+ // Wait (forever) for writable when already writable.
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 1, nullptr));
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_WRITABLE);
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_OK, w.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ EXPECT_EQ(1u, context);
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for zero time for writable when already writable.
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 2, nullptr));
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_WRITABLE);
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_OK, w.Wait(0, &context));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ EXPECT_EQ(2u, context);
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for non-zero, finite time for writable when already writable.
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 3, nullptr));
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_WRITABLE);
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_OK, w.Wait(2 * test::EpsilonDeadline(), &context));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ EXPECT_EQ(3u, context);
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for zero time for writable when not writable (will time out).
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4, nullptr));
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, w.Wait(0, nullptr));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for non-zero, finite time for writable when not writable (will time
+ // out).
+ w.Init();
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 5, nullptr));
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ w.Wait(2 * test::EpsilonDeadline(), nullptr));
+ MojoDeadline elapsed = stopwatch.Elapsed();
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+}
+
+TEST(SimpleDispatcherTest, BasicUnsatisfiable) {
+ test::Stopwatch stopwatch;
+
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ Waiter w;
+ uint32_t context = 0;
+ HandleSignalsState hss;
+
+ // Try adding a writable waiter when it can never be writable.
+ w.Init();
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ d->SetSatisfiedSignals(0);
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 1, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+ // Shouldn't need to remove the waiter (it was not added).
+
+ // Wait (forever) for writable and then it becomes never writable.
+ w.Init();
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 2, nullptr));
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ w.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ EXPECT_EQ(2u, context);
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Wait for zero time for writable and then it becomes never writable.
+ w.Init();
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 3, nullptr));
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, w.Wait(0, &context));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ EXPECT_EQ(3u, context);
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ // Wait for non-zero, finite time for writable and then it becomes never
+ // writable.
+ w.Init();
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE |
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4, nullptr));
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ w.Wait(2 * test::EpsilonDeadline(), &context));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ EXPECT_EQ(4u, context);
+ hss = HandleSignalsState();
+ d->RemoveAwakable(&w, &hss);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfiable_signals);
+
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+}
+
+TEST(SimpleDispatcherTest, BasicClosed) {
+ test::Stopwatch stopwatch;
+
+ scoped_refptr<MockSimpleDispatcher> d;
+ Waiter w;
+ uint32_t context = 0;
+ HandleSignalsState hss;
+
+ // Try adding a writable waiter when the dispatcher has been closed.
+ d = new MockSimpleDispatcher();
+ w.Init();
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ hss = HandleSignalsState();
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 1, &hss));
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+ // Shouldn't need to remove the waiter (it was not added).
+
+ // Wait (forever) for writable and then the dispatcher is closed.
+ d = new MockSimpleDispatcher();
+ w.Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 2, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, w.Wait(MOJO_DEADLINE_INDEFINITE, &context));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ EXPECT_EQ(2u, context);
+ // Don't need to remove waiters from closed dispatchers.
+
+ // Wait for zero time for writable and then the dispatcher is closed.
+ d = new MockSimpleDispatcher();
+ w.Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 3, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, w.Wait(0, &context));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ EXPECT_EQ(3u, context);
+ // Don't need to remove waiters from closed dispatchers.
+
+ // Wait for non-zero, finite time for writable and then the dispatcher is
+ // closed.
+ d = new MockSimpleDispatcher();
+ w.Init();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ d->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 4, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_CANCELLED,
+ w.Wait(2 * test::EpsilonDeadline(), &context));
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ EXPECT_EQ(4u, context);
+ // Don't need to remove waiters from closed dispatchers.
+}
+
+#if defined(OS_WIN)
+// http://crbug.com/396393
+#define MAYBE_BasicThreaded DISABLED_BasicThreaded
+#else
+#define MAYBE_BasicThreaded BasicThreaded
+#endif
+TEST(SimpleDispatcherTest, MAYBE_BasicThreaded) {
+ test::Stopwatch stopwatch;
+ bool did_wait;
+ MojoResult result;
+ uint32_t context;
+ HandleSignalsState hss;
+
+ // Wait for readable (already readable).
+ {
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ {
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ test::WaiterThread thread(d, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, 1, &did_wait, &result,
+ &context, &hss);
+ stopwatch.Start();
+ thread.Start();
+ } // Joins the thread.
+ // If we closed earlier, then probably we'd get a |MOJO_RESULT_CANCELLED|.
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ }
+ EXPECT_LT(stopwatch.Elapsed(), test::EpsilonDeadline());
+ EXPECT_FALSE(did_wait);
+ EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, result);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for readable and becomes readable after some time.
+ {
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ {
+ test::WaiterThread thread(d, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, 2, &did_wait, &result,
+ &context, &hss);
+ stopwatch.Start();
+ thread.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ } // Joins the thread.
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ }
+ MojoDeadline elapsed = stopwatch.Elapsed();
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
+ EXPECT_TRUE(did_wait);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(2u, context);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+
+ // Wait for readable and becomes never-readable after some time.
+ {
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ {
+ test::WaiterThread thread(d, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, 3, &did_wait, &result,
+ &context, &hss);
+ stopwatch.Start();
+ thread.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_NONE);
+ } // Joins the thread.
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ }
+ elapsed = stopwatch.Elapsed();
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
+ EXPECT_TRUE(did_wait);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
+ EXPECT_EQ(3u, context);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ // Wait for readable and dispatcher gets closed.
+ {
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ test::WaiterThread thread(d, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_DEADLINE_INDEFINITE, 4, &did_wait, &result,
+ &context, &hss);
+ stopwatch.Start();
+ thread.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ } // Joins the thread.
+ elapsed = stopwatch.Elapsed();
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
+ EXPECT_TRUE(did_wait);
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
+ EXPECT_EQ(4u, context);
+ EXPECT_EQ(0u, hss.satisfied_signals);
+ EXPECT_EQ(0u, hss.satisfiable_signals);
+
+ // Wait for readable and times out.
+ {
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ {
+ test::WaiterThread thread(d, MOJO_HANDLE_SIGNAL_READABLE,
+ 2 * test::EpsilonDeadline(), 5, &did_wait,
+ &result, &context, &hss);
+ stopwatch.Start();
+ thread.Start();
+ test::Sleep(1 * test::EpsilonDeadline());
+ // Not what we're waiting for.
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_WRITABLE);
+ } // Joins the thread (after its wait times out).
+ // If we closed earlier, then probably we'd get a |MOJO_RESULT_CANCELLED|.
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ }
+ elapsed = stopwatch.Elapsed();
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
+ EXPECT_TRUE(did_wait);
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, result);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
+ hss.satisfiable_signals);
+}
+
+#if defined(OS_WIN)
+// http://crbug.com/387124
+#define MAYBE_MultipleWaiters DISABLED_MultipleWaiters
+#else
+#define MAYBE_MultipleWaiters MultipleWaiters
+#endif
+TEST(SimpleDispatcherTest, MAYBE_MultipleWaiters) {
+ static const uint32_t kNumWaiters = 20;
+
+ bool did_wait[kNumWaiters];
+ MojoResult result[kNumWaiters];
+ uint32_t context[kNumWaiters];
+ HandleSignalsState hss[kNumWaiters];
+
+ // All wait for readable and becomes readable after some time.
+ {
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ ScopedVector<test::WaiterThread> threads;
+ for (uint32_t i = 0; i < kNumWaiters; i++) {
+ threads.push_back(new test::WaiterThread(
+ d, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE, i,
+ &did_wait[i], &result[i], &context[i], &hss[i]));
+ threads.back()->Start();
+ }
+ test::Sleep(2 * test::EpsilonDeadline());
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ } // Joins the threads.
+ for (uint32_t i = 0; i < kNumWaiters; i++) {
+ EXPECT_TRUE(did_wait[i]) << i;
+ EXPECT_EQ(MOJO_RESULT_OK, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+
+ // Some wait for readable, some for writable, and becomes readable after some
+ // time.
+ {
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ ScopedVector<test::WaiterThread> threads;
+ for (uint32_t i = 0; i < kNumWaiters / 2; i++) {
+ threads.push_back(new test::WaiterThread(
+ d, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE, i,
+ &did_wait[i], &result[i], &context[i], &hss[i]));
+ threads.back()->Start();
+ }
+ for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) {
+ threads.push_back(new test::WaiterThread(
+ d, MOJO_HANDLE_SIGNAL_WRITABLE, MOJO_DEADLINE_INDEFINITE, i,
+ &did_wait[i], &result[i], &context[i], &hss[i]));
+ threads.back()->Start();
+ }
+ test::Sleep(2 * test::EpsilonDeadline());
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ // This will wake up the ones waiting to write.
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ } // Joins the threads.
+ for (uint32_t i = 0; i < kNumWaiters / 2; i++) {
+ EXPECT_TRUE(did_wait[i]) << i;
+ EXPECT_EQ(MOJO_RESULT_OK, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+ for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) {
+ EXPECT_TRUE(did_wait[i]) << i;
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+
+ // Some wait for readable, some for writable, and becomes readable and
+ // never-writable after some time.
+ {
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ ScopedVector<test::WaiterThread> threads;
+ for (uint32_t i = 0; i < kNumWaiters / 2; i++) {
+ threads.push_back(new test::WaiterThread(
+ d, MOJO_HANDLE_SIGNAL_READABLE, MOJO_DEADLINE_INDEFINITE, i,
+ &did_wait[i], &result[i], &context[i], &hss[i]));
+ threads.back()->Start();
+ }
+ for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) {
+ threads.push_back(new test::WaiterThread(
+ d, MOJO_HANDLE_SIGNAL_WRITABLE, MOJO_DEADLINE_INDEFINITE, i,
+ &did_wait[i], &result[i], &context[i], &hss[i]));
+ threads.back()->Start();
+ }
+ test::Sleep(1 * test::EpsilonDeadline());
+ d->SetSatisfiableSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ test::Sleep(1 * test::EpsilonDeadline());
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ } // Joins the threads.
+ for (uint32_t i = 0; i < kNumWaiters / 2; i++) {
+ EXPECT_TRUE(did_wait[i]) << i;
+ EXPECT_EQ(MOJO_RESULT_OK, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+ for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) {
+ EXPECT_TRUE(did_wait[i]) << i;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+
+ // Some wait for readable, some for writable, and becomes readable after some
+ // time.
+ {
+ scoped_refptr<MockSimpleDispatcher> d(new MockSimpleDispatcher());
+ ScopedVector<test::WaiterThread> threads;
+ for (uint32_t i = 0; i < kNumWaiters / 2; i++) {
+ threads.push_back(new test::WaiterThread(
+ d, MOJO_HANDLE_SIGNAL_READABLE, 3 * test::EpsilonDeadline(), i,
+ &did_wait[i], &result[i], &context[i], &hss[i]));
+ threads.back()->Start();
+ }
+ for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) {
+ threads.push_back(new test::WaiterThread(
+ d, MOJO_HANDLE_SIGNAL_WRITABLE, 1 * test::EpsilonDeadline(), i,
+ &did_wait[i], &result[i], &context[i], &hss[i]));
+ threads.back()->Start();
+ }
+ test::Sleep(2 * test::EpsilonDeadline());
+ d->SetSatisfiedSignals(MOJO_HANDLE_SIGNAL_READABLE);
+ // All those waiting for writable should have timed out.
+ EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+ } // Joins the threads.
+ for (uint32_t i = 0; i < kNumWaiters / 2; i++) {
+ EXPECT_TRUE(did_wait[i]) << i;
+ EXPECT_EQ(MOJO_RESULT_OK, result[i]) << i;
+ EXPECT_EQ(i, context[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+ for (uint32_t i = kNumWaiters / 2; i < kNumWaiters; i++) {
+ EXPECT_TRUE(did_wait[i]) << i;
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, result[i]) << i;
+ // Since we closed before joining, we can't say much about what each thread
+ // saw as the state.
+ }
+}
+
+// TODO(vtl): Stress test?
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/system_impl_export.h b/mojo/edk/system/system_impl_export.h
new file mode 100644
index 0000000..5bbf005
--- /dev/null
+++ b/mojo/edk/system/system_impl_export.h
@@ -0,0 +1,29 @@
+// 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 MOJO_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_
+#define MOJO_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION)
+#define MOJO_SYSTEM_IMPL_EXPORT __declspec(dllexport)
+#else
+#define MOJO_SYSTEM_IMPL_EXPORT __declspec(dllimport)
+#endif // defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION)
+#define MOJO_SYSTEM_IMPL_EXPORT __attribute__((visibility("default")))
+#else
+#define MOJO_SYSTEM_IMPL_EXPORT
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define MOJO_SYSTEM_IMPL_EXPORT
+#endif
+
+#endif // MOJO_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_
diff --git a/mojo/edk/system/test_utils.cc b/mojo/edk/system/test_utils.cc
new file mode 100644
index 0000000..49e346b
--- /dev/null
+++ b/mojo/edk/system/test_utils.cc
@@ -0,0 +1,83 @@
+// 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 "mojo/edk/system/test_utils.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h" // For |Sleep()|.
+#include "build/build_config.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+MojoDeadline DeadlineFromMilliseconds(unsigned milliseconds) {
+ return static_cast<MojoDeadline>(milliseconds) * 1000;
+}
+
+MojoDeadline EpsilonDeadline() {
+// Originally, our epsilon timeout was 10 ms, which was mostly fine but flaky on
+// some Windows bots. I don't recall ever seeing flakes on other bots. At 30 ms
+// tests seem reliable on Windows bots, but not at 25 ms. We'd like this timeout
+// to be as small as possible (see the description in the .h file).
+//
+// Currently, |tiny_timeout()| is usually 100 ms (possibly scaled under ASAN,
+// etc.). Based on this, set it to (usually be) 30 ms on Windows and 20 ms
+// elsewhere.
+#if defined(OS_WIN) || defined(OS_ANDROID)
+ return (TinyDeadline() * 3) / 10;
+#else
+ return (TinyDeadline() * 2) / 10;
+#endif
+}
+
+MojoDeadline TinyDeadline() {
+ return static_cast<MojoDeadline>(
+ TestTimeouts::tiny_timeout().InMicroseconds());
+}
+
+MojoDeadline ActionDeadline() {
+ return static_cast<MojoDeadline>(
+ TestTimeouts::action_timeout().InMicroseconds());
+}
+
+void Sleep(MojoDeadline deadline) {
+ CHECK_LE(deadline,
+ static_cast<MojoDeadline>(std::numeric_limits<int64_t>::max()));
+ base::PlatformThread::Sleep(
+ base::TimeDelta::FromMicroseconds(static_cast<int64_t>(deadline)));
+}
+
+Stopwatch::Stopwatch() {
+}
+
+Stopwatch::~Stopwatch() {
+}
+
+void Stopwatch::Start() {
+ start_time_ = base::TimeTicks::Now();
+}
+
+MojoDeadline Stopwatch::Elapsed() {
+ int64_t result = (base::TimeTicks::Now() - start_time_).InMicroseconds();
+ // |DCHECK_GE|, not |CHECK_GE|, since this may be performance-important.
+ DCHECK_GE(result, 0);
+ return static_cast<MojoDeadline>(result);
+}
+
+
+MojoSystemTest::MojoSystemTest()
+ : test_io_thread_(base::TestIOThread::kAutoStart),
+ ipc_support_(test_io_thread_.task_runner()) {
+}
+
+MojoSystemTest::~MojoSystemTest() {
+}
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/test_utils.h b/mojo/edk/system/test_utils.h
new file mode 100644
index 0000000..cf244b3
--- /dev/null
+++ b/mojo/edk/system/test_utils.h
@@ -0,0 +1,78 @@
+// 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 MOJO_EDK_SYSTEM_TEST_UTILS_H_
+#define MOJO_EDK_SYSTEM_TEST_UTILS_H_
+
+#include "base/test/test_io_thread.h"
+#include "base/time/time.h"
+#include "mojo/edk/test/scoped_ipc_support.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+MojoDeadline DeadlineFromMilliseconds(unsigned milliseconds);
+
+// A timeout smaller than |TestTimeouts::tiny_timeout()|, as a |MojoDeadline|.
+// Warning: This may lead to flakiness, but this is unavoidable if, e.g., you're
+// trying to ensure that functions with timeouts are reasonably accurate. We
+// want this to be as small as possible without causing too much flakiness.
+MojoDeadline EpsilonDeadline();
+
+// |TestTimeouts::tiny_timeout()|, as a |MojoDeadline|. (Expect this to be on
+// the order of 100 ms.)
+MojoDeadline TinyDeadline();
+
+// |TestTimeouts::action_timeout()|, as a |MojoDeadline|. (Expect this to be on
+// the order of 10 s.)
+MojoDeadline ActionDeadline();
+
+// Sleeps for at least the specified duration.
+void Sleep(MojoDeadline deadline);
+
+// Stopwatch -------------------------------------------------------------------
+
+// A simple "stopwatch" for measuring time elapsed from a given starting point.
+class Stopwatch {
+ public:
+ Stopwatch();
+ ~Stopwatch();
+
+ void Start();
+ // Returns the amount of time elapsed since the last call to |Start()| (in
+ // microseconds).
+ MojoDeadline Elapsed();
+
+ private:
+ base::TimeTicks start_time_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Stopwatch);
+};
+
+// A base class which initializes and shuts down the necessary objects so that
+// Mojo system calls can be made.
+class MojoSystemTest : public testing::Test {
+ public:
+ MojoSystemTest();
+ ~MojoSystemTest() override;
+
+ base::TestIOThread* test_io_thread() { return &test_io_thread_; }
+
+ private:
+ base::MessageLoop message_loop_;
+ base::TestIOThread test_io_thread_;
+ test::ScopedIPCSupport ipc_support_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MojoSystemTest);
+};
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_TEST_UTILS_H_
diff --git a/mojo/edk/system/transport_data.cc b/mojo/edk/system/transport_data.cc
new file mode 100644
index 0000000..d751511
--- /dev/null
+++ b/mojo/edk/system/transport_data.cc
@@ -0,0 +1,336 @@
+// Copyright 2014 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 "mojo/edk/system/transport_data.h"
+
+#include "base/logging.h"
+#include "mojo/edk/system/configuration.h"
+#include "mojo/edk/system/message_in_transit.h"
+#include "mojo/edk/system/raw_channel.h"
+
+namespace mojo {
+namespace edk {
+
+// The maximum amount of space needed per platform handle.
+// (|{Channel,RawChannel}::GetSerializedPlatformHandleSize()| should always
+// return a value which is at most this. This is only used to calculate
+// |TransportData::kMaxBufferSize|. This value should be a multiple of the
+// alignment in order to simplify calculations, even though the actual amount of
+// space needed need not be a multiple of the alignment.
+const size_t kMaxSizePerPlatformHandle = 16;
+static_assert(kMaxSizePerPlatformHandle % MessageInTransit::kMessageAlignment ==
+ 0,
+ "kMaxSizePerPlatformHandle not a multiple of alignment");
+
+MOJO_STATIC_CONST_MEMBER_DEFINITION const size_t
+ TransportData::kMaxSerializedDispatcherSize;
+MOJO_STATIC_CONST_MEMBER_DEFINITION const size_t
+ TransportData::kMaxSerializedDispatcherPlatformHandles;
+
+// static
+size_t TransportData::GetMaxBufferSize() {
+ // In additional to the header, for each attached (Mojo) handle there'll be a
+ // handle table entry and serialized dispatcher data.
+ return sizeof(Header) +
+ GetConfiguration().max_message_num_handles *
+ (sizeof(HandleTableEntry) + kMaxSerializedDispatcherSize) +
+ GetMaxPlatformHandles() * kMaxSizePerPlatformHandle;
+}
+
+// static
+size_t TransportData::GetMaxPlatformHandles() {
+ return GetConfiguration().max_message_num_handles *
+ kMaxSerializedDispatcherPlatformHandles;
+}
+
+struct TransportData::PrivateStructForCompileAsserts {
+ static_assert(sizeof(Header) % MessageInTransit::kMessageAlignment == 0,
+ "sizeof(MessageInTransit::Header) not a multiple of alignment");
+ static_assert(kMaxSerializedDispatcherSize %
+ MessageInTransit::kMessageAlignment ==
+ 0,
+ "kMaxSerializedDispatcherSize not a multiple of alignment");
+ static_assert(sizeof(HandleTableEntry) %
+ MessageInTransit::kMessageAlignment ==
+ 0,
+ "sizeof(MessageInTransit::HandleTableEntry) not a multiple of "
+ "alignment");
+};
+
+TransportData::TransportData(scoped_ptr<DispatcherVector> dispatchers)
+ : buffer_size_() {
+ DCHECK(dispatchers);
+
+ const size_t num_handles = dispatchers->size();
+ DCHECK_GT(num_handles, 0u);
+
+ // The offset to the start of the (Mojo) handle table.
+ const size_t handle_table_start_offset = sizeof(Header);
+ // The offset to the start of the serialized dispatcher data.
+ const size_t serialized_dispatcher_start_offset =
+ handle_table_start_offset + num_handles * sizeof(HandleTableEntry);
+ // The estimated size of the secondary buffer. We compute this estimate below.
+ // It must be at least as big as the (eventual) actual size.
+ size_t estimated_size = serialized_dispatcher_start_offset;
+ size_t estimated_num_platform_handles = 0;
+#if DCHECK_IS_ON()
+ std::vector<size_t> all_max_sizes(num_handles);
+ std::vector<size_t> all_max_platform_handles(num_handles);
+#endif
+ for (size_t i = 0; i < num_handles; i++) {
+ if (Dispatcher* dispatcher = (*dispatchers)[i].get()) {
+ size_t max_size = 0;
+ size_t max_platform_handles = 0;
+ Dispatcher::TransportDataAccess::StartSerialize(
+ dispatcher, &max_size, &max_platform_handles);
+
+ DCHECK_LE(max_size, kMaxSerializedDispatcherSize);
+ estimated_size += MessageInTransit::RoundUpMessageAlignment(max_size);
+ DCHECK_LE(estimated_size, GetMaxBufferSize());
+
+ DCHECK_LE(max_platform_handles, kMaxSerializedDispatcherPlatformHandles);
+ estimated_num_platform_handles += max_platform_handles;
+ DCHECK_LE(estimated_num_platform_handles, GetMaxPlatformHandles());
+
+#if DCHECK_IS_ON()
+ all_max_sizes[i] = max_size;
+ all_max_platform_handles[i] = max_platform_handles;
+#endif
+ }
+ }
+
+ size_t size_per_platform_handle = 0;
+ if (estimated_num_platform_handles > 0) {
+ size_per_platform_handle = RawChannel::GetSerializedPlatformHandleSize();
+ DCHECK_LE(size_per_platform_handle, kMaxSizePerPlatformHandle);
+ estimated_size += estimated_num_platform_handles * size_per_platform_handle;
+ estimated_size = MessageInTransit::RoundUpMessageAlignment(estimated_size);
+ DCHECK_LE(estimated_size, GetMaxBufferSize());
+ }
+
+ buffer_.reset(static_cast<char*>(
+ base::AlignedAlloc(estimated_size, MessageInTransit::kMessageAlignment)));
+ // Entirely clear out the secondary buffer, since then we won't have to worry
+ // about clearing padding or unused space (e.g., if a dispatcher fails to
+ // serialize).
+ memset(buffer_.get(), 0, estimated_size);
+
+ if (estimated_num_platform_handles > 0) {
+ DCHECK(!platform_handles_);
+ platform_handles_.reset(new PlatformHandleVector());
+ }
+
+ Header* header = reinterpret_cast<Header*>(buffer_.get());
+ header->num_handles = static_cast<uint32_t>(num_handles);
+ // (Okay to leave |platform_handle_table_offset|, |num_platform_handles|, and
+ // |unused| be zero; we'll set the former two later if necessary.)
+
+ HandleTableEntry* handle_table = reinterpret_cast<HandleTableEntry*>(
+ buffer_.get() + handle_table_start_offset);
+ size_t current_offset = serialized_dispatcher_start_offset;
+ for (size_t i = 0; i < num_handles; i++) {
+ Dispatcher* dispatcher = (*dispatchers)[i].get();
+ if (!dispatcher) {
+ static_assert(static_cast<int32_t>(Dispatcher::Type::UNKNOWN) == 0,
+ "Value of Dispatcher::Type::UNKNOWN must be 0");
+ continue;
+ }
+
+#if DCHECK_IS_ON()
+ size_t old_platform_handles_size =
+ platform_handles_ ? platform_handles_->size() : 0;
+#endif
+
+ void* destination = buffer_.get() + current_offset;
+ size_t actual_size = 0;
+ if (Dispatcher::TransportDataAccess::EndSerializeAndClose(
+ dispatcher, destination, &actual_size,
+ platform_handles_.get())) {
+ handle_table[i].type = static_cast<int32_t>(dispatcher->GetType());
+ handle_table[i].offset = static_cast<uint32_t>(current_offset);
+ handle_table[i].size = static_cast<uint32_t>(actual_size);
+// (Okay to not set |unused| since we cleared the entire buffer.)
+
+#if DCHECK_IS_ON()
+ DCHECK_LE(actual_size, all_max_sizes[i]);
+ DCHECK_LE(platform_handles_
+ ? (platform_handles_->size() - old_platform_handles_size)
+ : 0,
+ all_max_platform_handles[i]);
+#endif
+ } else {
+ // Nothing to do on failure, since |buffer_| was cleared, and
+ // |Type::UNKNOWN| is zero. The handle was simply closed.
+ LOG(ERROR) << "Failed to serialize handle to remote message pipe";
+ }
+
+ current_offset += MessageInTransit::RoundUpMessageAlignment(actual_size);
+ DCHECK_LE(current_offset, estimated_size);
+ DCHECK_LE(platform_handles_ ? platform_handles_->size() : 0,
+ estimated_num_platform_handles);
+ }
+
+ if (platform_handles_ && platform_handles_->size() > 0) {
+ header->platform_handle_table_offset =
+ static_cast<uint32_t>(current_offset);
+ header->num_platform_handles =
+ static_cast<uint32_t>(platform_handles_->size());
+ current_offset += platform_handles_->size() * size_per_platform_handle;
+ current_offset = MessageInTransit::RoundUpMessageAlignment(current_offset);
+ }
+
+ // There's no aligned realloc, so it's no good way to release unused space (if
+ // we overshot our estimated space requirements).
+ buffer_size_ = current_offset;
+
+ // |dispatchers_| will be destroyed as it goes out of scope.
+}
+
+TransportData::TransportData(
+ ScopedPlatformHandleVectorPtr platform_handles,
+ size_t serialized_platform_handle_size)
+ : buffer_size_(), platform_handles_(platform_handles.Pass()) {
+ buffer_size_ = MessageInTransit::RoundUpMessageAlignment(
+ sizeof(Header) +
+ platform_handles_->size() * serialized_platform_handle_size);
+ buffer_.reset(static_cast<char*>(
+ base::AlignedAlloc(buffer_size_, MessageInTransit::kMessageAlignment)));
+ memset(buffer_.get(), 0, buffer_size_);
+
+ Header* header = reinterpret_cast<Header*>(buffer_.get());
+ header->platform_handle_table_offset = static_cast<uint32_t>(sizeof(Header));
+ header->num_platform_handles =
+ static_cast<uint32_t>(platform_handles_->size());
+}
+
+TransportData::~TransportData() {
+}
+
+// static
+const char* TransportData::ValidateBuffer(
+ size_t serialized_platform_handle_size,
+ const void* buffer,
+ size_t buffer_size) {
+ DCHECK(buffer);
+ DCHECK_GT(buffer_size, 0u);
+
+ // Always make sure that the buffer size is sane; if it's not, someone's
+ // messing with us.
+ if (buffer_size < sizeof(Header) || buffer_size > GetMaxBufferSize() ||
+ buffer_size % MessageInTransit::kMessageAlignment != 0)
+ return "Invalid message secondary buffer size";
+
+ const Header* header = static_cast<const Header*>(buffer);
+ const size_t num_handles = header->num_handles;
+
+ // Sanity-check |num_handles| (before multiplying it against anything).
+ if (num_handles > GetConfiguration().max_message_num_handles)
+ return "Message handle payload too large";
+
+ if (buffer_size < sizeof(Header) + num_handles * sizeof(HandleTableEntry))
+ return "Message secondary buffer too small";
+
+ if (header->num_platform_handles == 0) {
+ // Then |platform_handle_table_offset| should also be zero.
+ if (header->platform_handle_table_offset != 0) {
+ return "Message has no handles attached, but platform handle table "
+ "present";
+ }
+ } else {
+ if (header->num_platform_handles >
+ GetConfiguration().max_message_num_handles *
+ kMaxSerializedDispatcherPlatformHandles)
+ return "Message has too many platform handles attached";
+
+ static const char kInvalidPlatformHandleTableOffset[] =
+ "Message has invalid platform handle table offset";
+ // This doesn't check that the platform handle table doesn't alias other
+ // stuff, but it doesn't matter, since it's all read-only.
+ if (header->platform_handle_table_offset %
+ MessageInTransit::kMessageAlignment !=
+ 0)
+ return kInvalidPlatformHandleTableOffset;
+
+ // ">" instead of ">=" since the size per handle may be zero.
+ if (header->platform_handle_table_offset > buffer_size)
+ return kInvalidPlatformHandleTableOffset;
+
+ // We already checked |platform_handle_table_offset| and
+ // |num_platform_handles|, so the addition and multiplication are okay.
+ if (header->platform_handle_table_offset +
+ header->num_platform_handles * serialized_platform_handle_size >
+ buffer_size)
+ return kInvalidPlatformHandleTableOffset;
+ }
+
+ const HandleTableEntry* handle_table =
+ reinterpret_cast<const HandleTableEntry*>(
+ static_cast<const char*>(buffer) + sizeof(Header));
+ static const char kInvalidSerializedDispatcher[] =
+ "Message contains invalid serialized dispatcher";
+ for (size_t i = 0; i < num_handles; i++) {
+ size_t offset = handle_table[i].offset;
+ if (offset % MessageInTransit::kMessageAlignment != 0)
+ return kInvalidSerializedDispatcher;
+
+ size_t size = handle_table[i].size;
+ if (size > kMaxSerializedDispatcherSize || size > buffer_size)
+ return kInvalidSerializedDispatcher;
+
+ // Note: This is an overflow-safe check for |offset + size > buffer_size|
+ // (we know that |size <= buffer_size| from the previous check).
+ if (offset > buffer_size - size)
+ return kInvalidSerializedDispatcher;
+ }
+
+ return nullptr;
+}
+
+// static
+void TransportData::GetPlatformHandleTable(const void* transport_data_buffer,
+ size_t* num_platform_handles,
+ const void** platform_handle_table) {
+ DCHECK(transport_data_buffer);
+ DCHECK(num_platform_handles);
+ DCHECK(platform_handle_table);
+
+ const Header* header = static_cast<const Header*>(transport_data_buffer);
+ *num_platform_handles = header->num_platform_handles;
+ *platform_handle_table = static_cast<const char*>(transport_data_buffer) +
+ header->platform_handle_table_offset;
+}
+
+// static
+scoped_ptr<DispatcherVector> TransportData::DeserializeDispatchers(
+ const void* buffer,
+ size_t buffer_size,
+ ScopedPlatformHandleVectorPtr platform_handles) {
+ DCHECK(buffer);
+ DCHECK_GT(buffer_size, 0u);
+
+ const Header* header = static_cast<const Header*>(buffer);
+ const size_t num_handles = header->num_handles;
+ scoped_ptr<DispatcherVector> dispatchers(new DispatcherVector(num_handles));
+
+ const HandleTableEntry* handle_table =
+ reinterpret_cast<const HandleTableEntry*>(
+ static_cast<const char*>(buffer) + sizeof(Header));
+ for (size_t i = 0; i < num_handles; i++) {
+ size_t offset = handle_table[i].offset;
+ size_t size = handle_table[i].size;
+ // Should already have been checked by |ValidateBuffer()|:
+ DCHECK_EQ(offset % MessageInTransit::kMessageAlignment, 0u);
+ DCHECK_LE(offset, buffer_size);
+ DCHECK_LE(offset + size, buffer_size);
+
+ const void* source = static_cast<const char*>(buffer) + offset;
+ (*dispatchers)[i] = Dispatcher::TransportDataAccess::Deserialize(
+ handle_table[i].type, source, size, platform_handles.get());
+ }
+
+ return dispatchers.Pass();
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/transport_data.h b/mojo/edk/system/transport_data.h
new file mode 100644
index 0000000..7d51ee9
--- /dev/null
+++ b/mojo/edk/system/transport_data.h
@@ -0,0 +1,188 @@
+// Copyright 2014 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 MOJO_EDK_SYSTEM_TRANSPORT_DATA_H_
+#define MOJO_EDK_SYSTEM_TRANSPORT_DATA_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/memory/aligned_memory.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/platform_handle_vector.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// This class is used by |MessageInTransit| to represent handles (|Dispatcher|s)
+// in various stages of serialization.
+//
+// The stages are:
+// - Before reaching |TransportData|: Turn |DispatcherTransport|s into
+// |Dispatcher|s that are "owned" by (and attached to) a |MessageInTransit|.
+// This invalidates the handles in the space of the sending application
+// (and, e.g., if another thread is waiting on such a handle, it'll be
+// notified of this invalidation).
+// - Serialize these dispatchers into the |TransportData|: First, for each
+// attached dispatcher, there's an entry in the |TransportData|'s "handle
+// table", which points to a segment of (dispatcher-type-dependent) data.
+// - During the serialization of the dispatchers, |PlatformHandle|s may be
+// detached from the dispatchers and attached to the |TransportData|.
+// - Before sending the |MessageInTransit|, including its main buffer and the
+// |TransportData|'s buffer, the |Channel| sends any |PlatformHandle|s (in a
+// platform-, and possibly sandbox-situation-, specific way) first. In doing
+// so, it appends a "platform handle table" to the |TransportData|
+// containing information about how to deserialize these |PlatformHandle|s.
+// - Finally, at this point, to send the |MessageInTransit|, there only
+// remains "inert" data: the |MessageInTransit|'s main buffer and data from
+// the |TransportData|, consisting of the "handle table" (one entry for each
+// attached dispatcher), dispatcher-type-specific data (one segment for each
+// entry in the "handle table"), and the "platform handle table" (one entry
+// for each attached |PlatformHandle|).
+//
+// To receive a message (|MessageInTransit|), the "reverse" happens:
+// - On POSIX, receive and buffer |PlatformHandle|s (i.e., FDs), which were
+// sent before the "inert" data.
+// - Receive the "inert" data from the |MessageInTransit|. Examine its
+// "platform handle table". On POSIX, match its entries with the buffered
+// |PlatformHandle|s, which were previously received. On Windows, do what's
+// necessary to obtain |PlatformHandle|s (e.g.: i. if the sender is fully
+// trusted and able to duplicate handle into the receiver, then just pick
+// out the |HANDLE| value; ii. if the receiver is fully trusted and able to
+// duplicate handles from the receiver, do the |DuplicateHandle()|; iii.
+// otherwise, talk to a broker to get handles). Reattach all the
+// |PlatformHandle|s to the |MessageInTransit|.
+// - For each entry in the "handle table", use serialized dispatcher data to
+// reconstitute a dispatcher, taking ownership of associated
+// |PlatformHandle|s (and detaching them). Attach these dispatchers to the
+// |MessageInTransit|.
+// - At this point, the |MessageInTransit| consists of its main buffer
+// (primarily the data payload) and the attached dispatchers; the
+// |TransportData| can be discarded.
+// - When |MojoReadMessage()| is to give data to the application, attach the
+// dispatchers to the (global, "core") handle table, getting handles; give
+// the application the data payload and these handles.
+//
+// TODO(vtl): Everything above involving |PlatformHandle|s.
+class MOJO_SYSTEM_IMPL_EXPORT TransportData {
+ public:
+ // The maximum size of a single serialized dispatcher. This must be a multiple
+ // of |kMessageAlignment|.
+ static const size_t kMaxSerializedDispatcherSize = 10000;
+
+ // The maximum number of platform handles to attach for a single serialized
+ // dispatcher.
+ static const size_t kMaxSerializedDispatcherPlatformHandles = 2;
+
+ // The maximum possible size of a valid transport data buffer.
+ static size_t GetMaxBufferSize();
+
+ // The maximum total number of platform handles that may be attached.
+ static size_t GetMaxPlatformHandles();
+
+ explicit TransportData(scoped_ptr<DispatcherVector> dispatchers);
+
+ // This is used for users of |MessageInTransit|/|TransportData|/|RawChannel|
+ // that want to simply transport data and platform handles, and not
+ // |Dispatcher|s. (|Header| will be present, and zero except for
+ // |num_platform_handles|, and |platform_handle_table_offset| if necessary.)
+ explicit TransportData(
+ ScopedPlatformHandleVectorPtr platform_handles,
+ size_t serialized_platform_handle_size);
+
+ ~TransportData();
+
+ const void* buffer() const { return buffer_.get(); }
+ void* buffer() { return buffer_.get(); }
+ size_t buffer_size() const { return buffer_size_; }
+
+ uint32_t platform_handle_table_offset() const {
+ return header()->platform_handle_table_offset;
+ }
+
+ // Gets attached platform-specific handles; this may return null if there are
+ // none. Note that the caller may mutate the set of platform-specific handles.
+ const PlatformHandleVector* platform_handles() const {
+ return platform_handles_.get();
+ }
+ PlatformHandleVector* platform_handles() {
+ return platform_handles_.get();
+ }
+
+ // Receive-side functions:
+
+ // Checks if the given buffer (from the "wire") looks like a valid
+ // |TransportData| buffer. (Should only be called if |buffer_size| is
+ // nonzero.) Returns null if valid, and a pointer to a human-readable error
+ // message (for debug/logging purposes) on error. Note: This checks the
+ // validity of the handle table entries (i.e., does range checking), but does
+ // not check that the validity of the actual serialized dispatcher
+ // information.
+ static const char* ValidateBuffer(size_t serialized_platform_handle_size,
+ const void* buffer,
+ size_t buffer_size);
+
+ // Gets the platform handle table from a (valid) |TransportData| buffer (which
+ // should have been validated using |ValidateBuffer()| first).
+ static void GetPlatformHandleTable(const void* transport_data_buffer,
+ size_t* num_platform_handles,
+ const void** platform_handle_table);
+
+ // Deserializes dispatchers from the given (serialized) transport data buffer
+ // (typically from a |MessageInTransit::View|) and vector of platform handles.
+ // |buffer| should be non-null and |buffer_size| should be nonzero.
+ static scoped_ptr<DispatcherVector> DeserializeDispatchers(
+ const void* buffer,
+ size_t buffer_size,
+ ScopedPlatformHandleVectorPtr platform_handles);
+
+ private:
+ // To allow us to make compile-assertions about |Header|, etc. in the .cc
+ // file.
+ struct PrivateStructForCompileAsserts;
+
+ // Header for the "secondary buffer"/"transport data". Must be a multiple of
+ // |MessageInTransit::kMessageAlignment| in size. Must be POD.
+ struct Header {
+ uint32_t num_handles;
+ // TODO(vtl): Not used yet:
+ uint32_t platform_handle_table_offset;
+ uint32_t num_platform_handles;
+ uint32_t unused;
+ };
+
+ struct HandleTableEntry {
+ // TODO(vtl): Should I make |Dispatcher::Type| an |int32_t| enum class?
+ int32_t type; // From |Dispatcher::Type| (|UNKNOWN| for "invalid").
+ uint32_t offset; // Relative to the start of the "secondary buffer".
+ uint32_t size; // (Not including any padding.)
+ uint32_t unused;
+ };
+
+ const Header* header() const {
+ return reinterpret_cast<const Header*>(buffer_.get());
+ }
+
+ size_t buffer_size_;
+ scoped_ptr<char, base::AlignedFreeDeleter> buffer_; // Never null.
+
+ // Any platform-specific handles attached to this message (for inter-process
+ // transport). The vector (if any) owns the handles that it contains (and is
+ // responsible for closing them).
+ // TODO(vtl): With C++11, change it to a vector of |ScopedPlatformHandle|s.
+ ScopedPlatformHandleVectorPtr platform_handles_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TransportData);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_TRANSPORT_DATA_H_
diff --git a/mojo/edk/system/waiter.cc b/mojo/edk/system/waiter.cc
new file mode 100644
index 0000000..1a9c0b1
--- /dev/null
+++ b/mojo/edk/system/waiter.cc
@@ -0,0 +1,100 @@
+// 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 "mojo/edk/system/waiter.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace mojo {
+namespace edk {
+
+Waiter::Waiter()
+ : cv_(&lock_),
+#ifndef NDEBUG
+ initialized_(false),
+#endif
+ awoken_(false),
+ awake_result_(MOJO_RESULT_INTERNAL),
+ awake_context_(static_cast<uint32_t>(-1)) {
+}
+
+Waiter::~Waiter() {
+}
+
+void Waiter::Init() {
+#ifndef NDEBUG
+ initialized_ = true;
+#endif
+ awoken_ = false;
+ // NOTE(vtl): If performance ever becomes an issue, we can disable the setting
+ // of |awake_result_| (except the first one in |Awake()|) in Release builds.
+ awake_result_ = MOJO_RESULT_INTERNAL;
+}
+
+// TODO(vtl): Fast-path the |deadline == 0| case?
+MojoResult Waiter::Wait(MojoDeadline deadline, uint32_t* context) {
+ base::AutoLock locker(lock_);
+
+#ifndef NDEBUG
+ DCHECK(initialized_);
+ // It'll need to be re-initialized after this.
+ initialized_ = false;
+#endif
+
+ // Fast-path the already-awoken case:
+ if (awoken_) {
+ DCHECK_NE(awake_result_, MOJO_RESULT_INTERNAL);
+ if (context)
+ *context = static_cast<uint32_t>(awake_context_);
+ return awake_result_;
+ }
+
+ // |MojoDeadline| is actually a |uint64_t|, but we need a signed quantity.
+ // Treat any out-of-range deadline as "forever" (which is wrong, but okay
+ // since 2^63 microseconds is ~300000 years). Note that this also takes care
+ // of the |MOJO_DEADLINE_INDEFINITE| (= 2^64 - 1) case.
+ if (deadline > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
+ do {
+ cv_.Wait();
+ } while (!awoken_);
+ } else {
+ // NOTE(vtl): This is very inefficient on POSIX, since pthreads condition
+ // variables take an absolute deadline.
+ const base::TimeTicks end_time =
+ base::TimeTicks::Now() +
+ base::TimeDelta::FromMicroseconds(static_cast<int64_t>(deadline));
+ do {
+ base::TimeTicks now_time = base::TimeTicks::Now();
+ if (now_time >= end_time)
+ return MOJO_RESULT_DEADLINE_EXCEEDED;
+
+ cv_.TimedWait(end_time - now_time);
+ } while (!awoken_);
+ }
+
+ DCHECK_NE(awake_result_, MOJO_RESULT_INTERNAL);
+ if (context)
+ *context = static_cast<uint32_t>(awake_context_);
+ return awake_result_;
+}
+
+bool Waiter::Awake(MojoResult result, uintptr_t context) {
+ base::AutoLock locker(lock_);
+
+ if (awoken_)
+ return true;
+
+ awoken_ = true;
+ awake_result_ = result;
+ awake_context_ = context;
+ cv_.Signal();
+ // |cv_.Wait()|/|cv_.TimedWait()| will return after |lock_| is released.
+ return true;
+}
+
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/waiter.h b/mojo/edk/system/waiter.h
new file mode 100644
index 0000000..8e983ec
--- /dev/null
+++ b/mojo/edk/system/waiter.h
@@ -0,0 +1,79 @@
+// 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 MOJO_EDK_SYSTEM_WAITER_H_
+#define MOJO_EDK_SYSTEM_WAITER_H_
+
+#include <stdint.h>
+
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "mojo/edk/system/awakable.h"
+#include "mojo/edk/system/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+
+// IMPORTANT (all-caps gets your attention, right?): |Waiter| methods are called
+// under other locks, in particular, |Dispatcher::lock_|s, so |Waiter| methods
+// must never call out to other objects (in particular, |Dispatcher|s). This
+// class is thread-safe.
+class MOJO_SYSTEM_IMPL_EXPORT Waiter final : public Awakable {
+ public:
+ Waiter();
+ ~Waiter();
+
+ // A |Waiter| can be used multiple times; |Init()| should be called before
+ // each time it's used.
+ void Init();
+
+ // Waits until a suitable |Awake()| is called. (|context| may be null, in
+ // which case, obviously no context is ever returned.)
+ // Returns:
+ // - The result given to the first call to |Awake()| (possibly before this
+ // call to |Wait()|); in this case, |*context| is set to the value passed
+ // to that call to |Awake()|.
+ // - |MOJO_RESULT_DEADLINE_EXCEEDED| if the deadline was exceeded; in this
+ // case |*context| is not modified.
+ //
+ // Usually, the context passed to |Awake()| will be the value passed to
+ // |Dispatcher::AddAwakable()|, which is usually the index to the array of
+ // handles passed to |MojoWaitMany()| (or 0 for |MojoWait()|).
+ //
+ // Typical |Awake()| results are:
+ // - |MOJO_RESULT_OK| if one of the flags passed to
+ // |MojoWait()|/|MojoWaitMany()| (hence |Dispatcher::AddAwakable()|) was
+ // satisfied;
+ // - |MOJO_RESULT_CANCELLED| if a handle (on which
+ // |MojoWait()|/|MojoWaitMany()| was called) was closed (hence the
+ // dispatcher closed); and
+ // - |MOJO_RESULT_FAILED_PRECONDITION| if one of the set of flags passed to
+ // |MojoWait()|/|MojoWaitMany()| cannot or can no longer be satisfied by
+ // the corresponding handle (e.g., if the other end of a message or data
+ // pipe is closed).
+ MojoResult Wait(MojoDeadline deadline, uint32_t* context);
+
+ // Wake the waiter up with the given result and context (or no-op if it's been
+ // woken up already).
+ bool Awake(MojoResult result, uintptr_t context) override;
+
+ private:
+ base::ConditionVariable cv_; // Associated to |lock_|.
+ base::Lock lock_; // Protects the following members.
+#ifndef NDEBUG
+ bool initialized_;
+#endif
+ bool awoken_;
+ MojoResult awake_result_;
+ uintptr_t awake_context_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(Waiter);
+};
+
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_WAITER_H_
diff --git a/mojo/edk/system/waiter_test_utils.cc b/mojo/edk/system/waiter_test_utils.cc
new file mode 100644
index 0000000..bd7f55e
--- /dev/null
+++ b/mojo/edk/system/waiter_test_utils.cc
@@ -0,0 +1,70 @@
+// 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 "mojo/edk/system/waiter_test_utils.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+SimpleWaiterThread::SimpleWaiterThread(MojoResult* result, uint32_t* context)
+ : base::SimpleThread("waiter_thread"), result_(result), context_(context) {
+ waiter_.Init();
+ *result_ = 5420734; // Totally invalid result.
+ *context_ = 23489023; // "Random".
+}
+
+SimpleWaiterThread::~SimpleWaiterThread() {
+ Join();
+}
+
+void SimpleWaiterThread::Run() {
+ *result_ = waiter_.Wait(MOJO_DEADLINE_INDEFINITE, context_);
+}
+
+WaiterThread::WaiterThread(scoped_refptr<Dispatcher> dispatcher,
+ MojoHandleSignals handle_signals,
+ MojoDeadline deadline,
+ uint32_t context,
+ bool* did_wait_out,
+ MojoResult* result_out,
+ uint32_t* context_out,
+ HandleSignalsState* signals_state_out)
+ : base::SimpleThread("waiter_thread"),
+ dispatcher_(dispatcher),
+ handle_signals_(handle_signals),
+ deadline_(deadline),
+ context_(context),
+ did_wait_out_(did_wait_out),
+ result_out_(result_out),
+ context_out_(context_out),
+ signals_state_out_(signals_state_out) {
+ *did_wait_out_ = false;
+ // Initialize these with invalid results (so that we'll be sure to catch any
+ // case where they're not set).
+ *result_out_ = 8542346;
+ *context_out_ = 89023444;
+ *signals_state_out_ = HandleSignalsState(~0u, ~0u);
+}
+
+WaiterThread::~WaiterThread() {
+ Join();
+}
+
+void WaiterThread::Run() {
+ waiter_.Init();
+
+ *result_out_ = dispatcher_->AddAwakable(&waiter_, handle_signals_, context_,
+ signals_state_out_);
+ if (*result_out_ != MOJO_RESULT_OK)
+ return;
+
+ *did_wait_out_ = true;
+ *result_out_ = waiter_.Wait(deadline_, context_out_);
+ dispatcher_->RemoveAwakable(&waiter_, signals_state_out_);
+}
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/system/waiter_test_utils.h b/mojo/edk/system/waiter_test_utils.h
new file mode 100644
index 0000000..a55d507
--- /dev/null
+++ b/mojo/edk/system/waiter_test_utils.h
@@ -0,0 +1,104 @@
+// 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 MOJO_EDK_SYSTEM_WAITER_TEST_UTILS_H_
+#define MOJO_EDK_SYSTEM_WAITER_TEST_UTILS_H_
+
+#include <stdint.h>
+
+#include "base/memory/ref_counted.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/edk/system/dispatcher.h"
+#include "mojo/edk/system/handle_signals_state.h"
+#include "mojo/edk/system/waiter.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+// This is a very simple thread that has a |Waiter|, on which it waits
+// indefinitely (and records the result). It will create and initialize the
+// |Waiter| on creation, but the caller must start the thread with |Start()|. It
+// will join the thread on destruction.
+//
+// One usually uses it like:
+//
+// MojoResult result;
+// {
+// AwakableList awakable_list;
+// test::SimpleWaiterThread thread(&result);
+// awakable_list.Add(thread.waiter(), ...);
+// thread.Start();
+// ... some stuff to wake the waiter ...
+// awakable_list.Remove(thread.waiter());
+// } // Join |thread|.
+// EXPECT_EQ(..., result);
+//
+// There's a bit of unrealism in its use: In this sort of usage, calls such as
+// |Waiter::Init()|, |AddAwakable()|, and |RemoveAwakable()| are done in the
+// main (test) thread, not the waiter thread (as would actually happen in real
+// code). (We accept this unrealism for simplicity, since |AwakableList| is
+// thread-unsafe so making it more realistic would require adding nontrivial
+// synchronization machinery.)
+class SimpleWaiterThread : public base::SimpleThread {
+ public:
+ // For the duration of the lifetime of this object, |*result| belongs to it
+ // (in the sense that it will write to it whenever it wants).
+ SimpleWaiterThread(MojoResult* result, uint32_t* context);
+ ~SimpleWaiterThread() override; // Joins the thread.
+
+ Waiter* waiter() { return &waiter_; }
+
+ private:
+ void Run() override;
+
+ MojoResult* const result_;
+ uint32_t* const context_;
+ Waiter waiter_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(SimpleWaiterThread);
+};
+
+// This is a more complex and realistic thread that has a |Waiter|, on which it
+// waits for the given deadline (with the given flags). Unlike
+// |SimpleWaiterThread|, it requires the machinery of |Dispatcher|.
+class WaiterThread : public base::SimpleThread {
+ public:
+ // Note: |*did_wait_out|, |*result_out|, |*context_out| and
+ // |*signals_state_out| "belong" to this object (i.e., may be modified by, on
+ // some other thread) while it's alive.
+ WaiterThread(scoped_refptr<Dispatcher> dispatcher,
+ MojoHandleSignals handle_signals,
+ MojoDeadline deadline,
+ uint32_t context,
+ bool* did_wait_out,
+ MojoResult* result_out,
+ uint32_t* context_out,
+ HandleSignalsState* signals_state_out);
+ ~WaiterThread() override;
+
+ private:
+ void Run() override;
+
+ const scoped_refptr<Dispatcher> dispatcher_;
+ const MojoHandleSignals handle_signals_;
+ const MojoDeadline deadline_;
+ const uint32_t context_;
+ bool* const did_wait_out_;
+ MojoResult* const result_out_;
+ uint32_t* const context_out_;
+ HandleSignalsState* const signals_state_out_;
+
+ Waiter waiter_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(WaiterThread);
+};
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_SYSTEM_WAITER_TEST_UTILS_H_
diff --git a/mojo/edk/system/waiter_unittest.cc b/mojo/edk/system/waiter_unittest.cc
new file mode 100644
index 0000000..71d9d8c
--- /dev/null
+++ b/mojo/edk/system/waiter_unittest.cc
@@ -0,0 +1,298 @@
+// 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.
+
+// NOTE(vtl): Some of these tests are inherently flaky (e.g., if run on a
+// heavily-loaded system). Sorry. |test::EpsilonDeadline()| may be increased to
+// increase tolerance and reduce observed flakiness (though doing so reduces the
+// meaningfulness of the test).
+
+#include "mojo/edk/system/waiter.h"
+
+#include <stdint.h>
+
+#include "base/synchronization/lock.h"
+#include "base/threading/simple_thread.h"
+#include "mojo/edk/system/test_utils.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace edk {
+namespace {
+
+const unsigned kPollTimeMs = 10;
+
+class WaitingThread : public base::SimpleThread {
+ public:
+ explicit WaitingThread(MojoDeadline deadline)
+ : base::SimpleThread("waiting_thread"),
+ deadline_(deadline),
+ done_(false),
+ result_(MOJO_RESULT_UNKNOWN),
+ context_(static_cast<uint32_t>(-1)) {
+ waiter_.Init();
+ }
+
+ ~WaitingThread() override { Join(); }
+
+ void WaitUntilDone(MojoResult* result,
+ uint32_t* context,
+ MojoDeadline* elapsed) {
+ for (;;) {
+ {
+ base::AutoLock locker(lock_);
+ if (done_) {
+ *result = result_;
+ *context = context_;
+ *elapsed = elapsed_;
+ break;
+ }
+ }
+
+ test::Sleep(test::DeadlineFromMilliseconds(kPollTimeMs));
+ }
+ }
+
+ Waiter* waiter() { return &waiter_; }
+
+ private:
+ void Run() override {
+ test::Stopwatch stopwatch;
+ MojoResult result;
+ uint32_t context = static_cast<uint32_t>(-1);
+ MojoDeadline elapsed;
+
+ stopwatch.Start();
+ result = waiter_.Wait(deadline_, &context);
+ elapsed = stopwatch.Elapsed();
+
+ {
+ base::AutoLock locker(lock_);
+ done_ = true;
+ result_ = result;
+ context_ = context;
+ elapsed_ = elapsed;
+ }
+ }
+
+ const MojoDeadline deadline_;
+ Waiter waiter_; // Thread-safe.
+
+ base::Lock lock_; // Protects the following members.
+ bool done_;
+ MojoResult result_;
+ uint32_t context_;
+ MojoDeadline elapsed_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(WaitingThread);
+};
+
+TEST(WaiterTest, Basic) {
+ MojoResult result;
+ uint32_t context;
+ MojoDeadline elapsed;
+
+ // Finite deadline.
+
+ // Awake immediately after thread start.
+ {
+ WaitingThread thread(10 * test::EpsilonDeadline());
+ thread.Start();
+ thread.waiter()->Awake(MOJO_RESULT_OK, 1);
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(1u, context);
+ EXPECT_LT(elapsed, test::EpsilonDeadline());
+ }
+
+ // Awake before after thread start.
+ {
+ WaitingThread thread(10 * test::EpsilonDeadline());
+ thread.waiter()->Awake(MOJO_RESULT_CANCELLED, 2);
+ thread.Start();
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
+ EXPECT_EQ(2u, context);
+ EXPECT_LT(elapsed, test::EpsilonDeadline());
+ }
+
+ // Awake some time after thread start.
+ {
+ WaitingThread thread(10 * test::EpsilonDeadline());
+ thread.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ thread.waiter()->Awake(1, 3);
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(1u, result);
+ EXPECT_EQ(3u, context);
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
+ }
+
+ // Awake some longer time after thread start.
+ {
+ WaitingThread thread(10 * test::EpsilonDeadline());
+ thread.Start();
+ test::Sleep(5 * test::EpsilonDeadline());
+ thread.waiter()->Awake(2, 4);
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(2u, result);
+ EXPECT_EQ(4u, context);
+ EXPECT_GT(elapsed, (5 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (5 + 1) * test::EpsilonDeadline());
+ }
+
+ // Don't awake -- time out (on another thread).
+ {
+ WaitingThread thread(2 * test::EpsilonDeadline());
+ thread.Start();
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, result);
+ EXPECT_EQ(static_cast<uint32_t>(-1), context);
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
+ }
+
+ // No (indefinite) deadline.
+
+ // Awake immediately after thread start.
+ {
+ WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
+ thread.Start();
+ thread.waiter()->Awake(MOJO_RESULT_OK, 5);
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(5u, context);
+ EXPECT_LT(elapsed, test::EpsilonDeadline());
+ }
+
+ // Awake before after thread start.
+ {
+ WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
+ thread.waiter()->Awake(MOJO_RESULT_CANCELLED, 6);
+ thread.Start();
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(MOJO_RESULT_CANCELLED, result);
+ EXPECT_EQ(6u, context);
+ EXPECT_LT(elapsed, test::EpsilonDeadline());
+ }
+
+ // Awake some time after thread start.
+ {
+ WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
+ thread.Start();
+ test::Sleep(2 * test::EpsilonDeadline());
+ thread.waiter()->Awake(1, 7);
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(1u, result);
+ EXPECT_EQ(7u, context);
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
+ }
+
+ // Awake some longer time after thread start.
+ {
+ WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
+ thread.Start();
+ test::Sleep(5 * test::EpsilonDeadline());
+ thread.waiter()->Awake(2, 8);
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(2u, result);
+ EXPECT_EQ(8u, context);
+ EXPECT_GT(elapsed, (5 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (5 + 1) * test::EpsilonDeadline());
+ }
+}
+
+TEST(WaiterTest, TimeOut) {
+ test::Stopwatch stopwatch;
+ MojoDeadline elapsed;
+
+ Waiter waiter;
+ uint32_t context = 123;
+
+ waiter.Init();
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED, waiter.Wait(0, &context));
+ elapsed = stopwatch.Elapsed();
+ EXPECT_LT(elapsed, test::EpsilonDeadline());
+ EXPECT_EQ(123u, context);
+
+ waiter.Init();
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ waiter.Wait(2 * test::EpsilonDeadline(), &context));
+ elapsed = stopwatch.Elapsed();
+ EXPECT_GT(elapsed, (2 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (2 + 1) * test::EpsilonDeadline());
+ EXPECT_EQ(123u, context);
+
+ waiter.Init();
+ stopwatch.Start();
+ EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
+ waiter.Wait(5 * test::EpsilonDeadline(), &context));
+ elapsed = stopwatch.Elapsed();
+ EXPECT_GT(elapsed, (5 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (5 + 1) * test::EpsilonDeadline());
+ EXPECT_EQ(123u, context);
+}
+
+// The first |Awake()| should always win.
+TEST(WaiterTest, MultipleAwakes) {
+ MojoResult result;
+ uint32_t context;
+ MojoDeadline elapsed;
+
+ {
+ WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
+ thread.Start();
+ thread.waiter()->Awake(MOJO_RESULT_OK, 1);
+ thread.waiter()->Awake(1, 2);
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ EXPECT_EQ(1u, context);
+ EXPECT_LT(elapsed, test::EpsilonDeadline());
+ }
+
+ {
+ WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
+ thread.waiter()->Awake(1, 3);
+ thread.Start();
+ thread.waiter()->Awake(MOJO_RESULT_OK, 4);
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(1u, result);
+ EXPECT_EQ(3u, context);
+ EXPECT_LT(elapsed, test::EpsilonDeadline());
+ }
+
+ {
+ WaitingThread thread(MOJO_DEADLINE_INDEFINITE);
+ thread.Start();
+ thread.waiter()->Awake(10, 5);
+ test::Sleep(2 * test::EpsilonDeadline());
+ thread.waiter()->Awake(20, 6);
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(10u, result);
+ EXPECT_EQ(5u, context);
+ EXPECT_LT(elapsed, test::EpsilonDeadline());
+ }
+
+ {
+ WaitingThread thread(10 * test::EpsilonDeadline());
+ thread.Start();
+ test::Sleep(1 * test::EpsilonDeadline());
+ thread.waiter()->Awake(MOJO_RESULT_FAILED_PRECONDITION, 7);
+ test::Sleep(2 * test::EpsilonDeadline());
+ thread.waiter()->Awake(MOJO_RESULT_OK, 8);
+ thread.WaitUntilDone(&result, &context, &elapsed);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
+ EXPECT_EQ(7u, context);
+ EXPECT_GT(elapsed, (1 - 1) * test::EpsilonDeadline());
+ EXPECT_LT(elapsed, (1 + 1) * test::EpsilonDeadline());
+ }
+}
+
+} // namespace
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/test/BUILD.gn b/mojo/edk/test/BUILD.gn
new file mode 100644
index 0000000..a53907d
--- /dev/null
+++ b/mojo/edk/test/BUILD.gn
@@ -0,0 +1,159 @@
+# Copyright 2014 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.
+
+import("../mojo_edk.gni")
+import("../../public/mojo.gni")
+import("//testing/test.gni")
+
+mojo_edk_source_set("test_support") {
+ testonly = true
+ sources = [
+ "multiprocess_test_helper.cc",
+ "multiprocess_test_helper.h",
+ "scoped_ipc_support.cc",
+ "scoped_ipc_support.h",
+ "test_utils.h",
+ "test_utils_posix.cc",
+ "test_utils_win.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//mojo/edk/system",
+ "//testing/gtest",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ ]
+}
+
+mojo_edk_source_set("run_all_unittests") {
+ testonly = true
+ sources = [
+ "run_all_unittests.cc",
+ ]
+
+ deps = [
+ ":test_support",
+ ":test_support_impl",
+ "//base",
+ "//base/test:test_support",
+ "//mojo/edk/system",
+ "//testing/gtest",
+ "//third_party/mojo/src/mojo/public/c/test_support",
+ ]
+}
+
+mojo_edk_source_set("run_all_perftests") {
+ testonly = true
+ deps = [
+ ":test_support_impl",
+ "//base",
+ "//base/test:test_support",
+ "//mojo/edk/system",
+
+ # TODO(use_chrome_edk): temporary since the Mojo wrapper primitives are
+ # declared in third party only for now.
+ "//third_party/mojo/src/mojo/edk/system",
+ "//third_party/mojo/src/mojo/public/c/test_support",
+ ]
+
+ sources = [
+ "run_all_perftests.cc",
+ ]
+}
+
+mojo_edk_source_set("test_support_impl") {
+ testonly = true
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//third_party/mojo/src/mojo/public/c/test_support",
+ "//third_party/mojo/src/mojo/public/cpp/system",
+ ]
+
+ sources = [
+ "test_support_impl.cc",
+ "test_support_impl.h",
+ ]
+}
+
+# Public SDK test targets follow. These targets are not defined within the
+# public SDK itself as running the unittests requires the EDK.
+# TODO(vtl): These don't really belong here. (They should be converted to
+# apptests, but even apart from that these targets belong somewhere else.)
+
+group("public_tests") {
+ testonly = true
+ deps = [
+ # TODO(use_chrome_edk): remove "2"
+ ":mojo_public_bindings_unittests2",
+ ":mojo_public_environment_unittests2",
+ ":mojo_public_system_perftests2",
+ ":mojo_public_system_unittests2",
+ ":mojo_public_utility_unittests2",
+ ":mojo_system_impl_private_unittests2",
+ ]
+
+ if (mojo_use_application_in_sdk) {
+ deps += [ ":mojo_public_application_unittests" ]
+ }
+}
+
+if (mojo_use_application_in_sdk) {
+ # TODO(use_chrome_edk): remove "2"
+ test("mojo_public_application_unittests2") {
+ deps = [
+ ":run_all_unittests",
+ "../../../third_party/mojo/src/mojo/public/cpp/application/tests",
+ ]
+ }
+}
+
+# TODO(use_chrome_edk): remove "2"
+test("mojo_public_bindings_unittests2") {
+ deps = [
+ ":run_all_unittests",
+ "../../../third_party/mojo/src/mojo/public/cpp/bindings/tests",
+ ]
+}
+
+# TODO(use_chrome_edk): remove "2"
+test("mojo_public_environment_unittests2") {
+ deps = [
+ ":run_all_unittests",
+ "../../../third_party/mojo/src/mojo/public/cpp/environment/tests",
+ ]
+}
+
+# TODO(use_chrome_edk): remove "2"
+test("mojo_public_system_perftests2") {
+ deps = [
+ ":run_all_perftests",
+ "../../../third_party/mojo/src/mojo/public/c/system/tests:perftests",
+ ]
+}
+
+# TODO(use_chrome_edk): remove "2"
+test("mojo_public_system_unittests2") {
+ deps = [
+ ":run_all_unittests",
+ "../../../third_party/mojo/src/mojo/public/cpp/system/tests",
+ ]
+}
+
+# TODO(use_chrome_edk): remove "2"
+test("mojo_public_utility_unittests2") {
+ deps = [
+ ":run_all_unittests",
+ "../../../third_party/mojo/src/mojo/public/cpp/utility/tests",
+ ]
+}
+
+# TODO(use_chrome_edk): remove "2"
+test("mojo_system_impl_private_unittests2") {
+ deps = [
+ ":run_all_unittests",
+ "../../../third_party/mojo/src/mojo/public/platform/native:system_impl_private_tests",
+ ]
+}
diff --git a/mojo/edk/test/multiprocess_test_helper.cc b/mojo/edk/test/multiprocess_test_helper.cc
new file mode 100644
index 0000000..10645f9
--- /dev/null
+++ b/mojo/edk/test/multiprocess_test_helper.cc
@@ -0,0 +1,101 @@
+// 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 "mojo/edk/test/multiprocess_test_helper.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/process/kill.h"
+#include "base/process/process_handle.h"
+#include "build/build_config.h"
+#include "mojo/edk/embedder/platform_channel_pair.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+MultiprocessTestHelper::MultiprocessTestHelper() {
+ platform_channel_pair_.reset(new PlatformChannelPair());
+ server_platform_handle = platform_channel_pair_->PassServerHandle();
+}
+
+MultiprocessTestHelper::~MultiprocessTestHelper() {
+ CHECK(!test_child_.IsValid());
+ server_platform_handle.reset();
+ platform_channel_pair_.reset();
+}
+
+void MultiprocessTestHelper::StartChild(const std::string& test_child_name) {
+ StartChildWithExtraSwitch(test_child_name, std::string(), std::string());
+}
+
+void MultiprocessTestHelper::StartChildWithExtraSwitch(
+ const std::string& test_child_name,
+ const std::string& switch_string,
+ const std::string& switch_value) {
+ CHECK(platform_channel_pair_);
+ CHECK(!test_child_name.empty());
+ CHECK(!test_child_.IsValid());
+
+ std::string test_child_main = test_child_name + "TestChildMain";
+
+ base::CommandLine command_line(
+ base::GetMultiProcessTestChildBaseCommandLine());
+ HandlePassingInformation handle_passing_info;
+ platform_channel_pair_->PrepareToPassClientHandleToChildProcess(
+ &command_line, &handle_passing_info);
+
+ if (!switch_string.empty()) {
+ CHECK(!command_line.HasSwitch(switch_string));
+ if (!switch_value.empty())
+ command_line.AppendSwitchASCII(switch_string, switch_value);
+ else
+ command_line.AppendSwitch(switch_string);
+ }
+
+ base::LaunchOptions options;
+#if defined(OS_POSIX)
+ options.fds_to_remap = &handle_passing_info;
+#elif defined(OS_WIN)
+ options.start_hidden = true;
+ options.handles_to_inherit = &handle_passing_info;
+#else
+#error "Not supported yet."
+#endif
+
+ test_child_ =
+ base::SpawnMultiProcessTestChild(test_child_main, command_line, options);
+ platform_channel_pair_->ChildProcessLaunched();
+
+ CHECK(test_child_.IsValid());
+}
+
+int MultiprocessTestHelper::WaitForChildShutdown() {
+ CHECK(test_child_.IsValid());
+
+ int rv = -1;
+ CHECK(
+ test_child_.WaitForExitWithTimeout(TestTimeouts::action_timeout(), &rv));
+ test_child_.Close();
+ return rv;
+}
+
+bool MultiprocessTestHelper::WaitForChildTestShutdown() {
+ return WaitForChildShutdown() == 0;
+}
+
+// static
+void MultiprocessTestHelper::ChildSetup() {
+ CHECK(base::CommandLine::InitializedForCurrentProcess());
+ client_platform_handle =
+ PlatformChannelPair::PassClientHandleFromParentProcess(
+ *base::CommandLine::ForCurrentProcess());
+}
+
+// static
+ScopedPlatformHandle MultiprocessTestHelper::client_platform_handle;
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/test/multiprocess_test_helper.h b/mojo/edk/test/multiprocess_test_helper.h
new file mode 100644
index 0000000..3ff8b85
--- /dev/null
+++ b/mojo/edk/test/multiprocess_test_helper.h
@@ -0,0 +1,98 @@
+// 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 MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_
+#define MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_
+
+#include <string>
+
+#include "base/process/process.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_timeouts.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace mojo {
+
+namespace edk {
+class PlatformChannelPair;
+
+namespace test {
+
+class MultiprocessTestHelper {
+ public:
+ MultiprocessTestHelper();
+ ~MultiprocessTestHelper();
+
+ // Start a child process and run the "main" function "named" |test_child_name|
+ // declared using |MOJO_MULTIPROCESS_TEST_CHILD_MAIN()| or
+ // |MOJO_MULTIPROCESS_TEST_CHILD_TEST()| (below).
+ void StartChild(const std::string& test_child_name);
+ // Like |StartChild()|, but appends an extra switch (with ASCII value) to the
+ // command line. (The switch must not already be present in the default
+ // command line.)
+ void StartChildWithExtraSwitch(const std::string& test_child_name,
+ const std::string& switch_string,
+ const std::string& switch_value);
+ // Wait for the child process to terminate.
+ // Returns the exit code of the child process. Note that, though it's declared
+ // to be an |int|, the exit code is subject to mangling by the OS. E.g., we
+ // usually return -1 on error in the child (e.g., if |test_child_name| was not
+ // found), but this is mangled to 255 on Linux. You should only rely on codes
+ // 0-127 being preserved, and -1 being outside the range 0-127.
+ int WaitForChildShutdown();
+
+ // Like |WaitForChildShutdown()|, but returns true on success (exit code of 0)
+ // and false otherwise. You probably want to do something like
+ // |EXPECT_TRUE(WaitForChildTestShutdown());|. Mainly for use with
+ // |MOJO_MULTIPROCESS_TEST_CHILD_TEST()|.
+ bool WaitForChildTestShutdown();
+
+ // For use by |MOJO_MULTIPROCESS_TEST_CHILD_MAIN()| only:
+ static void ChildSetup();
+
+ // For use in the main process:
+ ScopedPlatformHandle server_platform_handle;
+
+ // For use (and only valid) in the child process:
+ static ScopedPlatformHandle client_platform_handle;
+
+ private:
+ scoped_ptr<PlatformChannelPair> platform_channel_pair_;
+
+ // Valid after |StartChild()| and before |WaitForChildShutdown()|.
+ base::Process test_child_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(MultiprocessTestHelper);
+};
+
+// Use this to declare the child process's "main()" function for tests using
+// |MultiprocessTestHelper|. It returns an |int|, which will be the process's
+// exit code (but see the comment about |WaitForChildShutdown()|).
+#define MOJO_MULTIPROCESS_TEST_CHILD_MAIN(test_child_name) \
+ MULTIPROCESS_TEST_MAIN_WITH_SETUP( \
+ test_child_name##TestChildMain, \
+ test::MultiprocessTestHelper::ChildSetup)
+
+// Use this (and |WaitForChildTestShutdown()|) for the child process's "main()",
+// if you want to use |EXPECT_...()| or |ASSERT_...()|; it has a |void| return
+// type. (Note that while an |ASSERT_...()| failure will abort the test in the
+// child, it will not abort the test in the parent.)
+#define MOJO_MULTIPROCESS_TEST_CHILD_TEST(test_child_name) \
+ void test_child_name##TestChildTest(); \
+ MOJO_MULTIPROCESS_TEST_CHILD_MAIN(test_child_name) { \
+ test_child_name##TestChildTest(); \
+ return (::testing::Test::HasFatalFailure() || \
+ ::testing::Test::HasNonfatalFailure()) \
+ ? 1 \
+ : 0; \
+ } \
+ void test_child_name##TestChildTest()
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_
diff --git a/mojo/edk/test/multiprocess_test_helper_unittest.cc b/mojo/edk/test/multiprocess_test_helper_unittest.cc
new file mode 100644
index 0000000..b3ae11d
--- /dev/null
+++ b/mojo/edk/test/multiprocess_test_helper_unittest.cc
@@ -0,0 +1,196 @@
+// 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 "mojo/edk/test/multiprocess_test_helper.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/edk/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_POSIX)
+#include <fcntl.h>
+#endif
+
+namespace mojo {
+namespace edk {
+namespace test {
+namespace {
+
+bool IsNonBlocking(const PlatformHandle& handle) {
+#if defined(OS_WIN)
+ // Haven't figured out a way to query whether a HANDLE was created with
+ // FILE_FLAG_OVERLAPPED.
+ return true;
+#else
+ return fcntl(handle.fd, F_GETFL) & O_NONBLOCK;
+#endif
+}
+
+bool WriteByte(const PlatformHandle& handle, char c) {
+ size_t bytes_written = 0;
+ BlockingWrite(handle, &c, 1, &bytes_written);
+ return bytes_written == 1;
+}
+
+bool ReadByte(const PlatformHandle& handle, char* c) {
+ size_t bytes_read = 0;
+ BlockingRead(handle, c, 1, &bytes_read);
+ return bytes_read == 1;
+}
+
+using MultiprocessTestHelperTest = testing::Test;
+
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_RunChild DISABLED_RunChild
+#else
+#define MAYBE_RunChild RunChild
+#endif // defined(OS_ANDROID)
+TEST_F(MultiprocessTestHelperTest, MAYBE_RunChild) {
+ MultiprocessTestHelper helper;
+ EXPECT_TRUE(helper.server_platform_handle.is_valid());
+
+ helper.StartChild("RunChild");
+ EXPECT_EQ(123, helper.WaitForChildShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(RunChild) {
+ CHECK(MultiprocessTestHelper::client_platform_handle.is_valid());
+ return 123;
+}
+
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_TestChildMainNotFound DISABLED_TestChildMainNotFound
+#else
+#define MAYBE_TestChildMainNotFound TestChildMainNotFound
+#endif // defined(OS_ANDROID)
+TEST_F(MultiprocessTestHelperTest, MAYBE_TestChildMainNotFound) {
+ MultiprocessTestHelper helper;
+ helper.StartChild("NoSuchTestChildMain");
+ int result = helper.WaitForChildShutdown();
+ EXPECT_FALSE(result >= 0 && result <= 127);
+}
+
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_PassedChannel DISABLED_PassedChannel
+#else
+#define MAYBE_PassedChannel PassedChannel
+#endif // defined(OS_ANDROID)
+TEST_F(MultiprocessTestHelperTest, MAYBE_PassedChannel) {
+ MultiprocessTestHelper helper;
+ EXPECT_TRUE(helper.server_platform_handle.is_valid());
+ helper.StartChild("PassedChannel");
+
+ // Take ownership of the handle.
+ ScopedPlatformHandle handle = helper.server_platform_handle.Pass();
+
+ // The handle should be non-blocking.
+ EXPECT_TRUE(IsNonBlocking(handle.get()));
+
+ // Write a byte.
+ const char c = 'X';
+ EXPECT_TRUE(WriteByte(handle.get(), c));
+
+ // It'll echo it back to us, incremented.
+ char d = 0;
+ EXPECT_TRUE(ReadByte(handle.get(), &d));
+ EXPECT_EQ(c + 1, d);
+
+ // And return it, incremented again.
+ EXPECT_EQ(c + 2, helper.WaitForChildShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_MAIN(PassedChannel) {
+ CHECK(MultiprocessTestHelper::client_platform_handle.is_valid());
+
+ // Take ownership of the handle.
+ ScopedPlatformHandle handle =
+ MultiprocessTestHelper::client_platform_handle.Pass();
+
+ // The handle should be non-blocking.
+ EXPECT_TRUE(IsNonBlocking(handle.get()));
+
+ // Read a byte.
+ char c = 0;
+ EXPECT_TRUE(ReadByte(handle.get(), &c));
+
+ // Write it back, incremented.
+ c++;
+ EXPECT_TRUE(WriteByte(handle.get(), c));
+
+ // And return it, incremented again.
+ c++;
+ return static_cast<int>(c);
+}
+
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_ChildTestPasses DISABLED_ChildTestPasses
+#else
+#define MAYBE_ChildTestPasses ChildTestPasses
+#endif // defined(OS_ANDROID)
+TEST_F(MultiprocessTestHelperTest, MAYBE_ChildTestPasses) {
+ MultiprocessTestHelper helper;
+ EXPECT_TRUE(helper.server_platform_handle.is_valid());
+ helper.StartChild("ChildTestPasses");
+ EXPECT_TRUE(helper.WaitForChildTestShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestPasses) {
+ ASSERT_TRUE(MultiprocessTestHelper::client_platform_handle.is_valid());
+ EXPECT_TRUE(
+ IsNonBlocking(MultiprocessTestHelper::client_platform_handle.get()));
+}
+
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_ChildTestFailsAssert DISABLED_ChildTestFailsAssert
+#else
+#define MAYBE_ChildTestFailsAssert ChildTestFailsAssert
+#endif // defined(OS_ANDROID)
+TEST_F(MultiprocessTestHelperTest, MAYBE_ChildTestFailsAssert) {
+ MultiprocessTestHelper helper;
+ EXPECT_TRUE(helper.server_platform_handle.is_valid());
+ helper.StartChild("ChildTestFailsAssert");
+ EXPECT_FALSE(helper.WaitForChildTestShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestFailsAssert) {
+ ASSERT_FALSE(MultiprocessTestHelper::client_platform_handle.is_valid())
+ << "DISREGARD: Expected failure in child process";
+ ASSERT_FALSE(
+ IsNonBlocking(MultiprocessTestHelper::client_platform_handle.get()))
+ << "Not reached";
+ CHECK(false) << "Not reached";
+}
+
+#if defined(OS_ANDROID)
+// Android multi-process tests are not executing the new process. This is flaky.
+#define MAYBE_ChildTestFailsExpect DISABLED_ChildTestFailsExpect
+#else
+#define MAYBE_ChildTestFailsExpect ChildTestFailsExpect
+#endif // defined(OS_ANDROID)
+TEST_F(MultiprocessTestHelperTest, MAYBE_ChildTestFailsExpect) {
+ MultiprocessTestHelper helper;
+ EXPECT_TRUE(helper.server_platform_handle.is_valid());
+ helper.StartChild("ChildTestFailsExpect");
+ EXPECT_FALSE(helper.WaitForChildTestShutdown());
+}
+
+MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestFailsExpect) {
+ EXPECT_FALSE(MultiprocessTestHelper::client_platform_handle.is_valid())
+ << "DISREGARD: Expected failure #1 in child process";
+ EXPECT_FALSE(
+ IsNonBlocking(MultiprocessTestHelper::client_platform_handle.get()))
+ << "DISREGARD: Expected failure #2 in child process";
+}
+
+} // namespace
+} // namespace test
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/test/run_all_perftests.cc b/mojo/edk/test/run_all_perftests.cc
new file mode 100644
index 0000000..d700bc3
--- /dev/null
+++ b/mojo/edk/test/run_all_perftests.cc
@@ -0,0 +1,20 @@
+// 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 "base/command_line.h"
+#include "base/test/perf_test_suite.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/test/test_support_impl.h"
+#include "mojo/public/tests/test_support_private.h"
+
+int main(int argc, char** argv) {
+ mojo::edk::Init();
+ mojo::test::TestSupport::Init(new mojo::edk::test::TestSupportImpl());
+
+ // TODO(use_chrome_edk): temporary to force new EDK.
+ base::PerfTestSuite test(argc, argv);
+ base::CommandLine::ForCurrentProcess()->AppendSwitch("--use-new-edk");
+
+ return test.Run();
+}
diff --git a/mojo/edk/test/run_all_unittests.cc b/mojo/edk/test/run_all_unittests.cc
new file mode 100644
index 0000000..23266444
--- /dev/null
+++ b/mojo/edk/test/run_all_unittests.cc
@@ -0,0 +1,43 @@
+// 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 <signal.h>
+
+#include "base/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_io_thread.h"
+#include "base/test/test_suite.h"
+#include "mojo/edk/embedder/embedder.h"
+#include "mojo/edk/test/scoped_ipc_support.h"
+#include "mojo/edk/test/test_support_impl.h"
+#include "mojo/public/tests/test_support_private.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+int main(int argc, char** argv) {
+#if !defined(OS_ANDROID)
+ // Silence death test thread warnings on Linux. We can afford to run our death
+ // tests a little more slowly (< 10 ms per death test on a Z620).
+ // On android, we need to run in the default mode, as the threadsafe mode
+ // relies on execve which is not available.
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
+#endif
+#if defined(OS_ANDROID)
+ // On android, the test framework has a signal handler that will print a
+ // [ CRASH ] line when the application crashes. This breaks death test has the
+ // test runner will consider the death of the child process a test failure.
+ // Removing the signal handler solves this issue.
+ signal(SIGABRT, SIG_DFL);
+#endif
+
+ base::TestSuite test_suite(argc, argv);
+
+ mojo::edk::Init();
+ mojo::test::TestSupport::Init(new mojo::edk::test::TestSupportImpl());
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ mojo::edk::test::ScopedIPCSupport ipc_support(test_io_thread.task_runner());
+
+ return base::LaunchUnitTests(
+ argc, argv,
+ base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/mojo/edk/test/scoped_ipc_support.cc b/mojo/edk/test/scoped_ipc_support.cc
new file mode 100644
index 0000000..c252529
--- /dev/null
+++ b/mojo/edk/test/scoped_ipc_support.cc
@@ -0,0 +1,59 @@
+// Copyright 2015 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 "mojo/edk/test/scoped_ipc_support.h"
+
+#include "base/message_loop/message_loop.h"
+#include "mojo/edk/embedder/embedder.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+namespace internal {
+
+ScopedIPCSupportHelper::ScopedIPCSupportHelper()
+ : event_(true, false) { // Manual reset.
+}
+
+ScopedIPCSupportHelper::~ScopedIPCSupportHelper() {
+ if (base::MessageLoop::current() &&
+ base::MessageLoop::current()->task_runner() == io_thread_task_runner_) {
+ ShutdownIPCSupportOnIOThread();
+ } else {
+ ShutdownIPCSupportAndWaitForNoChannels();
+ event_.Wait();
+ }
+}
+
+void ScopedIPCSupportHelper::Init(
+ ProcessDelegate* process_delegate,
+ scoped_refptr<base::TaskRunner> io_thread_task_runner) {
+ io_thread_task_runner_ = io_thread_task_runner;
+ // Note: Run delegate methods on the I/O thread.
+ InitIPCSupport(io_thread_task_runner_, process_delegate,
+ io_thread_task_runner_);
+}
+
+void ScopedIPCSupportHelper::OnShutdownCompleteImpl() {
+ event_.Signal();
+}
+
+} // namespace internal
+
+ScopedIPCSupport::ScopedIPCSupport(
+ scoped_refptr<base::TaskRunner> io_thread_task_runner) {
+ helper_.Init(this, io_thread_task_runner.Pass());
+}
+
+ScopedIPCSupport::~ScopedIPCSupport() {
+}
+
+void ScopedIPCSupport::OnShutdownComplete() {
+ helper_.OnShutdownCompleteImpl();
+}
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/test/scoped_ipc_support.h b/mojo/edk/test/scoped_ipc_support.h
new file mode 100644
index 0000000..cff0da7
--- /dev/null
+++ b/mojo/edk/test/scoped_ipc_support.h
@@ -0,0 +1,66 @@
+// Copyright 2015 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 MOJO_EDK_TEST_SCOPED_IPC_SUPPORT_H_
+#define MOJO_EDK_TEST_SCOPED_IPC_SUPPORT_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_runner.h"
+#include "mojo/edk/embedder/process_delegate.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+namespace internal {
+
+class ScopedIPCSupportHelper {
+ public:
+ ScopedIPCSupportHelper();
+ ~ScopedIPCSupportHelper();
+
+ void Init(ProcessDelegate* process_delegate,
+ scoped_refptr<base::TaskRunner> io_thread_task_runner);
+
+ void OnShutdownCompleteImpl();
+
+ private:
+ scoped_refptr<base::TaskRunner> io_thread_task_runner_;
+
+ // Set after shut down.
+ base::WaitableEvent event_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ScopedIPCSupportHelper);
+};
+
+} // namespace internal
+
+// A simple class that calls |InitIPCSupport()| on construction and
+// |ShutdownIPCSupport()| on destruction (or |ShutdownIPCSupportOnIOThread()|
+// if destroyed on the I/O thread).
+class ScopedIPCSupport : public ProcessDelegate {
+ public:
+ explicit ScopedIPCSupport(
+ scoped_refptr<base::TaskRunner> io_thread_task_runner);
+ ~ScopedIPCSupport() override;
+
+ private:
+ // |ProcessDelegate| implementation:
+ // Note: Executed on the I/O thread.
+ void OnShutdownComplete() override;
+
+ internal::ScopedIPCSupportHelper helper_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(ScopedIPCSupport);
+};
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_TEST_SCOPED_IPC_SUPPORT_H_
diff --git a/mojo/edk/test/test_support_impl.cc b/mojo/edk/test/test_support_impl.cc
new file mode 100644
index 0000000..c91fcf3
--- /dev/null
+++ b/mojo/edk/test/test_support_impl.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 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 "mojo/edk/test/test_support_impl.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <string>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/perf_log.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+namespace {
+
+base::FilePath ResolveSourceRootRelativePath(const char* relative_path) {
+ base::FilePath path;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &path))
+ return base::FilePath();
+
+ for (const base::StringPiece& component : base::SplitStringPiece(
+ relative_path, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ if (!component.empty())
+ path = path.AppendASCII(component);
+ }
+
+ return path;
+}
+
+} // namespace
+
+TestSupportImpl::TestSupportImpl() {
+}
+
+TestSupportImpl::~TestSupportImpl() {
+}
+
+void TestSupportImpl::LogPerfResult(const char* test_name,
+ const char* sub_test_name,
+ double value,
+ const char* units) {
+ DCHECK(test_name);
+ if (sub_test_name) {
+ std::string name = base::StringPrintf("%s/%s", test_name, sub_test_name);
+ base::LogPerfResult(name.c_str(), value, units);
+ } else {
+ base::LogPerfResult(test_name, value, units);
+ }
+}
+
+FILE* TestSupportImpl::OpenSourceRootRelativeFile(const char* relative_path) {
+ return base::OpenFile(ResolveSourceRootRelativePath(relative_path), "rb");
+}
+
+char** TestSupportImpl::EnumerateSourceRootRelativeDirectory(
+ const char* relative_path) {
+ std::vector<std::string> names;
+ base::FileEnumerator e(ResolveSourceRootRelativePath(relative_path), false,
+ base::FileEnumerator::FILES);
+ for (base::FilePath name = e.Next(); !name.empty(); name = e.Next())
+ names.push_back(name.BaseName().AsUTF8Unsafe());
+
+ // |names.size() + 1| for null terminator.
+ char** rv = static_cast<char**>(calloc(names.size() + 1, sizeof(char*)));
+ for (size_t i = 0; i < names.size(); ++i)
+ rv[i] = base::strdup(names[i].c_str());
+ return rv;
+}
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/test/test_support_impl.h b/mojo/edk/test/test_support_impl.h
new file mode 100644
index 0000000..e672999
--- /dev/null
+++ b/mojo/edk/test/test_support_impl.h
@@ -0,0 +1,38 @@
+// Copyright 2014 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 MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_
+#define MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_
+
+#include <stdio.h>
+
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/public/tests/test_support_private.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+class TestSupportImpl : public mojo::test::TestSupport {
+ public:
+ TestSupportImpl();
+ ~TestSupportImpl() override;
+
+ void LogPerfResult(const char* test_name,
+ const char* sub_test_name,
+ double value,
+ const char* units) override;
+ FILE* OpenSourceRootRelativeFile(const char* relative_path) override;
+ char** EnumerateSourceRootRelativeDirectory(
+ const char* relative_path) override;
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(TestSupportImpl);
+};
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_
diff --git a/mojo/edk/test/test_utils.h b/mojo/edk/test/test_utils.h
new file mode 100644
index 0000000..939171b
--- /dev/null
+++ b/mojo/edk/test/test_utils.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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 MOJO_EDK_TEST_TEST_UTILS_H_
+#define MOJO_EDK_TEST_TEST_UTILS_H_
+
+#include <stddef.h>
+#include <stdio.h>
+
+#include <string>
+
+#include "base/files/scoped_file.h"
+#include "mojo/edk/embedder/platform_handle.h"
+#include "mojo/edk/embedder/scoped_platform_handle.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+// On success, |bytes_written| is updated to the number of bytes written;
+// otherwise it is untouched.
+bool BlockingWrite(const PlatformHandle& handle,
+ const void* buffer,
+ size_t bytes_to_write,
+ size_t* bytes_written);
+
+// On success, |bytes_read| is updated to the number of bytes read; otherwise it
+// is untouched.
+bool BlockingRead(const PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read);
+
+// If the read is done successfully or would block, the function returns true
+// and updates |bytes_read| to the number of bytes read (0 if the read would
+// block); otherwise it returns false and leaves |bytes_read| untouched.
+// |handle| must already be in non-blocking mode.
+bool NonBlockingRead(const PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read);
+
+// Gets a (scoped) |PlatformHandle| from the given (scoped) |FILE|.
+ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp);
+
+// Gets a (scoped) |FILE| from a (scoped) |PlatformHandle|.
+base::ScopedFILE FILEFromPlatformHandle(ScopedPlatformHandle h,
+ const char* mode);
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
+
+#endif // MOJO_EDK_TEST_TEST_UTILS_H_
diff --git a/mojo/edk/test/test_utils_posix.cc b/mojo/edk/test/test_utils_posix.cc
new file mode 100644
index 0000000..66a6374
--- /dev/null
+++ b/mojo/edk/test/test_utils_posix.cc
@@ -0,0 +1,93 @@
+// Copyright 2014 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 "mojo/edk/test/test_utils.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+bool BlockingWrite(const PlatformHandle& handle,
+ const void* buffer,
+ size_t bytes_to_write,
+ size_t* bytes_written) {
+ int original_flags = fcntl(handle.fd, F_GETFL);
+ if (original_flags == -1 ||
+ fcntl(handle.fd, F_SETFL, original_flags & (~O_NONBLOCK)) != 0) {
+ return false;
+ }
+
+ ssize_t result = HANDLE_EINTR(write(handle.fd, buffer, bytes_to_write));
+
+ fcntl(handle.fd, F_SETFL, original_flags);
+
+ if (result < 0)
+ return false;
+
+ *bytes_written = result;
+ return true;
+}
+
+bool BlockingRead(const PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read) {
+ int original_flags = fcntl(handle.fd, F_GETFL);
+ if (original_flags == -1 ||
+ fcntl(handle.fd, F_SETFL, original_flags & (~O_NONBLOCK)) != 0) {
+ return false;
+ }
+
+ ssize_t result = HANDLE_EINTR(read(handle.fd, buffer, buffer_size));
+
+ fcntl(handle.fd, F_SETFL, original_flags);
+
+ if (result < 0)
+ return false;
+
+ *bytes_read = result;
+ return true;
+}
+
+bool NonBlockingRead(const PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read) {
+ ssize_t result = HANDLE_EINTR(read(handle.fd, buffer, buffer_size));
+
+ if (result < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK)
+ return false;
+
+ *bytes_read = 0;
+ } else {
+ *bytes_read = result;
+ }
+
+ return true;
+}
+
+ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) {
+ CHECK(fp);
+ int rv = dup(fileno(fp.get()));
+ PCHECK(rv != -1) << "dup";
+ return ScopedPlatformHandle(PlatformHandle(rv));
+}
+
+base::ScopedFILE FILEFromPlatformHandle(ScopedPlatformHandle h,
+ const char* mode) {
+ CHECK(h.is_valid());
+ base::ScopedFILE rv(fdopen(h.release().fd, mode));
+ PCHECK(rv) << "fdopen";
+ return rv.Pass();
+}
+
+} // namespace test
+} // namespace edk
+} // namespace mojo
diff --git a/mojo/edk/test/test_utils_win.cc b/mojo/edk/test/test_utils_win.cc
new file mode 100644
index 0000000..57a2550
--- /dev/null
+++ b/mojo/edk/test/test_utils_win.cc
@@ -0,0 +1,114 @@
+// Copyright 2014 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 "mojo/edk/test/test_utils.h"
+
+#include <windows.h>
+#include <fcntl.h>
+#include <io.h>
+#include <string.h>
+
+namespace mojo {
+namespace edk {
+namespace test {
+
+bool BlockingWrite(const PlatformHandle& handle,
+ const void* buffer,
+ size_t bytes_to_write,
+ size_t* bytes_written) {
+ OVERLAPPED overlapped = {0};
+ DWORD bytes_written_dword = 0;
+
+ if (!WriteFile(handle.handle, buffer, static_cast<DWORD>(bytes_to_write),
+ &bytes_written_dword, &overlapped)) {
+ if (GetLastError() != ERROR_IO_PENDING ||
+ !GetOverlappedResult(handle.handle, &overlapped, &bytes_written_dword,
+ TRUE)) {
+ return false;
+ }
+ }
+
+ *bytes_written = bytes_written_dword;
+ return true;
+}
+
+bool BlockingRead(const PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read) {
+ OVERLAPPED overlapped = {0};
+ DWORD bytes_read_dword = 0;
+
+ if (!ReadFile(handle.handle, buffer, static_cast<DWORD>(buffer_size),
+ &bytes_read_dword, &overlapped)) {
+ if (GetLastError() != ERROR_IO_PENDING ||
+ !GetOverlappedResult(handle.handle, &overlapped, &bytes_read_dword,
+ TRUE)) {
+ return false;
+ }
+ }
+
+ *bytes_read = bytes_read_dword;
+ return true;
+}
+
+bool NonBlockingRead(const PlatformHandle& handle,
+ void* buffer,
+ size_t buffer_size,
+ size_t* bytes_read) {
+ OVERLAPPED overlapped = {0};
+ DWORD bytes_read_dword = 0;
+
+ if (!ReadFile(handle.handle, buffer, static_cast<DWORD>(buffer_size),
+ &bytes_read_dword, &overlapped)) {
+ if (GetLastError() != ERROR_IO_PENDING)
+ return false;
+
+ CancelIo(handle.handle);
+
+ if (!GetOverlappedResult(handle.handle, &overlapped, &bytes_read_dword,
+ TRUE)) {
+ *bytes_read = 0;
+ return true;
+ }
+ }
+
+ *bytes_read = bytes_read_dword;
+ return true;
+}
+
+ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) {
+ CHECK(fp);
+
+ HANDLE rv = INVALID_HANDLE_VALUE;
+ PCHECK(DuplicateHandle(
+ GetCurrentProcess(),
+ reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fp.get()))),
+ GetCurrentProcess(), &rv, 0, TRUE, DUPLICATE_SAME_ACCESS))
+ << "DuplicateHandle";
+ return ScopedPlatformHandle(PlatformHandle(rv));
+}
+
+base::ScopedFILE FILEFromPlatformHandle(ScopedPlatformHandle h,
+ const char* mode) {
+ CHECK(h.is_valid());
+ // Microsoft's documentation for |_open_osfhandle()| only discusses these
+ // flags (and |_O_WTEXT|). Hmmm.
+ int flags = 0;
+ if (strchr(mode, 'a'))
+ flags |= _O_APPEND;
+ if (strchr(mode, 'r'))
+ flags |= _O_RDONLY;
+ if (strchr(mode, 't'))
+ flags |= _O_TEXT;
+ base::ScopedFILE rv(_fdopen(
+ _open_osfhandle(reinterpret_cast<intptr_t>(h.release().handle), flags),
+ mode));
+ PCHECK(rv) << "_fdopen";
+ return rv.Pass();
+}
+
+} // namespace test
+} // namespace edk
+} // namespace mojo