diff options
author | jam <jam@chromium.org> | 2015-10-02 14:01:28 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-02 21:02:13 +0000 |
commit | 76bcf0c21e332c08ee7a1601d0c878d1c75541a0 (patch) | |
tree | 9a1070172b98a5c0775967858a07d55585751839 /mojo/edk | |
parent | be81c48e3a52a050a6d07d0eb558a41c02594e6e (diff) | |
download | chromium_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')
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 |